From b6d59f2984c47d0f6fb1056e86ace952c8e948ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 24 Sep 2025 18:22:05 +0200 Subject: [PATCH 01/14] WIP --- compiler/lib/eval.ml | 170 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 2 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 7a691397e3..13dc485433 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -17,6 +17,15 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) +(* +Static evaluation +================= +- Limited amount of fuel +- Cannot return a static boxed constant + (`return x` where x is a constant global variable (not in env), + or a parameter from the initial call) +*) + open! Stdlib open Code open Flow @@ -530,7 +539,122 @@ let constant_equal a b = | (String _ | NativeString _), _ -> false | (Float_array _ | Tuple _), _ -> false -let eval_instr update_count inline_constant ~target info i = +let rec eval_block ~info ~blocks ~env pc args = + let block = Addr.Map.find pc blocks in + let env = + List.fold_left2 + ~f:(fun env x x' -> + match resolve ~info ~env (Pv x') with + | None -> Var.Map.remove x env + | Some c -> Var.Map.add x c env) + block.params + args + ~init:env + in + match eval_block_body ~info ~blocks ~env block.body with + | None -> None + | Some env -> ( + match block.branch with + | Return x -> Var.Map.find_opt x env + | Branch (pc', args') -> eval_block ~info ~blocks ~env pc' args' + | Cond (x, (pc1, args1), (pc2, args2)) -> ( + match resolve ~info ~env (Pv x) with + | Some (Int i) when Targetint.is_zero i -> + eval_block ~info ~blocks ~env pc2 args2 + | Some (Int _ | Tuple _) -> eval_block ~info ~blocks ~env pc1 args1 + | Some _ -> assert false + | None -> None) + | Switch (x, conts) -> ( + match resolve ~info ~env (Pv x) with + | Some (Int i) -> + let pc', args' = conts.(Targetint.to_int_exn i) in + eval_block ~info ~blocks ~env pc' args' + | _ -> None) + | Raise _ | Stop | Pushtrap _ | Poptrap _ -> None) + +and resolve ~info ~env a = + match + match a with + | Pv x -> Var.Map.find_opt x env + | _ -> None + with + | Some _ as c -> c + | None -> the_const_of ~eq:constant_equal info a + +and eval_block_body ~info ~blocks ~env instrs = + match instrs with + | [] -> Some env + | Event _ :: rem -> eval_block_body ~info ~blocks ~env rem + | Let (x, Prim (IsInt, [ y ])) :: rem -> ( + let res = + match is_int info y with + | Y -> Some true + | N -> Some false + | Unknown -> ( + match resolve ~info ~env y with + | Some (Int _) -> Some true + | Some _ -> Some false + | None -> None) + in + match res with + | None -> None + | Some b -> eval_block_body ~info ~blocks ~env:(Var.Map.add x (bool' b) env) rem) + | (Let (x, Prim (prim, prim_args)) as i) :: rem -> ( + let prim_args' = List.map prim_args ~f:(fun a -> resolve ~info ~env a) in + if + List.exists prim_args' ~f:(function + | Some _ -> false + | None -> true) + then None + else + let res = + eval_prim + ( prim + , List.map prim_args' ~f:(function + | Some c -> c + | None -> assert false) ) + in + match res with + | None -> + Format.eprintf "INSTR %a@." Code.Print.instr i; + None + | Some c -> eval_block_body ~info ~blocks ~env:(Var.Map.add x c env) rem) + | Let (x, Constant c) :: rem -> ( + match c with + | Float _ | Int _ | Int32 _ | Int64 _ | NativeInt _ | NativeString _ | Float_array _ + -> eval_block_body ~info ~blocks ~env:(Var.Map.add x c env) rem + | String _ (*ZZZ*) | Tuple _ -> None) + | Let (x, Apply { f; args; _ }) :: rem -> ( + match get_approx info (fun g -> Flow.Info.def info g) None (fun _ _ -> None) f with + | Some (Closure (params, (pc, args'), _)) when List.compare_lengths args params = 0 + -> + let args = + List.map args ~f:(fun x -> the_const_of ~eq:constant_equal info (Pv x)) + in + if + List.for_all args ~f:(fun x -> + match x with + | Some _ -> true + | None -> false) + then + let env = + List.fold_left2 + ~f:(fun s x v -> Var.Map.add x (Option.get v) s) + params + args + ~init:Var.Map.empty + in + let res = eval_block ~info ~blocks ~env pc args' in + match res with + | Some c -> eval_block_body ~info ~blocks ~env:(Var.Map.add x c env) rem + | None -> None + else None + | _ -> None) + | ( Let (_, (Block _ | Field _ | Closure _ | Special _)) + | Assign _ | Set_field _ | Offset_ref _ | Array_set _ ) + :: _ -> None + +let eval_instr update_count inline_constant ~target info ~blocks i = match i with | Let (x, Prim (Extern (("caml_equal" | "caml_notequal") as prim), [ y; z ])) -> ( let eq e1 e2 = @@ -726,6 +850,48 @@ let eval_instr update_count inline_constant ~target info i = they're not represented with constant in javascript. *) | None, _ -> arg)) ) ) ]) + | Let (x, Apply { f; args; _ }) -> ( + match + get_approx + info + (fun g -> + match Flow.Info.def info g with + | Some e -> Some (g, e) + | _ -> None) + None + (fun _ _ -> None) + f + with + | Some (f, Closure (params, (pc, args'), _)) + when List.compare_lengths args params = 0 -> + let args = + List.map args ~f:(fun x -> the_const_of ~eq:constant_equal info (Pv x)) + in + if + List.for_all args ~f:(fun x -> + match x with + | Some _ -> true + | None -> false) + then ( + Format.eprintf "ZZZ %a@." Code.Var.print f; + let env = + List.fold_left2 + ~f:(fun s x v -> Var.Map.add x (Option.get v) s) + params + args + ~init:Var.Map.empty + in + let res = eval_block ~info ~blocks ~env pc args' in + match res with + | Some c -> + Format.eprintf "===> STATIC@."; + let c = Constant c in + Flow.Info.update_def info x c; + incr update_count; + [ Let (x, c) ] + | None -> [ i ]) + else [ i ] + | _ -> [ i ]) | _ -> [ i ] type cond_of = @@ -858,7 +1024,7 @@ let eval update_count update_branch inline_constant ~target info blocks = let body = List.concat_map block.body - ~f:(eval_instr update_count inline_constant ~target info) + ~f:(eval_instr update_count inline_constant ~blocks ~target info) in let branch = eval_branch update_branch info block.branch in { block with Code.body; Code.branch }) From f48949f6425869c920be6002061e81284b7e9d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 24 Sep 2025 23:26:42 +0200 Subject: [PATCH 02/14] WIP --- compiler/lib/eval.ml | 51 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 13dc485433..7ca0cd28e3 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -24,6 +24,7 @@ Static evaluation - Cannot return a static boxed constant (`return x` where x is a constant global variable (not in env), or a parameter from the initial call) +- Deal with constant tuples (see one_ulp / lower_bound_for_int) *) open! Stdlib @@ -371,6 +372,10 @@ let eval_prim ~target x = Some (Int (Targetint.of_int_exn (Targetint.num_bits ()))) | "caml_sys_const_big_endian", [ _ ] -> Some (Int Targetint.zero) | "caml_sys_const_naked_pointers_checked", [ _ ] -> Some (Int Targetint.zero) + | "caml_obj_dup", [ x ] -> ( + match x with + | NativeString _ | Float _ | Int _ | Int32 _ | Int64 _ | NativeInt _ -> Some x + | String _ | Float_array _ | Tuple _ -> None) | _ -> None) | _ -> None @@ -554,8 +559,9 @@ let rec eval_block ~info ~blocks ~env pc args = match eval_block_body ~info ~blocks ~env block.body with | None -> None | Some env -> ( + Format.eprintf "instr %a@." Code.Print.last block.branch; match block.branch with - | Return x -> Var.Map.find_opt x env + | Return x -> resolve ~info ~env (Pv x) | Branch (pc', args') -> eval_block ~info ~blocks ~env pc' args' | Cond (x, (pc1, args1), (pc2, args2)) -> ( match resolve ~info ~env (Pv x) with @@ -572,19 +578,42 @@ let rec eval_block ~info ~blocks ~env pc args = | _ -> None) | Raise _ | Stop | Pushtrap _ | Poptrap _ -> None) -and resolve ~info ~env a = +and resolve ~info ~env ?(eq = constant_equal) a = match match a with | Pv x -> Var.Map.find_opt x env | _ -> None with | Some _ as c -> c - | None -> the_const_of ~eq:constant_equal info a + | None -> the_const_of ~eq info a and eval_block_body ~info ~blocks ~env instrs = + (match instrs with + | i :: _ -> Format.eprintf "instr %a@." Code.Print.instr i + | [] -> ()); match instrs with | [] -> Some env | Event _ :: rem -> eval_block_body ~info ~blocks ~env rem + | Let (x, Prim (Extern (("caml_equal" | "caml_notequal") as prim), [ y; z ])) :: rem + -> ( + let eq e1 e2 = + match Code.Constant.ocaml_equal e1 e2 with + | None -> false + | Some e -> e + in + match resolve ~info ~env ~eq y, resolve ~info ~env ~eq z with + | Some e1, Some e2 -> ( + match Code.Constant.ocaml_equal e1 e2 with + | None -> None + | Some c -> + let c = + match prim with + | "caml_equal" -> c + | "caml_notequal" -> not c + | _ -> assert false + in + eval_block_body ~info ~blocks ~env:(Var.Map.add x (bool' c) env) rem) + | _ -> None) | Let (x, Prim (IsInt, [ y ])) :: rem -> ( let res = match is_int info y with @@ -605,7 +634,15 @@ and eval_block_body ~info ~blocks ~env instrs = List.exists prim_args' ~f:(function | Some _ -> false | None -> true) - then None + then ( + List.iter prim_args' ~f:(fun x -> + Format.eprintf + "%s" + (match x with + | Some _ -> "x" + | None -> "?")); + Format.eprintf "@."; + None) else let res = eval_prim @@ -628,9 +665,7 @@ and eval_block_body ~info ~blocks ~env instrs = match get_approx info (fun g -> Flow.Info.def info g) None (fun _ _ -> None) f with | Some (Closure (params, (pc, args'), _)) when List.compare_lengths args params = 0 -> - let args = - List.map args ~f:(fun x -> the_const_of ~eq:constant_equal info (Pv x)) - in + let args = List.map args ~f:(fun x -> resolve ~info ~env (Pv x)) in if List.for_all args ~f:(fun x -> match x with @@ -642,7 +677,7 @@ and eval_block_body ~info ~blocks ~env instrs = ~f:(fun s x v -> Var.Map.add x (Option.get v) s) params args - ~init:Var.Map.empty + ~init:env in let res = eval_block ~info ~blocks ~env pc args' in match res with From 820601d0ebae4ff414f0eb39819892f32c4fd93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 17:56:37 +0200 Subject: [PATCH 03/14] WIP --- compiler/lib/eval.ml | 74 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 7ca0cd28e3..28584709bc 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -148,6 +148,16 @@ let nativeint_shiftop (l : constant list) (f : int32 -> int -> int32) : constant | [ NativeInt i; Int j ] -> Some (NativeInt (f i (Targetint.to_int_exn j))) | _ -> None +let eval_comparison op args = + match args with + | [ Int i; Int j ] -> bool (op (Targetint.compare i j) 0) + | [ Int32 i; Int32 j ] -> bool (op (Int32.compare i j) 0) + | [ Int64 i; Int64 j ] -> bool (op (Int64.compare i j) 0) + | [ NativeInt i; NativeInt j ] -> bool (op (Int32.compare i j) 0) + | [ Float f; Float g ] -> + bool (op (Float.compare (Int64.float_of_bits f) (Int64.float_of_bits g)) 0) + | _ -> None + let quiet_nan n = Int64.logor n 0x00_08_00_00_00_00_00_00L let eval_prim ~target x = @@ -376,6 +386,10 @@ let eval_prim ~target x = match x with | NativeString _ | Float _ | Int _ | Int32 _ | Int64 _ | NativeInt _ -> Some x | String _ | Float_array _ | Tuple _ -> None) + | "caml_greaterthan", args -> eval_comparison ( > ) args + | "caml_greaterequal", args -> eval_comparison ( >= ) args + | "caml_lessthan", args -> eval_comparison ( < ) args + | "caml_lessequal", args -> eval_comparison ( <= ) args | _ -> None) | _ -> None @@ -544,7 +558,7 @@ let constant_equal a b = | (String _ | NativeString _), _ -> false | (Float_array _ | Tuple _), _ -> false -let rec eval_block ~info ~blocks ~env pc args = +let rec eval_block ~info ~blocks ~target ~env pc args = let block = Addr.Map.find pc blocks in let env = List.fold_left2 @@ -556,25 +570,25 @@ let rec eval_block ~info ~blocks ~env pc args = args ~init:env in - match eval_block_body ~info ~blocks ~env block.body with + match eval_block_body ~info ~blocks ~target ~env block.body with | None -> None | Some env -> ( Format.eprintf "instr %a@." Code.Print.last block.branch; match block.branch with | Return x -> resolve ~info ~env (Pv x) - | Branch (pc', args') -> eval_block ~info ~blocks ~env pc' args' + | Branch (pc', args') -> eval_block ~info ~blocks ~target ~env pc' args' | Cond (x, (pc1, args1), (pc2, args2)) -> ( match resolve ~info ~env (Pv x) with | Some (Int i) when Targetint.is_zero i -> - eval_block ~info ~blocks ~env pc2 args2 - | Some (Int _ | Tuple _) -> eval_block ~info ~blocks ~env pc1 args1 + eval_block ~info ~blocks ~target ~env pc2 args2 + | Some (Int _ | Tuple _) -> eval_block ~info ~blocks ~target ~env pc1 args1 | Some _ -> assert false | None -> None) | Switch (x, conts) -> ( match resolve ~info ~env (Pv x) with | Some (Int i) -> let pc', args' = conts.(Targetint.to_int_exn i) in - eval_block ~info ~blocks ~env pc' args' + eval_block ~info ~blocks ~target ~env pc' args' | _ -> None) | Raise _ | Stop | Pushtrap _ | Poptrap _ -> None) @@ -587,13 +601,13 @@ and resolve ~info ~env ?(eq = constant_equal) a = | Some _ as c -> c | None -> the_const_of ~eq info a -and eval_block_body ~info ~blocks ~env instrs = +and eval_block_body ~info ~blocks ~target ~env instrs = (match instrs with | i :: _ -> Format.eprintf "instr %a@." Code.Print.instr i | [] -> ()); match instrs with | [] -> Some env - | Event _ :: rem -> eval_block_body ~info ~blocks ~env rem + | Event _ :: rem -> eval_block_body ~info ~blocks ~target ~env rem | Let (x, Prim (Extern (("caml_equal" | "caml_notequal") as prim), [ y; z ])) :: rem -> ( let eq e1 e2 = @@ -612,7 +626,8 @@ and eval_block_body ~info ~blocks ~env instrs = | "caml_notequal" -> not c | _ -> assert false in - eval_block_body ~info ~blocks ~env:(Var.Map.add x (bool' c) env) rem) + eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x (bool' c) env) rem + ) | _ -> None) | Let (x, Prim (IsInt, [ y ])) :: rem -> ( let res = @@ -627,7 +642,8 @@ and eval_block_body ~info ~blocks ~env instrs = in match res with | None -> None - | Some b -> eval_block_body ~info ~blocks ~env:(Var.Map.add x (bool' b) env) rem) + | Some b -> + eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x (bool' b) env) rem) | (Let (x, Prim (prim, prim_args)) as i) :: rem -> ( let prim_args' = List.map prim_args ~f:(fun a -> resolve ~info ~env a) in if @@ -646,6 +662,7 @@ and eval_block_body ~info ~blocks ~env instrs = else let res = eval_prim + ~target ( prim , List.map prim_args' ~f:(function | Some c -> c @@ -655,11 +672,11 @@ and eval_block_body ~info ~blocks ~env instrs = | None -> Format.eprintf "INSTR %a@." Code.Print.instr i; None - | Some c -> eval_block_body ~info ~blocks ~env:(Var.Map.add x c env) rem) + | Some c -> eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x c env) rem) | Let (x, Constant c) :: rem -> ( match c with | Float _ | Int _ | Int32 _ | Int64 _ | NativeInt _ | NativeString _ | Float_array _ - -> eval_block_body ~info ~blocks ~env:(Var.Map.add x c env) rem + -> eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x c env) rem | String _ (*ZZZ*) | Tuple _ -> None) | Let (x, Apply { f; args; _ }) :: rem -> ( match get_approx info (fun g -> Flow.Info.def info g) None (fun _ _ -> None) f with @@ -679,13 +696,38 @@ and eval_block_body ~info ~blocks ~env instrs = args ~init:env in - let res = eval_block ~info ~blocks ~env pc args' in + let res = eval_block ~info ~blocks ~target ~env pc args' in match res with - | Some c -> eval_block_body ~info ~blocks ~env:(Var.Map.add x c env) rem + | Some c -> + eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x c env) rem | None -> None else None | _ -> None) - | ( Let (_, (Block _ | Field _ | Closure _ | Special _)) + | Let (x, Block (tag, fields, _, _)) :: rem -> + let fields = Array.map fields ~f:(fun x -> resolve ~info ~env (Pv x)) in + if + Array.exists fields ~f:(function + | Some _ -> false + | None -> true) + then None + else + let fields = + Array.map fields ~f:(function + | Some c -> c + | None -> assert false) + in + eval_block_body + ~info + ~blocks + ~target + ~env:(Var.Map.add x (Tuple (tag, fields, Unknown)) env) + rem + | Let (x, Field (y, i, _)) :: rem -> ( + match resolve ~info ~env (Pv y) with + | Some (Tuple (_, fields, _)) when i < Array.length fields -> + eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x fields.(i) env) rem + | _ -> None) + | ( Let (_, (Closure _ | Special _)) | Assign _ | Set_field _ | Offset_ref _ | Array_set _ ) :: _ -> None @@ -916,7 +958,7 @@ let eval_instr update_count inline_constant ~target info ~blocks i = args ~init:Var.Map.empty in - let res = eval_block ~info ~blocks ~env pc args' in + let res = eval_block ~info ~blocks ~target ~env pc args' in match res with | Some c -> Format.eprintf "===> STATIC@."; From 54d5a549d39e34dec35ebe45d4bd1f275314d1ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 21 May 2026 22:01:48 +0200 Subject: [PATCH 04/14] Static eval: add fuel, gate debug prints, fix env-leak - Add fuel counter (1000) to prevent divergence on cyclic CFG. - Gate debug eprintf calls behind Debug.find "static-eval". - Inner Apply: build callee env from Var.Map.empty so caller locals don't leak into the callee body. - Cond: replace `assert false` (would crash on unexpected operand) with fall-through to `None`; drop spurious `Tuple` arm. - caml_obj_dup / Constant cases: add Float32 and Null_ to the immediate-constant set. --- compiler/lib/eval.ml | 207 +++++++++++++++++++++++-------------------- 1 file changed, 112 insertions(+), 95 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 28584709bc..57c522c075 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -37,6 +37,8 @@ let stats = Debug.find "stats" let debug_stats = Debug.find "stats-debug" +let debug_static_eval = Debug.find "static-eval" + let static_env = String.Hashtbl.create 17 let clear_static_env () = String.Hashtbl.clear static_env @@ -384,7 +386,14 @@ let eval_prim ~target x = | "caml_sys_const_naked_pointers_checked", [ _ ] -> Some (Int Targetint.zero) | "caml_obj_dup", [ x ] -> ( match x with - | NativeString _ | Float _ | Int _ | Int32 _ | Int64 _ | NativeInt _ -> Some x + | NativeString _ + | Float _ + | Float32 _ + | Int _ + | Int32 _ + | Int64 _ + | NativeInt _ + | Null_ -> Some x | String _ | Float_array _ | Tuple _ -> None) | "caml_greaterthan", args -> eval_comparison ( > ) args | "caml_greaterequal", args -> eval_comparison ( >= ) args @@ -558,39 +567,45 @@ let constant_equal a b = | (String _ | NativeString _), _ -> false | (Float_array _ | Tuple _), _ -> false -let rec eval_block ~info ~blocks ~target ~env pc args = - let block = Addr.Map.find pc blocks in - let env = - List.fold_left2 - ~f:(fun env x x' -> - match resolve ~info ~env (Pv x') with - | None -> Var.Map.remove x env - | Some c -> Var.Map.add x c env) - block.params - args - ~init:env - in - match eval_block_body ~info ~blocks ~target ~env block.body with - | None -> None - | Some env -> ( - Format.eprintf "instr %a@." Code.Print.last block.branch; - match block.branch with - | Return x -> resolve ~info ~env (Pv x) - | Branch (pc', args') -> eval_block ~info ~blocks ~target ~env pc' args' - | Cond (x, (pc1, args1), (pc2, args2)) -> ( - match resolve ~info ~env (Pv x) with - | Some (Int i) when Targetint.is_zero i -> - eval_block ~info ~blocks ~target ~env pc2 args2 - | Some (Int _ | Tuple _) -> eval_block ~info ~blocks ~target ~env pc1 args1 - | Some _ -> assert false - | None -> None) - | Switch (x, conts) -> ( - match resolve ~info ~env (Pv x) with - | Some (Int i) -> - let pc', args' = conts.(Targetint.to_int_exn i) in - eval_block ~info ~blocks ~target ~env pc' args' - | _ -> None) - | Raise _ | Stop | Pushtrap _ | Poptrap _ -> None) +let static_eval_fuel = 1000 + +let rec eval_block ~fuel ~info ~blocks ~target ~env pc args = + if !fuel <= 0 + then None + else ( + decr fuel; + let block = Addr.Map.find pc blocks in + let env = + List.fold_left2 + ~f:(fun env x x' -> + match resolve ~info ~env (Pv x') with + | None -> Var.Map.remove x env + | Some c -> Var.Map.add x c env) + block.params + args + ~init:env + in + match eval_block_body ~fuel ~info ~blocks ~target ~env block.body with + | None -> None + | Some env -> ( + if debug_static_eval () + then Format.eprintf "instr %a@." Code.Print.last block.branch; + match block.branch with + | Return x -> resolve ~info ~env (Pv x) + | Branch (pc', args') -> eval_block ~fuel ~info ~blocks ~target ~env pc' args' + | Cond (x, (pc1, args1), (pc2, args2)) -> ( + match resolve ~info ~env (Pv x) with + | Some (Int i) -> + let pc', args' = if Targetint.is_zero i then pc2, args2 else pc1, args1 in + eval_block ~fuel ~info ~blocks ~target ~env pc' args' + | _ -> None) + | Switch (x, conts) -> ( + match resolve ~info ~env (Pv x) with + | Some (Int i) -> + let pc', args' = conts.(Targetint.to_int_exn i) in + eval_block ~fuel ~info ~blocks ~target ~env pc' args' + | _ -> None) + | Raise _ | Stop | Pushtrap _ | Poptrap _ -> None)) and resolve ~info ~env ?(eq = constant_equal) a = match @@ -601,13 +616,15 @@ and resolve ~info ~env ?(eq = constant_equal) a = | Some _ as c -> c | None -> the_const_of ~eq info a -and eval_block_body ~info ~blocks ~target ~env instrs = - (match instrs with - | i :: _ -> Format.eprintf "instr %a@." Code.Print.instr i - | [] -> ()); +and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = + (if debug_static_eval () + then + match instrs with + | i :: _ -> Format.eprintf "instr %a@." Code.Print.instr i + | [] -> ()); match instrs with | [] -> Some env - | Event _ :: rem -> eval_block_body ~info ~blocks ~target ~env rem + | Event _ :: rem -> eval_block_body ~fuel ~info ~blocks ~target ~env rem | Let (x, Prim (Extern (("caml_equal" | "caml_notequal") as prim), [ y; z ])) :: rem -> ( let eq e1 e2 = @@ -626,8 +643,13 @@ and eval_block_body ~info ~blocks ~target ~env instrs = | "caml_notequal" -> not c | _ -> assert false in - eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x (bool' c) env) rem - ) + eval_block_body + ~fuel + ~info + ~blocks + ~target + ~env:(Var.Map.add x (bool' c) env) + rem) | _ -> None) | Let (x, Prim (IsInt, [ y ])) :: rem -> ( let res = @@ -643,21 +665,22 @@ and eval_block_body ~info ~blocks ~target ~env instrs = match res with | None -> None | Some b -> - eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x (bool' b) env) rem) + eval_block_body + ~fuel + ~info + ~blocks + ~target + ~env:(Var.Map.add x (bool' b) env) + rem) | (Let (x, Prim (prim, prim_args)) as i) :: rem -> ( let prim_args' = List.map prim_args ~f:(fun a -> resolve ~info ~env a) in - if - List.exists prim_args' ~f:(function - | Some _ -> false - | None -> true) + if List.exists prim_args' ~f:Option.is_none then ( - List.iter prim_args' ~f:(fun x -> - Format.eprintf - "%s" - (match x with - | Some _ -> "x" - | None -> "?")); - Format.eprintf "@."; + if debug_static_eval () + then ( + List.iter prim_args' ~f:(fun a -> + Format.eprintf "%s" (if Option.is_some a then "x" else "?")); + Format.eprintf "@."); None) else let res = @@ -670,45 +693,46 @@ and eval_block_body ~info ~blocks ~target ~env instrs = in match res with | None -> - Format.eprintf "INSTR %a@." Code.Print.instr i; + if debug_static_eval () then Format.eprintf "INSTR %a@." Code.Print.instr i; None - | Some c -> eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x c env) rem) + | Some c -> + eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x c env) rem) | Let (x, Constant c) :: rem -> ( match c with - | Float _ | Int _ | Int32 _ | Int64 _ | NativeInt _ | NativeString _ | Float_array _ - -> eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x c env) rem - | String _ (*ZZZ*) | Tuple _ -> None) + | Float _ + | Float32 _ + | Int _ + | Int32 _ + | Int64 _ + | NativeInt _ + | NativeString _ + | Float_array _ + | Null_ -> + eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x c env) rem + | String _ | Tuple _ -> None) | Let (x, Apply { f; args; _ }) :: rem -> ( match get_approx info (fun g -> Flow.Info.def info g) None (fun _ _ -> None) f with | Some (Closure (params, (pc, args'), _)) when List.compare_lengths args params = 0 -> let args = List.map args ~f:(fun x -> resolve ~info ~env (Pv x)) in - if - List.for_all args ~f:(fun x -> - match x with - | Some _ -> true - | None -> false) + if List.for_all args ~f:Option.is_some then - let env = + let callee_env = List.fold_left2 ~f:(fun s x v -> Var.Map.add x (Option.get v) s) params args - ~init:env + ~init:Var.Map.empty in - let res = eval_block ~info ~blocks ~target ~env pc args' in - match res with + match eval_block ~fuel ~info ~blocks ~target ~env:callee_env pc args' with | Some c -> - eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x c env) rem + eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x c env) rem | None -> None else None | _ -> None) | Let (x, Block (tag, fields, _, _)) :: rem -> let fields = Array.map fields ~f:(fun x -> resolve ~info ~env (Pv x)) in - if - Array.exists fields ~f:(function - | Some _ -> false - | None -> true) + if Array.exists fields ~f:Option.is_none then None else let fields = @@ -717,6 +741,7 @@ and eval_block_body ~info ~blocks ~target ~env instrs = | None -> assert false) in eval_block_body + ~fuel ~info ~blocks ~target @@ -725,7 +750,13 @@ and eval_block_body ~info ~blocks ~target ~env instrs = | Let (x, Field (y, i, _)) :: rem -> ( match resolve ~info ~env (Pv y) with | Some (Tuple (_, fields, _)) when i < Array.length fields -> - eval_block_body ~info ~blocks ~target ~env:(Var.Map.add x fields.(i) env) rem + eval_block_body + ~fuel + ~info + ~blocks + ~target + ~env:(Var.Map.add x fields.(i) env) + rem | _ -> None) | ( Let (_, (Closure _ | Special _)) | Assign _ | Set_field _ | Offset_ref _ | Array_set _ ) @@ -928,29 +959,15 @@ let eval_instr update_count inline_constant ~target info ~blocks i = | None, _ -> arg)) ) ) ]) | Let (x, Apply { f; args; _ }) -> ( - match - get_approx - info - (fun g -> - match Flow.Info.def info g with - | Some e -> Some (g, e) - | _ -> None) - None - (fun _ _ -> None) - f - with - | Some (f, Closure (params, (pc, args'), _)) - when List.compare_lengths args params = 0 -> + match get_approx info (fun g -> Flow.Info.def info g) None (fun _ _ -> None) f with + | Some (Closure (params, (pc, args'), _)) when List.compare_lengths args params = 0 + -> let args = List.map args ~f:(fun x -> the_const_of ~eq:constant_equal info (Pv x)) in - if - List.for_all args ~f:(fun x -> - match x with - | Some _ -> true - | None -> false) + if List.for_all args ~f:Option.is_some then ( - Format.eprintf "ZZZ %a@." Code.Var.print f; + if debug_static_eval () then Format.eprintf "ZZZ %a@." Code.Var.print f; let env = List.fold_left2 ~f:(fun s x v -> Var.Map.add x (Option.get v) s) @@ -958,10 +975,10 @@ let eval_instr update_count inline_constant ~target info ~blocks i = args ~init:Var.Map.empty in - let res = eval_block ~info ~blocks ~target ~env pc args' in - match res with + let fuel = ref static_eval_fuel in + match eval_block ~fuel ~info ~blocks ~target ~env pc args' with | Some c -> - Format.eprintf "===> STATIC@."; + if debug_static_eval () then Format.eprintf "===> STATIC@."; let c = Constant c in Flow.Info.update_def info x c; incr update_count; From fa800060a2fa8f91538d0db6cdb600059b214c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 21 May 2026 22:27:05 +0200 Subject: [PATCH 05/14] FIX --- compiler/lib/eval.ml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 57c522c075..0076445d11 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -595,9 +595,10 @@ let rec eval_block ~fuel ~info ~blocks ~target ~env pc args = | Branch (pc', args') -> eval_block ~fuel ~info ~blocks ~target ~env pc' args' | Cond (x, (pc1, args1), (pc2, args2)) -> ( match resolve ~info ~env (Pv x) with - | Some (Int i) -> - let pc', args' = if Targetint.is_zero i then pc2, args2 else pc1, args1 in - eval_block ~fuel ~info ~blocks ~target ~env pc' args' + | Some (Int i) when Targetint.is_zero i -> + eval_block ~fuel ~info ~blocks ~target ~env pc2 args2 + | Some (Int _ | Tuple _) -> + eval_block ~fuel ~info ~blocks ~target ~env pc1 args1 | _ -> None) | Switch (x, conts) -> ( match resolve ~info ~env (Pv x) with From 1dcf3d16f760ad195b1a1ed53c5707fd609d0a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 21 May 2026 22:35:54 +0200 Subject: [PATCH 06/14] Static eval: caml_sys_const_runtime5 returns 1 --- compiler/lib/eval.ml | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 0076445d11..de2829073e 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -384,6 +384,7 @@ let eval_prim ~target x = Some (Int (Targetint.of_int_exn (Targetint.num_bits ()))) | "caml_sys_const_big_endian", [ _ ] -> Some (Int Targetint.zero) | "caml_sys_const_naked_pointers_checked", [ _ ] -> Some (Int Targetint.zero) + | "caml_sys_const_runtime5", [ _ ] -> Some (Int Targetint.one) | "caml_obj_dup", [ x ] -> ( match x with | NativeString _ From e575ee0f49f3262a42d39d3c5113e94e2d4e6f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 21 May 2026 23:02:15 +0200 Subject: [PATCH 07/14] Runtime: annotate caml_argv as mutable and caml_alloc_dummy_* as pure --- runtime/js/obj.js | 4 ++-- runtime/js/sys.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/runtime/js/obj.js b/runtime/js/obj.js index f7decb9d99..aae38c96b3 100644 --- a/runtime/js/obj.js +++ b/runtime/js/obj.js @@ -30,7 +30,7 @@ function caml_update_dummy(x, y) { return 0; } -//Provides: caml_alloc_dummy_infix +//Provides: caml_alloc_dummy_infix pure //Requires: caml_call_gen //Version: < 5.4 function caml_alloc_dummy_infix() { @@ -39,7 +39,7 @@ function caml_alloc_dummy_infix() { }; } -//Provides: caml_alloc_dummy_lazy +//Provides: caml_alloc_dummy_lazy pure //Version: >= 5.4 function caml_alloc_dummy_lazy(_unit) { return [0, 0]; diff --git a/runtime/js/sys.js b/runtime/js/sys.js index a8d600870b..302feb4518 100644 --- a/runtime/js/sys.js +++ b/runtime/js/sys.js @@ -173,7 +173,7 @@ function caml_sys_unsafe_getenv(name) { return caml_sys_getenv(name); } -//Provides: caml_argv +//Provides: caml_argv mutable //Requires: caml_string_of_jsstring var caml_argv = (function () { var process = globalThis.process; From 6a8cd40e14cfcc57f63dde330a2cdc0ea84d4bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 21 May 2026 23:02:20 +0200 Subject: [PATCH 08/14] Static eval: handle caml_obj_tag on tuple constants --- compiler/lib/eval.ml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index de2829073e..0e74dd083a 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -674,6 +674,17 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = ~target ~env:(Var.Map.add x (bool' b) env) rem) + | Let (x, Prim (Extern "caml_obj_tag", [ y ])) :: rem -> ( + match resolve ~info ~env y with + | Some (Tuple (tag, _, _)) -> + eval_block_body + ~fuel + ~info + ~blocks + ~target + ~env:(Var.Map.add x (Int (Targetint.of_int_exn tag)) env) + rem + | _ -> None) | (Let (x, Prim (prim, prim_args)) as i) :: rem -> ( let prim_args' = List.map prim_args ~f:(fun a -> resolve ~info ~env a) in if List.exists prim_args' ~f:Option.is_none From 550380e470530943c2e212c4fc737ba0d665190e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 21 May 2026 23:19:05 +0200 Subject: [PATCH 09/14] Wasm: register purity of several primitives Match the kinds already used by the JS backend in compiler/lib/generate.ml: - Pure: caml_alloc_dummy{,_float,_mixed}, caml_js_{to_int32,to_nativeint, from_bool,to_bool} - Mutable: caml_array_unsafe_get, caml_js_{,strict_}equals --- compiler/lib-wasm/generate.ml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index 024ce84c0c..b5307b7f87 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -2499,6 +2499,16 @@ module Generate (Target : Target_sig.S) = struct Typing.reset (); Primitive.register "caml_make_array" `Mutable None None; Primitive.register "caml_array_of_uniform_array" `Mutable None None; + Primitive.register "caml_array_unsafe_get" `Mutable None None; + Primitive.register "caml_alloc_dummy" `Pure None None; + Primitive.register "caml_alloc_dummy_float" `Pure None None; + Primitive.register "caml_alloc_dummy_mixed" `Pure None None; + Primitive.register "caml_js_to_int32" `Pure None None; + Primitive.register "caml_js_to_nativeint" `Pure None None; + Primitive.register "caml_js_from_bool" `Pure None None; + Primitive.register "caml_js_to_bool" `Pure None None; + Primitive.register "caml_js_equals" `Mutable None None; + Primitive.register "caml_js_strict_equals" `Mutable None None; (* These primitives are rewritten by [Specialize_js] before code generation, but need to be registered so that [Primitive.get_external] reports them as available for the From 0eec05ac9abe36049553af92aa51403de41b5079 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 22 May 2026 00:21:47 +0200 Subject: [PATCH 10/14] Note --- compiler/lib/eval.ml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 0e74dd083a..4b4484e040 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -25,6 +25,13 @@ Static evaluation (`return x` where x is a constant global variable (not in env), or a parameter from the initial call) - Deal with constant tuples (see one_ulp / lower_bound_for_int) + +We need to keep track of whether we have a global constant, +a small constant, a tuple built during evaluation. +==> beware of sharing when building a tuple (maybe do not allow tuple of anything but small constants and global constants? at least after evaluation) +==> should keep referring to global constants and not duplicate them + +Add tests *) open! Stdlib From effd406791c5cc6702bfb75f42d2258fce2a7fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 22 May 2026 00:44:29 +0200 Subject: [PATCH 11/14] Static eval: don't substitute block-shaped outcomes A returned Tuple/Float_array constant would lose the array_or_not and field-type information the back-end needs (notably for float records), miscompiling e.g. let f x = { x; y = 2. } -> f 1.. Keep the original Apply in that case. --- compiler/lib/eval.ml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 4b4484e040..6bbf8e28ba 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -997,6 +997,11 @@ let eval_instr update_count inline_constant ~target info ~blocks i = in let fuel = ref static_eval_fuel in match eval_block ~fuel ~info ~blocks ~target ~env pc args' with + | Some (Tuple _ | Float_array _) -> + (* The outcome is not supposed to be a block; emitting one as + a constant loses the array_or_not / field-type information + the back-end needs (e.g. for float records). *) + [ i ] | Some c -> if debug_static_eval () then Format.eprintf "===> STATIC@."; let c = Constant c in From 2b4cea9d1dcc75046901562d2ac58a0d612a38ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 26 May 2026 15:24:53 +0200 Subject: [PATCH 12/14] Static eval: fix float NaN comparisons, Switch bounds, and update expect tests --- compiler/lib/eval.ml | 14 +++++-- compiler/tests-compiler/gh1007.ml | 35 +++++++---------- compiler/tests-compiler/side_effect.ml | 1 - compiler/tests-compiler/tailcall.ml | 54 +++++++++++--------------- 4 files changed, 48 insertions(+), 56 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 6bbf8e28ba..28c972be8f 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -164,7 +164,11 @@ let eval_comparison op args = | [ Int64 i; Int64 j ] -> bool (op (Int64.compare i j) 0) | [ NativeInt i; NativeInt j ] -> bool (op (Int32.compare i j) 0) | [ Float f; Float g ] -> - bool (op (Float.compare (Int64.float_of_bits f) (Int64.float_of_bits g)) 0) + let f = Int64.float_of_bits f in + let g = Int64.float_of_bits g in + if Float.is_nan f || Float.is_nan g + then bool false + else bool (op (Float.compare f g) 0) | _ -> None let quiet_nan n = Int64.logor n 0x00_08_00_00_00_00_00_00L @@ -611,8 +615,12 @@ let rec eval_block ~fuel ~info ~blocks ~target ~env pc args = | Switch (x, conts) -> ( match resolve ~info ~env (Pv x) with | Some (Int i) -> - let pc', args' = conts.(Targetint.to_int_exn i) in - eval_block ~fuel ~info ~blocks ~target ~env pc' args' + let idx = Targetint.to_int_exn i in + if idx >= 0 && idx < Array.length conts + then + let pc', args' = conts.(idx) in + eval_block ~fuel ~info ~blocks ~target ~env pc' args' + else None | _ -> None) | Raise _ | Stop | Pushtrap _ | Poptrap _ -> None)) diff --git a/compiler/tests-compiler/gh1007.ml b/compiler/tests-compiler/gh1007.ml index 4cd3e64e21..e9f83eee0e 100644 --- a/compiler/tests-compiler/gh1007.ml +++ b/compiler/tests-compiler/gh1007.ml @@ -375,29 +375,24 @@ let () = M.run () var i = 0; for(;;){ var - odd = - function(n){ - if(2 < n >>> 0) return 1 - (1 - even$0(n - 1 | 0)); - switch(n){ - case 0: - return 0; - case 1: - return 1 - (1 - even$0(0)); - default: return 1 - (1 - even$0(1)); - } - }, even = function(n){ - if(2 < n >>> 0) return 1 - (1 - odd$0(n - 1 | 0)); - switch(n){ - case 0: - return 1; - case 1: - return 1 - (1 - odd$0(0)); - default: return 1 - (1 - odd$0(1)); - } + if(2 >= n >>> 0) + switch(n){case 0: return 1;case 1: return 0;default: return 1;} + var n$0 = n - 1 | 0; + if(2 < n$0 >>> 0) + var _a_ = 1 - (1 - even$0(n$0 - 1 | 0)); + else + switch(n$0){ + case 0: + _a_ = 0; break; + case 1: + _a_ = 1; break; + default: _a_ = 0; + } + return 1 - (1 - _a_); }; - let odd$0 = odd, even$0 = even; + let even$0 = even; if(even(i)) caml_call1(Stdlib[42], cst); _a_ = i + 1 | 0; if(4 === i) return 0; diff --git a/compiler/tests-compiler/side_effect.ml b/compiler/tests-compiler/side_effect.ml index e7e56b6326..7504a0cca9 100644 --- a/compiler/tests-compiler/side_effect.ml +++ b/compiler/tests-compiler/side_effect.ml @@ -89,7 +89,6 @@ let%expect_test _ = if(yes){caml_call2(Stdlib_Printf[2], _a_, label); i[1]++;} return 0; } - side_effect(0, caml_string_of_jsbytes("this is only to avoid inlining")); var _b_ = [0, diff --git a/compiler/tests-compiler/tailcall.ml b/compiler/tests-compiler/tailcall.ml index c15b1bcbf8..24f9318427 100644 --- a/compiler/tests-compiler/tailcall.ml +++ b/compiler/tests-compiler/tailcall.ml @@ -44,27 +44,18 @@ let%expect_test _ = Util.print_fun_decl program (Some "fun1"); [%expect {| - function fun1(_b_){ - function odd$0(counter, x){ - if(0 === x) return 0; - var _b_ = x - 1 | 0; - return counter < 50 - ? even$0(counter + 1 | 0, _b_) - : caml_trampoline_return(even$0, [0, _b_]); + function fun1(_a_){ + try{ + var x = 5000; + for(;;){ + if(0 !== x){ + var x$0 = x - 1 | 0; + if(0 !== x$0){var x$1 = x$0 - 1 | 0; x = x$1; continue;} + } + _a_ = log_success(0); + return _a_; + } } - function odd(x){return caml_trampoline(odd$0(0, x));} - function even$0(counter, x){ - if(0 === x) return 1; - var _b_ = x - 1 | 0; - return counter < 50 - ? odd$0(counter + 1 | 0, _b_) - : caml_trampoline_return(odd$0, [0, _b_]); - } - function even(x){return caml_trampoline(even$0(0, x));} - var _b_ = even(1); - if(odd(1) === _b_) - throw caml_maybe_attach_backtrace([0, Assert_failure, _a_], 1); - try{odd(5000); _b_ = log_success(0); return _b_;} catch(exn){return caml_call1(log_failure, cst_too_much_recursion);} } //end @@ -94,19 +85,18 @@ let%expect_test _ = Util.print_fun_decl program (Some "fun1"); [%expect {| - function fun1(_b_){ - function odd$0(x){ - return 0 === x ? 0 : caml_trampoline_return(even$0, [0, x - 1 | 0]); - } - function odd(x){return caml_trampoline(odd$0(x));} - function even$0(x){ - return 0 === x ? 1 : caml_trampoline_return(odd$0, [0, x - 1 | 0]); + function fun1(_a_){ + try{ + var x = 5000; + for(;;){ + if(0 !== x){ + var x$0 = x - 1 | 0; + if(0 !== x$0){var x$1 = x$0 - 1 | 0; x = x$1; continue;} + } + _a_ = log_success(0); + return _a_; + } } - function even(x){return caml_trampoline(even$0(x));} - var _b_ = even(1); - if(odd(1) === _b_) - throw caml_maybe_attach_backtrace([0, Assert_failure, _a_], 1); - try{odd(5000); _b_ = log_success(0); return _b_;} catch(exn){return caml_call1(log_failure, cst_too_much_recursion);} } //end From 7f5b48d54b0b9c67f8c690cd9e06fce4f088d7ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 26 May 2026 15:45:57 +0200 Subject: [PATCH 13/14] Static eval: represent block environment values and emit them as Block expressions --- compiler/lib/eval.ml | 141 ++++++++++++++------- compiler/tests-compiler/gh2217.ml | 6 +- compiler/tests-full/stdlib.cma.expected.js | 4 +- 3 files changed, 98 insertions(+), 53 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 28c972be8f..cc16527f2d 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -579,6 +579,16 @@ let constant_equal a b = | (String _ | NativeString _), _ -> false | (Float_array _ | Tuple _), _ -> false +type value = + | Val_constant of constant + | Val_block of int * value array * array_or_not * mutability + +let rec value_of_constant c = + match c with + | Tuple (tag, fields, array_or_not) -> + Val_block (tag, Array.map ~f:value_of_constant fields, array_or_not, Immutable) + | c -> Val_constant c + let static_eval_fuel = 1000 let rec eval_block ~fuel ~info ~blocks ~target ~env pc args = @@ -607,14 +617,14 @@ let rec eval_block ~fuel ~info ~blocks ~target ~env pc args = | Branch (pc', args') -> eval_block ~fuel ~info ~blocks ~target ~env pc' args' | Cond (x, (pc1, args1), (pc2, args2)) -> ( match resolve ~info ~env (Pv x) with - | Some (Int i) when Targetint.is_zero i -> + | Some (Val_constant (Int i)) when Targetint.is_zero i -> eval_block ~fuel ~info ~blocks ~target ~env pc2 args2 - | Some (Int _ | Tuple _) -> + | Some (Val_constant (Int _ | Tuple _) | Val_block _) -> eval_block ~fuel ~info ~blocks ~target ~env pc1 args1 | _ -> None) | Switch (x, conts) -> ( match resolve ~info ~env (Pv x) with - | Some (Int i) -> + | Some (Val_constant (Int i)) -> let idx = Targetint.to_int_exn i in if idx >= 0 && idx < Array.length conts then @@ -630,8 +640,11 @@ and resolve ~info ~env ?(eq = constant_equal) a = | Pv x -> Var.Map.find_opt x env | _ -> None with - | Some _ as c -> c - | None -> the_const_of ~eq info a + | Some _ as v -> v + | None -> ( + match the_const_of ~eq info a with + | None -> None + | Some c -> Some (value_of_constant c)) and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = (if debug_static_eval () @@ -650,7 +663,7 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = | Some e -> e in match resolve ~info ~env ~eq y, resolve ~info ~env ~eq z with - | Some e1, Some e2 -> ( + | Some (Val_constant e1), Some (Val_constant e2) -> ( match Code.Constant.ocaml_equal e1 e2 with | None -> None | Some c -> @@ -665,7 +678,7 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = ~info ~blocks ~target - ~env:(Var.Map.add x (bool' c) env) + ~env:(Var.Map.add x (Val_constant (bool' c)) env) rem) | _ -> None) | Let (x, Prim (IsInt, [ y ])) :: rem -> ( @@ -675,8 +688,8 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = | N -> Some false | Unknown -> ( match resolve ~info ~env y with - | Some (Int _) -> Some true - | Some _ -> Some false + | Some (Val_constant (Int _)) -> Some true + | Some (Val_constant _ | Val_block _) -> Some false | None -> None) in match res with @@ -687,17 +700,18 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = ~info ~blocks ~target - ~env:(Var.Map.add x (bool' b) env) + ~env:(Var.Map.add x (Val_constant (bool' b)) env) rem) | Let (x, Prim (Extern "caml_obj_tag", [ y ])) :: rem -> ( match resolve ~info ~env y with - | Some (Tuple (tag, _, _)) -> + | Some (Val_constant (Tuple (tag, _, _))) + | Some (Val_block (tag, _, _, _)) -> eval_block_body ~fuel ~info ~blocks ~target - ~env:(Var.Map.add x (Int (Targetint.of_int_exn tag)) env) + ~env:(Var.Map.add x (Val_constant (Int (Targetint.of_int_exn tag))) env) rem | _ -> None) | (Let (x, Prim (prim, prim_args)) as i) :: rem -> ( @@ -711,20 +725,31 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = Format.eprintf "@."); None) else - let res = - eval_prim - ~target - ( prim - , List.map prim_args' ~f:(function - | Some c -> c - | None -> assert false) ) + let is_const = + List.for_all prim_args' ~f:(function + | Some (Val_constant _) -> true + | _ -> false) in - match res with - | None -> - if debug_static_eval () then Format.eprintf "INSTR %a@." Code.Print.instr i; - None - | Some c -> - eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x c env) rem) + if not is_const + then None + else + let prim_args'' = + List.map prim_args' ~f:(function + | Some (Val_constant c) -> c + | _ -> assert false) + in + let res = + eval_prim + ~target + ( prim + , prim_args'' ) + in + match res with + | None -> + if debug_static_eval () then Format.eprintf "INSTR %a@." Code.Print.instr i; + None + | Some c -> + eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x (Val_constant c) env) rem) | Let (x, Constant c) :: rem -> ( match c with | Float _ @@ -736,8 +761,17 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = | NativeString _ | Float_array _ | Null_ -> - eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x c env) rem - | String _ | Tuple _ -> None) + eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x (Val_constant c) env) rem + | Tuple (tag, fields, array_or_not) -> + let fields = Array.map ~f:value_of_constant fields in + eval_block_body + ~fuel + ~info + ~blocks + ~target + ~env:(Var.Map.add x (Val_block (tag, fields, array_or_not, Immutable)) env) + rem + | String _ -> None) | Let (x, Apply { f; args; _ }) :: rem -> ( match get_approx info (fun g -> Flow.Info.def info g) None (fun _ _ -> None) f with | Some (Closure (params, (pc, args'), _)) when List.compare_lengths args params = 0 @@ -753,13 +787,13 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = ~init:Var.Map.empty in match eval_block ~fuel ~info ~blocks ~target ~env:callee_env pc args' with - | Some c -> - eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x c env) rem + | Some v -> + eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x v env) rem | None -> None else None | _ -> None) - | Let (x, Block (tag, fields, _, _)) :: rem -> - let fields = Array.map fields ~f:(fun x -> resolve ~info ~env (Pv x)) in + | Let (x, Block (tag, fields, array_or_not, mutability)) :: rem -> + let fields = Array.map ~f:(fun x -> resolve ~info ~env (Pv x)) fields in if Array.exists fields ~f:Option.is_none then None else @@ -773,11 +807,11 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = ~info ~blocks ~target - ~env:(Var.Map.add x (Tuple (tag, fields, Unknown)) env) + ~env:(Var.Map.add x (Val_block (tag, fields, array_or_not, mutability)) env) rem | Let (x, Field (y, i, _)) :: rem -> ( match resolve ~info ~env (Pv y) with - | Some (Tuple (_, fields, _)) when i < Array.length fields -> + | Some (Val_block (_, fields, _, _)) when i < Array.length fields -> eval_block_body ~fuel ~info @@ -790,6 +824,25 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = | Assign _ | Set_field _ | Offset_ref _ | Array_set _ ) :: _ -> None +let rec emit_value update_count x v = + match v with + | Val_constant c -> + incr update_count; + [ Let (x, Constant c) ] + | Val_block (tag, fields, array_or_not, mutability) -> + let instrs, vars = + Array.fold_left + fields + ~init:([], []) + ~f:(fun (instrs, vars) field -> + let v = Code.Var.fresh () in + let field_instrs = emit_value update_count v field in + (instrs @ field_instrs, v :: vars)) + in + let vars = Array.of_list (List.rev vars) in + incr update_count; + instrs @ [ Let (x, Block (tag, vars, array_or_not, mutability)) ] + let eval_instr update_count inline_constant ~target info ~blocks i = match i with | Let (x, Prim (Extern (("caml_equal" | "caml_notequal") as prim), [ y; z ])) -> ( @@ -969,7 +1022,7 @@ let eval_instr update_count inline_constant ~target info ~blocks i = Pc c | Some (Int32 _ | NativeInt _ | NativeString _), `Wasm -> (* Avoid duplicating the constant here as it would cause an - allocation *) + allocation *) arg | Some ((Int32 _ | NativeInt _) as c), `JavaScript -> incr inline_constant; @@ -983,7 +1036,7 @@ let eval_instr update_count inline_constant ~target info ~blocks i = Pc c | Some _, _ (* do not be duplicated other constant as - they're not represented with constant in javascript. *) + they're not represented with constant in javascript. *) | None, _ -> arg)) ) ) ]) | Let (x, Apply { f; args; _ }) -> ( @@ -998,24 +1051,20 @@ let eval_instr update_count inline_constant ~target info ~blocks i = if debug_static_eval () then Format.eprintf "ZZZ %a@." Code.Var.print f; let env = List.fold_left2 - ~f:(fun s x v -> Var.Map.add x (Option.get v) s) + ~f:(fun s x v -> Var.Map.add x (Val_constant (Option.get v)) s) params args ~init:Var.Map.empty in let fuel = ref static_eval_fuel in match eval_block ~fuel ~info ~blocks ~target ~env pc args' with - | Some (Tuple _ | Float_array _) -> - (* The outcome is not supposed to be a block; emitting one as - a constant loses the array_or_not / field-type information - the back-end needs (e.g. for float records). *) - [ i ] - | Some c -> + | Some v -> if debug_static_eval () then Format.eprintf "===> STATIC@."; - let c = Constant c in - Flow.Info.update_def info x c; - incr update_count; - [ Let (x, c) ] + let res_instrs = emit_value update_count x v in + (match v with + | Val_constant c -> Flow.Info.update_def info x (Constant c) + | Val_block _ -> ()); + res_instrs | None -> [ i ]) else [ i ] | _ -> [ i ]) diff --git a/compiler/tests-compiler/gh2217.ml b/compiler/tests-compiler/gh2217.ml index 2e4bf523da..7bfd1a5cb5 100644 --- a/compiler/tests-compiler/gh2217.ml +++ b/compiler/tests-compiler/gh2217.ml @@ -31,10 +31,6 @@ let g () = print_fun_decl p (Some "g"); [%expect {| - function g(_a_){ - function f(b){return [0, b];} - var _a_ = f(0)[1]; - return [0, f(1)[1], _a_]; - } + function g(param){return [0, 1, 0];} //end |}] diff --git a/compiler/tests-full/stdlib.cma.expected.js b/compiler/tests-full/stdlib.cma.expected.js index a621e7ac4e..b0c7ce3fd8 100644 --- a/compiler/tests-full/stdlib.cma.expected.js +++ b/compiler/tests-full/stdlib.cma.expected.js @@ -13308,7 +13308,7 @@ i) /*<>*/ ; } function of_seq(g){ - var s = /*<>*/ create(0); + var s = /*<>*/ [0, 0, 0]; /*<>*/ add_seq(s, g); /*<>*/ return s; /*<>*/ } @@ -13519,7 +13519,7 @@ i) /*<>*/ ; } function of_seq(g){ - var q = /*<>*/ create(0); + var q = /*<>*/ [0, 0, 0, 0]; /*<>*/ add_seq(q, g); /*<>*/ return q; /*<>*/ } From 9a4a3214f495c6011d9b03be89a4a44baedf3199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 26 May 2026 16:12:37 +0200 Subject: [PATCH 14/14] Refactor static evaluation to use flat Val_block representation and avoid nested Val_blocks --- compiler/lib/eval.ml | 69 ++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index cc16527f2d..0814633d4e 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -581,13 +581,7 @@ let constant_equal a b = type value = | Val_constant of constant - | Val_block of int * value array * array_or_not * mutability - -let rec value_of_constant c = - match c with - | Tuple (tag, fields, array_or_not) -> - Val_block (tag, Array.map ~f:value_of_constant fields, array_or_not, Immutable) - | c -> Val_constant c + | Val_block of int * constant array * array_or_not * mutability let static_eval_fuel = 1000 @@ -644,7 +638,7 @@ and resolve ~info ~env ?(eq = constant_equal) a = | None -> ( match the_const_of ~eq info a with | None -> None - | Some c -> Some (value_of_constant c)) + | Some c -> Some (Val_constant c)) and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = (if debug_static_eval () @@ -704,8 +698,7 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = rem) | Let (x, Prim (Extern "caml_obj_tag", [ y ])) :: rem -> ( match resolve ~info ~env y with - | Some (Val_constant (Tuple (tag, _, _))) - | Some (Val_block (tag, _, _, _)) -> + | Some (Val_constant (Tuple (tag, _, _))) | Some (Val_block (tag, _, _, _)) -> eval_block_body ~fuel ~info @@ -738,18 +731,19 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = | Some (Val_constant c) -> c | _ -> assert false) in - let res = - eval_prim - ~target - ( prim - , prim_args'' ) - in + let res = eval_prim ~target (prim, prim_args'') in match res with | None -> if debug_static_eval () then Format.eprintf "INSTR %a@." Code.Print.instr i; None | Some c -> - eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x (Val_constant c) env) rem) + eval_block_body + ~fuel + ~info + ~blocks + ~target + ~env:(Var.Map.add x (Val_constant c) env) + rem) | Let (x, Constant c) :: rem -> ( match c with | Float _ @@ -760,16 +754,14 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = | NativeInt _ | NativeString _ | Float_array _ + | Tuple _ | Null_ -> - eval_block_body ~fuel ~info ~blocks ~target ~env:(Var.Map.add x (Val_constant c) env) rem - | Tuple (tag, fields, array_or_not) -> - let fields = Array.map ~f:value_of_constant fields in eval_block_body ~fuel ~info ~blocks ~target - ~env:(Var.Map.add x (Val_block (tag, fields, array_or_not, Immutable)) env) + ~env:(Var.Map.add x (Val_constant c) env) rem | String _ -> None) | Let (x, Apply { f; args; _ }) :: rem -> ( @@ -794,13 +786,16 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = | _ -> None) | Let (x, Block (tag, fields, array_or_not, mutability)) :: rem -> let fields = Array.map ~f:(fun x -> resolve ~info ~env (Pv x)) fields in - if Array.exists fields ~f:Option.is_none + if + Array.exists fields ~f:(function + | Some (Val_constant _) -> false + | _ -> true) then None else let fields = Array.map fields ~f:(function - | Some c -> c - | None -> assert false) + | Some (Val_constant c) -> c + | _ -> assert false) in eval_block_body ~fuel @@ -817,27 +812,33 @@ and eval_block_body ~fuel ~info ~blocks ~target ~env instrs = ~info ~blocks ~target - ~env:(Var.Map.add x fields.(i) env) + ~env:(Var.Map.add x (Val_constant fields.(i)) env) + rem + | Some (Val_constant (Tuple (_, fields, _))) when i < Array.length fields -> + eval_block_body + ~fuel + ~info + ~blocks + ~target + ~env:(Var.Map.add x (Val_constant fields.(i)) env) rem | _ -> None) | ( Let (_, (Closure _ | Special _)) | Assign _ | Set_field _ | Offset_ref _ | Array_set _ ) :: _ -> None -let rec emit_value update_count x v = +let emit_value update_count x v = match v with | Val_constant c -> incr update_count; [ Let (x, Constant c) ] | Val_block (tag, fields, array_or_not, mutability) -> let instrs, vars = - Array.fold_left - fields - ~init:([], []) - ~f:(fun (instrs, vars) field -> + Array.fold_left fields ~init:([], []) ~f:(fun (instrs, vars) c -> let v = Code.Var.fresh () in - let field_instrs = emit_value update_count v field in - (instrs @ field_instrs, v :: vars)) + incr update_count; + let field_instr = Let (v, Constant c) in + instrs @ [ field_instr ], v :: vars) in let vars = Array.of_list (List.rev vars) in incr update_count; @@ -1062,8 +1063,8 @@ let eval_instr update_count inline_constant ~target info ~blocks i = if debug_static_eval () then Format.eprintf "===> STATIC@."; let res_instrs = emit_value update_count x v in (match v with - | Val_constant c -> Flow.Info.update_def info x (Constant c) - | Val_block _ -> ()); + | Val_constant c -> Flow.Info.update_def info x (Constant c) + | Val_block _ -> ()); res_instrs | None -> [ i ]) else [ i ]