DCE cleanup#8
Conversation
Adds scripts/dce/run-dce.sh + README to run reanalyze DCE over the compiler's own OCaml source. The vendored rescript-tools reanalyze can't do this: the compiler's OCaml is built by host OCaml 5.3 (cmt magic Caml1999I035), while the vendored ml library only reads the 4.06-era ReScript cmt format, so it dies with Cmi_format.Error. Instead we build the standalone reanalyze against host compiler-libs, using the OCaml-5.3 support from rescript-lang/reanalyze#203 (pinned, not yet merged). It works end-to-end and produces a full report. Raw output is currently dominated by entry-point false positives (dune emits only .cmti for the bsc main, and jsoo isn't built in the default profile), so making it actionable needs root/entry-point fixes — documented in the README along with the lowest-noise starting categories (redundant/unused optional args) and the CI-gating proposal.
…ability blocker Two findings from triaging the DCE output: 1. Roots: a plain `dune build` emits only .cmti for modules with an .mli (incl the bsc main), so reanalyze missed the entry-point bodies and over-reported. Switch the runner to `dune build @check`, which emits impl .cmt for every module. Fixes the Bs_version-class false positives (Dead Value ~2740->2130, Dead Module ~203->146). 2. Blocker: even with roots fixed, reanalyze#203 flags obviously-live core modules as dead (Ast_helper has 510 in-tree refs; Lam_compile, Js_dump, Lam_convert likewise). Per the PR, its 5.3 value-dependency tracking is derived 'just based on the type' in cmt_infos and is incomplete (rescript-lang/reanalyze#202). So cross-reference-based categories aren't trustworthy yet. Documented in the README.
Investigated why obviously-live modules (Ast_helper, Lam_compile, Js_dump) are flagged dead. reanalyze's DCE is location-keyed, and OCaml 5.3 broke that on two fronts: cmt_declaration_dependencies is uid-based and carries few edges, and the dominant typedtree-walk reference source relies on Texp_ident val_loc that no longer matches declaration registration cross-module. Confirmed by experiment (local reanalyze branch): a global uid->decl pre-pass is a necessary foundation but insufficient; resolving references via val_uid alone regresses (2129->2526 dead values) because declarations are still keyed by the old location. The real fix keys both declarations and references by uid - a core refactor, not a patch.
| | Batch of ('k * 'v option) list | ||
|
|
||
| let set k v = (k, Some v) | ||
| let remove k = (k, None) |
There was a problem hiding this comment.
DCE-confirmed cleanup: removed unused reactive exports. Covers reactive.mli too.
| (** Emit a delta *) | ||
| let emit t delta = t.emit delta | ||
|
|
||
| (** Process a file if changed. Emits delta to subscribers. *) |
There was a problem hiding this comment.
DCE: trimmed dead APIs; trimmed reactive interfaces. Covers reactive_file_collection.mli too.
|
|
||
| let empty = {issues = []} | ||
|
|
||
| let add_issue result issue = {issues = issue :: result.issues} |
There was a problem hiding this comment.
DCE: removed unused result helpers. Covers analysis_result.mli too.
| ({cd_attributes; cd_loc; cd_args} : | ||
| Typedtree.constructor_declaration) | ||
| -> | ||
| let _process_inline_records = |
There was a problem hiding this comment.
DCE: dropped inline record bindings.
| val of_reactive : (string, Cross_file_items.t) Reactive.t -> t | ||
| (** Wrap reactive collection directly (no intermediate collection) *) | ||
|
|
||
| val iter_optional_arg_calls : |
There was a problem hiding this comment.
DCE: hid item store iterators.
| @@ -1,4 +0,0 @@ | |||
| (** Dead code analysis - cmt file processing. | |||
There was a problem hiding this comment.
DCE: removed dead code alias.
|
|
||
| type mutable_flag = Js_op.mutable_flag | ||
| type binop = Js_op.binop | ||
| type int_op = Js_op.int_op |
There was a problem hiding this comment.
DCE: trimmed js op types.
|
|
||
| val eq_expression : J.expression -> J.expression -> bool | ||
|
|
||
| val eq_statement : J.statement -> J.statement -> bool |
There was a problem hiding this comment.
DCE: trimmed js debug exports.
| close_in ic; | ||
| v | ||
|
|
||
| let from_file_with_digest name : t * Digest.t = |
There was a problem hiding this comment.
DCE: trimmed cmj readers. Covers js_cmj_format.mli too.
| (fun cxt f s -> statement top cxt f s) | ||
| (if top then P.at_least_two_lines else P.newline) | ||
|
|
||
| let string_of_block (block : J.block) = |
There was a problem hiding this comment.
DCE: trimmed js debug exports; trimmed js dump literals. Covers js_dump.mli too.
|
|
||
| let new_ = "new" | ||
|
|
||
| let array = "Array" |
There was a problem hiding this comment.
DCE: trimmed js dump literals.
| (* let mk ?comment exp : t = | ||
| {expression_desc = exp ; comment } *) | ||
|
|
||
| let var ?comment id : t = {expression_desc = Var (Id id); comment} |
There was a problem hiding this comment.
DCE: trimmed JS expression helpers and optional comment args. Covers js_exp_make.mli too.
| val set : 'k -> 'v -> 'k * 'v option | ||
| (** Create a batch entry that sets a key *) | ||
|
|
||
| val remove : 'k -> 'k * 'v option |
There was a problem hiding this comment.
DCE: trimmed dead APIs; trimmed reactive interfaces; removed unused reactive exports.
|
|
||
| (** {1 Processing} *) | ||
|
|
||
| val process_files : ('raw, 'v) t -> string list -> unit |
There was a problem hiding this comment.
DCE: trimmed dead APIs; trimmed reactive interfaces.
| val empty : t | ||
| (** Empty result with no issues *) | ||
|
|
||
| val add_issue : t -> Issue.t -> t |
There was a problem hiding this comment.
DCE: removed unused result helpers.
| val builder_to_list : builder -> (Lexing.position * Decl.t) list | ||
| (** Extract all declarations as a list for reactive merge *) | ||
|
|
||
| val create_from_hashtbl : Decl.t Pos_hash.t -> t |
There was a problem hiding this comment.
DCE: trimmed reactive exports; removed stale declaration helpers.
| val assert_failure : t | ||
| val decode_error : t | ||
| val division_by_zero : t | ||
| val end_of_file : t |
There was a problem hiding this comment.
DCE: removed unused exception names.
| x.times <- x0 + y0; | ||
| if captured then x.captured <- true | ||
|
|
||
| let pp_info fmt (x : used_info) = |
There was a problem hiding this comment.
DCE: trimmed lambda helpers. Covers lam_pass_count.mli too.
|
|
||
| let simplify_lets (lam : Lam.t) : Lam.t = | ||
| let occ = Lam_pass_count.collect_occurs lam in | ||
| (* Ext_log.dwarn ~__POS__ "@[%a@]@." Lam_pass_count.pp_occ_tbl occ ; *) |
There was a problem hiding this comment.
DCE: trimmed lambda helpers.
| | Some (Constant (Const_js_false | Const_js_null | Const_js_undefined _)) -> | ||
| Eval_false | ||
| | Some | ||
| ( Normal_optional _ | ImmutableBlock _ | MutableBlock _ | Constant _ |
There was a problem hiding this comment.
DCE: trimmed dead APIs.
|
|
||
| type ident = Ident.t | ||
|
|
||
| type record_representation = |
There was a problem hiding this comment.
DCE: trimmed lambda debug API; removed runtime apply primitive; removed undefined primitive. Covers lam_primitive.mli too.
| | Pinit_mod -> fprintf ppf "init_mod!" | ||
| | Pupdate_mod -> fprintf ppf "update_mod!" | ||
| | Pjs_apply -> fprintf ppf "#apply" | ||
| | Pjs_runtime_apply -> fprintf ppf "#runtime_apply" |
There was a problem hiding this comment.
DCE: trimmed lambda debug API; removed runtime apply primitive; removed undefined primitive. More of the same in this file. Covers lam_print.mli too.
|
|
||
|
|
||
|
|
||
| let is_function (lam : Lam.t) = |
There was a problem hiding this comment.
DCE: removed lam function helper. Covers lam_util.mli too.
|
|
||
| val read_ast_exn : fname:string -> 'a kind -> 'a | ||
|
|
||
| val magic_sep_char : char |
There was a problem hiding this comment.
DCE: trimmed lambda helpers.
| type error = | ||
| | Cmj_not_found of string | ||
| | Js_not_found of string | ||
| | Bs_cyclic_depends of string list |
There was a problem hiding this comment.
DCE: trimmed bs exception. Covers bs_exception.mli too.
|
|
||
| type string_action = | ||
| | String_call of (string -> unit) | ||
| | String_set of string ref |
There was a problem hiding this comment.
DCE: trimmed arg spec variants; removed unused args finish. Covers bsc_args.mli too.
| a.!(i + len - 1 - k) <- t | ||
| done | ||
|
|
||
| let reverse_in_place a = reverse_range a 0 (Array.length a) |
There was a problem hiding this comment.
DCE: trimmed array helpers. Covers ext_array.mli too.
|
|
||
| type t = Parsetree.pattern | ||
|
|
||
| let is_unit_cont ~yes ~no (p : t) = |
There was a problem hiding this comment.
DCE: trimmed frontend helpers. Covers ast_pat.mli too.
| in | ||
| List.rev acc | ||
|
|
||
| (** Note this is okay with enums, for variants, |
There was a problem hiding this comment.
DCE: trimmed frontend helpers. Covers ast_polyvar.mli too.
| Str.include_ ~loc | ||
| (Incl.mk ~loc | ||
| (Mod.constraint_ ~loc (Mod.structure ~loc stru) (Mty.signature ~loc sign))) | ||
|
|
There was a problem hiding this comment.
DCE: trimmed frontend helpers. Covers ast_structure.mli too.
|
|
||
| type exn += Error of int (* offset *) * error | ||
|
|
||
| val pp_error : Format.formatter -> error -> unit |
There was a problem hiding this comment.
DCE: rooted utf8 test hook for DCE.
| *) | ||
|
|
||
| type pos = { | ||
| lnum: int; |
There was a problem hiding this comment.
DCE: rooted unicode test helpers for DCE. Covers ast_utf8_string_interp.mli too.
| * along with this program; if not, write to the Free Software | ||
| * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) | ||
|
|
||
| type untagged_variant = OnlyOneUnknown | AtMostOneObject | AtMostOneArray |
There was a problem hiding this comment.
DCE: trimmed frontend constants. Covers bs_syntaxerr.mli too.
|
|
||
| val cst_string : string -> delim -> cst | ||
|
|
||
| val empty_label : label |
There was a problem hiding this comment.
DCE: trimmed frontend constants.
|
|
||
| type constructor_tag = { | ||
| cstr_name: Ast_untagged_variants.tag; | ||
| const: int; |
There was a problem hiding this comment.
DCE: trimmed frontend constants. Covers lam_constant.mli too.
| type bs_version = int * int * int | ||
|
|
||
| type t = { | ||
| bsb_project_root: string; |
There was a problem hiding this comment.
DCE: trimmed gentype config.
| @@ -1,7 +1,5 @@ | |||
| open Gentype_common | |||
|
|
|||
| let concat = Filename.concat | |||
There was a problem hiding this comment.
DCE: trimmed gentype config.
| interesting in case of errors. | ||
| *) | ||
|
|
||
| open Annot |
There was a problem hiding this comment.
DCE: trimmed debug helpers; trimmed call annotations. Covers stypes.mli too.
| val modtype : t -> module_type -> module_type | ||
| val signature : t -> signature -> signature | ||
| val modtype_declaration : t -> modtype_declaration -> modtype_declaration | ||
| val module_declaration : t -> module_declaration -> module_declaration |
There was a problem hiding this comment.
DCE: trimmed typing exports.
| let c = compare x v in | ||
| if c = 0 then d else find_str x (if c < 0 then l else r) | ||
|
|
||
| let rec mem x = function |
There was a problem hiding this comment.
DCE: trimmed debug helpers. Covers tbl.mli too.
|
|
||
| and transl_cases_try cases = List.map transl_case_try cases | ||
|
|
||
| and transl_apply ?(inlined = Default_inline) |
There was a problem hiding this comment.
DCE: made apply metadata explicit.
|
|
||
| type error | ||
| (* exception Error of Location.t * error *) | ||
|
|
There was a problem hiding this comment.
DCE: trimmed ml exports.
| (* Forward declaration, to be filled in by Typemod.type_open *) | ||
|
|
||
| let type_open : | ||
| (?used_slot:bool ref -> |
There was a problem hiding this comment.
DCE: narrowed typecore helpers; removed unused open slot; trimmed typing exports. Covers typecore.mli too.
| open Types | ||
| open Typetexp | ||
|
|
||
| type native_repr_kind = Unboxed | Untagged |
There was a problem hiding this comment.
DCE: trimmed ml exports; removed free_variables env. Covers typedecl.mli too.
|
|
||
| (* Compute the environment after opening a module *) | ||
|
|
||
| let type_open_ ?used_slot ?toplevel ovf env loc lid = |
There was a problem hiding this comment.
DCE: removed unused open slot; trimmed typing exports. Covers typemod.mli too.
| [Tobject (_, `Some (`A.ct', [t1;...;tn]')] ==> [(t1, ..., tn) A.ct]. | ||
| where A.ct is the type of some class. | ||
|
|
||
| There are also special cases for so-called "class-types", cf. [Typeclass] |
| *) | ||
| module Color = struct | ||
| (* use ANSI color codes, see https://en.wikipedia.org/wiki/ANSI_escape_code *) | ||
| type[@warning "-37"] color = |
There was a problem hiding this comment.
DCE: trimmed syntax CLI colors.
| @@ -1,3 +1,3 @@ | |||
| (* Interface to print source code to res. | |||
| * Takes a filename called "input" and returns the corresponding formatted res syntax *) | |||
| val print : ?ignore_parse_errors:bool -> string -> string [@@dead "+print"] | |||
There was a problem hiding this comment.
DCE: fixed live printer annotation.
| * We don't support custom operators. *) | ||
| let parenthesized_ident _name = true | ||
|
|
||
| (* TODO: better allocation strategy for the buffer *) |
There was a problem hiding this comment.
DCE: trimmed outcome printers.
| val has_inline_record_definition_attribute : Parsetree.attributes -> bool | ||
| val has_res_pat_variant_spread_attribute : Parsetree.attributes -> bool | ||
| val has_dict_pattern_attribute : Parsetree.attributes -> bool | ||
| val has_dict_spread_attribute : Parsetree.attributes -> bool |
There was a problem hiding this comment.
DCE: trimmed syntax helpers.
| in | ||
| skip_whitespace_and_check scanner.offset | ||
|
|
||
| let peek_minus scanner = peek_char scanner '-' |
There was a problem hiding this comment.
DCE: trimmed syntax helpers. Covers res_scanner.mli too.
| @@ -1,4 +1,4 @@ | |||
| type input = Filename of string | Source of string | |||
There was a problem hiding this comment.
DCE: trimmed syntax print engine.
No description provided.