diff --git a/compiler/bin-js_of_ocaml/build_fs.ml b/compiler/bin-js_of_ocaml/build_fs.ml index 84ed7fb55d..16ac22a23d 100644 --- a/compiler/bin-js_of_ocaml/build_fs.ml +++ b/compiler/bin-js_of_ocaml/build_fs.ml @@ -75,10 +75,10 @@ function jsoo_create_file_extern(name,content){ let pfs_fmt = Pretty_print.to_out_channel chan in let (_ : Source_map.t option) = Driver.f - ~target:(JavaScript pfs_fmt) ~standalone:true ~wrap_with_fun:`Iife ~link:`Needed + ~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 14bca57e0a..29b541914a 100644 --- a/compiler/bin-js_of_ocaml/check_runtime.ml +++ b/compiler/bin-js_of_ocaml/check_runtime.ml @@ -43,7 +43,8 @@ let print_groups output l = output_string output (Printf.sprintf "%s\n" name))) let f (runtime_files, bytecode, target_env) = - Generate.init (); + Config.set_target `JavaScript; + Linker.reset (); let runtime_files, builtin = List.partition_map runtime_files ~f:(fun name -> match Builtins.find name with diff --git a/compiler/bin-js_of_ocaml/compile.ml b/compiler/bin-js_of_ocaml/compile.ml index b456d69c36..caeb138842 100644 --- a/compiler/bin-js_of_ocaml/compile.ml +++ b/compiler/bin-js_of_ocaml/compile.ml @@ -91,6 +91,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; Generate.init (); (match output_file with @@ -184,7 +185,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 @@ -200,9 +201,10 @@ let run ~link ~wrap_with_fun ?source_map + ~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, [] @@ -224,6 +226,7 @@ let run ~link ~wrap_with_fun ?source_map + ~formatter one.debug code in diff --git a/compiler/bin-js_of_ocaml/link.ml b/compiler/bin-js_of_ocaml/link.ml index 090913d20b..7719679702 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; let with_output f = match output_file with diff --git a/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml b/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml index 9bf10680d9..c296919d62 100644 --- a/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml +++ b/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml @@ -16,6 +16,9 @@ let split_primitives p = external get_section_table : unit -> (string * Obj.t) list = "caml_get_section_table" 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 3f01473575..a482399814 100644 --- a/compiler/lib-runtime-files/gen/gen.ml +++ b/compiler/lib-runtime-files/gen/gen.ml @@ -47,6 +47,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 a05b1aefd1..0bb91ffffc 100644 --- a/compiler/lib/code.ml +++ b/compiler/lib/code.ml @@ -816,6 +816,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); @@ -830,6 +831,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 (_, _, _, _) -> () @@ -837,8 +851,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 95193f49c3..3f8e875453 100644 --- a/compiler/lib/config.ml +++ b/compiler/lib/config.ml @@ -162,7 +162,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 *) @@ -178,3 +178,15 @@ module Param = struct ~desc:"Set baseline for lifting deeply nested functions" (int 1) end + +(****) + +let target_ : [ `JavaScript | `Wasm | `None ] ref = ref `None + +let target () = + match !target_ with + | `None -> failwith "target was not set" + | (`JavaScript | `Wasm) as t -> t + +let set_target (t : [ `JavaScript | `Wasm ]) = + target_ := (t :> [ `JavaScript | `Wasm | `None ]) diff --git a/compiler/lib/config.mli b/compiler/lib/config.mli index ac1672bdef..ab1f49d980 100644 --- a/compiler/lib/config.mli +++ b/compiler/lib/config.mli @@ -78,6 +78,7 @@ module Flag : sig val disable : string -> unit end +(** This module contains parameters that may be modified through command-line flags. *) module Param : sig val set : string -> string -> unit @@ -102,3 +103,13 @@ module Param : sig val lambda_lifting_baseline : unit -> int end + +(****) + +(** {2 Parameters that are constant across a program run} *) + +(** These parameters should be set at most once at the beginning of the program. *) + +val target : unit -> [ `JavaScript | `Wasm ] + +val set_target : [ `JavaScript | `Wasm ] -> unit diff --git a/compiler/lib/driver.ml b/compiler/lib/driver.ml index bb4ce4aaf2..ba7c18511e 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 @@ -44,35 +52,35 @@ let deadcode p = let r, _ = deadcode' p in r -let inline ~target p = +let inline p = if Config.Flag.inline () && Config.Flag.deadcode () then ( let p, live_vars = deadcode' p in if debug () then Format.eprintf "Inlining...@."; - Inline.f ~target p live_vars) + Inline.f p live_vars) else p let specialize_1 (p, info) = if debug () then Format.eprintf "Specialize...@."; Specialize.f ~function_arity:(fun f -> Specialize.function_arity info f) p -let specialize_js ~target (p, info) = +let specialize_js (p, info) = if debug () then Format.eprintf "Specialize js...@."; - Specialize_js.f ~target info p + Specialize_js.f info p let specialize_js_once p = if debug () then Format.eprintf "Specialize js once...@."; Specialize_js.f_once p -let specialize' ~target (p, info) = +let specialize' (p, info) = let p = specialize_1 (p, info) in - let p = specialize_js ~target (p, info) in + let p = specialize_js (p, info) in p, info -let specialize ~target p = fst (specialize' ~target p) +let specialize p = fst (specialize' p) -let eval ~target (p, info) = - if Config.Flag.staticeval () then Eval.f ~target info p else p +let eval (p, info) = + if Config.Flag.staticeval () then Eval.f info p else p let flow p = if debug () then Format.eprintf "Data flow...@."; @@ -128,53 +136,53 @@ let identity x = x (* o1 *) -let o1 ~target : 'a -> 'a = +let o1 : 'a -> 'a = print +> tailcall +> flow_simple (* flow simple to keep information for future tailcall opt *) - +> specialize' ~target - +> eval ~target - +> inline ~target (* inlining may reveal new tailcall opt *) + +> specialize' + +> eval + +> inline (* inlining may reveal new tailcall opt *) +> deadcode +> tailcall +> phi +> flow - +> specialize' ~target - +> eval ~target - +> inline ~target + +> specialize' + +> eval + +> inline +> deadcode +> print +> flow - +> specialize' ~target - +> eval ~target - +> inline ~target + +> specialize' + +> eval + +> inline +> deadcode +> phi +> flow - +> specialize ~target + +> specialize +> identity (* o2 *) -let o2 ~target : 'a -> 'a = loop 10 "o1" (o1 ~target) 1 +> print +let o2 : 'a -> 'a = loop 10 "o1" o1 1 +> print (* o3 *) -let round1 ~target : 'a -> 'a = +let round1 : 'a -> 'a = print +> tailcall - +> inline ~target (* inlining may reveal new tailcall opt *) + +> inline (* inlining may reveal new tailcall opt *) +> deadcode (* deadcode required before flow simple -> provided by constant *) +> flow_simple (* flow simple to keep information for future tailcall opt *) - +> specialize' ~target - +> eval ~target + +> specialize' + +> eval +> identity -let round2 ~target = flow +> specialize' ~target +> eval ~target +> deadcode +> o1 ~target +let round2 = flow +> specialize' +> eval +> deadcode +> o1 -let o3 ~target = - loop 10 "tailcall+inline" (round1 ~target) 1 - +> loop 10 "flow" (round2 ~target) 1 +let o3 = + loop 10 "tailcall+inline" round1 1 + +> loop 10 "flow" round2 1 +> print let generate @@ -182,13 +190,13 @@ let generate ~exported_runtime ~wrap_with_fun ~warn_on_unhandled_effect - ((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 @@ -642,18 +650,7 @@ let configure formatter = Code.Var.set_pretty (pretty && not (Config.Flag.shortvar ())); Code.Var.set_stable (Config.Flag.stable_var ()) -type 'a target = - | JavaScript : Pretty_print.t -> Source_map.t option target - | Wasm - : (Deadcode.variable_uses * Effects.in_cps * Code.program * Parse_bytecode.Debug.t) - target - -let target_flag (type a) (t : a target) = - match t with - | JavaScript _ -> `JavaScript - | Wasm -> `Wasm - -let link_and_pack ?(standalone = true) ?(wrap_with_fun = `Iife) ~link p = +let link_and_pack ?(standalone = true) ?(wrap_with_fun = `Iife) ?(link = `No) p = let export_runtime = match link with | `All | `All_from _ -> true @@ -665,73 +662,57 @@ let link_and_pack ?(standalone = true) ?(wrap_with_fun = `Iife) ~link p = |> coloring |> check_js -let full - (type result) - ~(target : result target) - ~standalone - ~wrap_with_fun - ~profile - ~link - ~source_map - d - p : result = +let optimize ~profile p = + let deadcode_sentinal = + (* If deadcode is disabled, this field is just fresh variable *) + Code.Var.fresh_n "dummy" + in let opt = specialize_js_once +> (match profile with | O1 -> o1 | O2 -> o2 | O3 -> o3) - ~target:(target_flag target) +> exact_calls profile +> effects +> map_fst - ((match target with - | JavaScript _ -> Generate_closure.f - | Wasm -> Fun.id) - +> deadcode') + (match Config.target (), Config.Flag.effects () with + | `JavaScript, false -> Generate_closure.f + | `JavaScript, true | `Wasm, _ -> Fun.id) + +> map_fst deadcode' 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 - match target with - | JavaScript formatter -> - 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 - let source_map = emit formatter r in - source_map - | Wasm -> - let (p, live_vars), _, in_cps = r in - live_vars, in_cps, p, d + { 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 ~formatter ~standalone ~wrap_with_fun ~profile ~link d p = let (_ : Source_map.t option) = - full - ~target:(JavaScript formatter) - ~standalone - ~wrap_with_fun - ~profile - ~link - ~source_map:None - d - p + full ~standalone ~wrap_with_fun ~profile ~link ~source_map:None ~formatter d p in () let f - ~target ?(standalone = true) ?(wrap_with_fun = `Iife) ?(profile = O1) ~link ?source_map + ~formatter d p = - full ~target ~standalone ~wrap_with_fun ~profile ~link ~source_map 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 ~formatter ~standalone ~wrap_with_fun ~profile ~link d p diff --git a/compiler/lib/driver.mli b/compiler/lib/driver.mli index 8e8d0c97e4..91f846b989 100644 --- a/compiler/lib/driver.mli +++ b/compiler/lib/driver.mli @@ -20,22 +20,26 @@ type profile -type 'a target = - | JavaScript : Pretty_print.t -> Source_map.t option target - | Wasm - : (Deadcode.variable_uses * Effects.in_cps * Code.program * Parse_bytecode.Debug.t) - target +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 : - target:'result target - -> ?standalone:bool + ?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 + -> formatter:Pretty_print.t -> Parse_bytecode.Debug.t -> Code.program - -> 'result + -> Source_map.t option val f' : ?standalone:bool @@ -57,7 +61,7 @@ val from_string : val link_and_pack : ?standalone:bool -> ?wrap_with_fun:[ `Iife | `Anonymous | `Named of string ] - -> link:[ `All | `All_from of string list | `Needed | `No ] + -> ?link:[ `All | `All_from of string list | `Needed | `No ] -> Javascript.statement_list -> Javascript.statement_list diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 5faec48bfb..a5f78a38e5 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 w f = - match l with - | [ Int i; Int j ] -> Some (Int (w (f i j))) - | _ -> None + val int_unop : constant list -> (t -> t) -> constant option -let shift l w t f = - match l with - | [ Int i; Int j ] -> Some (Int (w (f (t 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 = @@ -83,34 +139,27 @@ let eval_prim ~target 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 name = Primitive.resolve name in - let wrap = + let (module Int : Int) = match target with - | `JavaScript -> fun i -> i - | `Wasm -> Int31.wrap + | `JavaScript -> (module Int32) + | `Wasm -> (module Int31) in + let name = Primitive.resolve name in match name, l with (* int *) - | "%int_add", _ -> int_binop l wrap Int.add - | "%int_sub", _ -> int_binop l wrap Int.sub - | "%direct_int_mul", _ -> int_binop l wrap 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 wrap Int.div - | "%direct_int_mod", _ -> int_binop l wrap Int.rem - | "%int_and", _ -> int_binop l wrap Int.logand - | "%int_or", _ -> int_binop l wrap Int.logor - | "%int_xor", _ -> int_binop l wrap Int.logxor - | "%int_lsl", _ -> shift l wrap Fun.id Int.shift_left - | "%int_lsr", _ -> - shift - l - wrap - (match target with - | `JavaScript -> Fun.id - | `Wasm -> fun i -> Int.logand i 0x7fffffffl) - Int.shift_right_logical - | "%int_asr", _ -> shift l wrap Fun.id 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.( <> ) @@ -123,9 +172,9 @@ let eval_prim ~target 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 (Int.of_float f)) - | "to_int", [ Float f ] -> Some (Int (Int.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 @@ -155,12 +204,7 @@ let eval_prim ~target x = | Some env -> Some (String env) | None -> None) | "caml_sys_const_word_size", [ _ ] -> Some (Int 32l) - | "caml_sys_const_int_size", [ _ ] -> - Some - (Int - (match target with - | `JavaScript -> 32l - | `Wasm -> 31l)) + | "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) @@ -187,7 +231,7 @@ type is_int = | N | Unknown -let is_int ~target info x = +let is_int info x = match x with | Pv x -> get_approx @@ -195,11 +239,10 @@ let is_int ~target info x = (fun x -> match info.info_defs.(Var.idx x) with | Expr (Constant (Int _)) -> Y - | Expr (Constant (Int32 _ | NativeInt _)) -> ( - match target with - | `JavaScript -> Y - | `Wasm -> N) - | Expr (Block (_, _, _, _)) | Expr (Constant _) -> N + | Expr (Constant (NativeInt _ | Int32 _)) -> + (* These Wasm-specific constants are boxed *) + N + | Expr (Block (_, _, _, _) | Constant _) -> N | _ -> Unknown) Unknown (fun u v -> @@ -209,10 +252,9 @@ let is_int ~target info x = | _ -> Unknown) x | Pc (Int _) -> Y - | Pc (Int32 _ | NativeInt _) -> ( - match target with - | `JavaScript -> Y - | `Wasm -> N) + | Pc (NativeInt _ | Int32 _) -> + (* These Wasm-specific constants are boxed *) + N | Pc _ -> N let the_tag_of info x get = @@ -336,7 +378,7 @@ let eval_instr ~target info ((x, loc) as i) = below fail. *) [ i ] | Let (x, Prim (IsInt, [ y ])) -> ( - match is_int ~target info y with + match is_int info y with | Unknown -> [ i ] | (Y | N) as b -> let c = Constant (bool' Poly.(b = Y)) in @@ -351,14 +393,12 @@ let eval_instr ~target 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 - (match target with - | `JavaScript -> "js_of_ocaml" - | `Wasm -> "wasm_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"), _)) -> @@ -388,10 +428,15 @@ let eval_instr ~target info ((x, loc) as i) = ( x , Prim ( prim - , List.map2 prim_args prim_args' ~f:(fun arg c -> - match (c : constant option), target with - | Some ((Int _ | NativeString _) as c), _ -> Pc c - | Some (Float _ as c), `JavaScript -> Pc c + , 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 _, _ @@ -527,7 +572,7 @@ let eval ~target info blocks = { block with Code.body; Code.branch }) blocks -let f ~target info p = - let blocks = eval ~target info p.blocks in +let f info p = + 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/eval.mli b/compiler/lib/eval.mli index 30a36b08f6..a71f611ca1 100644 --- a/compiler/lib/eval.mli +++ b/compiler/lib/eval.mli @@ -21,4 +21,4 @@ val clear_static_env : unit -> unit val set_static_env : string -> string -> unit -val f : target:[ `JavaScript | `Wasm ] -> Flow.info -> Code.program -> Code.program +val f : Flow.info -> Code.program -> Code.program diff --git a/compiler/lib/flow.ml b/compiler/lib/flow.ml index c37a9a30b5..c32428f2bc 100644 --- a/compiler/lib/flow.ml +++ b/compiler/lib/flow.ml @@ -331,23 +331,26 @@ let constant_identical ~(target : [ `JavaScript | `Wasm ]) a b = | 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 - | Int _, Float _, _ | Float _, Int _, _ -> false + | 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 + | 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 diff --git a/compiler/lib/inline.ml b/compiler/lib/inline.ml index 16b9ae5353..a87ca60225 100644 --- a/compiler/lib/inline.ml +++ b/compiler/lib/inline.ml @@ -264,9 +264,9 @@ let inline ~first_class_primitives live_vars closures pc (outer, blocks, free_pc let times = Debug.find "times" -let f ~target p live_vars = +let f p live_vars = let first_class_primitives = - match target with + match Config.target () with | `JavaScript -> not (Config.Flag.effects ()) | `Wasm -> false in diff --git a/compiler/lib/inline.mli b/compiler/lib/inline.mli index 2bc18bc4f2..9799e882a2 100644 --- a/compiler/lib/inline.mli +++ b/compiler/lib/inline.mli @@ -18,5 +18,4 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -val f : - target:[ `JavaScript | `Wasm ] -> Code.program -> Deadcode.variable_uses -> Code.program +val f : Code.program -> Deadcode.variable_uses -> Code.program diff --git a/compiler/lib/ocaml_compiler.ml b/compiler/lib/ocaml_compiler.ml index 9b2cf1d9ae..b2057d431f 100644 --- a/compiler/lib/ocaml_compiler.ml +++ b/compiler/lib/ocaml_compiler.ml @@ -18,27 +18,28 @@ open! Stdlib -let rec constant_of_const ~target c : Code.constant = +let rec constant_of_const c : Code.constant = let open Lambda in let open Asttypes in match c with | Const_base (Const_int i) -> Int - (match target with + (match Config.target () with | `JavaScript -> Int32.of_int_warning_on_overflow i - | `Wasm -> Int31.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) -> ( - match target with + match Config.target () with | `JavaScript -> Int i | `Wasm -> Int32 i) | Const_base (Const_int64 i) -> Int64 i | Const_base (Const_nativeint i) -> ( - match target with - | `JavaScript -> 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) | Const_immstring s -> String s | Const_float_array sl -> @@ -46,11 +47,11 @@ let rec constant_of_const ~target c : Code.constant = Float_array (Array.of_list l) | ((Const_pointer i) [@if ocaml_version < (4, 12, 0)]) -> Int - (match target with + (match Config.target () with | `JavaScript -> Int32.of_int_warning_on_overflow i - | `Wasm -> Int31.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:(fun c -> constant_of_const ~target c)) in + let l = Array.of_list (List.map l ~f:(fun c -> constant_of_const c)) in Tuple (tag, l, Unknown) let rec find_loc_in_summary ident' = function diff --git a/compiler/lib/parse_bytecode.ml b/compiler/lib/parse_bytecode.ml index 155c3cf3be..02a7053dfa 100644 --- a/compiler/lib/parse_bytecode.ml +++ b/compiler/lib/parse_bytecode.ml @@ -467,13 +467,14 @@ end = struct match ident_of_custom x with | Some name when same_ident name ident_32 -> ( let i : int32 = Obj.magic x in - match target with + 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 - match target with - | `JavaScript -> 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 -> @@ -492,9 +493,9 @@ end = struct else let i : int = Obj.magic x in Int - (match target with + (match Config.target () with | `JavaScript -> Int32.of_int_warning_on_overflow i - | `Wasm -> Int31.of_int_warning_on_overflow i) + | `Wasm -> Int31.(of_int_warning_on_overflow i |> to_int32)) let inlined = function | String _ | NativeString _ -> false @@ -745,76 +746,88 @@ let access_global g i = g.vars.(i) <- Some x; x -let register_global ~target ?(force = false) g i loc rem = - if g.is_exported.(i) - && - match target with - | `Wasm -> true - | `JavaScript -> false - then ( - 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) - else 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 +let register_global ?(force = false) g i loc 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 ~target 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 -> ( 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 if i < Array.length g.constants - || - match target with - | `Wasm -> false - | `JavaScript -> true - then ( - 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 g.named_value.(i) with - | None -> assert false - | Some name -> + 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 = get_global(%s)@." Var.print x name; - ( x - , state - , (Let (x, Prim (Extern "caml_get_global", [ Pc (String name) ])), loc) - :: instrs )) + 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.Set.empty @@ -3079,50 +3092,49 @@ let from_channel ic = `Exe | _ -> raise Magic_number.(Bad_magic_number (to_string magic))) -let predefined_exceptions ~target = +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 ] - @ (match target with - | `Wasm -> [] - | `JavaScript -> - [ ( Let (v_name_js, Constant (NativeString (Native_string.of_string name))) - , noloc ) - ]) - @ [ ( Let - ( v_index - , Constant - (Int - ((* Predefined exceptions are registered in - Symtable.init with [-index - 1] *) - Int32.of_int - (-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 - (match target with - | `JavaScript -> v_name_js - | `Wasm -> v_name) - ] ) ) - , noloc ) - ] + [ Let (v_name, Constant (String name)), noloc + ; ( Let + ( v_index + , Constant + (Int + ((* Predefined exceptions are registered in + Symtable.init with [-index - 1] *) + Int32.of_int + (-index - 1))) ) + , noloc ) + ; Let (exn, Block (248, [| v_name; v_index |], NotArray, Immutable)), noloc + ] @ - match target with - | `JavaScript -> [] + 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 ) diff --git a/compiler/lib/specialize_js.ml b/compiler/lib/specialize_js.ml index 39f0209029..ff5b89beb2 100644 --- a/compiler/lib/specialize_js.ml +++ b/compiler/lib/specialize_js.ml @@ -25,6 +25,9 @@ open Flow 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 ~target info z with @@ -41,13 +44,11 @@ let specialize_instr ~target info i = , Prim ( Extern (("caml_js_var" | "caml_js_expr" | "caml_pure_js_expr") as prim) , [ (Pv _ as y) ] ) ) - , _ ) - when Config.Flag.safe_string () -> ( + , 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 ])), `JavaScript - -> ( + | 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 ])) @@ -134,6 +135,9 @@ let specialize_instr ~target info i = | Some s -> Let (x, Constant (NativeString (Native_string.of_bytestring s))) | None -> i) | 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 ])) @@ -262,7 +266,7 @@ let specialize_all_instrs ~target info p = (****) -let f ~target info p = specialize_all_instrs ~target 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/specialize_js.mli b/compiler/lib/specialize_js.mli index 4bf26256a8..3ed1f1a6c5 100644 --- a/compiler/lib/specialize_js.mli +++ b/compiler/lib/specialize_js.mli @@ -18,6 +18,6 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *) -val f : target:[ `JavaScript | `Wasm ] -> Flow.info -> Code.program -> Code.program +val f : Flow.info -> Code.program -> Code.program val f_once : Code.program -> Code.program diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index d5c7122c4d..86d6e56bf1 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 1bd90f4076..056775229e 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) (enabled_if 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 ed1856d813..6cd544bd75 100644 --- a/dune-project +++ b/dune-project @@ -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: