Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ The Makefile’s targets build on each other in this order:
- **Test early and often** - Add tests immediately after modifying each compiler layer to catch problems early, rather than waiting until all changes are complete

- **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
- **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.

- **Don't use unit `()` with mandatory labeled arguments** - When a function has a mandatory labeled argument (like `~config`), don't add a trailing `()` parameter. The labeled argument already prevents accidental partial application. Only use `()` when all parameters are optional and you need to force evaluation. Example: `let forceDelayedItems ~config = ...` not `let forceDelayedItems ~config () = ...`

- **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.

Expand Down
529 changes: 529 additions & 0 deletions analysis/reanalyze/DEADCODE_REFACTOR_PLAN.md

Large diffs are not rendered by default.

109 changes: 60 additions & 49 deletions analysis/reanalyze/src/Arnold.ml
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ module Stats = struct

let logLoop () = incr nInfiniteLoops

let logCache ~functionCall ~hit ~loc =
let logCache ~config ~functionCall ~hit ~loc =
incr nCacheChecks;
if hit then incr nCacheHits;
if !Common.Cli.debug then
if config.DceConfig.cli.debug then
Log_.warning ~forStats:false ~loc
(Termination
{
Expand All @@ -123,8 +123,8 @@ module Stats = struct
(FunctionCall.toString functionCall);
})

let logResult ~functionCall ~loc ~resString =
if !Common.Cli.debug then
let logResult ~config ~functionCall ~loc ~resString =
if config.DceConfig.cli.debug then
Log_.warning ~forStats:false ~loc
(Termination
{
Expand Down Expand Up @@ -591,7 +591,8 @@ module ExtendFunctionTable = struct
if args |> List.for_all checkArg then Some (path, loc) else None
| _ -> None

let traverseExpr ~functionTable ~progressFunctions ~valueBindingsTable =
let traverseExpr ~config ~functionTable ~progressFunctions ~valueBindingsTable
=
let super = Tast_mapper.default in
let expr (self : Tast_mapper.mapper) (e : Typedtree.expression) =
(match e.exp_desc with
Expand All @@ -609,7 +610,7 @@ module ExtendFunctionTable = struct
if not (callee |> FunctionTable.isInFunctionInTable ~functionTable)
then (
functionTable |> FunctionTable.addFunction ~functionName;
if !Common.Cli.debug then
if config.DceConfig.cli.debug then
Log_.warning ~forStats:false ~loc
(Termination
{
Expand All @@ -631,7 +632,7 @@ module ExtendFunctionTable = struct
->
functionTable
|> FunctionTable.addLabelToKind ~functionName ~label;
if !Common.Cli.debug then
if config.DceConfig.cli.debug then
Log_.warning ~forStats:false ~loc
(Termination
{
Expand All @@ -648,16 +649,16 @@ module ExtendFunctionTable = struct
in
{super with Tast_mapper.expr}

let run ~functionTable ~progressFunctions ~valueBindingsTable
let run ~config ~functionTable ~progressFunctions ~valueBindingsTable
(expression : Typedtree.expression) =
let traverseExpr =
traverseExpr ~functionTable ~progressFunctions ~valueBindingsTable
traverseExpr ~config ~functionTable ~progressFunctions ~valueBindingsTable
in
expression |> traverseExpr.expr traverseExpr |> ignore
end

module CheckExpressionWellFormed = struct
let traverseExpr ~functionTable ~valueBindingsTable =
let traverseExpr ~config ~functionTable ~valueBindingsTable =
let super = Tast_mapper.default in
let checkIdent ~path ~loc =
if path |> FunctionTable.isInFunctionInTable ~functionTable then
Expand Down Expand Up @@ -698,7 +699,7 @@ module CheckExpressionWellFormed = struct
|> FunctionTable.addFunction ~functionName;
functionTable
|> FunctionTable.addLabelToKind ~functionName ~label;
if !Common.Cli.debug then
if config.DceConfig.cli.debug then
Log_.warning ~forStats:false ~loc:body.exp_loc
(Termination
{
Expand All @@ -717,22 +718,27 @@ module CheckExpressionWellFormed = struct
in
{super with Tast_mapper.expr}

let run ~functionTable ~valueBindingsTable (expression : Typedtree.expression)
=
let traverseExpr = traverseExpr ~functionTable ~valueBindingsTable in
let run ~config ~functionTable ~valueBindingsTable
(expression : Typedtree.expression) =
let traverseExpr =
traverseExpr ~config ~functionTable ~valueBindingsTable
in
expression |> traverseExpr.expr traverseExpr |> ignore
end

module Compile = struct
type ctx = {
config: DceConfig.t;
currentFunctionName: FunctionName.t;
functionTable: FunctionTable.t;
innerRecursiveFunctions: (FunctionName.t, FunctionName.t) Hashtbl.t;
isProgressFunction: Path.t -> bool;
}

let rec expression ~ctx (expr : Typedtree.expression) =
let {currentFunctionName; functionTable; isProgressFunction} = ctx in
let {config; currentFunctionName; functionTable; isProgressFunction} =
ctx
in
let loc = expr.exp_loc in
let notImplemented case =
Log_.error ~loc
Expand Down Expand Up @@ -872,7 +878,7 @@ module Compile = struct
Hashtbl.replace ctx.innerRecursiveFunctions oldFunctionName
newFunctionName;
newFunctionDefinition.body <- Some (vb_expr |> expression ~ctx:newCtx);
if !Common.Cli.debug then
if config.DceConfig.cli.debug then
Log_.warning ~forStats:false ~loc:pat_loc
(Termination
{
Expand Down Expand Up @@ -1067,8 +1073,9 @@ module Eval = struct
let lookupCache ~functionCall (cache : cache) =
Hashtbl.find_opt cache functionCall

let updateCache ~functionCall ~loc ~state (cache : cache) =
Stats.logResult ~functionCall ~resString:(state |> State.toString) ~loc;
let updateCache ~config ~functionCall ~loc ~state (cache : cache) =
Stats.logResult ~config ~functionCall ~resString:(state |> State.toString)
~loc;
if not (Hashtbl.mem cache functionCall) then
Hashtbl.replace cache functionCall state

Expand Down Expand Up @@ -1099,7 +1106,7 @@ module Eval = struct
true)
else false

let rec runFunctionCall ~cache ~callStack ~functionArgs ~functionTable
let rec runFunctionCall ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~loc ~state functionCallToInstantiate : State.t =
let pos = loc.Location.loc_start in
let functionCall =
Expand All @@ -1111,7 +1118,7 @@ module Eval = struct
let stateAfterCall =
match cache |> lookupCache ~functionCall with
| Some stateAfterCall ->
Stats.logCache ~functionCall ~hit:true ~loc;
Stats.logCache ~config ~functionCall ~hit:true ~loc;
{
stateAfterCall with
trace = Trace.Tcall (call, stateAfterCall.progress);
Expand All @@ -1124,7 +1131,7 @@ module Eval = struct
~loc ~state
then {state with trace = Trace.Tcall (call, state.progress)}
else (
Stats.logCache ~functionCall ~hit:false ~loc;
Stats.logCache ~config ~functionCall ~hit:false ~loc;
let functionDefinition =
functionTable |> FunctionTable.getFunctionDefinition ~functionName
in
Expand All @@ -1136,23 +1143,24 @@ module Eval = struct
in
let stateAfterCall =
body
|> run ~cache ~callStack ~functionArgs:functionCall.functionArgs
~functionTable ~madeProgressOn ~state:(State.init ())
|> run ~config ~cache ~callStack
~functionArgs:functionCall.functionArgs ~functionTable
~madeProgressOn ~state:(State.init ())
in
cache |> updateCache ~functionCall ~loc ~state:stateAfterCall;
cache |> updateCache ~config ~functionCall ~loc ~state:stateAfterCall;
(* Invariant: run should restore the callStack *)
callStack |> CallStack.removeFunctionCall ~functionCall;
let trace = Trace.Tcall (call, stateAfterCall.progress) in
{stateAfterCall with trace})
in
State.seq state stateAfterCall

and run ~(cache : cache) ~callStack ~functionArgs ~functionTable
and run ~config ~(cache : cache) ~callStack ~functionArgs ~functionTable
~madeProgressOn ~state (command : Command.t) : State.t =
match command with
| Call (FunctionCall functionCall, loc) ->
functionCall
|> runFunctionCall ~cache ~callStack ~functionArgs ~functionTable
|> runFunctionCall ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~loc ~state
| Call ((ProgressFunction _ as call), _pos) ->
let state1 =
Expand All @@ -1177,7 +1185,7 @@ module Eval = struct
| c :: nextCommands ->
let state1 =
c
|> run ~cache ~callStack ~functionArgs ~functionTable
|> run ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~state
in
let madeProgressOn, callStack =
Expand All @@ -1200,7 +1208,7 @@ module Eval = struct
commands
|> List.map (fun c ->
c
|> run ~cache ~callStack ~functionArgs ~functionTable
|> run ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~state:stateNoTrace)
in
State.seq state (states |> State.unorderedSequence)
Expand All @@ -1211,36 +1219,36 @@ module Eval = struct
commands
|> List.map (fun c ->
c
|> run ~cache ~callStack ~functionArgs ~functionTable
|> run ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~state:stateNoTrace)
in
State.seq state (states |> State.nondet)
| SwitchOption {functionCall; loc; some; none} -> (
let stateAfterCall =
functionCall
|> runFunctionCall ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~loc ~state
|> runFunctionCall ~config ~cache ~callStack ~functionArgs
~functionTable ~madeProgressOn ~loc ~state
in
match stateAfterCall.valuesOpt with
| None ->
Command.nondet [some; none]
|> run ~cache ~callStack ~functionArgs ~functionTable ~madeProgressOn
~state:stateAfterCall
|> run ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~state:stateAfterCall
| Some values ->
let runOpt c progressOpt =
match progressOpt with
| None -> State.init ~progress:Progress ()
| Some progress ->
c
|> run ~cache ~callStack ~functionArgs ~functionTable
|> run ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn ~state:(State.init ~progress ())
in
let stateNone = values |> Values.getNone |> runOpt none in
let stateSome = values |> Values.getSome |> runOpt some in
State.seq stateAfterCall (State.nondet [stateSome; stateNone]))

let analyzeFunction ~cache ~functionTable ~loc functionName =
if !Common.Cli.debug then
let analyzeFunction ~config ~cache ~functionTable ~loc functionName =
if config.DceConfig.cli.debug then
Log_.log "@[<v 2>@,@{<warning>Termination Analysis@} for @{<info>%s@}@]@."
functionName;
let pos = loc.Location.loc_start in
Expand All @@ -1261,10 +1269,10 @@ module Eval = struct
in
let state =
body
|> run ~cache ~callStack ~functionArgs ~functionTable
|> run ~config ~cache ~callStack ~functionArgs ~functionTable
~madeProgressOn:FunctionCallSet.empty ~state:(State.init ())
in
cache |> updateCache ~functionCall ~loc ~state
cache |> updateCache ~config ~functionCall ~loc ~state
end

let progressFunctionsFromAttributes attributes =
Expand All @@ -1283,7 +1291,7 @@ let progressFunctionsFromAttributes attributes =
| _ -> [])
else None

let traverseAst ~valueBindingsTable =
let traverseAst ~config ~valueBindingsTable =
let super = Tast_mapper.default in
let value_bindings (self : Tast_mapper.mapper) (recFlag, valueBindings) =
(* Update the table of value bindings for variables *)
Expand Down Expand Up @@ -1350,12 +1358,13 @@ let traverseAst ~valueBindingsTable =
recursiveDefinitions
|> List.iter (fun (_, body) ->
body
|> ExtendFunctionTable.run ~functionTable ~progressFunctions
~valueBindingsTable);
|> ExtendFunctionTable.run ~config ~functionTable
~progressFunctions ~valueBindingsTable);
recursiveDefinitions
|> List.iter (fun (_, body) ->
body
|> CheckExpressionWellFormed.run ~functionTable ~valueBindingsTable);
|> CheckExpressionWellFormed.run ~config ~functionTable
~valueBindingsTable);
functionTable
|> Hashtbl.iter
(fun
Expand All @@ -1374,17 +1383,19 @@ let traverseAst ~valueBindingsTable =
|> Compile.expression
~ctx:
{
config;
currentFunctionName = functionName;
functionTable;
innerRecursiveFunctions = Hashtbl.create 1;
isProgressFunction;
}))
~functionName);
if !Common.Cli.debug then FunctionTable.dump functionTable;
if config.DceConfig.cli.debug then FunctionTable.dump functionTable;
let cache = Eval.createCache () in
functionsToAnalyze
|> List.iter (fun (functionName, loc) ->
functionName |> Eval.analyzeFunction ~cache ~functionTable ~loc);
functionName
|> Eval.analyzeFunction ~config ~cache ~functionTable ~loc);
Stats.newRecursiveFunctions ~numFunctions:(Hashtbl.length functionTable));
valueBindings
|> List.iter (fun valueBinding ->
Expand All @@ -1393,16 +1404,16 @@ let traverseAst ~valueBindingsTable =
in
{super with Tast_mapper.value_bindings}

let processStructure (structure : Typedtree.structure) =
let processStructure ~config (structure : Typedtree.structure) =
Stats.newFile ();
let valueBindingsTable = Hashtbl.create 1 in
let traverseAst = traverseAst ~valueBindingsTable in
let traverseAst = traverseAst ~config ~valueBindingsTable in
structure |> traverseAst.structure traverseAst |> ignore

let processCmt (cmt_infos : Cmt_format.cmt_infos) =
let processCmt ~config ~file:_ (cmt_infos : Cmt_format.cmt_infos) =
match cmt_infos.cmt_annots with
| Interface _ -> ()
| Implementation structure -> processStructure structure
| Implementation structure -> processStructure ~config structure
| _ -> ()

let reportStats () = Stats.dump ~ppf:Format.std_formatter
let reportStats ~config:_ = Stats.dump ~ppf:Format.std_formatter
3 changes: 0 additions & 3 deletions analysis/reanalyze/src/Common.ml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
let currentSrc = ref ""
let currentModule = ref ""
let currentModuleName = ref ("" |> Name.create)
let runConfig = RunConfig.runConfig

(* Location printer: `filename:line: ' *)
Expand Down
34 changes: 34 additions & 0 deletions analysis/reanalyze/src/DceConfig.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
(** Configuration for dead code elimination analysis.

This module encapsulates all configuration needed for DCE,
gathered from RunConfig and CLI flags. *)

type cli_config = {
debug: bool;
ci: bool;
json: bool;
write: bool;
live_names: string list;
live_paths: string list;
exclude_paths: string list;
}

type t = {run: RunConfig.t; cli: cli_config}

(** Capture the current DCE configuration from global state.

This reads from [RunConfig.runConfig] and [Common.Cli] refs
to produce a single immutable configuration value. *)
let current () =
let cli =
{
debug = !Common.Cli.debug;
ci = !Common.Cli.ci;
json = !Common.Cli.json;
write = !Common.Cli.write;
live_names = !Common.Cli.liveNames;
live_paths = !Common.Cli.livePaths;
exclude_paths = !Common.Cli.excludePaths;
}
in
{run = Common.runConfig; cli}
Loading
Loading