diff --git a/compiler/lib-wasm/code_generation.ml b/compiler/lib-wasm/code_generation.ml index 4efeb11a1b..5185d815e3 100644 --- a/compiler/lib-wasm/code_generation.ml +++ b/compiler/lib-wasm/code_generation.ml @@ -193,6 +193,68 @@ let heap_type_sub (ty : W.heap_type) (ty' : W.heap_type) st = (* I31, struct, array and none have no other subtype *) | _, (I31 | Type _ | Struct | Array | None_) -> false, st +let rec type_index_lub ty ty' st = + (* Find the LUB efficiently by taking advantage of the fact that + types are defined after their supertypes, making their variables + compare greater. *) + let c = Var.compare ty ty' in + if c > 0 + then type_index_lub ty' ty st + else if c = 0 + then Some ty + else + let type_field = Var.Hashtbl.find st.context.types ty' in + match type_field.supertype with + | None -> None + | Some ty'' -> + assert (Var.compare ty'' ty' < 0); + type_index_lub ty ty'' st + +let heap_type_lub (ty : W.heap_type) (ty' : W.heap_type) = + match ty, ty' with + | (Func | Extern), _ | _, (Func | Extern) -> assert false + | None_, _ -> return ty' + | _, None_ | Struct, Struct | Array, Array -> return ty + | Any, _ | _, Any -> return W.Any + | Eq, _ + | _, Eq + | (Struct | Array | Type _), I31 + | I31, (Struct | Array | Type _) + | Struct, Array + | Array, Struct -> return (Eq : W.heap_type) + | Struct, Type t | Type t, Struct -> ( + fun st -> + let type_field = Var.Hashtbl.find st.context.types t in + match type_field.typ with + | Struct _ -> W.Struct, st + | Array _ | Func _ -> W.Eq, st) + | Array, Type t | Type t, Array -> ( + fun st -> + let type_field = Var.Hashtbl.find st.context.types t in + match type_field.typ with + | Array _ -> W.Struct, st + | Struct _ | Func _ -> W.Eq, st) + | Type t, Type t' -> ( + let* r = fun st -> type_index_lub t t' st, st in + match r with + | Some t'' -> return (Type t'' : W.heap_type) + | None -> ( + fun st -> + let type_field = Var.Hashtbl.find st.context.types t in + let type_field' = Var.Hashtbl.find st.context.types t' in + match type_field.typ, type_field'.typ with + | Struct _, Struct _ -> (Struct : W.heap_type), st + | Array _, Array _ -> W.Array, st + | (Array _ | Struct _ | Func _), (Array _ | Struct _ | Func _) -> W.Eq, st)) + | I31, I31 -> return W.I31 + +let value_type_lub (ty : W.value_type) (ty' : W.value_type) = + match ty, ty' with + | Ref { nullable; typ }, Ref { nullable = nullable'; typ = typ' } -> + let* typ = heap_type_lub typ typ' in + return (W.Ref { nullable = nullable || nullable'; typ }) + | _ -> assert false + let register_global name ?exported_name ?(constant = false) typ init st = st.context.other_fields <- W.Global { name; exported_name; typ; init } :: st.context.other_fields; @@ -705,7 +767,7 @@ let init_code context = instrs context.init_code let function_body ~context ~param_names ~body = let st = { var_count = 0; vars = Var.Map.empty; instrs = []; context } in - let (), st = body st in + let res, st = body st in let local_count, body = st.var_count, List.rev st.instrs in let local_types = Array.make local_count (Var.fresh (), None) in List.iteri ~f:(fun i x -> local_types.(i) <- x, None) param_names; @@ -723,4 +785,10 @@ let function_body ~context ~param_names ~body = |> (fun a -> Array.sub a ~pos:param_count ~len:(Array.length a - param_count)) |> Array.to_list in - locals, body + locals, res, body + +let eval ~context e = + let st = { var_count = 0; vars = Var.Map.empty; instrs = []; context } in + let r, st = e st in + assert (st.var_count = 0 && List.is_empty st.instrs); + r diff --git a/compiler/lib-wasm/code_generation.mli b/compiler/lib-wasm/code_generation.mli index 8655450dda..621572843f 100644 --- a/compiler/lib-wasm/code_generation.mli +++ b/compiler/lib-wasm/code_generation.mli @@ -155,6 +155,8 @@ 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 value_type_lub : Wasm_ast.value_type -> Wasm_ast.value_type -> Wasm_ast.value_type t + val register_import : ?import_module:string -> name:string -> Wasm_ast.import_desc -> Wasm_ast.var t @@ -195,13 +197,17 @@ val need_dummy_fun : cps:bool -> arity:int -> Code.Var.t t val function_body : context:context -> param_names:Code.Var.t list - -> body:unit t - -> (Wasm_ast.var * Wasm_ast.value_type) list * Wasm_ast.instruction list + -> body:'a t + -> (Wasm_ast.var * Wasm_ast.value_type) list * 'a * Wasm_ast.instruction list val variable_type : Code.Var.t -> Wasm_ast.value_type option t +val expression_type : Wasm_ast.expression -> Wasm_ast.value_type option t + val array_placeholder : Code.Var.t -> expression val default_value : Wasm_ast.value_type -> (Wasm_ast.expression * Wasm_ast.value_type * Wasm_ast.ref_type option) t + +val eval : context:context -> 'a t -> 'a diff --git a/compiler/lib-wasm/curry.ml b/compiler/lib-wasm/curry.ml index b6d5ab0cab..c75bd22788 100644 --- a/compiler/lib-wasm/curry.ml +++ b/compiler/lib-wasm/curry.ml @@ -95,11 +95,11 @@ module Make (Target : Target_sig.S) = struct loop m [] f None in let param_names = args @ [ f ] in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name ; exported_name = None - ; typ = None + ; typ = Some (eval ~context (Type.function_type ~cps:false 1)) ; signature = Type.func_type 1 ; param_names ; locals @@ -130,11 +130,11 @@ module Make (Target : Target_sig.S) = struct push (Closure.curry_allocate ~cps:false ~arity m ~f:name' ~closure:f ~arg:x) in let param_names = [ x; f ] in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name ; exported_name = None - ; typ = None + ; typ = Some (eval ~context (Type.function_type ~cps:false 1)) ; signature = Type.func_type 1 ; param_names ; locals @@ -181,11 +181,11 @@ module Make (Target : Target_sig.S) = struct loop m [] f None in let param_names = args @ [ f ] in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name ; exported_name = None - ; typ = None + ; typ = Some (eval ~context (Type.function_type ~cps:true 1)) ; signature = Type.func_type 2 ; param_names ; locals @@ -220,11 +220,11 @@ module Make (Target : Target_sig.S) = struct instr (W.Return (Some c)) in let param_names = [ x; cont; f ] in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name ; exported_name = None - ; typ = None + ; typ = Some (eval ~context (Type.function_type ~cps:true 1)) ; signature = Type.func_type 2 ; param_names ; locals @@ -264,7 +264,7 @@ module Make (Target : Target_sig.S) = struct build_applies (load f) l) in let param_names = l @ [ f ] in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name ; exported_name = None @@ -312,7 +312,7 @@ module Make (Target : Target_sig.S) = struct push (call ~cps:true ~arity:2 (load f) [ x; iterate ])) in let param_names = l @ [ f ] in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name ; exported_name = None @@ -347,11 +347,13 @@ module Make (Target : Target_sig.S) = struct instr (W.Return (Some e)) in let param_names = l @ [ f ] in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name ; exported_name = None - ; typ = None + ; typ = + Some + (eval ~context (Type.function_type ~cps (if cps then arity - 1 else arity))) ; signature = Type.func_type arity ; param_names ; locals diff --git a/compiler/lib-wasm/gc_target.ml b/compiler/lib-wasm/gc_target.ml index 36ca054e4c..b78aadccd3 100644 --- a/compiler/lib-wasm/gc_target.ml +++ b/compiler/lib-wasm/gc_target.ml @@ -205,12 +205,35 @@ module Type = struct let primitive_type n = { W.params = List.init ~len:n ~f:(fun _ -> value); result = [ value ] } - let func_type n = primitive_type (n + 1) - - let function_type ~cps n = - let n = if cps then n + 1 else n in - register_type (Printf.sprintf "function_%d" n) (fun () -> - return { supertype = None; final = true; typ = W.Func (func_type n) }) + let func_type ?(ret = value) n = + { W.params = List.init ~len:(n + 1) ~f:(fun _ -> value); result = [ ret ] } + + let rec function_type ~cps ?ret n = + let n' = if cps then n + 1 else n in + let ret_str = + match ret with + | None -> "" + | Some (W.Ref { nullable = false; typ }) -> ( + match typ with + | Eq -> "_eq" (*ZZZ remove ret in that case*) + | I31 -> "_i31" + | Struct -> "_struct" + | Array -> "_array" + | None_ -> "_none" + | Type v -> ( + match Code.Var.get_name v with + | None -> assert false + | Some name -> "_" ^ name) + | _ -> assert false) + | _ -> assert false + in + register_type (Printf.sprintf "function_%d%s" n' ret_str) (fun () -> + match ret with + | None -> return { supertype = None; final = false; typ = W.Func (func_type n') } + | Some ret -> + let* super = function_type ~cps n in + return + { supertype = Some super; final = false; typ = W.Func (func_type ~ret n') }) let closure_common_fields ~cps = let* fun_ty = function_type ~cps 1 in @@ -606,6 +629,14 @@ module Value = struct let int_asr = Arith.( asr ) end +let store_in_global ?(name = "const") c = + let name = Code.Var.fresh_n name in + let* typ = expression_type c in + let* () = + register_global name { mut = false; typ = Option.value ~default:Type.value typ } c + in + return (W.GlobalGet name) + module Memory = struct let wasm_cast ty e = let* e = e in @@ -862,7 +893,9 @@ module Memory = struct in let* ty = Type.int32_type in let* e = e in - return (W.StructNew (ty, [ GlobalGet int32_ops; e ])) + let e' = W.StructNew (ty, [ GlobalGet int32_ops; e ]) in + let* b = is_small_constant e in + if b then store_in_global e' else return e' let box_int32 e = make_int32 ~kind:`Int32 e @@ -880,7 +913,9 @@ module Memory = struct in let* ty = Type.int64_type in let* e = e in - return (W.StructNew (ty, [ GlobalGet int64_ops; e ])) + let e' = W.StructNew (ty, [ GlobalGet int64_ops; e ]) in + let* b = is_small_constant e in + if b then store_in_global e' else return e' let box_int64 e = make_int64 e @@ -900,11 +935,6 @@ module Constant = struct strings are encoded as a sequence of bytes in the wasm module. *) let string_length_threshold = 64 - let store_in_global ?(name = "const") c = - let name = Code.Var.fresh_n name in - let* () = register_global name { mut = false; typ = Type.value } c in - return (W.GlobalGet name) - let byte_string s = let b = Buffer.create (String.length s) in String.iter s ~f:(function @@ -1037,13 +1067,15 @@ module Constant = struct if b then return c else store_in_global c | Const_named name -> store_in_global ~name c | Mutated -> + let* typ = Type.string_type in let name = Code.Var.fresh_n "const" in + let* placeholder = array_placeholder typ in let* () = register_global ~constant:true name - { mut = true; typ = Type.value } - (W.RefI31 (Const (I32 0l))) + { mut = true; typ = Ref { nullable = false; typ = Type typ } } + placeholder in let* () = register_init_code (instr (W.GlobalSet (name, c))) in return (W.GlobalGet name)) diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index fa196e8571..bc3c65659a 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -1089,7 +1089,7 @@ module Generate (Target : Target_sig.S) = struct then handler else let* () = handler in - instr (W.Return (Some (RefI31 (Const (I32 0l))))) + instr W.Unreachable else body ~result_typ ~fall_through ~context let wrap_with_handlers p pc ~result_typ ~fall_through ~context body = @@ -1115,6 +1115,74 @@ module Generate (Target : Target_sig.S) = struct ~fall_through ~context + let return_type (p : Code.program) name_opt ~toplevel_name pc = + let* ty = + Code.preorder_traverse + { fold = Code.fold_children } + (fun pc ty -> + let block = Code.Addr.Map.find pc p.blocks in + match block.branch with + | Return x -> + let* ty = ty in + let* ty' = variable_type x in + value_type_lub + ty + (match Option.value ~default:Type.value ty' with + | I32 -> Ref { nullable = false; typ = I31 } + | ty' -> ty') + | Stop -> return Type.value + | Raise _ | Branch _ | Cond _ | Switch _ | Pushtrap _ | Poptrap _ -> ty) + pc + p.blocks + (return (W.Ref { nullable = false; typ = None_ })) + in + ignore (name_opt, toplevel_name); + (* + let nm v = Option.value ~default:"???" (Code.Var.get_name v) in + Format.eprintf + "%a: %s@." + (fun f ty -> + match (ty : W.value_type) with + | I32 | I64 | F32 | F64 -> assert false + | Ref { typ; _ } -> ( + match typ with + | Func | Extern -> assert false + | Any -> Format.fprintf f "any" + | Eq -> Format.fprintf f "eq" + | Struct -> Format.fprintf f "struct" + | Array -> Format.fprintf f "array" + | None_ -> Format.fprintf f "none" + | I31 -> Format.fprintf f "i31" + | Type v -> + Format.fprintf f "$%s" (Option.value ~default:"???" (Code.Var.get_name v)) + )) + ty + (nm + (match name_opt with + | None -> toplevel_name + | Some x -> x)); +*) + return ty + + let rec refine_type ~typ (instrs : W.instruction list) : W.instruction list = + match instrs with + | [ i ] -> [ refine_instr ~typ i ] + | [ i; (Event _ as i') ] -> [ refine_instr ~typ i; i' ] + | i :: rem -> i :: refine_type ~typ rem + | [] -> [] + + and refine_instr ~typ i = + match i with + | Block (ty, instrs') -> Block ({ ty with result = [ typ ] }, refine_type ~typ instrs') + | Loop (ty, instrs') -> Loop ({ ty with result = [ typ ] }, refine_type ~typ instrs') + | If (ty, e, instrs', instrs'') -> + If + ( { ty with result = [ typ ] } + , e + , refine_type ~typ instrs' + , refine_type ~typ instrs'' ) + | i -> i + let translate_function p ctx @@ -1287,7 +1355,7 @@ module Generate (Target : Target_sig.S) = struct (match name_opt with | None -> ctx.global_context.globalized_variables <- Globalize.f p g ctx.closures | Some _ -> ()); - let locals, body = + let locals, return_typ, body = function_body ~context:ctx.global_context ~param_names @@ -1309,9 +1377,12 @@ module Generate (Target : Target_sig.S) = struct (fun ~result_typ ~fall_through ~context -> translate_branch result_typ fall_through (-1) cont context) in - match cloc with - | Some loc -> event loc - | None -> return ()) + let* () = + match cloc with + | Some loc -> event loc + | None -> return () + in + return_type p name_opt ~toplevel_name pc) in let locals, body = post_process_function_body ~param_names ~locals body in W.Function @@ -1323,21 +1394,38 @@ module Generate (Target : Target_sig.S) = struct (match name_opt with | None -> Option.map ~f:(fun name -> name ^ ".init") unit_name | Some _ -> None) - ; typ = None + ; typ = + (match name_opt with + | None -> None + | Some f -> + let cps = Var.Set.mem f ctx.in_cps in + Some + (Code_generation.eval + ~context:ctx.global_context + (Type.function_type + ~cps + ?ret: + (if Poly.equal return_typ Type.value + then None + else Some return_typ) + (if cps then param_count - 2 else param_count - 1)))) ; signature = (match name_opt with | None -> Type.primitive_type param_count - | Some _ -> Type.func_type (param_count - 1)) + | Some _ -> Type.func_type ~ret:return_typ (param_count - 1)) ; param_names ; locals - ; body + ; body = + (if Poly.equal return_typ Type.value + then body + else refine_type ~typ:return_typ body) } :: acc let init_function ~context ~to_link = let name = Code.Var.fresh_n "initialize" in let signature = { W.params = []; result = [ Type.value ] } in - let locals, body = + let locals, _, body = function_body ~context ~param_names:[] @@ -1352,7 +1440,9 @@ module Generate (Target : Target_sig.S) = struct in let* () = instr (Drop (Call (f, []))) in cont) - ~init:(instr (Push (RefI31 (Const (I32 0l))))) + ~init: + (let* unit = Value.unit in + instr (Push unit)) to_link) in context.other_fields <- @@ -1370,7 +1460,7 @@ module Generate (Target : Target_sig.S) = struct let entry_point context toplevel_fun entry_name = let signature, param_names, body = entry_point ~toplevel_fun in - let locals, body = function_body ~context ~param_names ~body in + let locals, _, body = function_body ~context ~param_names ~body in W.Function { name = Var.fresh_n "entry_point" ; exported_name = Some entry_name diff --git a/compiler/lib-wasm/target_sig.ml b/compiler/lib-wasm/target_sig.ml index 053e3be066..2ef2ed353e 100644 --- a/compiler/lib-wasm/target_sig.ml +++ b/compiler/lib-wasm/target_sig.ml @@ -100,9 +100,12 @@ module type S = sig module Type : sig val value : Wasm_ast.value_type - val func_type : int -> Wasm_ast.func_type + val func_type : ?ret:Wasm_ast.value_type -> int -> Wasm_ast.func_type val primitive_type : int -> Wasm_ast.func_type + + val function_type : + cps:bool -> ?ret:Wasm_ast.value_type -> int -> Wasm_ast.var Code_generation.t end module Value : sig diff --git a/runtime/wasm/effect.wat b/runtime/wasm/effect.wat index 05bc0ad9c2..52c514b787 100644 --- a/runtime/wasm/effect.wat +++ b/runtime/wasm/effect.wat @@ -44,10 +44,10 @@ (type $block (array (mut (ref eq)))) (type $bytes (array (mut i8))) - (type $function_1 (func (param (ref eq) (ref eq)) (result (ref eq)))) + (type $function_1 (sub (func (param (ref eq) (ref eq)) (result (ref eq))))) (type $closure (sub (struct (;(field i32);) (field (ref $function_1))))) (type $function_3 - (func (param (ref eq) (ref eq) (ref eq) (ref eq)) (result (ref eq)))) + (sub (func (param (ref eq) (ref eq) (ref eq) (ref eq)) (result (ref eq))))) (type $closure_3 (sub $closure (struct (field (ref $function_1)) (field (ref $function_3))))) @@ -65,7 +65,7 @@ (@string $effect_unhandled "Effect.Unhandled") - (func $raise_unhandled + (func $raise_unhandled (type $function_1) (param $eff (ref eq)) (param (ref eq)) (result (ref eq)) (block $null (call $caml_raise_with_arg @@ -297,7 +297,7 @@ (field $eff (ref eq)) (field $cont (ref eq))))) - (func $call_effect_handler + (func $call_effect_handler (type $function_1) (param $tail (ref eq)) (param $venv (ref eq)) (result (ref eq)) (local $env (ref $call_handler_env)) (local $handler (ref $closure_3)) @@ -438,10 +438,10 @@ (@if (= effects "cps") (@then (type $function_2 - (func (param (ref eq) (ref eq) (ref eq)) (result (ref eq)))) + (sub (func (param (ref eq) (ref eq) (ref eq)) (result (ref eq))))) (type $function_4 - (func (param (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) - (result (ref eq)))) + (sub (func (param (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) + (result (ref eq))))) (type $cps_closure (sub (struct (field (ref $function_2))))) (type $cps_closure_0 (sub (struct (field (ref $function_1))))) (type $cps_closure_3 @@ -484,7 +484,7 @@ (struct.get $cps_fiber $exn_stack (global.get $cps_fiber_stack)))) (ref.i31 (i32.const 0))) - (func $raise_exception + (func $raise_exception (type $function_1) (param $exn (ref eq)) (param (ref eq)) (result (ref eq)) (throw $ocaml_exception (local.get $exn))) @@ -506,12 +506,13 @@ (param $exn (ref eq)) (param (ref eq)) (result (ref eq)) (local.get $exn)) - (func $identity (param (ref eq)) (param (ref eq)) (result (ref eq)) + (func $identity (type $function_1) + (param (ref eq)) (param (ref eq)) (result (ref eq)) (local.get 0)) (global $identity (ref $closure) (struct.new $closure (ref.func $identity))) - (func $trampoline_iterator + (func $trampoline_iterator (type $function_1) (param $f (ref eq)) (param $venv (ref eq)) (result (ref eq)) (local $env (ref $iterator)) (local $i i32) (local $args (ref $block)) @@ -531,7 +532,7 @@ (struct.get $cps_closure 0 (ref.cast (ref $cps_closure) (local.get $f))))) - (func $apply_iterator + (func $apply_iterator (type $function_1) (param $f (ref eq)) (param $venv (ref eq)) (result (ref eq)) (local $env (ref $iterator)) (local $i i32) (local $args (ref $block)) @@ -561,7 +562,7 @@ (i32.const 1) (ref.cast (ref $block) (local.get $args)))) - (func $dummy_cps_fun + (func $dummy_cps_fun (type $function_2) (param (ref eq)) (param (ref eq)) (param (ref eq)) (result (ref eq)) (unreachable)) @@ -749,7 +750,8 @@ (struct.get $cps_closure 0 (ref.cast (ref $cps_closure) (local.get $handler))))) - (func $value_handler (param $x (ref eq)) (param (ref eq)) (result (ref eq)) + (func $value_handler (type $function_1) + (param $x (ref eq)) (param (ref eq)) (result (ref eq)) (return_call $cps_call_handler (struct.get $cps_fiber $value (global.get $cps_fiber_stack)) (local.get $x))) @@ -757,7 +759,8 @@ (global $value_handler (ref $closure) (struct.new $closure (ref.func $value_handler))) - (func $exn_handler (param $x (ref eq)) (param (ref eq)) (result (ref eq)) + (func $exn_handler (type $function_1) + (param $x (ref eq)) (param (ref eq)) (result (ref eq)) (return_call $cps_call_handler (struct.get $cps_fiber $exn (global.get $cps_fiber_stack)) (local.get $x))) diff --git a/runtime/wasm/obj.wat b/runtime/wasm/obj.wat index 5e06a4a5ed..d780e83885 100644 --- a/runtime/wasm/obj.wat +++ b/runtime/wasm/obj.wat @@ -33,12 +33,12 @@ (type $bytes (array (mut i8))) (type $float (struct (field f64))) (type $float_array (array (mut f64))) - (type $function_1 (func (param (ref eq) (ref eq)) (result (ref eq)))) + (type $function_1 (sub (func (param (ref eq) (ref eq)) (result (ref eq))))) (type $closure (sub (struct (;(field i32);) (field (ref $function_1))))) (type $closure_last_arg (sub $closure (struct (;(field i32);) (field (ref $function_1))))) (type $function_2 - (func (param (ref eq) (ref eq) (ref eq)) (result (ref eq)))) + (sub (func (param (ref eq) (ref eq) (ref eq)) (result (ref eq))))) (type $cps_closure (sub (struct (field (ref $function_2))))) (type $cps_closure_last_arg (sub $cps_closure (struct (field (ref $function_2))))) @@ -59,7 +59,7 @@ (field (mut (ref null $closure_2)))))) (type $function_3 - (func (param (ref eq) (ref eq) (ref eq) (ref eq)) (result (ref eq)))) + (sub (func (param (ref eq) (ref eq) (ref eq) (ref eq)) (result (ref eq))))) (type $closure_3 (sub $closure @@ -71,8 +71,8 @@ (field (mut (ref null $closure_3)))))) (type $function_4 - (func (param (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) - (result (ref eq)))) + (sub (func (param (ref eq) (ref eq) (ref eq) (ref eq) (ref eq)) + (result (ref eq))))) (type $closure_4 (sub $closure