Skip to content

Commit 687ce52

Browse files
committed
refactor(reanalyze): thread binding state and remove globals
1 parent 176d7cc commit 687ce52

File tree

5 files changed

+77
-69
lines changed

5 files changed

+77
-69
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ The Makefile’s targets build on each other in this order:
4242
- **Test early and often** - Add tests immediately after modifying each compiler layer to catch problems early, rather than waiting until all changes are complete
4343

4444
- **Use underscore patterns carefully** - Don't use `_` patterns as lazy placeholders for new language features that then get forgotten. Only use them when you're certain the value should be ignored for that specific case. Ensure all new language features are handled correctly and completely across all compiler layers
45+
- **Avoid `let _ = …` for side effects** - If you need to call a function only for its side effects, use `ignore expr` (or bind the result and thread state explicitly). Do not write `let _ = expr in ()`, and do not discard stateful results—plumb them through instead.
4546

4647
- **Be careful with similar constructor names across different IRs** - Note that `Lam` (Lambda IR) and `Lambda` (typed lambda) have variants with similar constructor names like `Ltrywith`, but they represent different things in different compilation phases.
4748

analysis/reanalyze/DEADCODE_REFACTOR_PLAN.md

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -195,29 +195,13 @@ Goal: step‑wise removal of `Common.currentSrc`, `currentModule`, `currentModul
195195

196196
Each bullet above should be done as a separate patch touching only a small set of functions.
197197

198-
### 4.3 Localise `Current.*` binding state
198+
### 4.3 Localise `Current.*` binding/reporting state
199199

200-
Goal: remove `DeadCommon.Current.bindings`, `lastBinding`, and `maxValuePosEnd` as mutable globals by turning them into local state threaded through functions.
200+
Goal: remove `DeadCommon.Current` globals for binding/reporting by threading explicit state.
201201

202-
- [ ] In `DeadCommon`, define:
203-
```ocaml
204-
type current_state = {
205-
bindings : PosSet.t;
206-
last_binding : Location.t;
207-
max_value_pos_end : Lexing.position;
208-
}
209-
210-
let empty_current_state = {
211-
bindings = PosSet.empty;
212-
last_binding = Location.none;
213-
max_value_pos_end = Lexing.dummy_pos;
214-
}
215-
```
216-
- [ ] Change `addValueReference` to take a `current_state` and return an updated `current_state` instead of reading/writing `Current.*`. For the first patch, implement it by calling the existing global‑based logic and then mirroring the resulting values into a `current_state`, so behaviour is identical.
217-
- [ ] Update the places that call `addValueReference` (mainly in `DeadValue`) to thread a `current_state` value through, starting from `empty_current_state`, and ignore `Current.*`.
218-
- [ ] In a follow‑up patch, re‑implement `addValueReference` and any other helpers that touch `Current.*` purely in terms of `current_state` and delete the `Current.*` refs from DCE code.
219-
220-
At the end of this step, binding‑related state is explicit and confined to the call chains that need it.
202+
- [x] Add `Current.state`/helpers in `DeadCommon` and thread it through `DeadValue` (bindings) and `DeadException.markAsUsed` so `last_binding` is no longer a global ref.
203+
- [x] Replace `Current.maxValuePosEnd` with a per‑reporting `Current.state` in `Decl.report`/`reportDead`.
204+
- [ ] Follow‑up: remove `Current.state ref` usage by making traversals return an updated state (pure, no mutation). Adjust `addValueReference_state` (or its successor) to be purely functional and always return the new state.
221205

222206
### 4.4 Make `ProcessDeadAnnotations` state explicit
223207

@@ -398,4 +382,3 @@ Recommended rough order of tasks (each remains independent and small):
398382
8. 4.14 – Add and maintain order‑independence tests.
399383

400384
Each checkbox above should be updated to `[x]` as the corresponding change lands, keeping the codebase runnable and behaviour‑preserving after every step.
401-

analysis/reanalyze/src/DeadCommon.ml

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,26 @@ module Config = struct
1919
end
2020

2121
module Current = struct
22-
let bindings = ref PosSet.empty
23-
let lastBinding = ref Location.none
22+
type state = {
23+
last_binding: Location.t;
24+
max_value_pos_end: Lexing.position;
25+
}
2426

25-
(** max end position of a value reported dead *)
26-
let maxValuePosEnd = ref Lexing.dummy_pos
27+
let empty_state =
28+
{
29+
last_binding = Location.none;
30+
max_value_pos_end = Lexing.dummy_pos;
31+
}
32+
33+
let get_last_binding (s : state) = s.last_binding
34+
35+
let with_last_binding (loc : Location.t) (s : state) : state =
36+
{s with last_binding = loc}
37+
38+
let get_max_end (s : state) = s.max_value_pos_end
39+
40+
let with_max_end (pos : Lexing.position) (s : state) : state =
41+
{s with max_value_pos_end = pos}
2742
end
2843

2944
let rec checkSub s1 s2 n =
@@ -88,24 +103,26 @@ let declGetLoc decl =
88103
in
89104
{Location.loc_start; loc_end = decl.posEnd; loc_ghost = false}
90105

91-
let addValueReference ~addFileReference ~(locFrom : Location.t)
92-
~(locTo : Location.t) =
93-
let lastBinding = !Current.lastBinding in
94-
let locFrom =
106+
let addValueReference_state ~(current : Current.state) ~addFileReference
107+
~(locFrom : Location.t) ~(locTo : Location.t) : unit =
108+
let lastBinding = current.last_binding in
109+
let effectiveFrom =
95110
match lastBinding = Location.none with
96111
| true -> locFrom
97112
| false -> lastBinding
98113
in
99-
if not locFrom.loc_ghost then (
114+
if not effectiveFrom.loc_ghost then (
100115
if !Cli.debug then
101116
Log_.item "addValueReference %s --> %s@."
102-
(locFrom.loc_start |> posToString)
117+
(effectiveFrom.loc_start |> posToString)
103118
(locTo.loc_start |> posToString);
104-
ValueReferences.add locTo.loc_start locFrom.loc_start;
119+
ValueReferences.add locTo.loc_start effectiveFrom.loc_start;
105120
if
106-
addFileReference && (not locTo.loc_ghost) && (not locFrom.loc_ghost)
107-
&& locFrom.loc_start.pos_fname <> locTo.loc_start.pos_fname
108-
then FileReferences.add locFrom locTo)
121+
addFileReference && (not locTo.loc_ghost)
122+
&& (not effectiveFrom.loc_ghost)
123+
&& effectiveFrom.loc_start.pos_fname <> locTo.loc_start.pos_fname
124+
then FileReferences.add effectiveFrom locTo);
125+
()
109126

110127
let iterFilesFromRootsToLeaves iterFun =
111128
(* For each file, the number of incoming references *)
@@ -502,24 +519,20 @@ module Decl = struct
502519
(fname1, lnum1, bol1, cnum1, kind1)
503520
(fname2, lnum2, bol2, cnum2, kind2)
504521

505-
let isInsideReportedValue decl =
506-
let fileHasChanged =
507-
!Current.maxValuePosEnd.pos_fname <> decl.pos.pos_fname
508-
in
522+
let isInsideReportedValue (current_state : Current.state ref) decl =
523+
let max_end = Current.get_max_end !current_state in
524+
let fileHasChanged = max_end.pos_fname <> decl.pos.pos_fname in
509525
let insideReportedValue =
510-
decl |> isValue && (not fileHasChanged)
511-
&& !Current.maxValuePosEnd.pos_cnum > decl.pos.pos_cnum
526+
decl |> isValue && (not fileHasChanged) && max_end.pos_cnum > decl.pos.pos_cnum
512527
in
513528
if not insideReportedValue then
514529
if decl |> isValue then
515-
if
516-
fileHasChanged
517-
|| decl.posEnd.pos_cnum > !Current.maxValuePosEnd.pos_cnum
518-
then Current.maxValuePosEnd := decl.posEnd;
530+
if fileHasChanged || decl.posEnd.pos_cnum > max_end.pos_cnum then
531+
current_state := Current.with_max_end decl.posEnd !current_state;
519532
insideReportedValue
520533

521-
let report decl =
522-
let insideReportedValue = decl |> isInsideReportedValue in
534+
let report current_state decl =
535+
let insideReportedValue = decl |> isInsideReportedValue current_state in
523536
if decl.report then
524537
let name, message =
525538
match decl.declKind with
@@ -717,4 +730,5 @@ let reportDead ~checkOptionalArg =
717730
!deadDeclarations |> List.fast_sort Decl.compareForReporting
718731
in
719732
(* XXX *)
720-
sortedDeadDeclarations |> List.iter Decl.report
733+
let current_state = ref Current.empty_state in
734+
sortedDeadDeclarations |> List.iter (Decl.report current_state)

analysis/reanalyze/src/DeadException.ml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,19 @@ let forceDelayedItems () =
2121
match Hashtbl.find_opt declarations exceptionPath with
2222
| None -> ()
2323
| Some locTo ->
24-
addValueReference ~addFileReference:true ~locFrom ~locTo)
24+
(* Delayed exception references don't need a binding context; use an empty state. *)
25+
DeadCommon.addValueReference_state
26+
~current:DeadCommon.Current.empty_state ~addFileReference:true
27+
~locFrom ~locTo)
2528

26-
let markAsUsed ~(locFrom : Location.t) ~(locTo : Location.t) path_ =
29+
let markAsUsed ~(current_state : Current.state ref) ~(locFrom : Location.t)
30+
~(locTo : Location.t) path_ =
2731
if locTo.loc_ghost then
2832
(* Probably defined in another file, delay processing and check at the end *)
2933
let exceptionPath =
3034
path_ |> Path.fromPathT |> Path.moduleToImplementation
3135
in
3236
delayedItems := {exceptionPath; locFrom} :: !delayedItems
33-
else addValueReference ~addFileReference:true ~locFrom ~locTo
37+
else
38+
DeadCommon.addValueReference_state ~current:!current_state
39+
~addFileReference:true ~locFrom ~locTo

analysis/reanalyze/src/DeadValue.ml

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ let checkAnyValueBindingWithNoSideEffects
1515
~sideEffects:false
1616
| _ -> ()
1717

18-
let collectValueBinding super self (vb : Typedtree.value_binding) =
19-
let oldCurrentBindings = !Current.bindings in
20-
let oldLastBinding = !Current.lastBinding in
18+
let collectValueBinding current_state super self (vb : Typedtree.value_binding)
19+
=
20+
let oldLastBinding = Current.get_last_binding !current_state in
2121
checkAnyValueBindingWithNoSideEffects vb;
2222
let loc =
2323
match vb.vb_pat.pat_desc with
@@ -71,13 +71,11 @@ let collectValueBinding super self (vb : Typedtree.value_binding) =
7171
posStart = vb.vb_loc.loc_start;
7272
});
7373
loc
74-
| _ -> !Current.lastBinding
74+
| _ -> Current.get_last_binding !current_state
7575
in
76-
Current.bindings := PosSet.add loc.loc_start !Current.bindings;
77-
Current.lastBinding := loc;
76+
current_state := Current.with_last_binding loc !current_state;
7877
let r = super.Tast_mapper.value_binding self vb in
79-
Current.bindings := oldCurrentBindings;
80-
Current.lastBinding := oldLastBinding;
78+
current_state := Current.with_last_binding oldLastBinding !current_state;
8179
r
8280

8381
let processOptionalArgs ~expType ~(locFrom : Location.t) ~locTo ~path args =
@@ -111,7 +109,7 @@ let processOptionalArgs ~expType ~(locFrom : Location.t) ~locTo ~path args =
111109
(!supplied, !suppliedMaybe)
112110
|> DeadOptionalArgs.addReferences ~locFrom ~locTo ~path)
113111

114-
let rec collectExpr super self (e : Typedtree.expression) =
112+
let rec collectExpr current_state super self (e : Typedtree.expression) =
115113
let locFrom = e.exp_loc in
116114
(match e.exp_desc with
117115
| Texp_ident (_path, _, {Types.val_loc = {loc_ghost = false; _} as locTo}) ->
@@ -124,7 +122,9 @@ let rec collectExpr super self (e : Typedtree.expression) =
124122
(Location.none.loc_start |> Common.posToString)
125123
(locTo.loc_start |> Common.posToString);
126124
ValueReferences.add locTo.loc_start Location.none.loc_start)
127-
else addValueReference ~addFileReference:true ~locFrom ~locTo
125+
else
126+
DeadCommon.addValueReference_state ~current:!current_state
127+
~addFileReference:true ~locFrom ~locTo
128128
| Texp_apply
129129
{
130130
funct =
@@ -190,7 +190,8 @@ let rec collectExpr super self (e : Typedtree.expression) =
190190
{cstr_loc = {Location.loc_start = posTo; loc_ghost} as locTo; cstr_tag},
191191
_ ) ->
192192
(match cstr_tag with
193-
| Cstr_extension path -> path |> DeadException.markAsUsed ~locFrom ~locTo
193+
| Cstr_extension path ->
194+
path |> DeadException.markAsUsed ~current_state ~locFrom ~locTo
194195
| _ -> ());
195196
if !Config.analyzeTypes && not loc_ghost then
196197
DeadType.addTypeReference ~posTo ~posFrom:locFrom.loc_start
@@ -202,7 +203,7 @@ let rec collectExpr super self (e : Typedtree.expression) =
202203
->
203204
(* Punned field in OCaml projects has ghost location in expression *)
204205
let e = {e with exp_loc = {exp_loc with loc_ghost = false}} in
205-
collectExpr super self e |> ignore
206+
collectExpr current_state super self e |> ignore
206207
| _ -> ())
207208
| _ -> ());
208209
super.Tast_mapper.expr self e
@@ -286,9 +287,10 @@ let rec processSignatureItem ~doTypes ~doValues ~moduleLoc ~path
286287
(* Traverse the AST *)
287288
let traverseStructure ~doTypes ~doExternals =
288289
let super = Tast_mapper.default in
289-
let expr self e = e |> collectExpr super self in
290+
let current_state = ref Current.empty_state in
291+
let expr self e = e |> collectExpr current_state super self in
290292
let pat self p = p |> collectPattern super self in
291-
let value_binding self vb = vb |> collectValueBinding super self in
293+
let value_binding self vb = vb |> collectValueBinding current_state super self in
292294
let structure_item self (structureItem : Typedtree.structure_item) =
293295
let oldModulePath = ModulePath.getCurrent () in
294296
(match structureItem.str_desc with
@@ -365,7 +367,7 @@ let traverseStructure ~doTypes ~doExternals =
365367
{super with expr; pat; structure_item; value_binding}
366368

367369
(* Merge a location's references to another one's *)
368-
let processValueDependency
370+
let processValueDependency current_state
369371
( ({
370372
val_loc =
371373
{loc_start = {pos_fname = fnTo} as posTo; loc_ghost = ghost1} as
@@ -380,12 +382,14 @@ let processValueDependency
380382
Types.value_description) ) =
381383
if (not ghost1) && (not ghost2) && posTo <> posFrom then (
382384
let addFileReference = fileIsImplementationOf fnTo fnFrom in
383-
addValueReference ~addFileReference ~locFrom ~locTo;
385+
DeadCommon.addValueReference_state ~current:!current_state
386+
~addFileReference ~locFrom ~locTo;
384387
DeadOptionalArgs.addFunctionReference ~locFrom ~locTo)
385388

386389
let processStructure ~cmt_value_dependencies ~doTypes ~doExternals
387390
(structure : Typedtree.structure) =
388391
let traverseStructure = traverseStructure ~doTypes ~doExternals in
389392
structure |> traverseStructure.structure traverseStructure |> ignore;
390393
let valueDependencies = cmt_value_dependencies |> List.rev in
391-
valueDependencies |> List.iter processValueDependency
394+
let current_state = ref Current.empty_state in
395+
valueDependencies |> List.iter (processValueDependency current_state)

0 commit comments

Comments
 (0)