diff --git a/CHANGES.md b/CHANGES.md index 4ff15d42ae..8814ee7bda 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,8 @@ * Runtime: allow dynlink of precompiled js with separate compilation (#1676) * Lib: Modify Typed_array API for compatibility with WebAssembly * Compiler: improved global dead code elimination (#2206) +* Compiler: add support for the Wasm backend in parts of the pipeline, in + prevision for the merge of wasm_of_ocaml ## Bug fixes diff --git a/compiler/bin-js_of_ocaml/build_fs.ml b/compiler/bin-js_of_ocaml/build_fs.ml index 94fb9916cc..16ac22a23d 100644 --- a/compiler/bin-js_of_ocaml/build_fs.ml +++ b/compiler/bin-js_of_ocaml/build_fs.ml @@ -78,7 +78,7 @@ function jsoo_create_file_extern(name,content){ ~standalone:true ~wrap_with_fun:`Iife ~link:`Needed - pfs_fmt + ~formatter:pfs_fmt (Parse_bytecode.Debug.create ~include_cmis:false false) code in diff --git a/compiler/bin-js_of_ocaml/check_runtime.ml b/compiler/bin-js_of_ocaml/check_runtime.ml index 56c2c0a3f1..29b541914a 100644 --- a/compiler/bin-js_of_ocaml/check_runtime.ml +++ b/compiler/bin-js_of_ocaml/check_runtime.ml @@ -43,6 +43,7 @@ let print_groups output l = output_string output (Printf.sprintf "%s\n" name))) let f (runtime_files, bytecode, target_env) = + Config.set_target `JavaScript; Linker.reset (); let runtime_files, builtin = List.partition_map runtime_files ~f:(fun name -> diff --git a/compiler/bin-js_of_ocaml/compile.ml b/compiler/bin-js_of_ocaml/compile.ml index 3e01a30974..cc7b2c34c6 100644 --- a/compiler/bin-js_of_ocaml/compile.ml +++ b/compiler/bin-js_of_ocaml/compile.ml @@ -90,6 +90,7 @@ let run } = let include_cmis = toplevel && not no_cmis in let custom_header = common.Jsoo_cmdline.Arg.custom_header in + Config.set_target `JavaScript; Jsoo_cmdline.Arg.eval common; Linker.reset (); (match output_file with @@ -186,7 +187,7 @@ let run let init_pseudo_fs = fs_external && standalone in let sm = match output_file with - | `Stdout, fmt -> + | `Stdout, formatter -> let instr = List.concat [ pseudo_fs_instr `create_file one.debug one.cmis @@ -201,10 +202,10 @@ let run ~link ~wrap_with_fun ?source_map - fmt + ~formatter one.debug code - | `File, fmt -> + | `File, formatter -> let fs_instr1, fs_instr2 = match fs_output with | None -> pseudo_fs_instr `create_file one.debug one.cmis, [] @@ -225,7 +226,7 @@ let run ~link ~wrap_with_fun ?source_map - fmt + ~formatter one.debug code in diff --git a/compiler/bin-js_of_ocaml/link.ml b/compiler/bin-js_of_ocaml/link.ml index 45c2dd0c0e..0cd273b600 100644 --- a/compiler/bin-js_of_ocaml/link.ml +++ b/compiler/bin-js_of_ocaml/link.ml @@ -150,6 +150,7 @@ let f ; mklib ; toplevel } = + Config.set_target `JavaScript; Jsoo_cmdline.Arg.eval common; Linker.reset (); let with_output f = diff --git a/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml b/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml index 8ff9e3d8eb..d98e70651e 100644 --- a/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml +++ b/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml @@ -26,6 +26,9 @@ let normalize_bytecode code = Bytes.to_string b let () = + (match Sys.backend_type with + | Sys.Other "js_of_ocaml" -> Config.set_target `JavaScript + | Sys.(Native | Bytecode | Other _) -> failwith "Expected backend `js_of_ocaml`"); let global = J.pure_js_expr "globalThis" in Config.Flag.set "use-js-string" (Jsoo_runtime.Sys.Config.use_js_string ()); Config.Flag.set "effects" (Jsoo_runtime.Sys.Config.effects ()); diff --git a/compiler/lib-runtime-files/gen/gen.ml b/compiler/lib-runtime-files/gen/gen.ml index bdf330f4e8..7f920cc953 100644 --- a/compiler/lib-runtime-files/gen/gen.ml +++ b/compiler/lib-runtime-files/gen/gen.ml @@ -51,6 +51,7 @@ let rec list_product l = let bool = [ true; false ] let () = + Js_of_ocaml_compiler.Config.set_target `JavaScript; let () = set_binary_mode_out stdout true in match Array.to_list Sys.argv with | [] -> assert false diff --git a/compiler/lib/code.ml b/compiler/lib/code.ml index 5b52f0015a..cb3bf0e922 100644 --- a/compiler/lib/code.ml +++ b/compiler/lib/code.ml @@ -856,6 +856,7 @@ let with_invariant = Debug.find "invariant" let check_defs = false let invariant { blocks; start; _ } = + let target = Config.target () in if with_invariant () then ( assert (Addr.Map.mem start blocks); @@ -870,6 +871,19 @@ let invariant { blocks; start; _ } = assert (not (Var.ISet.mem defs x)); Var.ISet.add defs x) in + let check_constant = function + | NativeInt _ | Int32 _ -> + assert ( + match target with + | `Wasm -> true + | _ -> false) + | String _ | NativeString _ | Float _ | Float_array _ | Int _ | Int64 _ + | Tuple (_, _, _) -> () + in + let check_prim_arg = function + | Pc c -> check_constant c + | Pv _ -> () + in let check_expr = function | Apply _ -> () | Block (_, _, _, _) -> () @@ -877,8 +891,8 @@ let invariant { blocks; start; _ } = | Closure (l, cont) -> List.iter l ~f:define; check_cont cont - | Constant _ -> () - | Prim (_, _) -> () + | Constant c -> check_constant c + | Prim (_, args) -> List.iter ~f:check_prim_arg args | Special _ -> () in let check_instr (i, _loc) = diff --git a/compiler/lib/config.ml b/compiler/lib/config.ml index 97e8d1fdab..d38b798f69 100644 --- a/compiler/lib/config.ml +++ b/compiler/lib/config.ml @@ -164,7 +164,7 @@ module Param = struct p ~name:"tc" ~desc:"Set tailcall optimisation" - (enum [ "trampoline", TcTrampoline; (* default *) "none", TcNone ]) + (enum [ "trampoline", TcTrampoline (* default *); "none", TcNone ]) let lambda_lifting_threshold = (* When we reach this depth, we start looking for functions to be lifted *) @@ -183,8 +183,12 @@ end (****) -let target_ : [ `JavaScript | `Wasm ] ref = ref `JavaScript +let target_ : [ `JavaScript | `Wasm | `None ] ref = ref `None -let target () = !target_ +let target () = + match !target_ with + | `None -> failwith "target was not set" + | (`JavaScript | `Wasm) as t -> t -let set_target t = target_ := t +let set_target (t : [ `JavaScript | `Wasm ]) = + target_ := (t :> [ `JavaScript | `Wasm | `None ]) diff --git a/compiler/lib/driver.ml b/compiler/lib/driver.ml index 9e812c5024..4d2aa5ba90 100644 --- a/compiler/lib/driver.ml +++ b/compiler/lib/driver.ml @@ -23,6 +23,14 @@ let debug = Debug.find "main" let times = Debug.find "times" +type optimized_result = + { program : Code.program + ; variable_uses : Deadcode.variable_uses + ; trampolined_calls : Effects.trampolined_calls + ; in_cps : Effects.in_cps + ; deadcode_sentinal : Code.Var.t + } + type profile = | O1 | O2 @@ -194,14 +202,13 @@ let generate ~exported_runtime ~wrap_with_fun ~warn_on_unhandled_effect - ~deadcode_sentinal - ((p, live_vars), trampolined_calls, _) = + { program; variable_uses; trampolined_calls; deadcode_sentinal; in_cps = _ } = if times () then Format.eprintf "Start Generation...@."; let should_export = should_export wrap_with_fun in Generate.f - p + program ~exported_runtime - ~live_vars + ~live_vars:variable_uses ~trampolined_calls ~should_export ~warn_on_unhandled_effect @@ -658,13 +665,19 @@ let configure formatter = Code.Var.set_pretty (pretty && not (Config.Flag.shortvar ())); Code.Var.set_stable (Config.Flag.stable_var ()) -let full ~standalone ~wrap_with_fun ~profile ~link ~source_map formatter d p = - let exported_runtime = not standalone in +let link_and_pack ?(standalone = true) ?(wrap_with_fun = `Iife) ?(link = `No) p = let export_runtime = match link with | `All | `All_from _ -> true | `Needed | `No -> false in + p + |> link' ~export_runtime ~standalone ~link + |> pack ~wrap_with_fun ~standalone + |> coloring + |> check_js + +let optimize ~profile p = let deadcode_sentinal = (* If deadcode is disabled, this field is just fresh variable *) Code.Var.fresh_n "dummy" @@ -677,31 +690,31 @@ let full ~standalone ~wrap_with_fun ~profile ~link ~source_map formatter d p = | O3 -> o3) +> exact_calls ~deadcode_sentinal profile +> effects ~deadcode_sentinal - +> map_fst (if Config.Flag.effects () then fun x -> x else Generate_closure.f) + +> map_fst + (match Config.target (), Config.Flag.effects () with + | `JavaScript, false -> Generate_closure.f + | `JavaScript, true | `Wasm, _ -> Fun.id) +> map_fst deadcode' in - let emit = - generate - d - ~exported_runtime - ~wrap_with_fun - ~warn_on_unhandled_effect:standalone - ~deadcode_sentinal - +> link' ~export_runtime ~standalone ~link - +> pack ~wrap_with_fun ~standalone - +> coloring - +> check_js - +> output formatter ~source_map () - in if times () then Format.eprintf "Start Optimizing...@."; let t = Timer.make () in - let r = opt p in + let (program, variable_uses), trampolined_calls, in_cps = opt p in let () = if times () then Format.eprintf " optimizations : %a@." Timer.print t in - emit r + { program; variable_uses; trampolined_calls; in_cps; deadcode_sentinal } + +let full ~standalone ~wrap_with_fun ~profile ~link ~source_map ~formatter d p = + let optimized_code = optimize ~profile p in + let exported_runtime = not standalone in + let emit formatter = + generate d ~exported_runtime ~wrap_with_fun ~warn_on_unhandled_effect:standalone + +> link_and_pack ~standalone ~wrap_with_fun ~link + +> output formatter ~source_map () + in + emit formatter optimized_code -let full_no_source_map ~standalone ~wrap_with_fun ~profile ~link formatter d p = +let full_no_source_map ~formatter ~standalone ~wrap_with_fun ~profile ~link d p = let (_ : Source_map.t option) = - full ~standalone ~wrap_with_fun ~profile ~link ~source_map:None formatter d p + full ~standalone ~wrap_with_fun ~profile ~link ~source_map:None ~formatter d p in () @@ -711,22 +724,22 @@ let f ?(profile = O1) ~link ?source_map - formatter + ~formatter d p = - full ~standalone ~wrap_with_fun ~profile ~link ~source_map formatter d p + full ~standalone ~wrap_with_fun ~profile ~link ~source_map ~formatter d p let f' ?(standalone = true) ?(wrap_with_fun = `Iife) ?(profile = O1) ~link formatter d p = - full_no_source_map ~standalone ~wrap_with_fun ~profile ~link formatter d p + full_no_source_map ~formatter ~standalone ~wrap_with_fun ~profile ~link d p let from_string ~prims ~debug s formatter = let p, d = Parse_bytecode.from_string ~prims ~debug s in full_no_source_map + ~formatter ~standalone:false ~wrap_with_fun:`Anonymous ~profile:O1 ~link:`No - formatter d p diff --git a/compiler/lib/driver.mli b/compiler/lib/driver.mli index f4562f59e7..91f846b989 100644 --- a/compiler/lib/driver.mli +++ b/compiler/lib/driver.mli @@ -20,13 +20,23 @@ type profile +type optimized_result = + { program : Code.program + ; variable_uses : Deadcode.variable_uses + ; trampolined_calls : Effects.trampolined_calls + ; in_cps : Effects.in_cps + ; deadcode_sentinal : Code.Var.t + } + +val optimize : profile:profile -> Code.program -> optimized_result + val f : ?standalone:bool -> ?wrap_with_fun:[ `Iife | `Anonymous | `Named of string ] -> ?profile:profile -> link:[ `All | `All_from of string list | `Needed | `No ] -> ?source_map:Source_map.t - -> Pretty_print.t + -> formatter:Pretty_print.t -> Parse_bytecode.Debug.t -> Code.program -> Source_map.t option @@ -48,6 +58,13 @@ val from_string : -> Pretty_print.t -> unit +val link_and_pack : + ?standalone:bool + -> ?wrap_with_fun:[ `Iife | `Anonymous | `Named of string ] + -> ?link:[ `All | `All_from of string list | `Needed | `No ] + -> Javascript.statement_list + -> Javascript.statement_list + val configure : Pretty_print.t -> unit val profiles : (int * profile) list diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 24c8dbb5ad..433d4d656b 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -29,17 +29,73 @@ let set_static_env s value = Hashtbl.add static_env s value let get_static_env s = try Some (Hashtbl.find static_env s) with Not_found -> None -module Int = Int32 +module type Int = sig + include Arith_ops -let int_binop l f = - match l with - | [ Int i; Int j ] -> Some (Int (f i j)) - | _ -> None + val int_unop : constant list -> (t -> t) -> constant option -let shift l f = - match l with - | [ Int i; Int j ] -> Some (Int (f i (Int32.to_int j land 0x1f))) - | _ -> None + val int_binop : constant list -> (t -> t -> t) -> constant option + + val shift_op : constant list -> (t -> int -> t) -> constant option + + val of_int32_warning_on_overflow : int32 -> t + + val to_int32 : t -> int32 + + val numbits : int +end + +module Int32 = struct + include Int32 + + let int_unop l f = + match l with + | [ Int i ] -> Some (Int (f i)) + | _ -> None + + let int_binop l f = + match l with + | [ Int i; Int j ] -> Some (Int (f i j)) + | _ -> None + + (* For when the underlying function takes an [int] (not [t]) as its second argument *) + let shift_op l f = + match l with + | [ Int i; Int j ] -> Some (Int (f i (to_int j))) + | _ -> None + + let numbits = 32 + + let of_int32_warning_on_overflow = Fun.id + + let to_int32 = Fun.id +end + +module Int31 : Int = struct + include Int31 + + let int_unop l f = + match l with + | [ Int i ] -> Some (Int (to_int32 (f (of_int32_warning_on_overflow i)))) + | _ -> None + + let int_binop l f = + match l with + | [ Int i; Int j ] -> + Some + (Int + (to_int32 + (f (of_int32_warning_on_overflow i) (of_int32_warning_on_overflow j)))) + | _ -> None + + let shift_op l f = + match l with + | [ Int i; Int j ] -> + Some (Int (to_int32 (f (of_int32_warning_on_overflow i) (Int32.to_int j)))) + | _ -> None + + let numbits = 31 +end let float_binop_aux (l : constant list) (f : float -> float -> 'a) : 'a option = let args = @@ -74,7 +130,7 @@ let float_binop_bool l f = | Some b -> bool b | None -> None -let eval_prim x = +let eval_prim ~target x = match x with | Not, [ Int i ] -> bool Int32.(i = 0l) | Lt, [ Int i; Int j ] -> bool Int32.(i < j) @@ -83,22 +139,27 @@ let eval_prim x = | Neq, [ Int i; Int j ] -> bool Int32.(i <> j) | Ult, [ Int i; Int j ] -> bool (Int32.(j < 0l) || Int32.(i < j)) | Extern name, l -> ( + let (module Int : Int) = + match target with + | `JavaScript -> (module Int32) + | `Wasm -> (module Int31) + in let name = Primitive.resolve name in match name, l with (* int *) - | "%int_add", _ -> int_binop l Int.add - | "%int_sub", _ -> int_binop l Int.sub - | "%direct_int_mul", _ -> int_binop l Int.mul + | "%int_add", _ -> Int.int_binop l Int.add + | "%int_sub", _ -> Int.int_binop l Int.sub + | "%direct_int_mul", _ -> Int.int_binop l Int.mul | "%direct_int_div", [ _; Int 0l ] -> None - | "%direct_int_div", _ -> int_binop l Int.div - | "%direct_int_mod", _ -> int_binop l Int.rem - | "%int_and", _ -> int_binop l Int.logand - | "%int_or", _ -> int_binop l Int.logor - | "%int_xor", _ -> int_binop l Int.logxor - | "%int_lsl", _ -> shift l Int.shift_left - | "%int_lsr", _ -> shift l Int.shift_right_logical - | "%int_asr", _ -> shift l Int.shift_right - | "%int_neg", [ Int i ] -> Some (Int (Int.neg i)) + | "%direct_int_div", _ -> Int.int_binop l Int.div + | "%direct_int_mod", _ -> Int.int_binop l Int.rem + | "%int_and", _ -> Int.int_binop l Int.logand + | "%int_or", _ -> Int.int_binop l Int.logor + | "%int_xor", _ -> Int.int_binop l Int.logxor + | "%int_lsl", _ -> Int.shift_op l Int.shift_left + | "%int_lsr", _ -> Int.shift_op l Int.shift_right_logical + | "%int_asr", _ -> Int.shift_op l Int.shift_right + | "%int_neg", _ -> Int.int_unop l Int.neg (* float *) | "caml_eq_float", _ -> float_binop_bool l Float.( = ) | "caml_neq_float", _ -> float_binop_bool l Float.( <> ) @@ -111,9 +172,9 @@ let eval_prim x = | "caml_mul_float", _ -> float_binop l ( *. ) | "caml_div_float", _ -> float_binop l ( /. ) | "caml_fmod_float", _ -> float_binop l mod_float - | "caml_int_of_float", [ Float f ] -> Some (Int (Int32.of_float f)) - | "to_int", [ Float f ] -> Some (Int (Int32.of_float f)) - | "to_int", [ Int i ] -> Some (Int i) + | "caml_int_of_float", [ Float f ] -> + Some + (Int (Int32.of_float f |> Int.of_int32_warning_on_overflow |> Int.to_int32)) (* Math *) | "caml_neg_float", _ -> float_unop l ( ~-. ) | "caml_abs_float", _ -> float_unop l abs_float @@ -131,9 +192,9 @@ let eval_prim x = | "caml_sqrt_float", _ -> float_unop l sqrt | "caml_tan_float", _ -> float_unop l tan | ("caml_string_get" | "caml_string_unsafe_get"), [ String s; Int pos ] -> - let pos = Int.to_int pos in + let pos = Int32.to_int pos in if Config.Flag.safe_string () && pos >= 0 && pos < String.length s - then Some (Int (Int.of_int (Char.code s.[pos]))) + then Some (Int (Int32.of_int (Char.code s.[pos]))) else None | "caml_string_equal", [ String s1; String s2 ] -> bool (String.equal s1 s2) | "caml_string_notequal", [ String s1; String s2 ] -> @@ -143,20 +204,20 @@ let eval_prim x = | Some env -> Some (String env) | None -> None) | "caml_sys_const_word_size", [ _ ] -> Some (Int 32l) - | "caml_sys_const_int_size", [ _ ] -> Some (Int 32l) + | "caml_sys_const_int_size", [ _ ] -> Some (Int (Int32.of_int Int.numbits)) | "caml_sys_const_big_endian", [ _ ] -> Some (Int 0l) | "caml_sys_const_naked_pointers_checked", [ _ ] -> Some (Int 0l) | _ -> None) | _ -> None -let the_length_of info x = +let the_length_of ~target info x = get_approx info (fun x -> match Flow.Info.def info x with | Some (Constant (String s)) -> Some (Int32.of_int (String.length s)) | Some (Prim (Extern "caml_create_string", [ arg ])) - | Some (Prim (Extern "caml_create_bytes", [ arg ])) -> the_int info arg + | Some (Prim (Extern "caml_create_bytes", [ arg ])) -> the_int ~target info arg | None | Some _ -> None) None (fun u v -> @@ -178,6 +239,9 @@ let is_int info x = (fun x -> match Flow.Info.def info x with | Some (Constant (Int _)) -> Y + | Some (Constant (NativeInt _ | Int32 _)) -> + (* These Wasm-specific constants are boxed *) + N | Some (Block (_, _, _, _) | Constant _) -> N | None | Some _ -> Unknown) Unknown @@ -188,6 +252,9 @@ let is_int info x = | _ -> Unknown) x | Pc (Int _) -> Y + | Pc (NativeInt _ | Int32 _) -> + (* These Wasm-specific constants are boxed *) + N | Pc _ -> N let the_tag_of info x get = @@ -258,10 +325,10 @@ let constant_js_equal a b = | Tuple _, _ | _, Tuple _ -> None -let eval_instr info ((x, loc) as i) = +let eval_instr ~target info ((x, loc) as i) = match x with | Let (x, Prim (Extern (("caml_equal" | "caml_notequal") as prim), [ y; z ])) -> ( - match the_const_of info y, the_const_of info z with + match the_const_of ~target info y, the_const_of ~target info z with | Some e1, Some e2 -> ( match Code.Constant.ocaml_equal e1 e2 with | None -> [ i ] @@ -277,7 +344,7 @@ let eval_instr info ((x, loc) as i) = [ Let (x, c), loc ]) | _ -> [ i ]) | Let (x, Prim (Extern ("caml_js_equals" | "caml_js_strict_equals"), [ y; z ])) -> ( - match the_const_of info y, the_const_of info z with + match the_const_of ~target info y, the_const_of ~target info z with | Some e1, Some e2 -> ( match constant_js_equal e1 e2 with | None -> [ i ] @@ -290,7 +357,7 @@ let eval_instr info ((x, loc) as i) = let c = match s with | Pc (String s) -> Some (Int32.of_int (String.length s)) - | Pv v -> the_length_of info v + | Pv v -> the_length_of ~target info v | _ -> None in match c with @@ -330,19 +397,25 @@ let eval_instr info ((x, loc) as i) = | None -> [ i ]) | Let (x, Prim (Extern "caml_sys_const_backend_type", [ _ ])) -> let jsoo = Code.Var.fresh () in - [ Let (jsoo, Constant (String "js_of_ocaml")), noloc + let backend_name = + match target with + | `JavaScript -> "js_of_ocaml" + | `Wasm -> "wasm_of_ocaml" + in + [ Let (jsoo, Constant (String backend_name)), noloc ; Let (x, Block (0, [| jsoo |], NotArray, Immutable)), loc ] | Let (_, Prim (Extern ("%resume" | "%perform" | "%reperform"), _)) -> [ i ] (* We need that the arguments to this primitives remain variables *) | Let (x, Prim (prim, prim_args)) -> ( - let prim_args' = List.map prim_args ~f:(fun x -> the_const_of info x) in + let prim_args' = List.map prim_args ~f:(fun x -> the_const_of ~target info x) in let res = if List.for_all prim_args' ~f:(function | Some _ -> true | _ -> false) then eval_prim + ~target ( prim , List.map prim_args' ~f:(function | Some c -> c @@ -359,14 +432,21 @@ let eval_instr info ((x, loc) as i) = ( x , Prim ( prim - , List.map2 prim_args prim_args' ~f:(fun arg c -> - match c with - | Some ((Int _ | Float _ | NativeString _) as c) -> Pc c - | Some (String _ as c) when Config.Flag.use_js_string () -> Pc c - | Some _ + , List.map2 prim_args prim_args' ~f:(fun arg (c : constant option) -> + match c, target with + | Some (Int _ as c), _ -> Pc c + | Some (Int32 _ | NativeInt _ | NativeString _), `Wasm -> + (* Avoid duplicating the constant here as it would cause an + allocation *) + arg + | Some (Int32 _ | NativeInt _), `JavaScript -> assert false + | Some ((Float _ | NativeString _) as c), `JavaScript -> Pc c + | Some (String _ as c), `JavaScript + when Config.Flag.use_js_string () -> Pc c + | Some _, _ (* do not be duplicated other constant as they're not represented with constant in javascript. *) - | None -> arg) ) ) + | None, _ -> arg) ) ) , loc ) ]) | _ -> [ i ] @@ -488,15 +568,15 @@ let drop_exception_handler blocks = blocks blocks -let eval info blocks = +let eval ~target info blocks = Addr.Map.map (fun block -> - let body = List.concat_map block.body ~f:(eval_instr info) in + let body = List.concat_map block.body ~f:(eval_instr ~target info) in let branch = eval_branch info block.branch in { block with Code.body; Code.branch }) blocks let f info p = - let blocks = eval info p.blocks in + let blocks = eval ~target:(Config.target ()) info p.blocks in let blocks = drop_exception_handler blocks in { p with blocks } diff --git a/compiler/lib/flow.ml b/compiler/lib/flow.ml index 138622b4f7..bf7b079c0c 100644 --- a/compiler/lib/flow.ml +++ b/compiler/lib/flow.ml @@ -343,30 +343,34 @@ let the_def_of info x = (* If [constant_identical a b = true], then the two values cannot be distinguished, i.e., they are not different objects (and [caml_js_equals a b = true]) and if both are floats, they are bitwise equal. *) -let constant_identical a b = - match a, b with - | Int i, Int j -> Int32.equal i j - | Float a, Float b -> Float.bitwise_equal a b - | NativeString a, NativeString b -> Native_string.equal a b - | String a, String b -> Config.Flag.use_js_string () && String.equal a b - | Int _, Float _ | Float _, Int _ -> false +let constant_identical ~(target : [ `JavaScript | `Wasm ]) a b = + match a, b, target with + | Int i, Int j, _ -> Int32.equal i j + | Float a, Float b, `JavaScript -> Float.bitwise_equal a b + | Float _, Float _, `Wasm -> false + | NativeString a, NativeString b, `JavaScript -> Native_string.equal a b + | NativeString _, NativeString _, `Wasm -> + false + (* Native strings are boxed (JavaScript objects) in Wasm and are + possibly different objects *) + | String a, String b, `JavaScript -> Config.Flag.use_js_string () && String.equal a b + | String _, String _, `Wasm -> + false (* Strings are boxed in Wasm and are possibly different objects *) + | Int32 _, Int32 _, `Wasm -> + false (* [Int32]s are boxed in Wasm and are possibly different objects *) + | Int32 _, Int32 _, `JavaScript -> assert false + | NativeInt _, NativeInt _, `Wasm -> + false (* [NativeInt]s are boxed in Wasm and are possibly different objects *) + | NativeInt _, NativeInt _, `JavaScript -> assert false (* All other values may be distinct objects and thus different by [caml_js_equals]. *) - | String _, _ - | _, String _ - | NativeString _, _ - | _, NativeString _ - | Float_array _, _ - | _, Float_array _ - | Int64 _, _ - | _, Int64 _ - | Int32 _, _ - | _, Int32 _ - | NativeInt _, _ - | _, NativeInt _ - | Tuple _, _ - | _, Tuple _ -> false - -let the_const_of info x = + | Int64 _, Int64 _, _ -> false + | Tuple _, Tuple _, _ -> false + | Float_array _, Float_array _, _ -> false + | (Int _ | Float _ | Int64 _ | Int32 _ | NativeInt _), _, _ -> false + | (String _ | NativeString _), _, _ -> false + | (Float_array _ | Tuple _), _, _ -> false + +let the_const_of ~target info x = match x with | Pv x -> get_approx @@ -381,23 +385,23 @@ let the_const_of info x = None (fun u v -> match u, v with - | Some i, Some j when constant_identical i j -> u + | Some i, Some j when constant_identical ~target i j -> u | _ -> None) x | Pc c -> Some c -let the_int info x = - match the_const_of info x with +let the_int ~target info x = + match the_const_of ~target info x with | Some (Int i) -> Some i | _ -> None -let the_string_of info x = - match the_const_of info x with +let the_string_of ~target info x = + match the_const_of info ~target x with | Some (String i) -> Some i | _ -> None -let the_native_string_of info x = - match the_const_of info x with +let the_native_string_of ~target info x = + match the_const_of ~target info x with | Some (NativeString i) -> Some i | _ -> None diff --git a/compiler/lib/flow.mli b/compiler/lib/flow.mli index c2033e0c6c..1dd89af353 100644 --- a/compiler/lib/flow.mli +++ b/compiler/lib/flow.mli @@ -52,12 +52,15 @@ val get_approx : val the_def_of : Info.t -> Code.prim_arg -> Code.expr option -val the_const_of : Info.t -> Code.prim_arg -> Code.constant option +val the_const_of : + target:[ `JavaScript | `Wasm ] -> Info.t -> Code.prim_arg -> Code.constant option -val the_string_of : Info.t -> Code.prim_arg -> string option +val the_string_of : + target:[ `JavaScript | `Wasm ] -> Info.t -> Code.prim_arg -> string option -val the_native_string_of : Info.t -> Code.prim_arg -> Code.Native_string.t option +val the_native_string_of : + target:[ `JavaScript | `Wasm ] -> Info.t -> Code.prim_arg -> Code.Native_string.t option -val the_int : Info.t -> Code.prim_arg -> int32 option +val the_int : target:[ `JavaScript | `Wasm ] -> Info.t -> Code.prim_arg -> int32 option val f : ?skip_param:bool -> Code.program -> Code.program * Info.t diff --git a/compiler/lib/inline.ml b/compiler/lib/inline.ml index c6e8dd4b8e..e6707e50d5 100644 --- a/compiler/lib/inline.ml +++ b/compiler/lib/inline.ml @@ -167,7 +167,7 @@ let rec args_equal xs ys = | x :: xs, Pv y :: ys -> Code.Var.compare x y = 0 && args_equal xs ys | _ -> false -let inline live_vars closures name pc (outer, p) = +let inline ~first_class_primitives live_vars closures name pc (outer, p) = let block = Addr.Map.find pc p.blocks in let body, (outer, branch, p) = List.fold_right @@ -300,7 +300,7 @@ let inline live_vars closures name pc (outer, p) = , (outer, (Branch (fresh_addr, args), No), { p with blocks; free_pc }) ) | _ -> i :: rem, state) - | Let (x, Closure (l, (pc, []))), loc when not (Config.Flag.effects ()) -> ( + | Let (x, Closure (l, (pc, []))), loc when first_class_primitives -> ( let block = Addr.Map.find pc p.blocks in match block with | { body = [ (Let (y, Prim (Extern prim, args)), _loc) ] @@ -323,6 +323,11 @@ let inline live_vars closures name pc (outer, p) = let times = Debug.find "times" let f p live_vars = + let first_class_primitives = + match Config.target () with + | `JavaScript -> not (Config.Flag.effects ()) + | `Wasm -> false + in Code.invariant p; let t = Timer.make () in let closures = get_closures p in @@ -333,7 +338,7 @@ let f p live_vars = let traverse outer = Code.traverse { fold = Code.fold_children } - (inline live_vars closures name) + (inline ~first_class_primitives live_vars closures name) pc p.blocks (outer, p) diff --git a/compiler/lib/ocaml_compiler.ml b/compiler/lib/ocaml_compiler.ml index 12fcb3ab63..ba16dae2a4 100644 --- a/compiler/lib/ocaml_compiler.ml +++ b/compiler/lib/ocaml_compiler.ml @@ -18,24 +18,38 @@ open! Stdlib -let rec constant_of_const : _ -> Code.constant = +let rec constant_of_const c : Code.constant = let open Lambda in let open Asttypes in - function - | Const_base (Const_int i) -> Int (Int32.of_int_warning_on_overflow i) + match c with + | Const_base (Const_int i) -> + Int + (match Config.target () with + | `JavaScript -> Int32.of_int_warning_on_overflow i + | `Wasm -> Int31.(of_int_warning_on_overflow i |> to_int32)) | Const_base (Const_char c) -> Int (Int32.of_int (Char.code c)) | ((Const_base (Const_string (s, _))) [@if ocaml_version < (4, 11, 0)]) | ((Const_base (Const_string (s, _, _))) [@if ocaml_version >= (4, 11, 0)]) -> String s | Const_base (Const_float s) -> Float (float_of_string s) - | Const_base (Const_int32 i) -> Int i + | Const_base (Const_int32 i) -> ( + match Config.target () with + | `JavaScript -> Int i + | `Wasm -> Int32 i) | Const_base (Const_int64 i) -> Int64 i - | Const_base (Const_nativeint i) -> Int (Int32.of_nativeint_warning_on_overflow i) + | Const_base (Const_nativeint i) -> ( + let i = Int32.of_nativeint_warning_on_overflow i in + match Config.target () with + | `JavaScript -> Int i + | `Wasm -> NativeInt i) | Const_immstring s -> String s | Const_float_array sl -> let l = List.map ~f:(fun f -> float_of_string f) sl in Float_array (Array.of_list l) | ((Const_pointer i) [@if ocaml_version < (4, 12, 0)]) -> - Int (Int32.of_int_warning_on_overflow i) + Int + (match Config.target () with + | `JavaScript -> Int32.of_int_warning_on_overflow i + | `Wasm -> Int31.(of_int_warning_on_overflow i |> to_int32)) | Const_block (tag, l) -> let l = Array.of_list (List.map l ~f:constant_of_const) in Tuple (tag, l, Unknown) diff --git a/compiler/lib/parse_bytecode.ml b/compiler/lib/parse_bytecode.ml index 18ef20b8df..c8e7cfc5d3 100644 --- a/compiler/lib/parse_bytecode.ml +++ b/compiler/lib/parse_bytecode.ml @@ -490,10 +490,17 @@ end = struct else if tag = Obj.custom_tag then match ident_of_custom x with - | Some name when same_ident name ident_32 -> Int (Obj.magic x : int32) - | Some name when same_ident name ident_native -> + | Some name when same_ident name ident_32 -> ( + let i : int32 = Obj.magic x in + match Config.target () with + | `JavaScript -> Int i + | `Wasm -> Int32 i) + | Some name when same_ident name ident_native -> ( let i : nativeint = Obj.magic x in - Int (Int32.of_nativeint_warning_on_overflow i) + let i = Int32.of_nativeint_warning_on_overflow i in + match Config.target () with + | `JavaScript -> Int i + | `Wasm -> NativeInt i) | Some name when same_ident name ident_64 -> Int64 (Obj.magic x : int64) | Some name -> failwith @@ -507,7 +514,10 @@ end = struct else assert false else let i : int = Obj.magic x in - Int (Int32.of_int_warning_on_overflow i) + Int + (match Config.target () with + | `JavaScript -> Int32.of_int_warning_on_overflow i + | `Wasm -> Int31.(of_int_warning_on_overflow i |> to_int32)) let inlined = function | String _ | NativeString _ -> false @@ -764,43 +774,87 @@ let access_global g i = x let register_global ?(force = false) g i loc rem = - if force || g.is_exported.(i) - then - let args = - match g.named_value.(i) with - | None -> [] - | Some name -> - Code.Var.name (access_global g i) name; - [ Pc (NativeString (Native_string.of_string name)) ] - in - ( Let - ( Var.fresh () - , Prim - ( Extern "caml_register_global" - , Pc (Int (Int32.of_int i)) :: Pv (access_global g i) :: args ) ) - , loc ) - :: rem - else rem + match g.is_exported.(i), force, Config.target () with + | true, _, `Wasm -> + (* Register a compilation unit (Wasm) *) + assert (not force); + let name = + match g.named_value.(i) with + | None -> assert false + | Some name -> name + in + Code.Var.name (access_global g i) name; + ( Let + ( Var.fresh () + , Prim (Extern "caml_set_global", [ Pc (String name); Pv (access_global g i) ]) + ) + , loc ) + :: rem + | true, _, (`JavaScript as target) | false, true, ((`Wasm | `JavaScript) as target) -> + (* Register an exception (if force = true), or a compilation unit + (Javascript) *) + let args = + match g.named_value.(i) with + | None -> [] + | Some name -> + Code.Var.name (access_global g i) name; + [ Pc + (match target with + | `JavaScript -> NativeString (Native_string.of_string name) + | `Wasm -> String name) + ] + in + ( Let + ( Var.fresh () + , Prim + ( Extern "caml_register_global" + , Pc (Int (Int32.of_int i)) :: Pv (access_global g i) :: args ) ) + , loc ) + :: rem + | false, false, (`JavaScript | `Wasm) -> rem let get_global state instrs i loc = State.size_globals state (i + 1); let g = State.globals state in match g.vars.(i) with | Some x -> + (* Registered global *) if debug_parser () then Format.printf "(global access %a)@." Var.print x; x, State.set_accu state x loc, instrs - | None -> + | None -> ( if i < Array.length g.constants && Constants.inlined g.constants.(i) then + (* Inlined constant *) let x, state = State.fresh_var state loc in let cst = g.constants.(i) in x, state, (Let (x, Constant cst), loc) :: instrs - else ( - g.is_const.(i) <- true; - let x, state = State.fresh_var state loc in - if debug_parser () then Format.printf "%a = CONST(%d)@." Var.print x i; - g.vars.(i) <- Some x; - x, state, instrs) + else + match i < Array.length g.constants, Config.target () with + | true, _ | false, `JavaScript -> + (* Non-inlined constant, and reference to another compilation + units in case of separate compilation (JavaScript). + Some code is generated in a prelude to store the relevant + module in variable [x]. *) + g.is_const.(i) <- true; + let x, state = State.fresh_var state loc in + if debug_parser () then Format.printf "%a = CONST(%d)@." Var.print x i; + g.vars.(i) <- Some x; + x, state, instrs + | false, `Wasm -> ( + (* Reference to another compilation units in case of separate + compilation (Wasm). + The toplevel module is available in an imported global + variables. *) + match g.named_value.(i) with + | None -> assert false + | Some name -> + let x, state = State.fresh_var state loc in + if debug_parser () + then Format.printf "%a = get_global(%s)@." Var.print x name; + ( x + , state + , (Let (x, Prim (Extern "caml_get_global", [ Pc (String name) ])), loc) + :: instrs ))) let tagged_blocks = ref Addr.Map.empty @@ -3100,16 +3154,15 @@ let from_channel ic = | _ -> raise Magic_number.(Bad_magic_number (to_string magic))) let predefined_exceptions () = + (* Register predefined exceptions in case of separate compilation *) let body = let open Code in List.map predefined_exceptions ~f:(fun (index, name) -> assert (String.is_valid_utf_8 name); let exn = Var.fresh () in let v_name = Var.fresh () in - let v_name_js = Var.fresh () in let v_index = Var.fresh () in [ Let (v_name, Constant (String name)), noloc - ; Let (v_name_js, Constant (NativeString (Native_string.of_string name))), noloc ; ( Let ( v_index , Constant @@ -3120,13 +3173,33 @@ let predefined_exceptions () = (-index - 1))) ) , noloc ) ; Let (exn, Block (248, [| v_name; v_index |], NotArray, Immutable)), noloc - ; ( Let - ( Var.fresh () - , Prim - ( Extern "caml_register_global" - , [ Pc (Int (Int32.of_int index)); Pv exn; Pv v_name_js ] ) ) - , noloc ) - ]) + ] + @ + match Config.target () with + | `JavaScript -> + let v_name_js = Var.fresh () in + [ ( Let (v_name_js, Constant (NativeString (Native_string.of_string name))) + , noloc ) + ; ( Let + ( Var.fresh () + , Prim + ( Extern "caml_register_global" + , [ Pc (Int (Int32.of_int index)); Pv exn; Pv v_name_js ] ) ) + , noloc ) + ] + | `Wasm -> + [ ( Let + ( Var.fresh () + , Prim + ( Extern "caml_register_global" + , [ Pc (Int (Int32.of_int index)); Pv exn; Pv v_name ] ) ) + , noloc ) + (* Also make the exception available to the generated code *) + ; ( Let + ( Var.fresh () + , Prim (Extern "caml_set_global", [ Pc (String name); Pv exn ]) ) + , noloc ) + ]) |> List.concat in let block = { params = []; body; branch = Stop, noloc } in diff --git a/compiler/lib/specialize_js.ml b/compiler/lib/specialize_js.ml index 9fdce562be..106a86ce06 100644 --- a/compiler/lib/specialize_js.ml +++ b/compiler/lib/specialize_js.ml @@ -22,49 +22,52 @@ open! Stdlib open Code open Flow -let specialize_instr info i = - match i with - | Let (x, Prim (Extern "caml_format_int", [ y; z ])) -> ( - match the_string_of info y with +let specialize_instr ~target info i = + match i, target with + | Let (x, Prim (Extern "caml_format_int", [ y; z ])), `JavaScript -> ( + (* We can implement the special case where the format string is "%s" in JavaScript + in a concise and efficient way with [""+x]. It does not make as much sense in + Wasm to have a special case for this. *) + match the_string_of ~target info y with | Some "%d" -> ( - match the_int info z with + match the_int ~target info z with | Some i -> Let (x, Constant (String (Int32.to_string i))) | None -> Let (x, Prim (Extern "%caml_format_int_special", [ z ]))) | _ -> i) - | Let (x, Prim (Extern "%caml_format_int_special", [ z ])) -> ( - match the_int info z with + | Let (x, Prim (Extern "%caml_format_int_special", [ z ])), `JavaScript -> ( + match the_int ~target info z with | Some i -> Let (x, Constant (String (Int32.to_string i))) | None -> i) (* inline the String constant argument so that generate.ml can attempt to parse it *) - | Let - ( x - , Prim - ( Extern (("caml_js_var" | "caml_js_expr" | "caml_pure_js_expr") as prim) - , [ (Pv _ as y) ] ) ) - when Config.Flag.safe_string () -> ( - match the_string_of info y with + | ( Let + ( x + , Prim + ( Extern (("caml_js_var" | "caml_js_expr" | "caml_pure_js_expr") as prim) + , [ (Pv _ as y) ] ) ) + , target ) -> ( + match the_string_of ~target info y with | Some s -> Let (x, Prim (Extern prim, [ Pc (String s) ])) | _ -> i) - | Let (x, Prim (Extern ("caml_register_named_value" as prim), [ y; z ])) -> ( - match the_string_of info y with + | Let (x, Prim (Extern ("caml_register_named_value" as prim), [ y; z ])), _ -> ( + match the_string_of ~target info y with | Some s when Primitive.need_named_value s -> Let (x, Prim (Extern prim, [ Pc (String s); z ])) | Some _ -> Let (x, Constant (Int 0l)) | None -> i) - | Let (x, Prim (Extern "caml_js_call", [ f; o; a ])) -> ( + | Let (x, Prim (Extern "caml_js_call", [ f; o; a ])), _ -> ( match the_def_of info a with | Some (Block (_, a, _, _)) -> let a = Array.map a ~f:(fun x -> Pv x) in Let (x, Prim (Extern "%caml_js_opt_call", f :: o :: Array.to_list a)) | _ -> i) - | Let (x, Prim (Extern "caml_js_fun_call", [ f; a ])) -> ( + | Let (x, Prim (Extern "caml_js_fun_call", [ f; a ])), _ -> ( match the_def_of info a with | Some (Block (_, a, _, _)) -> let a = Array.map a ~f:(fun x -> Pv x) in Let (x, Prim (Extern "%caml_js_opt_fun_call", f :: Array.to_list a)) | _ -> i) - | Let (x, Prim (Extern "caml_js_meth_call", [ o; m; a ])) -> ( - match the_string_of info m with + | Let (x, Prim (Extern "caml_js_meth_call", [ o; m; a ])), _ -> ( + match the_string_of ~target info m with | Some m when Javascript.is_ident m -> ( match the_def_of info a with | Some (Block (_, a, _, _)) -> @@ -78,13 +81,13 @@ let specialize_instr info i = :: Array.to_list a ) ) | _ -> i) | _ -> i) - | Let (x, Prim (Extern "caml_js_new", [ c; a ])) -> ( + | Let (x, Prim (Extern "caml_js_new", [ c; a ])), _ -> ( match the_def_of info a with | Some (Block (_, a, _, _)) -> let a = Array.map a ~f:(fun x -> Pv x) in Let (x, Prim (Extern "%caml_js_opt_new", c :: Array.to_list a)) | _ -> i) - | Let (x, Prim (Extern "caml_js_object", [ a ])) -> ( + | Let (x, Prim (Extern "caml_js_object", [ a ])), _ -> ( try let a = match the_def_of info a with @@ -96,7 +99,7 @@ let specialize_instr info i = match the_def_of info (Pv x) with | Some (Block (_, [| k; v |], _, _)) -> let k = - match the_string_of info (Pv k) with + match the_string_of ~target info (Pv k) with | Some s when String.is_valid_utf_8 s -> Pc (NativeString (Native_string.of_string s)) | Some _ | None -> raise Exit @@ -109,43 +112,47 @@ let specialize_instr info i = in Let (x, Prim (Extern "%caml_js_opt_object", List.flatten (Array.to_list a))) with Exit -> i) - | Let (x, Prim (Extern "caml_js_get", [ o; (Pv _ as f) ])) -> ( - match the_native_string_of info f with + | Let (x, Prim (Extern "caml_js_get", [ o; (Pv _ as f) ])), _ -> ( + match the_native_string_of ~target info f with | Some s -> Let (x, Prim (Extern "caml_js_get", [ o; Pc (NativeString s) ])) | _ -> i) - | Let (x, Prim (Extern "caml_js_set", [ o; (Pv _ as f); v ])) -> ( - match the_native_string_of info f with + | Let (x, Prim (Extern "caml_js_set", [ o; (Pv _ as f); v ])), _ -> ( + match the_native_string_of ~target info f with | Some s -> Let (x, Prim (Extern "caml_js_set", [ o; Pc (NativeString s); v ])) | _ -> i) - | Let (x, Prim (Extern "caml_js_delete", [ o; (Pv _ as f) ])) -> ( - match the_native_string_of info f with + | Let (x, Prim (Extern "caml_js_delete", [ o; (Pv _ as f) ])), _ -> ( + match the_native_string_of ~target info f with | Some s -> Let (x, Prim (Extern "caml_js_delete", [ o; Pc (NativeString s) ])) | _ -> i) - | Let (x, Prim (Extern ("caml_jsstring_of_string" | "caml_js_from_string"), [ y ])) -> ( - match the_string_of info y with + | Let (x, Prim (Extern ("caml_jsstring_of_string" | "caml_js_from_string"), [ y ])), _ + -> ( + match the_string_of ~target info y with | Some s when String.is_valid_utf_8 s -> Let (x, Constant (NativeString (Native_string.of_string s))) | Some _ | None -> i) - | Let (x, Prim (Extern "caml_jsbytes_of_string", [ y ])) -> ( - match the_string_of info y with + | Let (x, Prim (Extern "caml_jsbytes_of_string", [ y ])), _ -> ( + match the_string_of ~target info y with | Some s -> Let (x, Constant (NativeString (Native_string.of_bytestring s))) | None -> i) - | Let (x, Prim (Extern "%int_mul", [ y; z ])) -> ( - match the_int info y, the_int info z with + | Let (x, Prim (Extern "%int_mul", [ y; z ])), `JavaScript -> ( + (* Using * to multiply integers in JavaScript yields a float; and if the + float is large enough, some bits can be lost. So, in the general case, + we have to use Math.imul. There is no such issue in Wasm. *) + match the_int ~target info y, the_int ~target info z with | Some j, _ when Int32.(abs j < 0x200000l) -> Let (x, Prim (Extern "%direct_int_mul", [ y; z ])) | _, Some j when Int32.(abs j < 0x200000l) -> Let (x, Prim (Extern "%direct_int_mul", [ y; z ])) | _ -> i) - | Let (x, Prim (Extern "%int_div", [ y; z ])) -> ( - match the_int info z with + | Let (x, Prim (Extern "%int_div", [ y; z ])), _ -> ( + match the_int ~target info z with | Some j when Int32.(j <> 0l) -> Let (x, Prim (Extern "%direct_int_div", [ y; z ])) | _ -> i) - | Let (x, Prim (Extern "%int_mod", [ y; z ])) -> ( - match the_int info z with + | Let (x, Prim (Extern "%int_mod", [ y; z ])), _ -> ( + match the_int ~target info z with | Some j when Int32.(j <> 0l) -> Let (x, Prim (Extern "%direct_int_mod", [ y; z ])) | _ -> i) - | _ -> i + | _, _ -> i let equal2 a b = Code.Var.equal a b @@ -153,7 +160,7 @@ let equal3 a b c = Code.Var.equal a b && Code.Var.equal b c let equal4 a b c d = Code.Var.equal a b && Code.Var.equal b c && Code.Var.equal c d -let specialize_instrs info l = +let specialize_instrs ~target info l = let rec aux info checks l acc = match l with | [] -> List.rev acc @@ -205,7 +212,7 @@ let specialize_instrs info l = | "caml_array_get_addr" ) as prim) , [ y; z ] ) ) -> let idx = - match the_int info z with + match the_int ~target info z with | Some idx -> `Cst idx | None -> `Var z in @@ -248,7 +255,7 @@ let specialize_instrs info l = | "caml_array_set_addr" ) as prim) , [ y; z; t ] ) ) -> let idx = - match the_int info z with + match the_int ~target info z with | Some idx -> `Cst idx | None -> `Var z in @@ -282,22 +289,22 @@ let specialize_instrs info l = in aux info ((y, idx) :: checks) r acc | _ -> - let i = specialize_instr info i in + let i = specialize_instr ~target info i in aux info checks r ((i, loc) :: acc)) in aux info [] l [] -let specialize_all_instrs info p = +let specialize_all_instrs ~target info p = let blocks = Addr.Map.map - (fun block -> { block with Code.body = specialize_instrs info block.body }) + (fun block -> { block with Code.body = specialize_instrs ~target info block.body }) p.blocks in { p with blocks } (****) -let f info p = specialize_all_instrs info p +let f info p = specialize_all_instrs ~target:(Config.target ()) info p let f_once p = let rec loop acc l = diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index d67f4b0483..bb01e08dab 100644 --- a/compiler/lib/stdlib.ml +++ b/compiler/lib/stdlib.ml @@ -341,7 +341,49 @@ module Int32 = struct n end -module Int31 = struct +module type Arith_ops = sig + type t + + val neg : t -> t + + val add : t -> t -> t + + val sub : t -> t -> t + + val mul : t -> t -> t + + val div : t -> t -> t + + val rem : t -> t -> t + + val logand : t -> t -> t + + val logor : t -> t -> t + + val logxor : t -> t -> t + + val shift_left : t -> int -> t + + val shift_right : t -> int -> t + + val shift_right_logical : t -> int -> t +end + +module Int31 : sig + type t + + include Arith_ops with type t := t + + val of_int_warning_on_overflow : int -> t + + val of_nativeint_warning_on_overflow : nativeint -> t + + val of_int32_warning_on_overflow : int32 -> t + + val to_int32 : t -> int32 +end = struct + type t = int32 + let wrap i = Int32.(shift_right (shift_left i 1) 1) let of_int_warning_on_overflow i = @@ -361,6 +403,54 @@ module Int31 = struct ~to_dec:(Printf.sprintf "%nd") ~to_hex:(Printf.sprintf "%nx") n + + let of_int32_warning_on_overflow n = + Int32.convert_warning_on_overflow + ~to_int32:(fun i -> wrap i) + ~of_int32:Fun.id + ~equal:Int32.equal + ~to_dec:(Printf.sprintf "%ld") + ~to_hex:(Printf.sprintf "%lx") + n + + let two_pow n = + assert (0 <= n && n <= 31); + Int32.shift_left 1l n + + let min_int = Int32.neg (two_pow 30) + + let neg x = if Int32.equal x min_int then x else Int32.neg x + + let int_binop f x y = wrap (f x y) + + let add = int_binop Int32.add + + let sub = int_binop Int32.sub + + let mul = int_binop Int32.mul + + let div = int_binop Int32.div + + let rem = int_binop Int32.rem + + let logand = int_binop Int32.logand + + let logor = int_binop Int32.logor + + let logxor = int_binop Int32.logxor + + let shift_op f x y = + (* Limit the shift offset to [0, 31] *) + wrap (f x (y land 0x1f)) + + let shift_left = shift_op Int32.shift_left + + let shift_right = shift_op Int32.shift_right + + let shift_right_logical a b = + shift_op Int32.shift_right_logical (Int32.logand a 0x7fffffffl) b + + let to_int32 (x : t) : int32 = x end module Option = struct diff --git a/compiler/tests-num/dune b/compiler/tests-num/dune index 6cb923a8d5..9322dbda78 100644 --- a/compiler/tests-num/dune +++ b/compiler/tests-num/dune @@ -1,5 +1,6 @@ (executable (name main) + (modules main test_nats test test_big_ints test_ratios test_nums test_io) (libraries num) (modes js @@ -7,6 +8,15 @@ (flags (:standard -linkall -w -3-7-33-35-37 -safe-string -no-strict-sequence))) +(library + (name test_int31) + (modules test_int31) + (inline_tests) + (enabled_if %{lib-available:qcheck}) + (preprocess + (pps ppx_expect)) + (libraries js_of_ocaml-compiler qcheck)) + (rule (target main.referencejs) (deps main.bc.js) diff --git a/compiler/tests-num/test_int31.ml b/compiler/tests-num/test_int31.ml new file mode 100644 index 0000000000..2b4743dd0d --- /dev/null +++ b/compiler/tests-num/test_int31.ml @@ -0,0 +1,194 @@ +open! Js_of_ocaml_compiler.Stdlib +open QCheck2 + +let () = Printexc.record_backtrace false + +let min_int31 = Int32.(neg (shift_left 1l 30)) +let max_int31 = Int32.(sub (shift_left 1l 30) 1l) + +let in_range i = + Int32.(min_int31 <= i && i <= max_int31) + +let in_range_i32 = + Gen.(Int32.of_int <$> int_range (- (1 lsl 30)) (1 lsl 30 - 1)) + +let out_of_range_int = + let open Gen in + oneof [ int_range (- (1 lsl 31)) (- (1 lsl 30) - 1); + int_range (1 lsl 30) (1 lsl 31 - 1) ] + +let out_of_range_i32 = + out_of_range_int |> Gen.map Int32.of_int + +let t_corner = + let open Gen in + graft_corners in_range_i32 [min_int31; max_int31] () + |> map Int31.of_int32_warning_on_overflow + +let print_t t = + Format.sprintf "%ld" (Int31.to_int32 t) + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.of_int32_warning_on_overflow: normal" + in_range_i32 + (fun i -> + Int32.equal Int31.(to_int32 (of_int32_warning_on_overflow i)) i); + [%expect ""] + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.of_int_warning_on_overflow: normal" + (Gen.map Int32.to_int in_range_i32) + (fun i -> + Int.equal (Int31.(to_int32 (of_int_warning_on_overflow i)) |> Int32.to_int) i); + [%expect ""] + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.of_nativeint_warning_on_overflow: normal" + (Gen.map Nativeint.of_int32 in_range_i32) + (fun i -> + Nativeint.equal + (Int31.(to_int32 (of_nativeint_warning_on_overflow i)) |> Nativeint.of_int32) + i); + [%expect ""] + +let%expect_test _ = + let i = Gen.(generate1 (no_shrink out_of_range_i32)) in + let i_trunc = Int32.(shift_right (shift_left i 1) 1) in + ignore (Int31.of_int32_warning_on_overflow i); + let output = [%expect.output] in + let expected = + Format.sprintf "Warning: integer overflow: integer 0x%lx (%ld) truncated to 0x%lx (%ld); the generated code might be incorrect.@." i i i_trunc i_trunc + in + if not (String.equal output expected) then + Format.printf "Unexpected output string@.%s@.Expected:@.%s@." output expected; + [%expect ""] + +let%expect_test _ = + let i = Gen.(generate1 (no_shrink out_of_range_int)) in + let i_trunc = Int32.(shift_right (shift_left (of_int i) 1) 1) in + ignore (Int31.of_int_warning_on_overflow i); + let output = [%expect.output] in + let expected = + Format.sprintf "Warning: integer overflow: integer 0x%x (%d) truncated to 0x%lx (%ld); the generated code might be incorrect.@." i i i_trunc i_trunc + in + if not (String.equal output expected) then + Format.printf "Unexpected output string@.%s@.Expected:@.%s@." output expected; + [%expect ""] + +let%expect_test _ = + let i = Gen.(generate1 (no_shrink (Nativeint.of_int <$> out_of_range_int))) in + let i_trunc = Int32.(shift_right (shift_left (Nativeint.to_int32 i) 1) 1) in + ignore (Int31.of_nativeint_warning_on_overflow i); + let output = [%expect.output] in + let expected = + Format.sprintf "Warning: integer overflow: integer 0x%nx (%nd) truncated to 0x%lx (%ld); the generated code might be incorrect.@." i i i_trunc i_trunc + in + if not (String.equal output expected) then + Format.printf "Unexpected output string@.%s@.Expected:@.%s@." output expected; + [%expect ""] + +let modulus = Int32.(shift_left 1l 31) + +let canonicalize x = + if in_range x then x else Int32.(sub x modulus) + +let canon_equal x y = + Int32.(=) (canonicalize x) (canonicalize y) + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.neg" + t_corner + ~print:print_t + (fun i -> + let r_int31 = Int31.(neg i |> to_int32) in + let r_int32 = Int32.neg (Int31.to_int32 i) in + in_range r_int31 && canon_equal r_int31 r_int32); + [%expect ""] + +let binop_prop op_i31 op_i32 i j = + let r_int31 = op_i31 i j |> Int31.to_int32 in + let r_int32 = op_i32 (Int31.to_int32 i) (Int31.to_int32 j) in + in_range r_int31 && canon_equal r_int31 r_int32 + +let binop_check ~count ~name op_i31 op_i32 = + Test.check_exn @@ Test.make ~count ~name + Gen.(tup2 t_corner t_corner) + ~print:(Print.tup2 print_t print_t) + (fun (i, j) -> binop_prop op_i31 op_i32 i j) + +let%expect_test _ = + binop_check ~count:1000 ~name:"Int31.add" Int31.add Int32.add; + [%expect ""] + +let%expect_test _ = + binop_check ~count:1000 ~name:"Int31.sub" Int31.sub Int32.sub; + [%expect ""] + +let%expect_test _ = + binop_check ~count:1000 ~name:"Int31.mul" Int31.mul Int32.mul; + [%expect ""] + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.div" + Gen.(tup2 t_corner t_corner) + ~print:(Print.tup2 print_t print_t) + (fun (i, j) -> + try binop_prop Int31.div Int32.div i j + with Division_by_zero -> Int32.equal (Int31.to_int32 j) 0l) + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.rem" + Gen.(tup2 t_corner t_corner) + ~print:(Print.tup2 print_t print_t) + (fun (i, j) -> + try binop_prop Int31.rem Int32.rem i j + with Division_by_zero -> Int32.equal (Int31.to_int32 j) 0l) + +let%expect_test _ = + binop_check ~count:1000 ~name:"Int31.logand" Int31.logand Int32.logand; + [%expect ""] + +let%expect_test _ = + binop_check ~count:1000 ~name:"Int31.logor" Int31.logor Int32.logor; + [%expect ""] + +let%expect_test _ = + binop_check ~count:1000 ~name:"Int31.logxor" Int31.logxor Int32.logxor; + [%expect ""] + +let shift_op_prop op_i31 op_i32 x i = + let r_int31 = op_i31 x i |> Int31.to_int32 in + let r_int32 = op_i32 (Int31.to_int32 x) i in + in_range r_int31 && canon_equal r_int31 r_int32 + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.shift_left" + Gen.(tup2 t_corner (int_bound 31)) + ~print:Print.(tup2 print_t int) + (fun (x, i) -> shift_op_prop Int31.shift_left Int32.shift_left x i) + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:1000 ~name:"Int31.shift_right" + Gen.(tup2 t_corner (int_bound 31)) + ~print:Print.(tup2 print_t int) + (fun (x, i) -> shift_op_prop Int31.shift_right Int32.shift_right x i) + +(* Logical implication *) +let (-->) p q = not p || q + +let%expect_test _ = + Test.check_exn @@ Test.make ~count:10_000 ~name:"Int31.shift_right_logical" + Gen.(tup2 t_corner (int_bound 31)) + ~print:Print.(tup2 print_t int) + (fun (x, i) -> + let r_int31 = Int31.shift_right_logical x i |> Int31.to_int32 in + let x_int32 = Int31.to_int32 x in + let r_int32 = + if Int_replace_polymorphic_compare.( i = 0 ) then x_int32 + else Int32.(shift_right_logical (logand 0x7fffffffl x_int32) i) + in + (* The bits should be unchanged if the shift amount is zero, otherwise they should + match the result of shifting the 31 lower bits of the canonical representation *) + in_range r_int31 && Int32.equal r_int31 r_int32 + && (Int.equal i 0 --> Int32.(r_int31 = x_int32))); + [%expect ""] diff --git a/dune-project b/dune-project index 39d190fe65..e7880e1cd8 100644 --- a/dune-project +++ b/dune-project @@ -1,4 +1,4 @@ -(lang dune 3.7) +(lang dune 3.15) (using menhir 2.0) (name js_of_ocaml) @@ -25,6 +25,7 @@ (re :with-test) (cmdliner (>= 1.1.0)) (sedlex (>= 2.3)) + (qcheck :with-test) menhir menhirLib menhirSdk diff --git a/dune-workspace.dev b/dune-workspace.dev index a21211017e..0f4dc203a3 100644 --- a/dune-workspace.dev +++ b/dune-workspace.dev @@ -1,4 +1,4 @@ -(lang dune 3.7) +(lang dune 3.15) ;; Install the following opam switches, copy this file as ;; dune-workspace and run: diff --git a/js_of_ocaml-compiler.opam b/js_of_ocaml-compiler.opam index 6c9ba412f2..d8fee92ae9 100644 --- a/js_of_ocaml-compiler.opam +++ b/js_of_ocaml-compiler.opam @@ -12,7 +12,7 @@ homepage: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" doc: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" bug-reports: "https://github.com/ocsigen/js_of_ocaml/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.15"} "ocaml" {>= "4.08" & < "5.4"} "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} @@ -20,6 +20,7 @@ depends: [ "re" {with-test} "cmdliner" {>= "1.1.0"} "sedlex" {>= "2.3"} + "qcheck" {with-test} "menhir" "menhirLib" "menhirSdk" diff --git a/js_of_ocaml-lwt.opam b/js_of_ocaml-lwt.opam index b0cc7c1784..040e9c1009 100644 --- a/js_of_ocaml-lwt.opam +++ b/js_of_ocaml-lwt.opam @@ -12,7 +12,7 @@ homepage: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" doc: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" bug-reports: "https://github.com/ocsigen/js_of_ocaml/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.15"} "ocaml" {>= "4.08"} "js_of_ocaml" {= version} "js_of_ocaml-ppx" {= version} diff --git a/js_of_ocaml-ppx.opam b/js_of_ocaml-ppx.opam index 72e581304d..7e65f4f2db 100644 --- a/js_of_ocaml-ppx.opam +++ b/js_of_ocaml-ppx.opam @@ -12,7 +12,7 @@ homepage: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" doc: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" bug-reports: "https://github.com/ocsigen/js_of_ocaml/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.15"} "ocaml" {>= "4.08"} "js_of_ocaml" {= version} "ppxlib" {>= "0.15.0"} diff --git a/js_of_ocaml-ppx_deriving_json.opam b/js_of_ocaml-ppx_deriving_json.opam index 90cdfe507d..23afe4fa25 100644 --- a/js_of_ocaml-ppx_deriving_json.opam +++ b/js_of_ocaml-ppx_deriving_json.opam @@ -12,7 +12,7 @@ homepage: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" doc: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" bug-reports: "https://github.com/ocsigen/js_of_ocaml/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.15"} "ocaml" {>= "4.08"} "js_of_ocaml" {= version} "ppxlib" {>= "0.15"} diff --git a/js_of_ocaml-toplevel.opam b/js_of_ocaml-toplevel.opam index 7b2a882fcf..6e0e3d675c 100644 --- a/js_of_ocaml-toplevel.opam +++ b/js_of_ocaml-toplevel.opam @@ -12,7 +12,7 @@ homepage: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" doc: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" bug-reports: "https://github.com/ocsigen/js_of_ocaml/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.15"} "ocaml" {>= "4.08"} "js_of_ocaml-compiler" {= version} "ocamlfind" {>= "1.5.1"} diff --git a/js_of_ocaml-tyxml.opam b/js_of_ocaml-tyxml.opam index 737f3d26d4..0feed79e0e 100644 --- a/js_of_ocaml-tyxml.opam +++ b/js_of_ocaml-tyxml.opam @@ -12,7 +12,7 @@ homepage: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" doc: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" bug-reports: "https://github.com/ocsigen/js_of_ocaml/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.15"} "ocaml" {>= "4.08"} "js_of_ocaml" {= version} "js_of_ocaml-ppx" {= version} diff --git a/js_of_ocaml.opam b/js_of_ocaml.opam index 284b04bc3e..b3300ecde4 100644 --- a/js_of_ocaml.opam +++ b/js_of_ocaml.opam @@ -12,7 +12,7 @@ homepage: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" doc: "https://ocsigen.org/js_of_ocaml/latest/manual/overview" bug-reports: "https://github.com/ocsigen/js_of_ocaml/issues" depends: [ - "dune" {>= "3.7"} + "dune" {>= "3.15"} "ocaml" {>= "4.08"} "js_of_ocaml-compiler" {= version} "ppxlib" {>= "0.15"}