Skip to content

Function optimizations #2054

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
97bb1dd
Clean-up: closures will not explicitly contain their arity
vouillon Jun 19, 2025
f6324ab
Function call analysis
vouillon Jun 19, 2025
df67de0
Do not store the code pointer in the closure if we know we don't need it
vouillon Jun 19, 2025
2d5895c
Add debug timing for Call_graph_analysis and Typing
vouillon Jul 9, 2025
b39946d
Wasm output: accept Seq anywhere
vouillon Jul 4, 2025
f7ad7b5
Wasm code generation: separate functions to allocate regular blocks a…
vouillon Jul 4, 2025
e5c0835
Unboxed numbers
vouillon Jul 2, 2025
a322c95
When a function is only called directy, pass its integer parameters a…
vouillon Jun 22, 2025
8b0b255
Handle dummy value through type coercion
vouillon Jul 5, 2025
401945d
Number unboxing in function parameters
vouillon Jul 5, 2025
d7e113e
Type analysis: small API change
vouillon Jul 10, 2025
e7d04ea
Unboxed returned values
vouillon Jun 22, 2025
f846c11
Disable tail-call optimization for Math builtins
vouillon Jul 10, 2025
abfaaf4
Reference unboxing
vouillon Apr 29, 2025
d19c1d4
Runtime: fix annotation of caml_compare
vouillon Apr 23, 2025
aeaba57
Wasm: update comparison primitives to return an i32
vouillon Apr 23, 2025
8212890
Wasm: specialization of number comparisons
vouillon Apr 23, 2025
19ffadd
Inline min/max
vouillon Jul 7, 2025
f28d06e
Runtime: fix annotation of caml_lxm_next
vouillon Jul 9, 2025
36c3387
More refined type for caml_ba_uint8_* operations
vouillon Jun 13, 2025
85d8239
More refined type for caml_string/bytes_get/set* operations
vouillon Jul 17, 2025
af45c96
More refined type for caml_ldexp_float
vouillon Jul 17, 2025
260d742
Wasm: specialization of bigarray accesses
vouillon May 12, 2025
89ca989
Wasm/runtime: put back caml_string_of_array
vouillon Jul 16, 2025
d7de337
fixup! Wasm: specialization of bigarray accesses
vouillon Jul 18, 2025
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
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Features/Changes
* Compiler: exit-loop-early in more cases (#2077)
* Compiler/wasm: omit code pointer from closures when not used (#2059)

## Bug fixes
* Runtime/wasm: add back legacy function caml_string_of_array (#2081)

# 6.1.1 (2025-07-07) - Lille

Expand Down
86 changes: 86 additions & 0 deletions compiler/lib-wasm/call_graph_analysis.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
open! Stdlib
open Code

let debug = Debug.find "call-graph"

let times = Debug.find "times"

let block_deps ~info ~non_escaping ~ambiguous ~blocks pc =
let block = Addr.Map.find pc blocks in
List.iter block.body ~f:(fun i ->
match i with
| Let (_, Apply { f; _ }) -> (
try
match Var.Tbl.get info.Global_flow.info_approximation f with
| Top -> ()
| Values { known; others } ->
if others || Var.Set.cardinal known > 1
then Var.Set.iter (fun x -> Var.Hashtbl.replace ambiguous x ()) known;
if debug ()
then
Format.eprintf
"CALL others:%b known:%d@."
others
(Var.Set.cardinal known)
with Invalid_argument _ -> ())
| Let (x, Closure _) -> (
match Var.Tbl.get info.Global_flow.info_approximation x with
| Top -> ()
| Values { known; others } ->
if Var.Set.cardinal known = 1 && (not others) && Var.Set.mem x known
then (
let may_escape = Var.ISet.mem info.Global_flow.info_may_escape x in
if debug () then Format.eprintf "CLOSURE may-escape:%b@." may_escape;
if not may_escape then Var.Hashtbl.replace non_escaping x ()))
| Let (_, (Prim _ | Block _ | Constant _ | Field _ | Special _))
| Event _ | Assign _ | Set_field _ | Offset_ref _ | Array_set _ -> ())

type t =
{ unambiguous_non_escaping : unit Var.Hashtbl.t
; has_tail_calls : unit Var.Hashtbl.t
}

let direct_calls_only info f =
Config.Flag.optcall () && Var.Hashtbl.mem info.unambiguous_non_escaping f

let has_tail_calls info f = Var.Hashtbl.mem info.has_tail_calls f

let f p info =
let t = Timer.make () in
let non_escaping = Var.Hashtbl.create 128 in
let ambiguous = Var.Hashtbl.create 128 in
let has_tail_calls = Var.Hashtbl.create 128 in
fold_closures
p
(fun name_opt _ (pc, _) _ () ->
traverse
{ fold = Code.fold_children }
(fun pc () -> block_deps ~info ~non_escaping ~ambiguous ~blocks:p.blocks pc)
pc
p.blocks
();
Option.iter
~f:(fun f ->
traverse
{ fold = Code.fold_children }
(fun pc () ->
let block = Addr.Map.find pc p.blocks in
match block.branch with
| Return x -> (
match last_instr block.body with
| Some (Let (x', Apply _)) when Code.Var.equal x x' ->
Var.Hashtbl.replace has_tail_calls f ()
| _ -> ())
| _ -> ())
pc
p.blocks
())
name_opt)
();
if debug ()
then Format.eprintf "SUMMARY non-escaping:%d" (Var.Hashtbl.length non_escaping);
Var.Hashtbl.iter (fun x () -> Var.Hashtbl.remove non_escaping x) ambiguous;
if debug ()
then Format.eprintf " unambiguous-non-escaping:%d@." (Var.Hashtbl.length non_escaping);
if times () then Format.eprintf " call graph analysis: %a@." Timer.print t;
{ unambiguous_non_escaping = non_escaping; has_tail_calls }
7 changes: 7 additions & 0 deletions compiler/lib-wasm/call_graph_analysis.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type t

val direct_calls_only : t -> Code.Var.t -> bool

val has_tail_calls : t -> Code.Var.t -> bool

val f : Code.program -> Global_flow.info -> t
50 changes: 28 additions & 22 deletions compiler/lib-wasm/code_generation.ml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type context =
; mutable globalized_variables : Var.Set.t
; value_type : W.value_type
; mutable unit_name : string option
; mutable no_tail_call : unit Var.Hashtbl.t
}

let make_context ~value_type =
Expand All @@ -82,6 +83,7 @@ let make_context ~value_type =
; globalized_variables = Var.Set.empty
; value_type
; unit_name = None
; no_tail_call = Var.Hashtbl.create 16
}

type var =
Expand Down Expand Up @@ -224,27 +226,30 @@ let get_global name =
| Some { init; _ } -> init
| _ -> None)

let register_import ?(import_module = "env") ~name typ st =
( (try
let x, typ' =
StringMap.find name (StringMap.find import_module st.context.imports)
in
(*ZZZ error message*)
assert (Poly.equal typ typ');
x
with Not_found ->
let x = Var.fresh_n name in
st.context.imports <-
StringMap.update
import_module
(fun m ->
Some
(match m with
| None -> StringMap.singleton name (x, typ)
| Some m -> StringMap.add name (x, typ) m))
st.context.imports;
x)
, st )
let register_import ?(allow_tail_call = true) ?(import_module = "env") ~name typ st =
let x =
try
let x, typ' =
StringMap.find name (StringMap.find import_module st.context.imports)
in
(*ZZZ error message*)
assert (Poly.equal typ typ');
x
with Not_found ->
let x = Var.fresh_n name in
st.context.imports <-
StringMap.update
import_module
(fun m ->
Some
(match m with
| None -> StringMap.singleton name (x, typ)
| Some m -> StringMap.add name (x, typ) m))
st.context.imports;
x
in
if not allow_tail_call then Var.Hashtbl.replace st.context.no_tail_call x ();
x, st

let register_init_code code st =
let st' = { var_count = 0; vars = Var.Map.empty; instrs = []; context = st.context } in
Expand Down Expand Up @@ -368,6 +373,7 @@ module Arith = struct
(match e, e' with
| W.Const (I32 n), W.Const (I32 n') when Int32.(n' < 31l) ->
W.Const (I32 (Int32.shift_left n (Int32.to_int n')))
| _, W.Const (I32 0l) -> e
| _ -> W.BinOp (I32 Shl, e, e'))

let ( lsr ) = binary (Shr U)
Expand Down Expand Up @@ -715,7 +721,7 @@ let function_body ~context ~param_names ~body =
| Local (i, x, typ) -> local_types.(i) <- x, typ
| Expr _ -> ())
st.vars;
let body = Tail_call.f body in
let body = Tail_call.f ~no_tail_call:context.no_tail_call body in
let param_count = List.length param_names in
let locals =
local_types
Expand Down
7 changes: 6 additions & 1 deletion compiler/lib-wasm/code_generation.mli
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ type context =
; mutable globalized_variables : Code.Var.Set.t
; value_type : Wasm_ast.value_type
; mutable unit_name : string option
; mutable no_tail_call : unit Code.Var.Hashtbl.t
}

val make_context : value_type:Wasm_ast.value_type -> context
Expand Down Expand Up @@ -156,7 +157,11 @@ val register_type : string -> (unit -> type_def t) -> Wasm_ast.var t
val heap_type_sub : Wasm_ast.heap_type -> Wasm_ast.heap_type -> bool t

val register_import :
?import_module:string -> name:string -> Wasm_ast.import_desc -> Wasm_ast.var t
?allow_tail_call:bool
-> ?import_module:string
-> name:string
-> Wasm_ast.import_desc
-> Wasm_ast.var t

val register_global :
Wasm_ast.var
Expand Down
9 changes: 1 addition & 8 deletions compiler/lib-wasm/curry.ml
Original file line number Diff line number Diff line change
Expand Up @@ -293,14 +293,7 @@ module Make (Target : Target_sig.S) = struct
(fun ~typ closure ->
let* l = expression_list load l in
call ?typ ~cps:true ~arity closure l)
(let* args =
(* We don't need the deadcode sentinal when the tag is 0 *)
Memory.allocate
~tag:0
~deadcode_sentinal:(Code.Var.fresh ())
~load
(List.map ~f:(fun x -> `Var x) (List.tl l))
in
(let* args = Memory.allocate ~tag:0 (expression_list load (List.tl l)) in
let* make_iterator =
register_import ~name:"caml_apply_continuation" (Fun (Type.primitive_type 1))
in
Expand Down
Loading
Loading