From ffcab72846cae6c0a7d90795ca801d8d235d0b6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 12:53:04 +0200 Subject: [PATCH 01/36] Avoid duplicating information between modules Typing and Generate --- compiler/lib-wasm/generate.ml | 454 +++++++++++++++++++++++++++------- compiler/lib-wasm/typing.ml | 282 +-------------------- compiler/lib-wasm/typing.mli | 4 + runtime/wasm/float.wat | 27 +- 4 files changed, 391 insertions(+), 376 deletions(-) diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index 5c51a05321..86f0c323b8 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -65,23 +65,37 @@ module Generate (Target : Target_sig.S) = struct let zero_divide_pc = -2 + type normalized = + | Normalized + | Unnormalized + type repr = | Value | Float - | Int + | Int of normalized | Int32 | Nativeint | Int64 - let repr_type r = + let repr_type r : Typing.typ option = + match r with + | Value -> None + | Float -> Some (Number (Float, Unboxed)) + | Int Normalized -> Some (Int Normalized) + | Int Unnormalized -> Some (Int Unnormalized) + | Int32 -> Some (Number (Int32, Unboxed)) + | Nativeint -> Some (Number (Nativeint, Unboxed)) + | Int64 -> Some (Number (Int64, Unboxed)) + + let repr_wasm_type r = match r with | Value -> Type.value | Float -> F64 - | Int | Int32 | Nativeint -> I32 + | Int _ | Int32 | Nativeint -> I32 | Int64 -> I64 let specialized_primitive_type (_, params, result) = - { W.params = List.map ~f:repr_type params; result = [ repr_type result ] } + { W.params = List.map ~f:repr_wasm_type params; result = [ repr_wasm_type result ] } let specialized_primitives = let h = String.Hashtbl.create 18 in @@ -90,31 +104,32 @@ module Generate (Target : Target_sig.S) = struct [ "caml_int32_bswap", (`Pure, [ Int32 ], Int32) ; "caml_nativeint_bswap", (`Pure, [ Nativeint ], Nativeint) ; "caml_int64_bswap", (`Pure, [ Int64 ], Int64) - ; "caml_int32_compare", (`Pure, [ Int32; Int32 ], Int) - ; "caml_nativeint_compare", (`Pure, [ Nativeint; Nativeint ], Int) - ; "caml_int64_compare", (`Pure, [ Int64; Int64 ], Int) - ; "caml_string_get16", (`Mutator, [ Value; Int ], Int) - ; "caml_string_get32", (`Mutator, [ Value; Int ], Int32) - ; "caml_string_get64", (`Mutator, [ Value; Int ], Int64) - ; "caml_bytes_get16", (`Mutator, [ Value; Int ], Int) - ; "caml_bytes_get32", (`Mutator, [ Value; Int ], Int32) - ; "caml_bytes_get64", (`Mutator, [ Value; Int ], Int64) - ; "caml_bytes_set16", (`Mutator, [ Value; Int; Int ], Value) - ; "caml_bytes_set32", (`Mutator, [ Value; Int; Int32 ], Value) - ; "caml_bytes_set64", (`Mutator, [ Value; Int; Int64 ], Value) + ; "caml_int32_compare", (`Pure, [ Int32; Int32 ], Int Normalized) + ; "caml_nativeint_compare", (`Pure, [ Nativeint; Nativeint ], Int Normalized) + ; "caml_int64_compare", (`Pure, [ Int64; Int64 ], Int Normalized) + ; "caml_string_get16", (`Mutator, [ Value; Int Normalized ], Int Normalized) + ; "caml_string_get32", (`Mutator, [ Value; Int Normalized ], Int32) + ; "caml_string_get64", (`Mutator, [ Value; Int Normalized ], Int64) + ; "caml_bytes_get16", (`Mutator, [ Value; Int Normalized ], Int Normalized) + ; "caml_bytes_get32", (`Mutator, [ Value; Int Normalized ], Int32) + ; "caml_bytes_get64", (`Mutator, [ Value; Int Normalized ], Int64) + ; "caml_bytes_set16", (`Mutator, [ Value; Int Normalized; Int Unnormalized ], Value) + ; "caml_bytes_set32", (`Mutator, [ Value; Int Normalized; Int32 ], Value) + ; "caml_bytes_set64", (`Mutator, [ Value; Int Normalized; Int64 ], Value) ; "caml_lxm_next", (`Mutable, [ Value ], Int64) - ; "caml_ba_uint8_get16", (`Mutator, [ Value; Int ], Int) - ; "caml_ba_uint8_get32", (`Mutator, [ Value; Int ], Int32) - ; "caml_ba_uint8_get64", (`Mutator, [ Value; Int ], Int64) - ; "caml_ba_uint8_set16", (`Mutator, [ Value; Int; Int ], Value) - ; "caml_ba_uint8_set32", (`Mutator, [ Value; Int; Int32 ], Value) - ; "caml_ba_uint8_set64", (`Mutator, [ Value; Int; Int64 ], Value) + ; "caml_ba_uint8_get16", (`Mutator, [ Value; Int Normalized ], Int Normalized) + ; "caml_ba_uint8_get32", (`Mutator, [ Value; Int Normalized ], Int32) + ; "caml_ba_uint8_get64", (`Mutator, [ Value; Int Normalized ], Int64) + ; ( "caml_ba_uint8_set16" + , (`Mutator, [ Value; Int Normalized; Int Unnormalized ], Value) ) + ; "caml_ba_uint8_set32", (`Mutator, [ Value; Int Normalized; Int32 ], Value) + ; "caml_ba_uint8_set64", (`Mutator, [ Value; Int Normalized; Int64 ], Value) ; "caml_nextafter_float", (`Pure, [ Float; Float ], Float) - ; "caml_classify_float", (`Pure, [ Float ], Value) - ; "caml_ldexp_float", (`Pure, [ Float; Int ], Float) + ; "caml_classify_float", (`Pure, [ Float ], Int Normalized) + ; "caml_ldexp_float", (`Pure, [ Float; Int Normalized ], Float) ; "caml_erf_float", (`Pure, [ Float ], Float) ; "caml_erfc_float", (`Pure, [ Float ], Float) - ; "caml_float_compare", (`Pure, [ Float; Float ], Int) + ; "caml_float_compare", (`Pure, [ Float; Float ], Int Normalized) ]; h @@ -230,11 +245,15 @@ module Generate (Target : Target_sig.S) = struct let h = String.Hashtbl.create 128 in List.iter ~f:(fun (nm, k, f) -> - String.Hashtbl.add h nm (k, fun ctx _ l -> f (fun x -> transl_prim_arg ctx x) l)) + String.Hashtbl.add + h + nm + (k, false, Typing.Top, fun ctx _ l -> f (fun x -> transl_prim_arg ctx x) l)) internal_primitives; h - let register_prim name k f = String.Hashtbl.add internal_primitives name (k, f) + let register_prim name ?(unbox = false) ?(ret_typ = Typing.Top) kind f = + String.Hashtbl.add internal_primitives name (kind, unbox, ret_typ, f) let invalid_arity name l ~expected = failwith @@ -244,27 +263,35 @@ module Generate (Target : Target_sig.S) = struct expected (List.length l)) - let register_un_prim name k ?typ f = - register_prim name k (fun ctx _ l -> + let is_unboxed typ = + match typ with + | Some (Typing.Number (_, Unboxed)) -> true + | _ -> false + + let register_un_prim name k ?typ ?ret_typ f = + register_prim name k ~unbox:(is_unboxed typ) ?ret_typ (fun ctx _ l -> match l with | [ x ] -> f (transl_prim_arg ctx ?typ x) | l -> invalid_arity name l ~expected:1) - let register_bin_prim name k ?tx ?ty f = - register_prim name k (fun ctx _ l -> + let register_bin_prim name k ?tx ?ty ?ret_typ f = + let unbox = is_unboxed tx || is_unboxed ty in + register_prim name k ~unbox ?ret_typ (fun ctx _ l -> match l with | [ x; y ] -> f (transl_prim_arg ctx ?typ:tx x) (transl_prim_arg ctx ?typ:ty y) | _ -> invalid_arity name l ~expected:2) - let register_bin_prim_ctx name ?tx ?ty f = - register_prim name `Mutator (fun ctx context l -> + let register_bin_prim_ctx name ?tx ?ty ?ret_typ f = + let unbox = is_unboxed tx || is_unboxed ty in + register_prim name `Mutator ~unbox ?ret_typ (fun ctx context l -> match l with | [ x; y ] -> f context (transl_prim_arg ctx ?typ:tx x) (transl_prim_arg ctx ?typ:ty y) | _ -> invalid_arity name l ~expected:2) - let register_tern_prim name ?ty ?tz f = - register_prim name `Mutator (fun ctx _ l -> + let register_tern_prim name ?ty ?tz ?ret_typ f = + let unbox = is_unboxed ty || is_unboxed tz in + register_prim name `Mutator ~unbox ?ret_typ (fun ctx _ l -> match l with | [ x; y; z ] -> f @@ -273,8 +300,9 @@ module Generate (Target : Target_sig.S) = struct (transl_prim_arg ctx ?typ:tz z) | _ -> invalid_arity name l ~expected:3) - let register_tern_prim_ctx name ?ty ?tz f = - register_prim name `Mutator (fun ctx context l -> + let register_tern_prim_ctx name ?ty ?tz ?ret_typ f = + let unbox = is_unboxed ty || is_unboxed tz in + register_prim name `Mutator ~unbox ?ret_typ (fun ctx context l -> match l with | [ x; y; z ] -> f @@ -285,7 +313,7 @@ module Generate (Target : Target_sig.S) = struct | _ -> invalid_arity name l ~expected:3) let register_comparison name cmp_int cmp_boxed_int cmp_float = - register_prim name `Mutator (fun ctx _ l -> + register_prim name `Mutator ~ret_typ:(Int Normalized) (fun ctx _ l -> match l with | [ x; y ] -> ( match get_type ctx x, get_type ctx y with @@ -322,6 +350,7 @@ module Generate (Target : Target_sig.S) = struct "caml_floatarray_unsafe_get" `Mutable ~ty:(Int Normalized) + ~ret_typ:(Number (Float, Unboxed)) Memory.float_array_get; register_tern_prim "caml_array_unsafe_set" ~ty:(Int Normalized) (fun x y z -> seq (Memory.gen_array_set x y z) Value.unit); @@ -332,11 +361,17 @@ module Generate (Target : Target_sig.S) = struct ~ty:(Int Normalized) ~tz:(Number (Float, Unboxed)) (fun x y z -> seq (Memory.float_array_set x y z) Value.unit); - register_bin_prim "caml_string_unsafe_get" `Pure ~ty:(Int Normalized) Memory.bytes_get; + register_bin_prim + "caml_string_unsafe_get" + `Pure + ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) + Memory.bytes_get; register_bin_prim "caml_bytes_unsafe_get" `Mutable ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) Memory.bytes_get; register_tern_prim "caml_string_unsafe_set" @@ -354,8 +389,16 @@ module Generate (Target : Target_sig.S) = struct instr (W.Br_if (label_index context bound_error_pc, cond))) (Memory.bytes_get x y) in - register_bin_prim_ctx "caml_string_get" ~ty:(Int Normalized) bytes_get; - register_bin_prim_ctx "caml_bytes_get" ~ty:(Int Normalized) bytes_get; + register_bin_prim_ctx + "caml_string_get" + ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) + bytes_get; + register_bin_prim_ctx + "caml_bytes_get" + ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) + bytes_get; let bytes_set context x y z = seq (let* cond = Arith.uge y (Memory.bytes_length x) in @@ -373,42 +416,50 @@ module Generate (Target : Target_sig.S) = struct ~ty:(Int Normalized) ~tz:(Int Unnormalized) bytes_set; - register_un_prim "caml_ml_string_length" `Pure (fun x -> Memory.bytes_length x); - register_un_prim "caml_ml_bytes_length" `Pure (fun x -> Memory.bytes_length x); + register_un_prim "caml_ml_string_length" `Pure ~ret_typ:(Int Normalized) (fun x -> + Memory.bytes_length x); + register_un_prim "caml_ml_bytes_length" `Pure ~ret_typ:(Int Normalized) (fun x -> + Memory.bytes_length x); register_bin_prim "%int_add" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_add; register_bin_prim "%int_sub" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_sub; register_bin_prim "%int_mul" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_mul; register_bin_prim "%direct_int_mul" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_mul; register_bin_prim "%direct_int_div" `Pure ~tx:(Int Normalized) ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) Value.int_div; register_bin_prim_ctx "%int_div" ~tx:(Int Normalized) ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) (fun context x y -> seq (let* cond = Arith.eqz y in @@ -419,54 +470,67 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Int Normalized) ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) Value.int_mod; register_bin_prim_ctx "%int_mod" ~tx:(Int Normalized) ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) (fun context x y -> seq (let* cond = Arith.eqz y in instr (W.Br_if (label_index context zero_divide_pc, cond))) (Value.int_mod x y)); - register_un_prim "%int_neg" `Pure ~typ:(Int Unnormalized) Value.int_neg; + register_un_prim + "%int_neg" + `Pure + ~typ:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) + Value.int_neg; register_bin_prim "%int_or" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_or; register_bin_prim "%int_and" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_and; register_bin_prim "%int_xor" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_xor; register_bin_prim "%int_lsl" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Unnormalized) Value.int_lsl; register_bin_prim "%int_lsr" `Pure ~tx:(Int Unnormalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Normalized) Value.int_lsr; register_bin_prim "%int_asr" `Pure ~tx:(Int Normalized) ~ty:(Int Unnormalized) + ~ret_typ:(Int Normalized) Value.int_asr; - register_un_prim "%direct_obj_tag" `Pure Memory.tag; + register_un_prim "%direct_obj_tag" `Pure ~ret_typ:(Int Ref) Memory.tag; register_bin_prim_ctx "caml_check_bound" ~ty:(Int Normalized) (fun context x y -> seq (let* cond = Arith.uge y (Memory.array_length x) in @@ -497,23 +561,27 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f g -> float_bin_op Add f g); register_bin_prim "caml_sub_float" ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) `Pure (fun f g -> float_bin_op Sub f g); register_bin_prim "caml_mul_float" ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) `Pure (fun f g -> float_bin_op Mul f g); register_bin_prim "caml_div_float" ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) `Pure (fun f g -> float_bin_op Div f g); register_bin_prim @@ -521,11 +589,13 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f g -> float_bin_op CopySign f g); register_un_prim "caml_signbit_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Int Normalized) (fun f -> let* f = f in let sign = W.BinOp (F64 CopySign, Const (F64 1.), f) in @@ -534,127 +604,255 @@ module Generate (Target : Target_sig.S) = struct "caml_neg_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f -> float_un_op Neg f); register_un_prim "caml_abs_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f -> float_un_op Abs f); register_un_prim "caml_ceil_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f -> float_un_op Ceil f); register_un_prim "caml_floor_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f -> float_un_op Floor f); register_un_prim "caml_trunc_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f -> float_un_op Trunc f); - register_un_prim "caml_round_float" `Pure ~typ:(Number (Float, Unboxed)) Math.round; + register_un_prim + "caml_round_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.round; register_un_prim "caml_sqrt_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun f -> float_un_op Sqrt f); register_bin_prim "caml_eq_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Int Normalized) (fun f g -> float_bin_op Eq f g); register_bin_prim "caml_neq_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Int Normalized) (fun f g -> float_bin_op Ne f g); register_bin_prim "caml_ge_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Int Normalized) (fun f g -> float_bin_op Ge f g); register_bin_prim "caml_le_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Int Normalized) (fun f g -> float_bin_op Le f g); register_bin_prim "caml_gt_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Int Normalized) (fun f g -> float_bin_op Gt f g); register_bin_prim "caml_lt_float" ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Int Normalized) `Pure (fun f g -> float_bin_op Lt f g); register_un_prim "caml_int_of_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Int Unnormalized) (fun f -> let* f = f in return (W.UnOp (I32 (TruncSatF64 S), f))); - register_un_prim "caml_float_of_int" `Pure ~typ:(Int Normalized) (fun n -> + register_un_prim + "caml_float_of_int" + `Pure + ~typ:(Int Normalized) + ~ret_typ:(Number (Float, Unboxed)) + (fun n -> let* n = n in return (W.UnOp (F64 (Convert (`I32, S)), n))); - register_un_prim "caml_cos_float" `Pure ~typ:(Number (Float, Unboxed)) Math.cos; - register_un_prim "caml_sin_float" `Pure ~typ:(Number (Float, Unboxed)) Math.sin; - register_un_prim "caml_tan_float" `Pure ~typ:(Number (Float, Unboxed)) Math.tan; - register_un_prim "caml_acos_float" `Pure ~typ:(Number (Float, Unboxed)) Math.acos; - register_un_prim "caml_asin_float" `Pure ~typ:(Number (Float, Unboxed)) Math.asin; - register_un_prim "caml_atan_float" `Pure ~typ:(Number (Float, Unboxed)) Math.atan; + register_un_prim + "caml_cos_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.cos; + register_un_prim + "caml_sin_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.sin; + register_un_prim + "caml_tan_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.tan; + register_un_prim + "caml_acos_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.acos; + register_un_prim + "caml_asin_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.asin; + register_un_prim + "caml_atan_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.atan; register_bin_prim "caml_atan2_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) Math.atan2; - register_un_prim "caml_cosh_float" `Pure ~typ:(Number (Float, Unboxed)) Math.cosh; - register_un_prim "caml_sinh_float" `Pure ~typ:(Number (Float, Unboxed)) Math.sinh; - register_un_prim "caml_tanh_float" `Pure ~typ:(Number (Float, Unboxed)) Math.tanh; - register_un_prim "caml_acosh_float" `Pure ~typ:(Number (Float, Unboxed)) Math.acosh; - register_un_prim "caml_asinh_float" `Pure ~typ:(Number (Float, Unboxed)) Math.asinh; - register_un_prim "caml_atanh_float" `Pure ~typ:(Number (Float, Unboxed)) Math.atanh; - register_un_prim "caml_cbrt_float" `Pure ~typ:(Number (Float, Unboxed)) Math.cbrt; - register_un_prim "caml_exp_float" `Pure ~typ:(Number (Float, Unboxed)) Math.exp; - register_un_prim "caml_exp2_float" `Pure ~typ:(Number (Float, Unboxed)) Math.exp2; - register_un_prim "caml_log_float" `Pure ~typ:(Number (Float, Unboxed)) Math.log; - register_un_prim "caml_expm1_float" `Pure ~typ:(Number (Float, Unboxed)) Math.expm1; - register_un_prim "caml_log1p_float" `Pure ~typ:(Number (Float, Unboxed)) Math.log1p; - register_un_prim "caml_log2_float" `Pure ~typ:(Number (Float, Unboxed)) Math.log2; - register_un_prim "caml_log10_float" `Pure ~typ:(Number (Float, Unboxed)) Math.log10; + register_un_prim + "caml_cosh_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.cosh; + register_un_prim + "caml_sinh_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.sinh; + register_un_prim + "caml_tanh_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.tanh; + register_un_prim + "caml_acosh_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.acosh; + register_un_prim + "caml_asinh_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.asinh; + register_un_prim + "caml_atanh_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.atanh; + register_un_prim + "caml_cbrt_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.cbrt; + register_un_prim + "caml_exp_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.exp; + register_un_prim + "caml_exp2_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.exp2; + register_un_prim + "caml_log_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.log; + register_un_prim + "caml_expm1_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.expm1; + register_un_prim + "caml_log1p_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.log1p; + register_un_prim + "caml_log2_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.log2; + register_un_prim + "caml_log10_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + Math.log10; register_bin_prim "caml_power_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) Math.power; register_bin_prim "caml_hypot_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) Math.hypot; register_bin_prim "caml_fmod_float" `Pure ~tx:(Number (Float, Unboxed)) ~ty:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) Math.fmod; register_un_prim "caml_int32_bits_of_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun f -> let* f = f in return (W.UnOp (I32 ReinterpretF, F32DemoteF64 f))); @@ -662,6 +860,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int32_float_of_bits" `Pure ~typ:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun i -> let* i = i in return (W.F64PromoteF32 (UnOp (F32 ReinterpretI, i)))); @@ -669,6 +868,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int32_of_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun f -> let* f = f in return (W.UnOp (I32 (TruncSatF64 S), f))); @@ -676,6 +876,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int32_to_float" `Pure ~typ:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun n -> let* n = n in return (W.UnOp (F64 (Convert (`I32, S)), n))); @@ -683,6 +884,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int32_neg" `Pure ~typ:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i -> let* i = i in return (W.BinOp (I32 Sub, Const (I32 0l), i))); @@ -691,41 +893,48 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op Add i j); register_bin_prim "caml_int32_sub" `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op Sub i j); register_bin_prim "caml_int32_mul" `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op Mul i j); register_bin_prim "caml_int32_and" `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op And i j); register_bin_prim "caml_int32_or" `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op Or i j); register_bin_prim "caml_int32_xor" `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op Xor i j); register_bin_prim_ctx "caml_int32_div" ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun context i j -> let res = Var.fresh () in (*ZZZ Can we do better?*) @@ -758,6 +967,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int32_mod" ~tx:(Number (Int32, Unboxed)) ~ty:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun context i j -> let j' = Var.fresh () in seq @@ -772,35 +982,51 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op Shl i j); register_bin_prim "caml_int32_shift_right" `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op (Shr S) i j); register_bin_prim "caml_int32_shift_right_unsigned" `Pure ~tx:(Number (Int32, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Int32, Unboxed)) (fun i j -> int32_bin_op (Shr U) i j); - register_un_prim "caml_int32_to_int" `Pure ~typ:(Number (Int32, Unboxed)) (fun i -> i); - register_un_prim "caml_int32_of_int" `Pure ~typ:(Int Normalized) (fun i -> i); + register_un_prim + "caml_int32_to_int" + `Pure + ~typ:(Number (Int32, Unboxed)) + ~ret_typ:(Int Unnormalized) + (fun i -> i); + register_un_prim + "caml_int32_of_int" + `Pure + ~typ:(Int Normalized) + ~ret_typ:(Number (Int32, Unboxed)) + (fun i -> i); register_un_prim "caml_nativeint_of_int32" `Pure ~typ:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i -> i); register_un_prim "caml_nativeint_to_int32" `Pure ~typ:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i -> i); register_un_prim "caml_int64_bits_of_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun f -> let* f = f in return (W.UnOp (I64 ReinterpretF, f))); @@ -808,6 +1034,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_float_of_bits" `Pure ~typ:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun i -> let* i = i in return (W.UnOp (F64 ReinterpretI, i))); @@ -815,6 +1042,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_of_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun f -> let* f = f in return (W.UnOp (I64 (TruncSatF64 S), f))); @@ -822,6 +1050,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_to_float" `Pure ~typ:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun n -> let* n = n in return (W.UnOp (F64 (Convert (`I64, S)), n))); @@ -829,6 +1058,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_neg" `Pure ~typ:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i -> let* i = i in return (W.BinOp (I64 Sub, Const (I64 0L), i))); @@ -837,41 +1067,48 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_bin_op Add i j); register_bin_prim "caml_int64_sub" `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_bin_op Sub i j); register_bin_prim "caml_int64_mul" `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_bin_op Mul i j); register_bin_prim "caml_int64_and" `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_bin_op And i j); register_bin_prim "caml_int64_or" `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_bin_op Or i j); register_bin_prim "caml_int64_xor" `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_bin_op Xor i j); register_bin_prim_ctx "caml_int64_div" ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun context i j -> let res = Var.fresh () in (*ZZZ Can we do better?*) @@ -904,6 +1141,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_mod" ~tx:(Number (Int64, Unboxed)) ~ty:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun context i j -> let j' = Var.fresh () in seq @@ -918,27 +1156,36 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_shift_op Shl i j); register_bin_prim "caml_int64_shift_right" `Pure ~tx:(Number (Int64, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Int64, Unboxed)) (fun i j -> int64_shift_op (Shr S) i j); register_bin_prim "caml_int64_shift_right_unsigned" ~tx:(Number (Int64, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Int64, Unboxed)) `Pure (fun i j -> int64_shift_op (Shr U) i j); register_un_prim "caml_int64_to_int" `Pure ~typ:(Number (Int64, Unboxed)) + ~ret_typ:(Int Unnormalized) (fun i -> let* i = i in return (W.I32WrapI64 i)); - register_un_prim "caml_int64_of_int" `Pure ~typ:(Int Normalized) (fun i -> + register_un_prim + "caml_int64_of_int" + `Pure + ~typ:(Int Normalized) + ~ret_typ:(Number (Int64, Unboxed)) + (fun i -> let* i = i in return (match i with @@ -948,6 +1195,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_to_int32" `Pure ~typ:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) (fun i -> let* i = i in return (W.I32WrapI64 i)); @@ -955,6 +1203,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_of_int32" `Pure ~typ:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i -> let* i = i in return (W.I64ExtendI32 (S, i))); @@ -962,6 +1211,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_to_nativeint" `Pure ~typ:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i -> let* i = i in return (W.I32WrapI64 i)); @@ -969,6 +1219,7 @@ module Generate (Target : Target_sig.S) = struct "caml_int64_of_nativeint" `Pure ~typ:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) (fun i -> let* i = i in return (W.I64ExtendI32 (S, i))); @@ -976,6 +1227,7 @@ module Generate (Target : Target_sig.S) = struct "caml_nativeint_bits_of_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun f -> let* f = f in return (W.UnOp (I32 ReinterpretF, F32DemoteF64 f))); @@ -983,6 +1235,7 @@ module Generate (Target : Target_sig.S) = struct "caml_nativeint_float_of_bits" `Pure ~typ:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun i -> let* i = i in return (W.F64PromoteF32 (UnOp (I32 ReinterpretF, i)))); @@ -990,6 +1243,7 @@ module Generate (Target : Target_sig.S) = struct "caml_nativeint_of_float" `Pure ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun f -> let* f = f in return (W.UnOp (I32 (TruncSatF64 S), f))); @@ -997,6 +1251,7 @@ module Generate (Target : Target_sig.S) = struct "caml_nativeint_to_float" `Pure ~typ:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) (fun n -> let* n = n in return (W.UnOp (F64 (Convert (`I32, S)), n))); @@ -1004,6 +1259,7 @@ module Generate (Target : Target_sig.S) = struct "caml_nativeint_neg" `Pure ~typ:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i -> let* i = i in return (W.BinOp (I32 Sub, Const (I32 0l), i))); @@ -1012,41 +1268,48 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op Add i j); register_bin_prim "caml_nativeint_sub" `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op Sub i j); register_bin_prim "caml_nativeint_mul" `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op Mul i j); register_bin_prim "caml_nativeint_and" `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op And i j); register_bin_prim "caml_nativeint_or" `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op Or i j); register_bin_prim "caml_nativeint_xor" `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op Xor i j); register_bin_prim_ctx "caml_nativeint_div" ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun context i j -> let res = Var.fresh () in (*ZZZ Can we do better?*) @@ -1079,6 +1342,7 @@ module Generate (Target : Target_sig.S) = struct "caml_nativeint_mod" ~tx:(Number (Nativeint, Unboxed)) ~ty:(Number (Nativeint, Unboxed)) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun context i j -> let j' = Var.fresh () in seq @@ -1093,30 +1357,40 @@ module Generate (Target : Target_sig.S) = struct `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op Shl i j); register_bin_prim "caml_nativeint_shift_right" `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op (Shr S) i j); register_bin_prim "caml_nativeint_shift_right_unsigned" `Pure ~tx:(Number (Nativeint, Unboxed)) ~ty:(Int Unnormalized) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i j -> nativeint_bin_op (Shr U) i j); register_un_prim "caml_nativeint_to_int" `Pure ~typ:(Number (Nativeint, Unboxed)) + ~ret_typ:(Int Unnormalized) + (fun i -> i); + register_un_prim + "caml_nativeint_of_int" + `Pure + ~typ:(Int Normalized) + ~ret_typ:(Number (Nativeint, Unboxed)) (fun i -> i); - register_un_prim "caml_nativeint_of_int" `Pure ~typ:(Int Normalized) (fun i -> i); register_bin_prim "caml_int_compare" `Pure ~tx:(Int Normalized) ~ty:(Int Normalized) + ~ret_typ:(Int Normalized) (fun i j -> Arith.((j < i) - (i < j))); register_prim "%js_array" `Pure (fun ctx _ l -> Memory.allocate ~tag:0 (expression_list (fun x -> transl_prim_arg ctx x) l)); @@ -1150,7 +1424,7 @@ module Generate (Target : Target_sig.S) = struct (fun ctx x y -> translate_int_equality ctx ~negate:true x y) Ne Ne; - register_prim "caml_compare" `Mutator (fun ctx _ l -> + register_prim "caml_compare" `Mutator ~ret_typ:(Int Normalized) (fun ctx _ l -> match l with | [ x; y ] -> ( match get_type ctx x, get_type ctx y with @@ -1483,8 +1757,8 @@ module Generate (Target : Target_sig.S) = struct | Prim (p, l) -> ( match p with | Extern name when String.Hashtbl.mem internal_primitives name -> - snd (String.Hashtbl.find internal_primitives name) ctx context l - |> box_number_if_needed ctx x + let _, _, _, f = String.Hashtbl.find internal_primitives name in + f ctx context l |> box_number_if_needed ctx x | Extern name when String.Hashtbl.mem specialized_primitives name -> let ((_, arg_typ, _) as typ) = String.Hashtbl.find specialized_primitives name @@ -1494,19 +1768,7 @@ module Generate (Target : Target_sig.S) = struct match arg_typ, l with | [], [] -> return (W.Call (f, List.rev acc)) | repr :: rem, x :: r -> - let* x = - transl_prim_arg - ctx - ?typ: - (match repr with - | Value -> None - | Float -> Some (Number (Float, Unboxed)) - | Int -> Some (Int Normalized) - | Int32 -> Some (Number (Int32, Unboxed)) - | Nativeint -> Some (Number (Nativeint, Unboxed)) - | Int64 -> Some (Number (Int64, Unboxed))) - x - in + let* x = transl_prim_arg ctx ?typ:(repr_type repr) x in loop (x :: acc) rem r | [], _ :: _ | _ :: _, [] -> assert false in @@ -2086,13 +2348,27 @@ module Generate (Target : Target_sig.S) = struct List.rev_append context.other_fields (imports @ constant_data) let init () = + Typing.reset (); Primitive.register "caml_make_array" `Mutable None None; Primitive.register "caml_array_of_uniform_array" `Mutable None None; String.Hashtbl.iter - (fun name (k, _) -> Primitive.register name k None None) + (fun name (k, unbox, typ, _) -> + Primitive.register name k None None; + Typing.register_prim name ~unbox typ) internal_primitives; String.Hashtbl.iter - (fun name (k, _, _) -> Primitive.register name k None None) + (fun name (k, param_types, typ) -> + Primitive.register name k None None; + Typing.register_prim + name + ~unbox: + (List.exists + ~f:(fun ty -> + match ty with + | Int32 | Nativeint | Int64 | Float -> true + | Value | Int _ -> false) + param_types) + (Option.value ~default:Typing.Top (repr_type typ))) specialized_primitives end diff --git a/compiler/lib-wasm/typing.ml b/compiler/lib-wasm/typing.ml index 8785c88dfc..90b8819c43 100644 --- a/compiler/lib-wasm/typing.ml +++ b/compiler/lib-wasm/typing.ml @@ -313,10 +313,10 @@ let bigarray_type ~approx ba = | Bigarray { kind; _ } -> bigarray_element_type kind | _ -> Top +let primitive_types = String.Hashtbl.create 16 + let prim_type ~st ~approx prim args = match prim with - | "%int_add" | "%int_sub" | "%int_mul" | "%direct_int_mul" | "%int_lsl" | "%int_neg" -> - Int Unnormalized | "%int_and" -> ( match List.map ~f:(fun x -> arg_type ~approx x) args with | [ (Bot | Int (Ref | Normalized)); _ ] | [ _; (Bot | Int (Ref | Normalized)) ] -> @@ -327,152 +327,6 @@ let prim_type ~st ~approx prim args = | [ (Bot | Int (Ref | Normalized)); (Bot | Int (Ref | Normalized)) ] -> Int Normalized | _ -> Int Unnormalized) - | "%int_lsr" - | "%int_asr" - | "%int_div" - | "%int_mod" - | "%direct_int_div" - | "%direct_int_mod" -> Int Normalized - | "caml_greaterthan" - | "caml_greaterequal" - | "caml_lessthan" - | "caml_lessequal" - | "caml_equal" - | "caml_notequal" - | "caml_compare" -> Int Normalized - | "caml_int32_bswap" -> Number (Int32, Unboxed) - | "caml_nativeint_bswap" -> Number (Nativeint, Unboxed) - | "caml_int64_bswap" -> Number (Int64, Unboxed) - | "caml_int32_compare" | "caml_nativeint_compare" | "caml_int64_compare" -> - Int Normalized - | "caml_string_get16" -> Int Normalized - | "caml_string_get32" -> Number (Int32, Unboxed) - | "caml_string_get64" -> Number (Int64, Unboxed) - | "caml_bytes_get16" -> Int Normalized - | "caml_bytes_get32" -> Number (Int32, Unboxed) - | "caml_bytes_get64" -> Number (Int64, Unboxed) - | "caml_lxm_next" -> Number (Int64, Unboxed) - | "caml_ba_uint8_get16" -> Int Normalized - | "caml_ba_uint8_get32" -> Number (Int32, Unboxed) - | "caml_ba_uint8_get64" -> Number (Int64, Unboxed) - | "caml_nextafter_float" -> Number (Float, Unboxed) - | "caml_classify_float" -> Int Ref - | "caml_ldexp_float" | "caml_erf_float" | "caml_erfc_float" -> Number (Float, Unboxed) - | "caml_float_compare" -> Int Normalized - | "caml_floatarray_unsafe_get" -> Number (Float, Unboxed) - | "caml_bytes_unsafe_get" - | "caml_string_unsafe_get" - | "caml_bytes_get" - | "caml_string_get" - | "caml_ml_string_length" - | "caml_ml_bytes_length" -> Int Normalized - | "%direct_obj_tag" -> Int Ref - | "caml_add_float" - | "caml_sub_float" - | "caml_mul_float" - | "caml_div_float" - | "caml_copysign_float" -> Number (Float, Unboxed) - | "caml_signbit_float" -> Int Normalized - | "caml_neg_float" - | "caml_abs_float" - | "caml_ceil_float" - | "caml_floor_float" - | "caml_trunc_float" - | "caml_round_float" - | "caml_sqrt_float" -> Number (Float, Unboxed) - | "caml_eq_float" - | "caml_neq_float" - | "caml_ge_float" - | "caml_le_float" - | "caml_gt_float" - | "caml_lt_float" -> Int Normalized - | "caml_int_of_float" -> Int Unnormalized - | "caml_float_of_int" - | "caml_cos_float" - | "caml_sin_float" - | "caml_tan_float" - | "caml_acos_float" - | "caml_asin_float" - | "caml_atan_float" - | "caml_atan2_float" - | "caml_cosh_float" - | "caml_sinh_float" - | "caml_tanh_float" - | "caml_acosh_float" - | "caml_asinh_float" - | "caml_atanh_float" - | "caml_cbrt_float" - | "caml_exp_float" - | "caml_exp2_float" - | "caml_log_float" - | "caml_expm1_float" - | "caml_log1p_float" - | "caml_log2_float" - | "caml_log10_float" - | "caml_power_float" - | "caml_hypot_float" - | "caml_fmod_float" -> Number (Float, Unboxed) - | "caml_int32_bits_of_float" -> Number (Int32, Unboxed) - | "caml_int32_float_of_bits" -> Number (Float, Unboxed) - | "caml_int32_of_float" -> Number (Int32, Unboxed) - | "caml_int32_to_float" -> Number (Float, Unboxed) - | "caml_int32_neg" - | "caml_int32_add" - | "caml_int32_sub" - | "caml_int32_mul" - | "caml_int32_and" - | "caml_int32_or" - | "caml_int32_xor" - | "caml_int32_div" - | "caml_int32_mod" - | "caml_int32_shift_left" - | "caml_int32_shift_right" - | "caml_int32_shift_right_unsigned" -> Number (Int32, Unboxed) - | "caml_int32_to_int" -> Int Unnormalized - | "caml_int32_of_int" -> Number (Int32, Unboxed) - | "caml_nativeint_of_int32" -> Number (Nativeint, Unboxed) - | "caml_nativeint_to_int32" -> Number (Int32, Unboxed) - | "caml_int64_bits_of_float" -> Number (Int64, Unboxed) - | "caml_int64_float_of_bits" -> Number (Float, Unboxed) - | "caml_int64_of_float" -> Number (Int64, Unboxed) - | "caml_int64_to_float" -> Number (Float, Unboxed) - | "caml_int64_neg" - | "caml_int64_add" - | "caml_int64_sub" - | "caml_int64_mul" - | "caml_int64_and" - | "caml_int64_or" - | "caml_int64_xor" - | "caml_int64_div" - | "caml_int64_mod" - | "caml_int64_shift_left" - | "caml_int64_shift_right" - | "caml_int64_shift_right_unsigned" -> Number (Int64, Unboxed) - | "caml_int64_to_int" -> Int Unnormalized - | "caml_int64_of_int" -> Number (Int64, Unboxed) - | "caml_int64_to_int32" -> Number (Int32, Unboxed) - | "caml_int64_of_int32" -> Number (Int64, Unboxed) - | "caml_int64_to_nativeint" -> Number (Nativeint, Unboxed) - | "caml_int64_of_nativeint" -> Number (Int64, Unboxed) - | "caml_nativeint_bits_of_float" -> Number (Nativeint, Unboxed) - | "caml_nativeint_float_of_bits" -> Number (Float, Unboxed) - | "caml_nativeint_of_float" -> Number (Nativeint, Unboxed) - | "caml_nativeint_to_float" -> Number (Float, Unboxed) - | "caml_nativeint_neg" - | "caml_nativeint_add" - | "caml_nativeint_sub" - | "caml_nativeint_mul" - | "caml_nativeint_and" - | "caml_nativeint_or" - | "caml_nativeint_xor" - | "caml_nativeint_div" - | "caml_nativeint_mod" - | "caml_nativeint_shift_left" - | "caml_nativeint_shift_right" - | "caml_nativeint_shift_right_unsigned" -> Number (Nativeint, Unboxed) - | "caml_nativeint_to_int" -> Int Unnormalized - | "caml_nativeint_of_int" -> Number (Nativeint, Unboxed) - | "caml_int_compare" -> Int Normalized | "caml_ba_create" -> ( match args with | [ Pc (Int kind); Pc (Int layout); _ ] -> @@ -492,7 +346,11 @@ let prim_type ~st ~approx prim args = | Expr (Block _) -> bigarray_type ~approx ba | _ -> Top) | [] | [ _ ] | _ :: Pc _ :: _ -> Top) - | _ -> Top + | _ -> ( try snd (String.Hashtbl.find primitive_types prim) with Not_found -> Top) + +let reset () = String.Hashtbl.reset primitive_types + +let register_prim nm ~unbox typ = String.Hashtbl.replace primitive_types nm (unbox, typ) let propagate st approx x : Domain.t = match st.global_flow_state.defs.(Var.idx x) with @@ -621,129 +479,6 @@ let solver st = in Solver.f () g (propagate st) -(* These are primitives which are handled internally by the compiler, - plus the specialized primitives listed in Generate. *) -let primitives_with_unboxed_parameters = - let h = String.Hashtbl.create 256 in - List.iter - ~f:(fun s -> String.Hashtbl.add h s ()) - [ "caml_int32_bswap" - ; "caml_nativeint_bswap" - ; "caml_int64_bswap" - ; "caml_int32_compare" - ; "caml_nativeint_compare" - ; "caml_int64_compare" - ; "caml_nextafter_float" - ; "caml_classify_float" - ; "caml_ldexp_float" - ; "caml_erf_float" - ; "caml_erfc_float" - ; "caml_float_compare" - ; "caml_add_float" - ; "caml_sub_float" - ; "caml_mul_float" - ; "caml_div_float" - ; "caml_copysign_float" - ; "caml_signbit_float" - ; "caml_neg_float" - ; "caml_abs_float" - ; "caml_ceil_float" - ; "caml_floor_float" - ; "caml_trunc_float" - ; "caml_round_float" - ; "caml_sqrt_float" - ; "caml_eq_float" - ; "caml_neq_float" - ; "caml_ge_float" - ; "caml_le_float" - ; "caml_gt_float" - ; "caml_lt_float" - ; "caml_int_of_float" - ; "caml_cos_float" - ; "caml_sin_float" - ; "caml_tan_float" - ; "caml_acos_float" - ; "caml_asin_float" - ; "caml_atan_float" - ; "caml_atan2_float" - ; "caml_cosh_float" - ; "caml_sinh_float" - ; "caml_tanh_float" - ; "caml_acosh_float" - ; "caml_asinh_float" - ; "caml_atanh_float" - ; "caml_cbrt_float" - ; "caml_exp_float" - ; "caml_exp2_float" - ; "caml_log_float" - ; "caml_expm1_float" - ; "caml_log1p_float" - ; "caml_log2_float" - ; "caml_log10_float" - ; "caml_power_float" - ; "caml_hypot_float" - ; "caml_fmod_float" - ; "caml_int32_bits_of_float" - ; "caml_int32_float_of_bits" - ; "caml_int32_of_float" - ; "caml_int32_to_float" - ; "caml_int32_neg" - ; "caml_int32_add" - ; "caml_int32_sub" - ; "caml_int32_mul" - ; "caml_int32_and" - ; "caml_int32_or" - ; "caml_int32_xor" - ; "caml_int32_div" - ; "caml_int32_mod" - ; "caml_int32_shift_left" - ; "caml_int32_shift_right" - ; "caml_int32_shift_right_unsigned" - ; "caml_int32_to_int" - ; "caml_nativeint_of_int32" - ; "caml_nativeint_to_int32" - ; "caml_int64_bits_of_float" - ; "caml_int64_float_of_bits" - ; "caml_int64_of_float" - ; "caml_int64_to_float" - ; "caml_int64_neg" - ; "caml_int64_add" - ; "caml_int64_sub" - ; "caml_int64_mul" - ; "caml_int64_and" - ; "caml_int64_or" - ; "caml_int64_xor" - ; "caml_int64_div" - ; "caml_int64_mod" - ; "caml_int64_shift_left" - ; "caml_int64_shift_right" - ; "caml_int64_shift_right_unsigned" - ; "caml_int64_to_int" - ; "caml_int64_to_int32" - ; "caml_int64_of_int32" - ; "caml_int64_to_nativeint" - ; "caml_int64_of_nativeint" - ; "caml_nativeint_bits_of_float" - ; "caml_nativeint_float_of_bits" - ; "caml_nativeint_of_float" - ; "caml_nativeint_to_float" - ; "caml_nativeint_neg" - ; "caml_nativeint_add" - ; "caml_nativeint_sub" - ; "caml_nativeint_mul" - ; "caml_nativeint_and" - ; "caml_nativeint_or" - ; "caml_nativeint_xor" - ; "caml_nativeint_div" - ; "caml_nativeint_mod" - ; "caml_nativeint_shift_left" - ; "caml_nativeint_shift_right" - ; "caml_nativeint_shift_right_unsigned" - ; "caml_nativeint_to_int" - ; "caml_floatarray_unsafe_set" - ]; - h - let type_specialized_primitive types global_flow_state name args = match name with | "caml_greaterthan" @@ -829,7 +564,8 @@ let box_numbers p st types = | Prim (Extern s, args) -> if not - (String.Hashtbl.mem primitives_with_unboxed_parameters s + (String.Hashtbl.mem primitive_types s + && fst (String.Hashtbl.find primitive_types s) || type_specialized_primitive types st.global_flow_state s args ) then diff --git a/compiler/lib-wasm/typing.mli b/compiler/lib-wasm/typing.mli index 5ea4e7da51..40b02510e9 100644 --- a/compiler/lib-wasm/typing.mli +++ b/compiler/lib-wasm/typing.mli @@ -61,6 +61,10 @@ val var_type : t -> Code.Var.t -> typ val return_type : t -> Code.Var.t -> typ +val reset : unit -> unit + +val register_prim : string -> unbox:bool -> typ -> unit + val f : global_flow_state:Global_flow.state -> global_flow_info:Global_flow.info diff --git a/runtime/wasm/float.wat b/runtime/wasm/float.wat index 295a358cfe..6e72e10400 100644 --- a/runtime/wasm/float.wat +++ b/runtime/wasm/float.wat @@ -679,22 +679,21 @@ (else (local.set $i (i64.sub (local.get $i) (i64.const 1))))) (return (f64.reinterpret_i64 (local.get $i)))))) - (func (export "caml_classify_float") (param $x f64) (result (ref eq)) + (func (export "caml_classify_float") (param $x f64) (result i32) (local $a f64) (local.set $a (f64.abs (local.get $x))) - (ref.i31 - (if (result i32) (f64.ge (local.get $a) (f64.const 0x1p-1022)) - (then - (if (result i32) (f64.lt (local.get $a) (f64.const inf)) - (then (i32.const 0)) ;; normal - (else (i32.const 3)))) ;; infinity - (else - (if (result i32) (f64.eq (local.get $a) (f64.const 0)) - (then (i32.const 2)) ;; zero - (else - (if (result i32) (f64.eq (local.get $a) (local.get $a)) - (then (i32.const 1)) ;; subnormal - (else (i32.const 4))))))))) ;; nan + (if (result i32) (f64.ge (local.get $a) (f64.const 0x1p-1022)) + (then + (if (result i32) (f64.lt (local.get $a) (f64.const inf)) + (then (i32.const 0)) ;; normal + (else (i32.const 3)))) ;; infinity + (else + (if (result i32) (f64.eq (local.get $a) (f64.const 0)) + (then (i32.const 2)) ;; zero + (else + (if (result i32) (f64.eq (local.get $a) (local.get $a)) + (then (i32.const 1)) ;; subnormal + (else (i32.const 4)))))))) ;; nan (func (export "caml_modf_float") (param (ref eq)) (result (ref eq)) (local $x f64) (local $a f64) (local $i f64) (local $f f64) From c3ede290525dfa78644bbcb0dfb4f235c2238525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 00:05:53 +0200 Subject: [PATCH 02/36] Improved implementation of signbit --- compiler/lib-wasm/generate.ml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index 86f0c323b8..cc9161af71 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -598,8 +598,9 @@ module Generate (Target : Target_sig.S) = struct ~ret_typ:(Int Normalized) (fun f -> let* f = f in - let sign = W.BinOp (F64 CopySign, Const (F64 1.), f) in - return (W.BinOp (F64 Lt, sign, Const (F64 0.)))); + return + (W.I32WrapI64 + (BinOp (I64 (Shr U), W.UnOp (I64 ReinterpretF, f), W.Const (I64 63L))))); register_un_prim "caml_neg_float" `Pure From d6780362f06bac9deecfb6b30b1fa052844c5215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 3 Oct 2025 10:45:18 +0200 Subject: [PATCH 03/36] Fix static evaluation of caml_int64_float_of_bits Do not statically evaluate caml_int64_float_of_bits when it would result in a non-canonical NaN. --- compiler/lib/eval.ml | 18 +++++++++++++++--- compiler/tests-full/stdlib.cma.expected.js | 8 ++++++-- compiler/tests-jsoo/test_nan.ml | 15 +++++++++++++++ runtime/js/ieee_754.js | 1 + 4 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 compiler/tests-jsoo/test_nan.ml diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 27c78c6f35..65a3ab3d41 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -139,7 +139,7 @@ let nativeint_shiftop (l : constant list) (f : int32 -> int -> int32) : constant | [ NativeInt i; Int j ] -> Some (NativeInt (f i (Targetint.to_int_exn j))) | _ -> None -let eval_prim x = +let eval_prim ~target x = match x with | Not, [ Int i ] -> bool (Targetint.is_zero i) | Lt, [ Int i; Int j ] -> bool Targetint.(i < j) @@ -231,7 +231,13 @@ let eval_prim x = (* int32 *) | "caml_int32_bits_of_float", [ Float f ] -> int32 (Int32.bits_of_float (Int64.float_of_bits f)) - | "caml_int32_float_of_bits", [ Int32 i ] -> Some (float (Int32.float_of_bits i)) + | "caml_int32_float_of_bits", [ Int32 i ] + when match target with + | `JavaScript -> + let f = Int32.float_of_bits i in + (not (Float.is_nan f)) + || Int64.equal (Int64.bits_of_float f) (Int64.bits_of_float nan) + | `Wasm -> true -> Some (float (Int32.float_of_bits i)) | "caml_int32_of_float", [ Float f ] -> int32 (Int32.of_float (Int64.float_of_bits f)) | "caml_int32_to_float", [ Int32 i ] -> Some (float (Int32.to_float i)) @@ -284,7 +290,12 @@ let eval_prim x = | "caml_nativeint_of_int", [ Int i ] -> nativeint (Targetint.to_int32 i) (* int64 *) | "caml_int64_bits_of_float", [ Float f ] -> int64 f - | "caml_int64_float_of_bits", [ Int64 i ] -> Some (Float i) + | "caml_int64_float_of_bits", [ Int64 i ] + when match target with + | `JavaScript -> + (not (Float.is_nan (Int64.float_of_bits i))) + || Int64.equal i (Int64.bits_of_float nan) + | `Wasm -> true -> Some (Float i) | "caml_int64_of_float", [ Float f ] -> int64 (Int64.of_float (Int64.float_of_bits f)) | "caml_int64_to_float", [ Int64 i ] -> Some (float (Int64.to_float i)) @@ -632,6 +643,7 @@ let eval_instr update_count inline_constant ~target info i = | _ -> false) then eval_prim + ~target ( prim , List.map prim_args' ~f:(function | Some c -> c diff --git a/compiler/tests-full/stdlib.cma.expected.js b/compiler/tests-full/stdlib.cma.expected.js index 43fbad854a..7950c28507 100644 --- a/compiler/tests-full/stdlib.cma.expected.js +++ b/compiler/tests-full/stdlib.cma.expected.js @@ -8028,6 +8028,7 @@ caml_floatarray_make = runtime.caml_floatarray_make, caml_floatarray_sub = runtime.caml_floatarray_sub, caml_hash = runtime.caml_hash, + caml_int64_create_lo_mi_hi = runtime.caml_int64_create_lo_mi_hi, caml_maybe_attach_backtrace = runtime.caml_maybe_attach_backtrace, caml_nextafter_float = runtime.caml_nextafter_float, caml_signbit_float = runtime.caml_signbit_float, @@ -8050,7 +8051,10 @@ Stdlib = global_data.Stdlib, infinity = Stdlib[22], neg_infinity = Stdlib[23], - nan = Stdlib[24]; + nan = Stdlib[24], + signaling_nan = + /*<>*/ runtime.caml_int64_float_of_bits + (caml_int64_create_lo_mi_hi(1, 0, 32752)); function is_finite(x){ /*<>*/ return x - x === 0. ? 1 : 0; /*<>*/ } @@ -8983,7 +8987,7 @@ infinity, neg_infinity, nan, - NaN, + signaling_nan, nan, 3.141592653589793, max_float, diff --git a/compiler/tests-jsoo/test_nan.ml b/compiler/tests-jsoo/test_nan.ml new file mode 100644 index 0000000000..6b142adc0b --- /dev/null +++ b/compiler/tests-jsoo/test_nan.ml @@ -0,0 +1,15 @@ +let none = Int64.float_of_bits 0x7ff0_1234_5678_90ABL + +let sign_mask = 0x8000_0000_0000_0000L + +let some x = + if Float.is_nan x + then if Int64.(logand (bits_of_float x) sign_mask = 0L) then nan else -.nan + else x + +let is_none t = Int64.equal (Int64.bits_of_float t) (Int64.bits_of_float none) + +let () = + assert (is_none none); + let l = [ nan; -.nan; 1.; -7.; infinity; neg_infinity; 0.; none ] in + List.iter (fun f -> assert (not (is_none (some f)))) l diff --git a/runtime/js/ieee_754.js b/runtime/js/ieee_754.js index f0fc1cf124..d0190143ed 100644 --- a/runtime/js/ieee_754.js +++ b/runtime/js/ieee_754.js @@ -108,6 +108,7 @@ function caml_int64_float_of_bits(x) { var lo = x.lo; var mi = x.mi; var hi = x.hi; + if ((hi & 0x7ff8) === 0x7ff0 && (mi | lo | (hi & 0xf)) !== 0) hi |= 8; jsoo_dataview.setUint32(0, lo | (mi << 24), true); jsoo_dataview.setUint32(4, (mi >>> 8) | (hi << 16), true); return jsoo_dataview.getFloat64(0, true); From 7f7dab905859561a94131ce8312c3a7791bf1647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 18:53:39 +0200 Subject: [PATCH 04/36] WAT output: signed nan Not very important since it is only used for debugging --- compiler/lib-wasm/wat_output.ml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/lib-wasm/wat_output.ml b/compiler/lib-wasm/wat_output.ml index ed1e446d40..58d8250c05 100644 --- a/compiler/lib-wasm/wat_output.ml +++ b/compiler/lib-wasm/wat_output.ml @@ -328,18 +328,22 @@ let float64 _ f = match classify_float f with | FP_normal | FP_subnormal | FP_zero -> Printf.sprintf "%h" f | FP_nan -> + let f = Int64.(bits_of_float f) in Printf.sprintf - "nan:0x%Lx" - Int64.(logand (bits_of_float f) (of_int ((1 lsl 52) - 1))) + "%snan:0x%Lx" + (if Int64.( >= ) f 0L then "" else "-") + Int64.(logand f (of_int ((1 lsl 52) - 1))) | FP_infinite -> if Float.(f > 0.) then "inf" else "-inf" let float32 _ f = match classify_float f with | FP_normal | FP_subnormal | FP_zero -> Printf.sprintf "%h" f | FP_nan -> + let f = Int32.(bits_of_float f) in Printf.sprintf - "nan:0x%lx" - Int32.(logand (bits_of_float f) (of_int ((1 lsl 23) - 1))) + "%snan:0x%lx" + (if Int32.( >= ) f 0l then "" else "-") + Int32.(logand f (of_int ((1 lsl 23) - 1))) | FP_infinite -> if Float.(f > 0.) then "inf" else "-inf" let expression_or_instructions ctx st in_function = From a5bc2c8fc36380d127bb6171f4e027662101ccb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 10 Jul 2025 21:39:58 +0200 Subject: [PATCH 05/36] Wasm runtime: provide access to JavaScript eval function --- runtime/wasm/jslib.wat | 8 ++++++++ runtime/wasm/runtime.js | 2 ++ 2 files changed, 10 insertions(+) diff --git a/runtime/wasm/jslib.wat b/runtime/wasm/jslib.wat index 23542f08e5..400da18a2b 100644 --- a/runtime/wasm/jslib.wat +++ b/runtime/wasm/jslib.wat @@ -22,6 +22,7 @@ (import "bindings" "identity" (func $to_int32 (param anyref) (result i32))) (import "bindings" "identity" (func $from_int32 (param i32) (result anyref))) (import "bindings" "from_bool" (func $from_bool (param i32) (result anyref))) + (import "bindings" "eval" (func $eval (param anyref) (result anyref))) (import "bindings" "get" (func $get (param (ref extern)) (param anyref) (result anyref))) (import "bindings" "set" @@ -128,6 +129,13 @@ (ref.i31 (call $strict_equals (call $unwrap (local.get 0)) (call $unwrap (local.get 1))))) + (func (export "caml_js_expr") (export "caml_pure_js_expr") + (export "caml_js_var") (export "caml_js_eval_string") + (param (ref eq)) (result (ref eq)) + (local $s (ref $bytes)) + (local.set $s (ref.cast (ref $bytes) (local.get 0))) + (return_call $wrap (call $eval (call $jsstring_of_bytes (local.get $s))))) + (func (export "caml_js_global") (param (ref eq)) (result (ref eq)) (call $wrap (global.get $global_this))) diff --git a/runtime/wasm/runtime.js b/runtime/wasm/runtime.js index 9994c9b32d..a109de46f4 100644 --- a/runtime/wasm/runtime.js +++ b/runtime/wasm/runtime.js @@ -211,6 +211,8 @@ typeof: (x) => typeof x, // biome-ignore lint/suspicious/noDoubleEquals: equals: (x, y) => x == y, + // biome-ignore lint/security/noGlobalEval: + eval: (x) => globalThis.eval("(" + x + ")"), strict_equals: (x, y) => x === y, fun_call: (f, o, args) => f.apply(o, args), meth_call: (o, f, args) => o[f].apply(o, args), From e76a14c6aead5ad2f04cffa3a1329c9a05072a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 19:48:42 +0200 Subject: [PATCH 06/36] Float clean-up --- compiler/lib-wasm/gc_target.ml | 5 ----- compiler/lib-wasm/generate.ml | 7 +------ compiler/lib-wasm/target_sig.ml | 2 -- runtime/wasm/float.wat | 2 +- 4 files changed, 2 insertions(+), 14 deletions(-) diff --git a/compiler/lib-wasm/gc_target.ml b/compiler/lib-wasm/gc_target.ml index df63676c68..eaaa6b6498 100644 --- a/compiler/lib-wasm/gc_target.ml +++ b/compiler/lib-wasm/gc_target.ml @@ -1397,11 +1397,6 @@ module Math = struct let fmod f g = binary "fmod" f g - let round x = - let* f = register_import ~name:"caml_round" (Fun (float_func_type 1)) in - let* x = x in - return (W.Call (f, [ x ])) - let exp2 x = power (return (W.Const (F64 2.))) x end diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index cc9161af71..3ae60d0829 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -124,6 +124,7 @@ module Generate (Target : Target_sig.S) = struct , (`Mutator, [ Value; Int Normalized; Int Unnormalized ], Value) ) ; "caml_ba_uint8_set32", (`Mutator, [ Value; Int Normalized; Int32 ], Value) ; "caml_ba_uint8_set64", (`Mutator, [ Value; Int Normalized; Int64 ], Value) + ; "caml_round_float", (`Pure, [ Float ], Float) ; "caml_nextafter_float", (`Pure, [ Float; Float ], Float) ; "caml_classify_float", (`Pure, [ Float ], Int Normalized) ; "caml_ldexp_float", (`Pure, [ Float; Int Normalized ], Float) @@ -631,12 +632,6 @@ module Generate (Target : Target_sig.S) = struct ~typ:(Number (Float, Unboxed)) ~ret_typ:(Number (Float, Unboxed)) (fun f -> float_un_op Trunc f); - register_un_prim - "caml_round_float" - `Pure - ~typ:(Number (Float, Unboxed)) - ~ret_typ:(Number (Float, Unboxed)) - Math.round; register_un_prim "caml_sqrt_float" `Pure diff --git a/compiler/lib-wasm/target_sig.ml b/compiler/lib-wasm/target_sig.ml index a0fc5e8ce9..307aba9a9a 100644 --- a/compiler/lib-wasm/target_sig.ml +++ b/compiler/lib-wasm/target_sig.ml @@ -250,8 +250,6 @@ module type S = sig val power : expression -> expression -> expression val fmod : expression -> expression -> expression - - val round : expression -> expression end module Bigarray : sig diff --git a/runtime/wasm/float.wat b/runtime/wasm/float.wat index 6e72e10400..5c58371d9f 100644 --- a/runtime/wasm/float.wat +++ b/runtime/wasm/float.wat @@ -1133,7 +1133,7 @@ (i32.sub (f64.eq (local.get $x) (local.get $x)) (f64.eq (local.get $y) (local.get $y))))) - (func (export "caml_round") (param $x f64) (result f64) + (func (export "caml_round_float") (param $x f64) (result f64) (local $y f64) (if (result f64) (f64.ge (local.get $x) (f64.const 0)) (then From b5b6770d721123b6c1fa1387a1dac997e606b0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 26 Sep 2025 12:24:10 +0200 Subject: [PATCH 07/36] Get magic number version from compiler-libs --- compiler/lib/compiler_version.mli | 2 ++ compiler/lib/magic_number.ml | 32 +++++++++++-------------------- tools/version/gen/dune | 2 +- tools/version/gen/gen.ml | 12 ++++++++++-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/compiler/lib/compiler_version.mli b/compiler/lib/compiler_version.mli index 58b490c567..28463e49d0 100644 --- a/compiler/lib/compiler_version.mli +++ b/compiler/lib/compiler_version.mli @@ -21,3 +21,5 @@ val s : string val git_version : string + +val ocaml_magic_number_version : int diff --git a/compiler/lib/magic_number.ml b/compiler/lib/magic_number.ml index 693430bc5e..e80e7b57a0 100644 --- a/compiler/lib/magic_number.ml +++ b/compiler/lib/magic_number.ml @@ -64,28 +64,18 @@ let compare (p1, n1) (p2, n2) = let equal a b = compare a b = 0 -let v = +let () = let current = Ocaml_version.current in - match current with - | 4 :: 13 :: _ -> 30 - | 4 :: 14 :: _ -> 31 - | 5 :: 00 :: _ -> 32 - | 5 :: 01 :: _ -> 33 - | 5 :: 02 :: _ -> 34 - | 5 :: 03 :: _ -> 35 - | 5 :: 04 :: _ -> 36 - | _ -> - if Ocaml_version.compare current [ 4; 13 ] < 0 - then failwith "OCaml version unsupported. Upgrade to OCaml 4.13 or newer." - else ( - assert (Ocaml_version.compare current [ 5; 5 ] >= 0); - failwith "OCaml version unsupported. Upgrade js_of_ocaml.") - -let current_exe = "Caml1999X", v - -let current_cmo = "Caml1999O", v - -let current_cma = "Caml1999A", v + if Ocaml_version.compare current [ 4; 13 ] < 0 + then failwith "OCaml version unsupported. Upgrade to OCaml 4.13 or newer." + else if Ocaml_version.compare current [ 5; 5 ] >= 0 + then failwith "OCaml version unsupported. Upgrade js_of_ocaml." + +let current_exe = "Caml1999X", Compiler_version.ocaml_magic_number_version + +let current_cmo = "Caml1999O", Compiler_version.ocaml_magic_number_version + +let current_cma = "Caml1999A", Compiler_version.ocaml_magic_number_version let current = function | `Exe -> current_exe diff --git a/tools/version/gen/dune b/tools/version/gen/dune index 8fadbfebab..ff08ab796e 100644 --- a/tools/version/gen/dune +++ b/tools/version/gen/dune @@ -1,3 +1,3 @@ (executable (name gen) - (libraries unix)) + (libraries unix compiler-libs.common)) diff --git a/tools/version/gen/gen.ml b/tools/version/gen/gen.ml index 1af6c52203..487ef123b0 100644 --- a/tools/version/gen/gen.ml +++ b/tools/version/gen/gen.ml @@ -31,7 +31,15 @@ let git_version = | Some v -> v | None -> git_version -let () = Printf.printf {| +let ocaml_magic_number_version = int_of_string (String.sub Config.cmo_magic_number 9 3) + +let () = + Printf.printf + {| let s = "%s" let git_version = "%s" -|} version git_version +let ocaml_magic_number_version = %d +|} + version + git_version + ocaml_magic_number_version From 32beb3bbb13b8333c80731589506d6ed572fe87c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 21:00:58 +0200 Subject: [PATCH 08/36] Bigarray fix --- runtime/wasm/bigarray.wat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/wasm/bigarray.wat b/runtime/wasm/bigarray.wat index 0fe421874e..58d1b3984f 100644 --- a/runtime/wasm/bigarray.wat +++ b/runtime/wasm/bigarray.wat @@ -2045,7 +2045,7 @@ (struct.get $bigarray $ba_dim (local.get $ba)) (i32.const 0))) (then (call $caml_bound_error))) - (return_call $dv_get_i32_unaligned + (call $dv_get_i32_unaligned (local.get $view) (local.get $i) (i32.const 1))) (func (export "caml_ba_uint8_get64") From 7dc25f1cc006583ac7c5a8b927624318fdcedb72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 19:12:06 +0200 Subject: [PATCH 09/36] ppx_optcomp_light: mark the attribute as handled --- compiler/ppx/ppx_optcomp_light.ml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/ppx/ppx_optcomp_light.ml b/compiler/ppx/ppx_optcomp_light.ml index b4634230cc..df92ae0359 100644 --- a/compiler/ppx/ppx_optcomp_light.ml +++ b/compiler/ppx/ppx_optcomp_light.ml @@ -148,7 +148,8 @@ let keep loc (attrs : attributes) = | [] -> true | _ -> ( try - let keep_one { attr_payload; attr_loc; _ } = + let keep_one ({ attr_payload; attr_loc; _ } as attr) = + Ppxlib.Attribute.mark_as_handled_manually attr; let e = match attr_payload with | PStr [ { pstr_desc = Pstr_eval (e, []); _ } ] -> e From 6ec558ac2cc6e67fd572d6949de7c1a3c31c89bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 11:54:10 +0200 Subject: [PATCH 10/36] ppx_optcomp_light: compatibility with OxCaml --- compiler/ppx/ppx_optcomp_light.ml | 120 ++++++++++++++++-------------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/compiler/ppx/ppx_optcomp_light.ml b/compiler/ppx/ppx_optcomp_light.ml index df92ae0359..cfea45b727 100644 --- a/compiler/ppx/ppx_optcomp_light.ml +++ b/compiler/ppx/ppx_optcomp_light.ml @@ -155,61 +155,71 @@ let keep loc (attrs : attributes) = | PStr [ { pstr_desc = Pstr_eval (e, []); _ } ] -> e | _ -> raise (Invalid attr_loc) in - let loc = e.pexp_loc in - let rec eval = function - | { pexp_desc = Pexp_ident { txt = Lident "ocaml_version"; _ }; _ } -> - Version Version.current - | { pexp_desc = Pexp_ident { txt = Lident "ast_version"; _ }; _ } -> - Int Ppxlib.Selected_ast.version - | { pexp_desc = Pexp_construct ({ txt = Lident "true"; _ }, None); _ } -> - Bool true - | { pexp_desc = Pexp_construct ({ txt = Lident "false"; _ }, None); _ } -> - Bool false - | { pexp_desc = Pexp_constant (Pconst_integer (d, None)); _ } -> - Int (int_of_string d) - | { pexp_desc = Pexp_tuple l; _ } -> Tuple (List.map l ~f:eval) - | { pexp_desc = Pexp_apply (op, [ (Nolabel, a); (Nolabel, b) ]); pexp_loc; _ } - -> ( - let op = get_bin_op op in - let a = eval a in - let b = eval b in - match op with - | LE | GE | LT | GT | NEQ | EQ -> - let comp = - match a, b with - | Version _, _ | _, Version _ -> - Version.compare (version a) (version b) - | Int a, Int b -> compare a b - | _ -> raise (Invalid pexp_loc) - in - let op = - match op with - | LE -> ( <= ) - | GE -> ( >= ) - | LT -> ( < ) - | GT -> ( > ) - | EQ -> ( = ) - | NEQ -> ( <> ) - | _ -> assert false - in - Bool (op comp 0) - | AND -> ( - match a, b with - | Bool a, Bool b -> Bool (a && b) - | _ -> raise (Invalid loc)) - | OR -> ( - match a, b with - | Bool a, Bool b -> Bool (a || b) - | _ -> raise (Invalid loc)) - | NOT -> raise (Invalid loc)) - | { pexp_desc = Pexp_apply (op, [ (Nolabel, a) ]); _ } -> ( - let op = get_un_op op in - let a = eval a in - match op, a with - | NOT, Bool b -> Bool (not b) - | NOT, _ -> raise (Invalid loc) - | _ -> raise (Invalid loc)) - | _ -> raise (Invalid loc) + let rec eval e = + let open Ppxlib.Ast_pattern in + let loc = e.pexp_loc in + match + (parse_res + (pexp_ident (lident (string "ocaml_version")) + >>| (fun () -> Version Version.current) + ||| (pexp_ident (lident (string "ast_version")) + >>| fun () -> Int Ppxlib.Selected_ast.version) + ||| (pexp_construct (lident (string "true")) drop >>| fun () -> Bool true) + ||| (pexp_constant (pconst_integer __ none) + >>| fun () d -> Int (int_of_string d)) + ||| (pexp_construct (lident (string "false")) drop + >>| fun () -> Bool false) + ||| (pexp_tuple __ >>| fun () l -> Tuple (List.map l ~f:eval)) + ||| (pexp_apply __ __ + >>| fun () op l -> + match l with + | [ (Nolabel, a); (Nolabel, b) ] -> ( + let op = get_bin_op op in + let a = eval a in + let b = eval b in + match op with + | LE | GE | LT | GT | NEQ | EQ -> + let comp = + match a, b with + | Version _, _ | _, Version _ -> + Version.compare (version a) (version b) + | Int a, Int b -> compare a b + | _ -> raise (Invalid loc) + in + let op = + match op with + | LE -> ( <= ) + | GE -> ( >= ) + | LT -> ( < ) + | GT -> ( > ) + | EQ -> ( = ) + | NEQ -> ( <> ) + | _ -> assert false + in + Bool (op comp 0) + | AND -> ( + match a, b with + | Bool a, Bool b -> Bool (a && b) + | _ -> raise (Invalid loc)) + | OR -> ( + match a, b with + | Bool a, Bool b -> Bool (a || b) + | _ -> raise (Invalid loc)) + | NOT -> raise (Invalid loc)) + | [ (Nolabel, a) ] -> ( + let op = get_un_op op in + let a = eval a in + match op, a with + | NOT, Bool b -> Bool (not b) + | NOT, _ -> raise (Invalid loc) + | _ -> raise (Invalid loc)) + | _ -> raise (Invalid loc)))) + loc + e + () + with + | Ok res -> res + | Error _ -> raise (Invalid loc) in match eval e with | Bool b -> b From 6cd74ddbae0edb4e8fa55a59f69569706a3a168a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 11:58:56 +0200 Subject: [PATCH 11/36] ppx_optcomp_light: conditional inclusion of modules in signatures --- compiler/ppx/ppx_optcomp_light.ml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/compiler/ppx/ppx_optcomp_light.ml b/compiler/ppx/ppx_optcomp_light.ml index cfea45b727..e88c13587a 100644 --- a/compiler/ppx/ppx_optcomp_light.ml +++ b/compiler/ppx/ppx_optcomp_light.ml @@ -24,7 +24,8 @@ ]} on module (Pstr_module), toplevel bindings (Pstr_value, Pstr_primitive) - and pattern in case (pc_lhs) + pattern in case (pc_lhs) + and module in signature (Psig_module) *) open StdLabels @@ -296,7 +297,21 @@ let traverse = | Some pattern -> Some { case with pc_lhs = pattern }) in super#cases cases + + method! signature_item item = + match item.psig_desc with + | Psig_module { pmd_attributes; pmd_loc; _ } -> + if keep pmd_loc pmd_attributes + then item + else + let open Ppxlib.Ast_builder.Default in + let loc = Location.none in + psig_include ~loc (include_infos ~loc (pmty_signature ~loc [])) + | _ -> item end let () = - Ppxlib.Driver.register_transformation ~impl:traverse#structure "ppx_optcomp_light" + Ppxlib.Driver.register_transformation + ~impl:traverse#structure + ~intf:traverse#signature + "ppx_optcomp_light" From 86769d46db9a40a01c5ed21c4f5eefb9990b2e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:07:45 +0200 Subject: [PATCH 12/36] ppx_optcomp_light: add oxcaml flag --- compiler/ppx/ppx_optcomp_light.ml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/compiler/ppx/ppx_optcomp_light.ml b/compiler/ppx/ppx_optcomp_light.ml index e88c13587a..808c71d22e 100644 --- a/compiler/ppx/ppx_optcomp_light.ml +++ b/compiler/ppx/ppx_optcomp_light.ml @@ -39,6 +39,12 @@ module Version : sig val compare : t -> t -> int val current : t + + type extra_prefix = + | Plus + | Tilde + + val extra : (extra_prefix * string) option end = struct type t = int list @@ -87,6 +93,23 @@ end = struct match compint x y with | 0 -> compare xs ys | n -> n) + + type extra_prefix = + | Plus + | Tilde + + type release_info = { extra : (extra_prefix * string) option } + + let extra = + let ocaml_release = { extra = None } in + ignore ocaml_release.extra; + match + let open! Sys in + ocaml_release.extra + with + | None -> None + | Some (Plus, tag) -> Some (Plus, tag) + | Some (Tilde, tag) -> Some (Tilde, tag) end exception Invalid of Location.t @@ -165,6 +188,12 @@ let keep loc (attrs : attributes) = >>| (fun () -> Version Version.current) ||| (pexp_ident (lident (string "ast_version")) >>| fun () -> Int Ppxlib.Selected_ast.version) + ||| (pexp_ident (lident (string "oxcaml")) + >>| fun () -> + Bool + (match Version.extra with + | Some (Plus, "ox") -> true + | _ -> false)) ||| (pexp_construct (lident (string "true")) drop >>| fun () -> Bool true) ||| (pexp_constant (pconst_integer __ none) >>| fun () d -> Int (int_of_string d)) From 380769caebdb79ba9786b66417fed5e65314f52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:04:38 +0200 Subject: [PATCH 13/36] OxCaml support: differences in bytecode binary format --- compiler/bin-js_of_ocaml/compile.ml | 2 +- .../js_of_ocaml_compiler_dynlink.ml | 2 +- compiler/lib/instr.ml | 2 + compiler/lib/instr.mli | 1 + compiler/lib/ocaml_compiler.ml | 121 ++++++++++++++++-- compiler/lib/ocaml_compiler.mli | 42 +++++- compiler/lib/parse_bytecode.ml | 59 ++++++--- compiler/lib/parse_bytecode.mli | 6 +- compiler/lib/unit_info.ml | 2 +- compiler/lib/unit_info.mli | 2 +- 10 files changed, 203 insertions(+), 36 deletions(-) diff --git a/compiler/bin-js_of_ocaml/compile.ml b/compiler/bin-js_of_ocaml/compile.ml index deb995b991..df088b4fc1 100644 --- a/compiler/bin-js_of_ocaml/compile.ml +++ b/compiler/bin-js_of_ocaml/compile.ml @@ -318,7 +318,7 @@ let run sm in let output_partial - (cmo : Cmo_format.compilation_unit) + (cmo : Ocaml_compiler.Cmo_format.t) ~standalone ~shapes ~source_map diff --git a/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml b/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml index 941997b98d..b46c7f0daa 100644 --- a/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml +++ b/compiler/lib-dynlink/js_of_ocaml_compiler_dynlink.ml @@ -4,7 +4,7 @@ module J = Jsoo_runtime.Js type bytecode_sections = { symb : Ocaml_compiler.Symtable.GlobalMap.t - ; crcs : (string * Digest.t option) list + ; crcs : Ocaml_compiler.Import_info.table ; prim : string list ; dlpt : string list } diff --git a/compiler/lib/instr.ml b/compiler/lib/instr.ml index 83528cc032..ecb2fdd4aa 100644 --- a/compiler/lib/instr.ml +++ b/compiler/lib/instr.ml @@ -173,6 +173,7 @@ type t = | RESUME | RESUMETERM | REPERFORMTERM + | MAKE_FAUX_MIXEDBLOCK | FIRST_UNIMPLEMENTED_OP let equal (a : t) b = Poly.equal a b @@ -360,6 +361,7 @@ let ops = ; RESUME, if_v500 KNullaryCall, "RESUME" ; RESUMETERM, if_v500 (KStop 1), "RESUMETERM" ; REPERFORMTERM, if_v500 (KStop 1), "REPERFORMTERM" + ; MAKE_FAUX_MIXEDBLOCK, KBinary, "MAKE_FAUX_MIXEDBLOCK" ; FIRST_UNIMPLEMENTED_OP, K_will_not_happen, "FIRST_UNIMPLEMENTED_OP" |] in diff --git a/compiler/lib/instr.mli b/compiler/lib/instr.mli index 43fe2cbfb8..69a5fbf3a6 100644 --- a/compiler/lib/instr.mli +++ b/compiler/lib/instr.mli @@ -172,6 +172,7 @@ type t = | RESUME | RESUMETERM | REPERFORMTERM + | MAKE_FAUX_MIXEDBLOCK | FIRST_UNIMPLEMENTED_OP type kind = diff --git a/compiler/lib/ocaml_compiler.ml b/compiler/lib/ocaml_compiler.ml index 0c4ed37a34..b9831aa341 100644 --- a/compiler/lib/ocaml_compiler.ml +++ b/compiler/lib/ocaml_compiler.ml @@ -26,16 +26,31 @@ let rec constant_of_const c : Code.constant = | Const_base (Const_char c) -> Int (Targetint.of_int_exn (Char.code c)) | Const_base (Const_string (s, _, _)) -> String s | Const_base (Const_float s) -> Float (Int64.bits_of_float (float_of_string s)) + | ((Const_base (Const_unboxed_float s)) [@if oxcaml]) -> + Float (Int64.bits_of_float (float_of_string s)) | Const_base (Const_int32 i) -> Int32 i + | ((Const_base (Const_unboxed_int32 i)) [@if oxcaml]) -> Int32 i | Const_base (Const_int64 i) -> Int64 i + | ((Const_base (Const_unboxed_int64 i)) [@if oxcaml]) -> Int64 i | Const_base (Const_nativeint i) -> NativeInt (Int32.of_nativeint_warning_on_overflow i) + | ((Const_base (Const_unboxed_nativeint i)) [@if oxcaml]) -> + NativeInt (Int32.of_nativeint_warning_on_overflow i) | Const_immstring s -> String s | Const_float_array sl -> let l = List.map ~f:(fun f -> Int64.bits_of_float (float_of_string f)) sl in Float_array (Array.of_list l) + | ((Const_float_block sl) [@if oxcaml]) -> + let l = List.map ~f:(fun f -> Int64.bits_of_float (float_of_string f)) sl in + Float_array (Array.of_list l) | Const_block (tag, l) -> let l = Array.of_list (List.map l ~f:constant_of_const) in Tuple (tag, l, Unknown) + | ((Const_mixed_block (tag, _, l)) [@if oxcaml]) -> + let l = Array.of_list (List.map l ~f:constant_of_const) in + Tuple (tag, l, Unknown) + | ((Const_base (Const_float32 _ | Const_unboxed_float32 _)) [@if oxcaml]) -> + failwith "Float32 unsupported" + | (Const_null [@if oxcaml]) -> failwith "Null unsupported" type module_or_not = | Module @@ -59,12 +74,17 @@ let rec is_module_in_summary deep ident' summary = then deep, Not_module else is_module_in_summary (deep + 1) ident' summary (* Lowercase ident *) - | Env.Env_value (summary, ident, _) | Env.Env_type (summary, ident, _) | Env.Env_class (summary, ident, _) | Env.Env_cltype (summary, ident, _) -> ignore (ident : Ident.t); is_module_in_summary (deep + 1) ident' summary + | ((Env.Env_value (summary, ident, _)) [@if not oxcaml]) -> + ignore (ident : Ident.t); + is_module_in_summary (deep + 1) ident' summary + | ((Env.Env_value (summary, ident, _, _)) [@if oxcaml]) -> + ignore (ident : Ident.t); + is_module_in_summary (deep + 1) ident' summary (* Other, no ident *) | Env.Env_open (summary, _) | Env.Env_constraints (summary, _) @@ -77,6 +97,17 @@ let is_module_in_summary ident summary = let _deep, b = is_module_in_summary 0 ident summary in b +module Compilation_unit = struct + type t = Cmo_format.compunit + + let name_as_string (Compunit x : t) = x + + let of_string x : t = Compunit x +end +[@@if (not oxcaml) && ocaml_version >= (5, 2, 0)] + +module Compilation_unit = Compilation_unit [@@if oxcaml] + module Symtable = struct (* Copied from ocaml/bytecomp/symtable.ml *) module Num_tbl (M : Map.S) = struct @@ -116,18 +147,22 @@ module Symtable = struct | Glob_compunit cu -> cu | Glob_predef exn -> exn + let is_global = Ident.global [@@if not oxcaml] + + let is_global = Ident.is_global [@@if oxcaml] + let of_ident id = let name = Ident.name id in if Ident.is_predef id then Some (Glob_predef name) - else if Ident.global id + else if is_global id then Some (Glob_compunit name) else None let to_ident = function | Glob_compunit x -> Ident.create_persistent x | Glob_predef x -> Ident.create_predef x - [@@ocaml.warning "-32"] + [@@if ocaml_version < (5, 2, 0)] end module GlobalMap = struct @@ -163,11 +198,13 @@ module Symtable = struct include GlobalMap let to_local = function - | Symtable.Global.Glob_compunit (Compunit x) -> Global.Glob_compunit x + | Symtable.Global.Glob_compunit x -> + Global.Glob_compunit (Compilation_unit.name_as_string x) | Symtable.Global.Glob_predef (Predef_exn x) -> Global.Glob_predef x let of_local = function - | Global.Glob_compunit x -> Symtable.Global.Glob_compunit (Compunit x) + | Global.Glob_compunit x -> + Symtable.Global.Glob_compunit (Compilation_unit.of_string x) | Global.Glob_predef x -> Symtable.Global.Glob_predef (Predef_exn x) let filter (p : Global.t -> bool) (gmap : t) = @@ -194,10 +231,12 @@ module Symtable = struct let reloc_set_of_string name = Cmo_format.Reloc_setglobal (Ident.create_persistent name) [@@if ocaml_version < (5, 2, 0)] - let reloc_get_of_string name = Cmo_format.Reloc_getcompunit (Compunit name) + let reloc_get_of_string name = + Cmo_format.Reloc_getcompunit (Compilation_unit.of_string name) [@@if ocaml_version >= (5, 2, 0)] - let reloc_set_of_string name = Cmo_format.Reloc_setcompunit (Compunit name) + let reloc_set_of_string name = + Cmo_format.Reloc_setcompunit (Compilation_unit.of_string name) [@@if ocaml_version >= (5, 2, 0)] let reloc_ident name = @@ -210,7 +249,7 @@ module Symtable = struct let get i = Char.code (Bytes.get buf i) in let n = get 0 + (get 1 lsl 8) + (get 2 lsl 16) + (get 3 lsl 24) in n - [@@if ocaml_version < (5, 2, 0)] + [@@if oxcaml || ocaml_version < (5, 2, 0)] let reloc_ident name = let buf = Bigarray.(Array1.create char c_layout 4) in @@ -222,7 +261,7 @@ module Symtable = struct let get i = Char.code (Bigarray.Array1.get buf i) in let n = get 0 + (get 1 lsl 8) + (get 2 lsl 16) + (get 3 lsl 24) in n - [@@if ocaml_version >= (5, 2, 0)] + [@@if (not oxcaml) && ocaml_version >= (5, 2, 0)] let current_state () : GlobalMap.t = let x : Symtable.global_map = Symtable.current_state () in @@ -247,6 +286,46 @@ module Symtable = struct [@@if ocaml_version >= (5, 2)] end +module Import_info = struct + type t = string * Digest.t option + + type table = t list + + let to_list l = l + + let of_list l = l + + let name (n, _) = n + + let crc (_, c) = c +end +[@@if not oxcaml] + +module Import_info = struct + type t = Import_info.t + + type table = t array + + let to_list = Array.to_list + + let of_list = Array.of_list + + let name i = Import_info.name i |> Compilation_unit.Name.to_string + + let crc = Import_info.crc +end +[@@if oxcaml] + +module Compilation_unit_descr = struct + type t = Cmo_format.compilation_unit +end +[@@if not oxcaml] + +module Compilation_unit_descr = struct + type t = Cmo_format.compilation_unit_descr +end +[@@if oxcaml] + module Cmo_format = struct type t = Cmo_format.compilation_unit @@ -284,3 +363,27 @@ module Cmo_format = struct let force_link (t : t) = t.cu_force_link end +[@@if not oxcaml] + +module Cmo_format = struct + type t = Cmo_format.compilation_unit_descr + + let name (t : t) = Compilation_unit.name_as_string t.cu_name + + let requires (t : t) = + List.map t.cu_required_compunits ~f:Compilation_unit.name_as_string + + let provides (t : t) = + List.filter_map t.cu_reloc ~f:(fun ((reloc : Cmo_format.reloc_info), _) -> + match reloc with + | Reloc_setcompunit u -> Some (Compilation_unit.name_as_string u) + | Reloc_getcompunit _ | Reloc_getpredef _ | Reloc_literal _ | Reloc_primitive _ -> + None) + + let primitives (t : t) = t.cu_primitives + + let imports (t : t) = Array.to_list t.cu_imports + + let force_link (t : t) = t.cu_force_link +end +[@@if oxcaml] diff --git a/compiler/lib/ocaml_compiler.mli b/compiler/lib/ocaml_compiler.mli index 0c4c31dd8a..f23031d69a 100644 --- a/compiler/lib/ocaml_compiler.mli +++ b/compiler/lib/ocaml_compiler.mli @@ -59,8 +59,46 @@ module Symtable : sig val all_primitives : unit -> string list end -module Cmo_format : sig +module Import_info : sig + type t + + type table + + val to_list : table -> t list + + val of_list : t list -> table + + val name : t -> string + + val crc : t -> Digest.t option +end + +module Compilation_unit : sig + type t = Cmo_format.compunit + + val name_as_string : t -> string +end +[@@if (not oxcaml) && ocaml_version >= (5, 2, 0)] + +module Compilation_unit : sig + type t = Compilation_unit.t + + val name_as_string : t -> string +end +[@@if oxcaml] + +module Compilation_unit_descr : sig type t = Cmo_format.compilation_unit +end +[@@if not oxcaml] + +module Compilation_unit_descr : sig + type t = Cmo_format.compilation_unit_descr +end +[@@if oxcaml] + +module Cmo_format : sig + type t = Compilation_unit_descr.t val name : t -> string @@ -72,5 +110,5 @@ module Cmo_format : sig val force_link : t -> bool - val imports : t -> (string * string option) list + val imports : t -> Import_info.t list end diff --git a/compiler/lib/parse_bytecode.ml b/compiler/lib/parse_bytecode.ml index 4f089e7f61..8d6f033a95 100644 --- a/compiler/lib/parse_bytecode.ml +++ b/compiler/lib/parse_bytecode.ml @@ -70,11 +70,15 @@ module Debug : sig -> unit val read : - t -> crcs:(string * string option) list -> includes:string list -> in_channel -> unit + t + -> crcs:Ocaml_compiler.Import_info.t list + -> includes:string list + -> in_channel + -> unit val read_event_list : t - -> crcs:(string * string option) list + -> crcs:Ocaml_compiler.Import_info.t list -> includes:string list -> orig:int -> in_channel @@ -222,7 +226,11 @@ end = struct fun debug ~crcs ~includes ~orig ic -> let crcs = let t = String.Hashtbl.create 17 in - List.iter crcs ~f:(fun (m, crc) -> String.Hashtbl.add t m crc); + List.iter crcs ~f:(fun info -> + String.Hashtbl.add + t + (Ocaml_compiler.Import_info.name info) + (Ocaml_compiler.Import_info.crc info)); t in let evl : debug_event list = input_value ic in @@ -1447,7 +1455,7 @@ and compile infos pc state (instrs : instr list) = if debug_parser () then Format.printf "%a = ATOM(%d)@." Var.print x i; let imm = is_immutable instr infos pc in compile infos (pc + 2) state (Let (x, Block (i, [||], Unknown, imm)) :: instrs) - | MAKEBLOCK -> + | MAKE_FAUX_MIXEDBLOCK | MAKEBLOCK -> let size = getu code (pc + 1) in let tag = getu code (pc + 2) in let state = State.push state in @@ -2594,7 +2602,7 @@ module Toc : sig val read_data : t -> in_channel -> Obj.t array - val read_crcs : t -> in_channel -> (string * Digest.t option) list + val read_crcs : t -> in_channel -> Ocaml_compiler.Import_info.t list val read_prim : t -> in_channel -> string @@ -2643,8 +2651,8 @@ end = struct let read_crcs toc ic = ignore (seek_section toc ic "CRCS"); - let orig_crcs : (string * Digest.t option) list = input_value ic in - orig_crcs + let orig_crcs : Ocaml_compiler.Import_info.table = input_value ic in + Ocaml_compiler.Import_info.to_list orig_crcs let read_prim toc ic = let prim_size = seek_section toc ic "PRIM" in @@ -2659,7 +2667,7 @@ let read_primitives toc ic = type bytesections = { symb : Ocaml_compiler.Symtable.GlobalMap.t - ; crcs : (string * Digest.t option) list + ; crcs : Ocaml_compiler.Import_info.table ; prim : string list ; dlpt : string list } @@ -2697,7 +2705,9 @@ let from_exe in String.Hashtbl.mem keeps in - let crcs = List.filter ~f:(fun (unit, _crc) -> keep unit) orig_crcs in + let crcs = + List.filter ~f:(fun info -> keep (Ocaml_compiler.Import_info.name info)) orig_crcs + in let symbols = Ocaml_compiler.Symtable.GlobalMap.filter (function @@ -2755,7 +2765,13 @@ let from_exe |> Array.of_list in (* Include linking information *) - let sections = { symb = symbols; crcs; prim = primitives; dlpt = [] } in + let sections = + { symb = symbols + ; crcs = Ocaml_compiler.Import_info.of_list crcs + ; prim = primitives + ; dlpt = [] + } + in let gdata = Var.fresh () in let need_gdata = ref false in let aliases = Primitive.aliases () in @@ -2917,9 +2933,10 @@ module Reloc = struct } let constant_of_const x = Ocaml_compiler.constant_of_const x - [@@if ocaml_version < (5, 1, 0)] + [@@if oxcaml || ocaml_version < (5, 1, 0)] - let constant_of_const x = Constants.parse x [@@if ocaml_version >= (5, 1, 0)] + let constant_of_const x = Constants.parse x + [@@if (not oxcaml) && ocaml_version >= (5, 1, 0)] (* We currently rely on constants to be relocated before globals. *) let step1 t compunit code = @@ -2964,12 +2981,12 @@ module Reloc = struct patch (slot_for_global (Ident.name id)) | ((Reloc_setglobal id) [@if ocaml_version < (5, 2, 0)]) -> patch (slot_for_global (Ident.name id)) - | ((Reloc_getcompunit (Compunit id)) [@if ocaml_version >= (5, 2, 0)]) -> - patch (slot_for_global id) + | ((Reloc_getcompunit id) [@if ocaml_version >= (5, 2, 0)]) -> + patch (slot_for_global (Ocaml_compiler.Compilation_unit.name_as_string id)) | ((Reloc_getpredef (Predef_exn id)) [@if ocaml_version >= (5, 2, 0)]) -> patch (slot_for_global id) - | ((Reloc_setcompunit (Compunit id)) [@if ocaml_version >= (5, 2, 0)]) -> - patch (slot_for_global id) + | ((Reloc_setcompunit id) [@if ocaml_version >= (5, 2, 0)]) -> + patch (slot_for_global (Ocaml_compiler.Compilation_unit.name_as_string id)) | _ -> ()) let primitives t = @@ -3098,7 +3115,7 @@ let from_channel ic = then raise Magic_number.(Bad_magic_version magic); let compunit_pos = input_binary_int ic in seek_in ic compunit_pos; - let compunit : Cmo_format.compilation_unit = input_value ic in + let compunit : Ocaml_compiler.Cmo_format.t = input_value ic in `Cmo compunit | `Cma -> if @@ -3193,7 +3210,13 @@ let link_info ~symbols ~primitives ~crcs = let body = [] in let body = (* Include linking information *) - let sections = { symb = symbols; crcs; prim = primitives; dlpt = [] } in + let sections = + { symb = symbols + ; crcs = Ocaml_compiler.Import_info.of_list crcs + ; prim = primitives + ; dlpt = [] + } + in let aliases = Primitive.aliases () in let infos = [ "sections", Constants.parse (Obj.repr sections) diff --git a/compiler/lib/parse_bytecode.mli b/compiler/lib/parse_bytecode.mli index 5eaa9c396f..b2ff03e5d3 100644 --- a/compiler/lib/parse_bytecode.mli +++ b/compiler/lib/parse_bytecode.mli @@ -58,7 +58,7 @@ val from_cmo : ?includes:string list -> ?include_cmis:bool -> ?debug:bool - -> Cmo_format.compilation_unit + -> Ocaml_compiler.Cmo_format.t -> in_channel -> one @@ -72,7 +72,7 @@ val from_cma : val from_channel : in_channel - -> [ `Cmo of Cmo_format.compilation_unit | `Cma of Cmo_format.library | `Exe ] + -> [ `Cmo of Ocaml_compiler.Cmo_format.t | `Cma of Cmo_format.library | `Exe ] val from_string : prims:string array -> debug:Instruct.debug_event list array -> string -> Code.program @@ -82,5 +82,5 @@ val predefined_exceptions : unit -> Code.program * Unit_info.t val link_info : symbols:Ocaml_compiler.Symtable.GlobalMap.t -> primitives:StringSet.t - -> crcs:(string * Digest.t option) list + -> crcs:Ocaml_compiler.Import_info.t list -> Code.program diff --git a/compiler/lib/unit_info.ml b/compiler/lib/unit_info.ml index 190ba2ea71..ba32d7cdfb 100644 --- a/compiler/lib/unit_info.ml +++ b/compiler/lib/unit_info.ml @@ -46,7 +46,7 @@ let of_primitives ~aliases l = ; effects_without_cps = false } -let of_cmo (cmo : Cmo_format.compilation_unit) = +let of_cmo (cmo : Ocaml_compiler.Cmo_format.t) = let open Ocaml_compiler in (* A packed librariy register global for packed modules. *) let provides = StringSet.of_list (Cmo_format.name cmo :: Cmo_format.provides cmo) in diff --git a/compiler/lib/unit_info.mli b/compiler/lib/unit_info.mli index dd616fda91..cb7f2e07a6 100644 --- a/compiler/lib/unit_info.mli +++ b/compiler/lib/unit_info.mli @@ -28,7 +28,7 @@ type t = ; effects_without_cps : bool } -val of_cmo : Cmo_format.compilation_unit -> t +val of_cmo : Ocaml_compiler.Cmo_format.t -> t val of_primitives : aliases:(string * string) list -> string list -> t From 3feec6a0503b948665a5413043c75f4d15a31b29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:12:24 +0200 Subject: [PATCH 14/36] Require dune 3.20 This adds the oxcaml_supported predicate. --- dune-project | 3 ++- js_of_ocaml-compiler.opam | 2 +- js_of_ocaml-lwt.opam | 2 +- js_of_ocaml-ppx.opam | 2 +- js_of_ocaml-ppx_deriving_json.opam | 2 +- js_of_ocaml-toplevel.opam | 2 +- js_of_ocaml-tyxml.opam | 2 +- js_of_ocaml.opam | 2 +- wasm_of_ocaml-compiler.opam | 2 +- 9 files changed, 10 insertions(+), 9 deletions(-) diff --git a/dune-project b/dune-project index 676fe82cb9..6ba3e9d4ee 100644 --- a/dune-project +++ b/dune-project @@ -1,5 +1,6 @@ -(lang dune 3.19) +(lang dune 3.20) (using menhir 3.0) +(using oxcaml 0.1) (name js_of_ocaml) (generate_opam_files true) (executables_implicit_empty_intf true) diff --git a/js_of_ocaml-compiler.opam b/js_of_ocaml-compiler.opam index 98e4e00b15..a1ef027852 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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.13" & < "5.5"} "num" {with-test} "ppx_expect" {>= "v0.16.1" & with-test} diff --git a/js_of_ocaml-lwt.opam b/js_of_ocaml-lwt.opam index 8f821e5f52..f40e8cdfa3 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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml" {= version} "js_of_ocaml-ppx" {= version} diff --git a/js_of_ocaml-ppx.opam b/js_of_ocaml-ppx.opam index 65872d8ecf..73a06dc6ae 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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml" {= version} "ppxlib" {>= "0.35"} diff --git a/js_of_ocaml-ppx_deriving_json.opam b/js_of_ocaml-ppx_deriving_json.opam index 65872d8ecf..73a06dc6ae 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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml" {= version} "ppxlib" {>= "0.35"} diff --git a/js_of_ocaml-toplevel.opam b/js_of_ocaml-toplevel.opam index 6244f14f77..f28c0052e2 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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml-compiler" {= version} "ocamlfind" {>= "1.5.1"} diff --git a/js_of_ocaml-tyxml.opam b/js_of_ocaml-tyxml.opam index 1228c707d4..2ce14c0b6b 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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml" {= version} "js_of_ocaml-ppx" {= version} diff --git a/js_of_ocaml.opam b/js_of_ocaml.opam index 6a04b51e3f..5e98d76fed 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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml-compiler" {= version} "num" {with-test} diff --git a/wasm_of_ocaml-compiler.opam b/wasm_of_ocaml-compiler.opam index 1a2af784a3..dbf96478a0 100644 --- a/wasm_of_ocaml-compiler.opam +++ b/wasm_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.19"} + "dune" {>= "3.20"} "ocaml" {>= "4.14"} "js_of_ocaml" {= version} "num" {with-test} From 9a8fbb31aacb0a39b52c042b844b3a6f07e6313e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 26 Sep 2025 19:01:24 +0200 Subject: [PATCH 15/36] OxCaml support: fix OCaml expect tests --- compiler/tests-ocaml/expect.ml | 2 +- tools/toplevel_expect/gen.ml | 36 +- .../toplevel_expect_test.ml-oxcaml | 388 ++++++++++++++++++ 3 files changed, 400 insertions(+), 26 deletions(-) create mode 100644 tools/toplevel_expect/toplevel_expect_test.ml-oxcaml diff --git a/compiler/tests-ocaml/expect.ml b/compiler/tests-ocaml/expect.ml index 793f0968e3..809c31b1a5 100644 --- a/compiler/tests-ocaml/expect.ml +++ b/compiler/tests-ocaml/expect.ml @@ -1,7 +1,7 @@ let () = Js_of_ocaml_toplevel.JsooTop.initialize () -let () = Printexc.register_printer (fun x -> +let () = (Printexc.register_printer[@ocaml.alert "-unsafe_multidomain"]) (fun x -> match Js_of_ocaml.Js_error.of_exn x with | None -> None | Some e -> Some (Js_of_ocaml.Js_error.message e)) diff --git a/tools/toplevel_expect/gen.ml b/tools/toplevel_expect/gen.ml index 7bc9db25be..60d82092a3 100644 --- a/tools/toplevel_expect/gen.ml +++ b/tools/toplevel_expect/gen.ml @@ -30,29 +30,15 @@ let dump_file file = in loop () -let split_on_char sep s = - let r = ref [] in - let j = ref (String.length s) in - for i = String.length s - 1 downto 0 do - if String.unsafe_get s i = sep - then ( - r := String.sub s (i + 1) (!j - i - 1) :: !r; - j := i) - done; - String.sub s 0 !j :: !r - let () = - let version = Sys.ocaml_version in - let maj, min = - match split_on_char '.' version with - | maj :: min :: _ -> int_of_string maj, int_of_string min - | _ -> assert false - in - match maj, min with - | 4, min -> - assert (min >= 11); - dump_file "toplevel_expect_test.ml-4.11" - | 5, 0 | 5, 1 | 5, 2 -> dump_file "toplevel_expect_test.ml-4.11" - | 5, 3 -> dump_file "toplevel_expect_test.ml-5.3" - | 5, 4 -> dump_file "toplevel_expect_test.ml-5.4" - | _ -> failwith ("unsupported version " ^ Sys.ocaml_version) + match Sys.ocaml_release with + | { extra = Some (Plus, "ox"); _ } -> dump_file "toplevel_expect_test.ml-oxcaml" + | { major; minor; _ } -> ( + match major, minor with + | 4, min -> + assert (min >= 11); + dump_file "toplevel_expect_test.ml-4.11" + | 5, 0 | 5, 1 | 5, 2 -> dump_file "toplevel_expect_test.ml-4.11" + | 5, 3 -> dump_file "toplevel_expect_test.ml-5.3" + | 5, 4 -> dump_file "toplevel_expect_test.ml-5.4" + | _ -> failwith ("unsupported version " ^ Sys.ocaml_version)) diff --git a/tools/toplevel_expect/toplevel_expect_test.ml-oxcaml b/tools/toplevel_expect/toplevel_expect_test.ml-oxcaml new file mode 100644 index 0000000000..5e2f5ddd7c --- /dev/null +++ b/tools/toplevel_expect/toplevel_expect_test.ml-oxcaml @@ -0,0 +1,388 @@ +(**************************************************************************) +(* *) +(* OCaml *) +(* *) +(* Jeremie Dimino, Jane Street Europe *) +(* *) +(* Copyright 2016 Jane Street Group LLC *) +(* *) +(* All rights reserved. This file is distributed under the terms of *) +(* the GNU Lesser General Public License version 2.1, with the *) +(* special exception on linking described in the file LICENSE. *) +(* *) +(**************************************************************************) + +(* Execute a list of phrases from a .ml file and compare the result to the + expected output, written inside [%%expect ...] nodes. At the end, create + a .corrected file containing the corrected expectations. The test is + successful if there is no differences between the two files. + + An [%%expect] node always contains both the expected outcome with and + without -principal. When the two differ the expectation is written as + follows: + + {[ + [%%expect {| + output without -principal + |}, Principal{| + output with -principal + |}] + ]} +*) + +[@@@ocaml.warning "-40"] + +open StdLabels + +(* representation of: {tag|str|tag} *) +type string_constant = + { str : string + ; tag : string + } + +type expectation = + { extid_loc : Location.t (* Location of "expect" in "[%%expect ...]" *) + ; payload_loc : Location.t (* Location of the whole payload *) + ; normal : string_constant (* expectation without -principal *) + ; principal : string_constant (* expectation with -principal *) + } + +(* A list of phrases with the expected toplevel output *) +type chunk = + { phrases : Parsetree.toplevel_phrase list + ; expectation : expectation + } + +type correction = + { corrected_expectations : expectation list + ; trailing_output : string + } + +let match_expect_extension (ext : Parsetree.extension) = + match ext with + | ({Asttypes.txt="expect"|"ocaml.expect"; loc = extid_loc}, payload) -> + let invalid_payload () = + Location.raise_errorf ~loc:extid_loc + "invalid [%%%%expect payload]" + in + let string_constant (e : Parsetree.expression) = + match e.pexp_desc with + | Pexp_constant (Pconst_string (str, _, Some tag)) -> + { str; tag } + | _ -> invalid_payload () + in + let expectation = + match payload with + | PStr [{ pstr_desc = Pstr_eval (e, []); _ }] -> + let normal, principal = + match e.pexp_desc with + | Pexp_tuple + [ None, a + ; None, { pexp_desc = Pexp_construct + ({ txt = Lident "Principal"; _ }, Some b); _ } + ] -> + (string_constant a, string_constant b) + | _ -> let s = string_constant e in (s, s) + in + { extid_loc + ; payload_loc = e.pexp_loc + ; normal + ; principal + } + | PStr [] -> + let s = { tag = ""; str = "" } in + { extid_loc + ; payload_loc = { extid_loc with loc_start = extid_loc.loc_end } + ; normal = s + ; principal = s + } + | _ -> invalid_payload () + in + Some expectation + | _ -> + None + +(* Split a list of phrases from a .ml file *) +let split_chunks phrases = + let rec loop (phrases : Parsetree.toplevel_phrase list) code_acc acc = + match phrases with + | [] -> + if code_acc = [] then + (List.rev acc, None) + else + (List.rev acc, Some (List.rev code_acc)) + | phrase :: phrases -> + match phrase with + | Ptop_def [] -> loop phrases code_acc acc + | Ptop_def [{pstr_desc = Pstr_extension(ext, []); _}] -> begin + match match_expect_extension ext with + | None -> loop phrases (phrase :: code_acc) acc + | Some expectation -> + let chunk = + { phrases = List.rev code_acc + ; expectation + } + in + loop phrases [] (chunk :: acc) + end + | _ -> loop phrases (phrase :: code_acc) acc + in + loop phrases [] [] + +module Compiler_messages = struct + let print_loc ppf (loc : Location.t) = + let startchar = loc.loc_start.pos_cnum - loc.loc_start.pos_bol in + let endchar = loc.loc_end.pos_cnum - loc.loc_start.pos_cnum + startchar in + Format.fprintf ppf "Line _"; + if startchar >= 0 then + Format.fprintf ppf ", characters %d-%d" startchar endchar; + Format.fprintf ppf ":@." + + let () = + let default = !Location.report_printer () in + Location.report_printer := (fun _ -> + { default with + Location.pp_main_loc = (fun _ _ fmt loc -> print_loc fmt loc); + Location.pp_submsg_loc = (fun _ _ fmt loc -> print_loc fmt loc); + }) + + let capture ppf ~f = + Misc.protect_refs + [ R (Location.formatter_for_warnings , ppf ) + ] + f +end + +let collect_formatters buf pps ~f = + List.iter ~f:(fun pp -> Format.pp_print_flush pp ()) pps; + let save = + List.map ~f:(fun pp -> Format.pp_get_formatter_out_functions pp ()) pps + in + let restore () = + List.iter2 + ~f:(fun pp out_functions -> + Format.pp_print_flush pp (); + Format.pp_set_formatter_out_functions pp out_functions) + pps save + in + let out_string str ofs len = Buffer.add_substring buf str ofs len + and out_flush = ignore + and out_newline () = Buffer.add_char buf '\n' + and out_spaces n = for _i = 1 to n do Buffer.add_char buf ' ' done + and out_indent n = for _i = 1 to n do Buffer.add_char buf ' ' done in + let out_functions = + { Format.out_string; out_flush; out_newline; out_spaces; out_indent } + in + List.iter + ~f:(fun pp -> Format.pp_set_formatter_out_functions pp out_functions) + pps; + match f () with + | x -> restore (); x + | exception exn -> restore (); raise exn + +(* Invariant: ppf = Format.formatter_of_buffer buf *) +let capture_everything buf ppf ~f = + collect_formatters buf [Format.std_formatter; Format.err_formatter] + ~f:(fun () -> Compiler_messages.capture ppf ~f) + +let exec_phrase ppf phrase = + if !Clflags.dump_parsetree then Printast. top_phrase ppf phrase; + if !Clflags.dump_source then Pprintast.top_phrase ppf phrase; + Toploop.execute_phrase true ppf phrase + +let parse_contents ~fname contents = + let lexbuf = Lexing.from_string contents in + Location.init lexbuf fname; + Location.input_name := fname; + Parse.use_file lexbuf + +let eval_expectation expectation ~output = + let s = + if !Clflags.principal then + expectation.principal + else + expectation.normal + in + if s.str = output then + None + else + let trimmed = String.trim output in + let normalized = if String.exists ~f:(function '\n' -> true | _ -> false) output + then "\n" ^ trimmed ^ "\n" + else trimmed + in + let s = { s with str = normalized } in + Some ( + if !Clflags.principal then + { expectation with principal = s } + else + { expectation with normal = s } + ) + +let preprocess_structure mappers str = + let open Ast_mapper in + List.fold_right + ~f:(fun ppx_rewriter str -> + let mapper : Ast_mapper.mapper = ppx_rewriter [] in + mapper.structure mapper str) + mappers + ~init:str + +let preprocess_phrase mappers phrase = + let open Parsetree in + match phrase with + | Ptop_def str -> Ptop_def (preprocess_structure mappers str) + | Ptop_dir _ as x -> x + + +let shift_lines delta = + let position (pos : Lexing.position) = + { pos with pos_lnum = pos.pos_lnum + delta } + in + let location _this (loc : Location.t) = + { loc with + loc_start = position loc.loc_start + ; loc_end = position loc.loc_end + } + in + fun _ -> { Ast_mapper.default_mapper with location } + +let rec min_line_number : Parsetree.toplevel_phrase list -> int option = +function + | [] -> None + | (Ptop_dir _ | Ptop_def []) :: l -> min_line_number l + | Ptop_def (st :: _) :: _ -> Some st.pstr_loc.loc_start.pos_lnum + +let eval_expect_file mapper fname ~file_contents = + Warnings.reset_fatal (); + let chunks, trailing_code = + parse_contents ~fname:fname file_contents |> split_chunks + in + let buf = Buffer.create 1024 in + let ppf = Format.formatter_of_buffer buf in + let out_fun = Format.pp_get_formatter_out_functions ppf () in + Format.pp_set_formatter_out_functions Format.std_formatter out_fun; + + let exec_phrases phrases = + + let mappers = + match min_line_number phrases with + | None -> [] + | Some lnum -> [shift_lines (1 - lnum)] + in + let mappers = mapper :: mappers in + let phrases = List.map ~f:(preprocess_phrase mappers) phrases in + + (* For formatting purposes *) + Buffer.add_char buf '\n'; + let _ : bool = + List.fold_left phrases ~init:true ~f:(fun acc phrase -> + acc && + try + Location.reset (); + exec_phrase ppf phrase + with exn -> + Location.report_exception ppf exn; + false) + in + Format.pp_print_flush ppf (); + let len = Buffer.length buf in + if len > 0 && Buffer.nth buf (len - 1) <> '\n' then + (* For formatting purposes *) + Buffer.add_char buf '\n'; + let s = Buffer.contents buf in + Buffer.clear buf; + Misc.delete_eol_spaces s + in + let corrected_expectations = + capture_everything buf ppf ~f:(fun () -> + List.fold_left chunks ~init:[] ~f:(fun acc chunk -> + let output = exec_phrases chunk.phrases in + match eval_expectation chunk.expectation ~output with + | None -> acc + | Some correction -> correction :: acc) + |> List.rev) + in + let trailing_output = + match trailing_code with + | None -> "" + | Some phrases -> + capture_everything buf ppf ~f:(fun () -> exec_phrases phrases) + in + { corrected_expectations; trailing_output } + +let output_slice oc s a b = + output_string oc (String.sub s ~pos:a ~len:(b - a)) + +let output_corrected oc ~file_contents correction = + let output_body oc { str; tag } = + Printf.fprintf oc "{%s|%s|%s}" tag str tag + in + let ofs = + List.fold_left correction.corrected_expectations ~init:0 + ~f:(fun ofs c -> + output_slice oc file_contents ofs c.payload_loc.loc_start.pos_cnum; + output_body oc c.normal; + if !Clflags.principal && c.normal.str <> c.principal.str then begin + output_string oc ", Principal"; + output_body oc c.principal + end; + c.payload_loc.loc_end.pos_cnum) + in + output_slice oc file_contents ofs (String.length file_contents); + match correction.trailing_output with + | "" -> () + | s -> Printf.fprintf oc "\n[%%%%expect{|%s|}]\n" s + +let write_corrected ~file ~file_contents correction = + let oc = open_out file in + output_corrected oc ~file_contents correction; + close_out oc + +let process_expect_file mapper fname = + let corrected_fname = fname ^ ".corrected" in + let file_contents = + let ic = open_in_bin fname in + match really_input_string ic (in_channel_length ic) with + | s -> close_in ic; Misc.normalise_eol s + | exception e -> close_in ic; raise e + in + let correction = eval_expect_file mapper fname ~file_contents in + write_corrected ~file:corrected_fname ~file_contents correction + +let repo_root = ref "" + +let main mapper fname = + Toploop.override_sys_argv + (Array.sub Sys.argv ~pos:!Arg.current + ~len:(Array.length Sys.argv - !Arg.current)); + (* Ignore OCAMLRUNPARAM=b to be reproducible *) + Printexc.record_backtrace false; + List.iter [ "stdlib" ] ~f:(fun s -> + Topdirs.dir_directory (Filename.concat !repo_root s)); + Toploop.initialize_toplevel_env (); + Sys.interactive := false; + process_expect_file mapper fname; + exit 0 + +let args = + Arg.align + [ "-repo-root", Set_string repo_root, + " root of the OCaml repository" + ; "-principal", Set Clflags.principal, + " Evaluate the file with -principal set" + ] + +let usage = "Usage: expect_test [script-file [arguments]]\n\ + options are:" + +let run mapper = + Toploop.set_paths (); + Clflags.error_style := Some Misc.Error_style.Short; + try + Arg.parse args (main mapper) usage; + Printf.eprintf "expect_test: no input file\n"; + exit 2 + with exn -> + Location.report_exception Format.err_formatter exn; + exit 2 From 53c593d4bafb797278840b89043aeed428c81f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:10:43 +0200 Subject: [PATCH 16/36] OxCaml support: update tests --- .../double-translation/dune.inc | 4 +-- compiler/tests-compiler/dune.inc | 20 ++++++------- compiler/tests-compiler/error.ml | 30 +++++++++++++------ compiler/tests-compiler/gen-rules/gen.ml | 18 ++++++++++- compiler/tests-jsoo/bin/error1.ml | 2 +- compiler/tests-jsoo/dune | 4 ++- compiler/tests-jsoo/gh_1307.ml | 10 ++----- .../tests-jsoo/lib-effects/test_domain.ml | 2 ++ compiler/tests-linkall/dune | 3 ++ compiler/tests-ocaml/basic-more/dune | 10 ++++++- compiler/tests-ocaml/basic/pr7253.ml | 2 +- compiler/tests-ocaml/effects/test_lazy.ml | 2 ++ compiler/tests-ocaml/lazy/lazy2.ml | 1 + compiler/tests-ocaml/lazy/lazy5.ml | 2 ++ compiler/tests-ocaml/lazy/lazy8.ml | 1 + compiler/tests-ocaml/lib-marshal/dune | 4 ++- .../tests-ocaml/lib-marshal/intext_par.ml | 2 ++ lib/js_of_ocaml/js.ml | 2 +- lib/runtime/jsoo_runtime.ml | 5 +++- 19 files changed, 88 insertions(+), 36 deletions(-) diff --git a/compiler/tests-compiler/double-translation/dune.inc b/compiler/tests-compiler/double-translation/dune.inc index 1cecd7aa8b..7c0009916a 100644 --- a/compiler/tests-compiler/double-translation/dune.inc +++ b/compiler/tests-compiler/double-translation/dune.inc @@ -21,7 +21,7 @@ (modules effects_continuations) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -36,7 +36,7 @@ (modules effects_exceptions) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) diff --git a/compiler/tests-compiler/dune.inc b/compiler/tests-compiler/dune.inc index 0d8a74e6c4..719282389b 100644 --- a/compiler/tests-compiler/dune.inc +++ b/compiler/tests-compiler/dune.inc @@ -96,7 +96,7 @@ (modules effects) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -111,7 +111,7 @@ (modules effects_continuations) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -126,7 +126,7 @@ (modules effects_exceptions) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -156,7 +156,7 @@ (modules eliminate_exception_handler) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -231,7 +231,7 @@ (modules exceptions) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -336,7 +336,7 @@ (modules gh1354) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -456,7 +456,7 @@ (modules gh1868) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -501,7 +501,7 @@ (modules global_deadcode) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -591,7 +591,7 @@ (modules loops) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if true) + (enabled_if (not %{oxcaml_supported})) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) @@ -666,7 +666,7 @@ (modules obj) (libraries js_of_ocaml_compiler unix str jsoo_compiler_expect_tests_helper) (inline_tests - (enabled_if (>= %{ocaml_version} 5)) + (enabled_if (and (>= %{ocaml_version} 5) (not %{oxcaml_supported}))) (deps (file %{project_root}/compiler/bin-js_of_ocaml/js_of_ocaml.exe) (file %{project_root}/compiler/bin-jsoo_minify/jsoo_minify.exe))) diff --git a/compiler/tests-compiler/error.ml b/compiler/tests-compiler/error.ml index 2faf9b1131..bf67ae1aa0 100644 --- a/compiler/tests-compiler/error.ml +++ b/compiler/tests-compiler/error.ml @@ -46,7 +46,9 @@ let%expect_test "uncaugh error" = let prog = {| let null = Array.unsafe_get [|1|] 1 -let () = Callback.register "Printexc.handle_uncaught_exception" null +let () = + (Callback.register [@ocaml.alert "-unsafe_multidomain"]) + "Printexc.handle_uncaught_exception" null exception C let _ = raise C |} in @@ -61,7 +63,9 @@ let _ = raise C |} let prog = {| let null = Array.unsafe_get [|1|] 1 -let () = Callback.register "Printexc.handle_uncaught_exception" null +let () = + (Callback.register [@ocaml.alert "-unsafe_multidomain"]) + "Printexc.handle_uncaught_exception" null exception D of int * string * Int64.t let _ = raise (D(2,"test",43L)) |} @@ -77,37 +81,45 @@ let _ = raise (D(2,"test",43L)) let prog = {| let null = Array.unsafe_get [|1|] 1 -let () = Callback.register "Printexc.handle_uncaught_exception" null +let () = + (Callback.register [@ocaml.alert "-unsafe_multidomain"]) + "Printexc.handle_uncaught_exception" null let _ = assert false |} in compile_and_run prog; print_endline (normalize [%expect.output]); [%expect {| - Fatal error: exception Assert_failure("test.ml", 4, 8) + Fatal error: exception Assert_failure("test.ml", 6, 8) process exited with error code 2 - %{NODE} test.js |}]; + %{NODE} test.js + |}]; let prog = {| let null = Array.unsafe_get [|1|] 1 -let () = Callback.register "Printexc.handle_uncaught_exception" null +let () = + (Callback.register [@ocaml.alert "-unsafe_multidomain"]) + "Printexc.handle_uncaught_exception" null [@@@ocaml.warning "-8"] let _ = match 3 with 2 -> () |} in compile_and_run prog; print_endline (normalize [%expect.output]); [%expect {| - Fatal error: exception Match_failure("test.ml", 4, 33) + Fatal error: exception Match_failure("test.ml", 6, 33) process exited with error code 2 - %{NODE} test.js |}]; + %{NODE} test.js + |}]; (* Uncaught javascript exception *) let prog = {| let null : _ -> _ -> _ = Array.unsafe_get [||] 0 -let () = Callback.register "Printexc.handle_uncaught_exception" null +let () = + (Callback.register [@ocaml.alert "-unsafe_multidomain"]) + "Printexc.handle_uncaught_exception" null exception D of int * string * Int64.t let _ = null 1 2 |} diff --git a/compiler/tests-compiler/gen-rules/gen.ml b/compiler/tests-compiler/gen-rules/gen.ml index 2595d3adac..58cf248c12 100644 --- a/compiler/tests-compiler/gen-rules/gen.ml +++ b/compiler/tests-compiler/gen-rules/gen.ml @@ -51,6 +51,8 @@ type enabled_if = | GE52 | LT52 | B64 + | NotOxCaml + | GE5NotOxCaml | Any let lib_enabled_if = function @@ -59,10 +61,22 @@ let lib_enabled_if = function | _ -> Any let test_enabled_if = function - | "obj" | "lazy" -> GE5 + | "obj" -> GE5NotOxCaml (* Some Obj functions are no longer primitives *) + | "lazy" -> GE5 | "gh1051" -> B64 | "rec52" -> GE52 | "rec" -> LT52 + | "gh1354" + | "gh1868" + | "exceptions" + | "effects_continuations" + | "effects_exceptions" + | "eliminate_exception_handler" + | "loops" + | "global_deadcode" -> NotOxCaml (* In OxCaml, raise is always reraise *) + | "effects" -> + (* Call to Printf.printf is somehow compiled differently *) + NotOxCaml | _ -> Any let enabled_if = function @@ -71,6 +85,8 @@ let enabled_if = function | GE52 -> "(>= %{ocaml_version} 5.2)" | LT52 -> "(< %{ocaml_version} 5.2)" | B64 -> "%{arch_sixtyfour}" + | GE5NotOxCaml -> "(and (>= %{ocaml_version} 5) (not %{oxcaml_supported}))" + | NotOxCaml -> "(not %{oxcaml_supported})" let () = Array.to_list (Sys.readdir ".") diff --git a/compiler/tests-jsoo/bin/error1.ml b/compiler/tests-jsoo/bin/error1.ml index 2a2e6504ac..d773d7e068 100644 --- a/compiler/tests-jsoo/bin/error1.ml +++ b/compiler/tests-jsoo/bin/error1.ml @@ -8,7 +8,7 @@ let () = exception D of int * string * Int64.t let _ = - Printexc.register_printer (function + (Printexc.register_printer [@ocaml.alert "-unsafe_multidomain"]) (function | D _ -> Some "custom printer" | _ -> None) diff --git a/compiler/tests-jsoo/dune b/compiler/tests-jsoo/dune index 60c7a46832..5186661bf4 100644 --- a/compiler/tests-jsoo/dune +++ b/compiler/tests-jsoo/dune @@ -20,7 +20,9 @@ (modules test_marshal_compressed) (libraries unix compiler-libs.common js_of_ocaml-compiler) (enabled_if - (>= %{ocaml_version} 5.1.1)) + (and + (>= %{ocaml_version} 5.1.1) + (not %{oxcaml_supported}))) (inline_tests (modes js wasm best)) (preprocess diff --git a/compiler/tests-jsoo/gh_1307.ml b/compiler/tests-jsoo/gh_1307.ml index 6ac526d6e0..1d8de8c9eb 100644 --- a/compiler/tests-jsoo/gh_1307.ml +++ b/compiler/tests-jsoo/gh_1307.ml @@ -5,9 +5,7 @@ let test content = | n -> Printf.printf "%d\n" n; print_endline "success" - | exception e -> - print_endline (Printexc.to_string e); - print_endline "failure" + | exception Parsing.Parse_error -> print_endline "Parse_error" let%expect_test "parsing" = (* use [Parsing.set_trace true] once https://github.com/janestreet/ppx_expect/issues/43 is fixed *) @@ -15,8 +13,7 @@ let%expect_test "parsing" = test "a"; [%expect {| input: "a" - Stdlib.Parsing.Parse_error - failure |}]; + Parse_error |}]; test "aa"; [%expect {| input: "aa" @@ -25,7 +22,6 @@ let%expect_test "parsing" = test "aaa"; [%expect {| input: "aaa" - Stdlib.Parsing.Parse_error - failure |}]; + Parse_error |}]; let (_ : bool) = Parsing.set_trace old in () diff --git a/compiler/tests-jsoo/lib-effects/test_domain.ml b/compiler/tests-jsoo/lib-effects/test_domain.ml index a63b5c5ffb..93b20dc9a7 100644 --- a/compiler/tests-jsoo/lib-effects/test_domain.ml +++ b/compiler/tests-jsoo/lib-effects/test_domain.ml @@ -1,3 +1,5 @@ +[@@@ocaml.alert "-unsafe_parallelism-unsafe_multidomain"] + let%expect_test _ = let d = Domain.spawn (fun () -> 1 + 2) in print_int (Domain.join d); diff --git a/compiler/tests-linkall/dune b/compiler/tests-linkall/dune index 5d1fd7d45f..dbfe6e4c78 100644 --- a/compiler/tests-linkall/dune +++ b/compiler/tests-linkall/dune @@ -5,6 +5,9 @@ (js_of_ocaml (compilation_mode separate)) (wasm_of_ocaml + ; dynlink not compiling + (enabled_if + (not %{oxcaml_supported})) (compilation_mode separate)))) (test diff --git a/compiler/tests-ocaml/basic-more/dune b/compiler/tests-ocaml/basic-more/dune index a92fb3ebc2..47e7802dfd 100644 --- a/compiler/tests-ocaml/basic-more/dune +++ b/compiler/tests-ocaml/basic-more/dune @@ -4,7 +4,6 @@ div_by_zero function_in_ref if_in_if - labels_evaluation_order morematch opaque_prim pr10294 @@ -24,3 +23,12 @@ (build_if (>= %{ocaml_version} 5.2)) (modes js wasm)) + +(tests + (names labels_evaluation_order) + (libraries ocaml_testing) + (build_if + (and + (>= %{ocaml_version} 5.2) + (not %{oxcaml_supported}))) + (modes js wasm)) diff --git a/compiler/tests-ocaml/basic/pr7253.ml b/compiler/tests-ocaml/basic/pr7253.ml index 23c51f21a5..cce90e148a 100644 --- a/compiler/tests-ocaml/basic/pr7253.ml +++ b/compiler/tests-ocaml/basic/pr7253.ml @@ -6,7 +6,7 @@ exception My_exception let () = - Printexc.set_uncaught_exception_handler (fun exn bt -> + (Printexc.set_uncaught_exception_handler [@ocaml.alert "-unsafe_multidomain"]) (fun exn bt -> match exn with | My_exception -> print_endline "Caught"; exit 0 | _ -> print_endline "Unexpected uncaught exception"); diff --git a/compiler/tests-ocaml/effects/test_lazy.ml b/compiler/tests-ocaml/effects/test_lazy.ml index 24f457f0af..a209f9cea6 100644 --- a/compiler/tests-ocaml/effects/test_lazy.ml +++ b/compiler/tests-ocaml/effects/test_lazy.ml @@ -3,6 +3,8 @@ open Effect open Effect.Deep +[@@@ocaml.alert "-unsafe_parallelism-unsafe_multidomain"] + type _ t += Stop : unit t let f count = diff --git a/compiler/tests-ocaml/lazy/lazy2.ml b/compiler/tests-ocaml/lazy/lazy2.ml index e386b97d6a..d71890c281 100644 --- a/compiler/tests-ocaml/lazy/lazy2.ml +++ b/compiler/tests-ocaml/lazy/lazy2.ml @@ -3,6 +3,7 @@ *) open Domain +[@@@ocaml.alert "-unsafe_multidomain-unsafe_parallelism"] let () = let l = lazy (print_string "Lazy Forced\n") in diff --git a/compiler/tests-ocaml/lazy/lazy5.ml b/compiler/tests-ocaml/lazy/lazy5.ml index 4a8ac59fff..fdb9536c71 100644 --- a/compiler/tests-ocaml/lazy/lazy5.ml +++ b/compiler/tests-ocaml/lazy/lazy5.ml @@ -1,6 +1,8 @@ (* TEST ocamlopt_flags += " -O3 "; *) +[@@@ocaml.alert "-unsafe_multidomain-unsafe_parallelism"] + let rec safe_force l = try Lazy.force l with | Lazy.Undefined -> diff --git a/compiler/tests-ocaml/lazy/lazy8.ml b/compiler/tests-ocaml/lazy/lazy8.ml index 1ecf578fad..88cc2e3d24 100644 --- a/compiler/tests-ocaml/lazy/lazy8.ml +++ b/compiler/tests-ocaml/lazy/lazy8.ml @@ -1,6 +1,7 @@ (* TEST ocamlopt_flags += " -O3 "; *) +[@@@ocaml.alert "-unsafe_multidomain-unsafe_parallelism"] exception E diff --git a/compiler/tests-ocaml/lib-marshal/dune b/compiler/tests-ocaml/lib-marshal/dune index d9abe8be7d..a361a821b5 100644 --- a/compiler/tests-ocaml/lib-marshal/dune +++ b/compiler/tests-ocaml/lib-marshal/dune @@ -2,7 +2,9 @@ (names compressed) (libraries compiler-libs.common) (build_if - (>= %{ocaml_version} 5.2)) + (and + (>= %{ocaml_version} 5.2) + (not %{oxcaml_supported}))) (modes js wasm)) (tests diff --git a/compiler/tests-ocaml/lib-marshal/intext_par.ml b/compiler/tests-ocaml/lib-marshal/intext_par.ml index f93c55c685..0b3350efff 100644 --- a/compiler/tests-ocaml/lib-marshal/intext_par.ml +++ b/compiler/tests-ocaml/lib-marshal/intext_par.ml @@ -9,6 +9,8 @@ } *) +[@@@ocaml.alert "-unsafe_multidomain-unsafe_parallelism"] + (* Test for output_value / input_value *) let test_size = diff --git a/lib/js_of_ocaml/js.ml b/lib/js_of_ocaml/js.ml index afdbffa93a..8d04dd4247 100644 --- a/lib/js_of_ocaml/js.ml +++ b/lib/js_of_ocaml/js.ml @@ -815,7 +815,7 @@ let parseFloat (s : js_string t) : number_t = if isNaN s then failwith "parseFloat" else s let _ = - Printexc.register_printer (fun e -> + (Printexc.register_printer [@ocaml.alert "-unsafe_multidomain"]) (fun e -> if instanceof (Obj.magic e : < .. > t) error_constr then let e = Js_error.of_error (Obj.magic e : error t) in diff --git a/lib/runtime/jsoo_runtime.ml b/lib/runtime/jsoo_runtime.ml index 4dee5f64a9..06e096b429 100644 --- a/lib/runtime/jsoo_runtime.ml +++ b/lib/runtime/jsoo_runtime.ml @@ -175,7 +175,10 @@ end = struct exception Exn of t - let _ = Callback.register_exception "jsError" (Exn (Obj.magic [||])) + let _ = + (Callback.register_exception [@ocaml.alert "-unsafe_multidomain"]) + "jsError" + (Exn (Obj.magic [||])) external raise_ : t -> 'a = "caml_throw_js_exception" From d11dae05dd904af919dc53b7015a5d6f7ab5fde0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:41:24 +0200 Subject: [PATCH 17/36] OxCaml support: ppx updates --- ppx/ppx_deriving_json/lib/dune | 2 +- .../lib/ppx_deriving_json.ml | 65 ++++++++++++------- ppx/ppx_js/lib_internal/ppx_js_internal.ml | 9 ++- 3 files changed, 49 insertions(+), 27 deletions(-) diff --git a/ppx/ppx_deriving_json/lib/dune b/ppx/ppx_deriving_json/lib/dune index 62c8f7e705..edbabc70eb 100644 --- a/ppx/ppx_deriving_json/lib/dune +++ b/ppx/ppx_deriving_json/lib/dune @@ -6,4 +6,4 @@ (ppx_runtime_libraries js_of_ocaml.deriving) (kind ppx_deriver) (preprocess - (pps ppxlib.metaquot))) + (pps ppx_optcomp_light ppxlib.metaquot))) diff --git a/ppx/ppx_deriving_json/lib/ppx_deriving_json.ml b/ppx/ppx_deriving_json/lib/ppx_deriving_json.ml index 22df5a8a01..d84e84b2fb 100644 --- a/ppx/ppx_deriving_json/lib/ppx_deriving_json.ml +++ b/ppx/ppx_deriving_json/lib/ppx_deriving_json.ml @@ -23,6 +23,8 @@ open Ppxlib.Ast open Ppxlib.Ast_helper open Ppxlib.Parsetree +[@@@ocaml.alert "-prefer_jane_syntax"] + let nolabel = Nolabel let unflatten l = @@ -132,30 +134,28 @@ let core_type_of_type_decl { ptype_name = name; ptype_params; _ } = let name = mkloc (Longident.Lident name.txt) name.loc in Typ.constr name (List.map ~f:fst ptype_params) +let wrap fn = + fun (param, _) accum -> + match param with + | ({ ptyp_desc = Ptyp_any; _ } [@if not oxcaml]) -> accum + | ({ ptyp_desc = Ptyp_any _; _ } [@if oxcaml]) -> accum + | ({ ptyp_desc = Ptyp_var name; _ } [@if not oxcaml]) -> + let name = mkloc name param.ptyp_loc in + fn name accum + | ({ ptyp_desc = Ptyp_var (name, _); _ } [@if oxcaml]) -> + let name = mkloc name param.ptyp_loc in + fn name accum + | _ -> assert false + let fold_right_type_params fn params accum = - List.fold_right - ~f:(fun (param, _) accum -> - match param with - | { ptyp_desc = Ptyp_any; _ } -> accum - | { ptyp_desc = Ptyp_var name; _ } -> - let name = mkloc name param.ptyp_loc in - fn name accum - | _ -> assert false) - params - ~init:accum + List.fold_right ~f:(wrap fn) params ~init:accum let fold_right_type_decl fn { ptype_params; _ } accum = fold_right_type_params fn ptype_params accum let fold_left_type_params fn accum params = List.fold_left - ~f:(fun accum (param, _) -> - match param with - | { ptyp_desc = Ptyp_any; _ } -> accum - | { ptyp_desc = Ptyp_var name; _ } -> - let name = mkloc name param.ptyp_loc in - fn accum name - | _ -> assert false) + ~f:(fun accum param -> wrap (fun accum name -> fn name accum) param accum) ~init:accum params @@ -338,10 +338,14 @@ and write_body_of_type y ~(arg : string) ~poly = | [%type: [%t? y] array] -> let e = write_of_type y ~poly in [%expr [%e rt "write_array"] [%e e] buf [%e arg]] - | { Parsetree.ptyp_desc = Ptyp_var v; _ } when poly -> + | ({ Parsetree.ptyp_desc = Ptyp_var v; _ } [@if not oxcaml]) when poly -> + [%expr [%e evar ("poly_" ^ v)] buf [%e arg]] + | ({ Parsetree.ptyp_desc = Ptyp_var (v, _); _ } [@if oxcaml]) when poly -> [%expr [%e evar ("poly_" ^ v)] buf [%e arg]] - | { Parsetree.ptyp_desc = Ptyp_tuple l; _ } -> + | ({ Parsetree.ptyp_desc = Ptyp_tuple l; _ } [@if not oxcaml]) -> write_body_of_tuple_type l ~arg ~poly ~tag:0 + | ({ Parsetree.ptyp_desc = Ptyp_tuple l; _ } [@if oxcaml]) -> + write_body_of_tuple_type (List.map ~f:snd l) ~arg ~poly ~tag:0 | { Parsetree.ptyp_desc = Ptyp_variant (l, _, _); _ } -> Exp.match_ arg (List.map ~f:(write_poly_case ~arg:arg' ~poly) l) | { Parsetree.ptyp_desc = Ptyp_constr (lid, l); _ } -> @@ -498,7 +502,10 @@ and read_body_of_type ?decl y = | [%type: [%t? y] ref] -> [%expr [%e rt "read_ref"] [%e read_of_type ?decl y] buf] | [%type: [%t? y] option] -> [%expr [%e rt "read_option"] [%e read_of_type ?decl y] buf] | [%type: [%t? y] array] -> [%expr [%e rt "read_array"] [%e read_of_type ?decl y] buf] - | { Parsetree.ptyp_desc = Ptyp_tuple l; _ } -> read_body_of_tuple_type l ?decl + | ({ Parsetree.ptyp_desc = Ptyp_tuple l; _ } [@if not oxcaml]) -> + read_body_of_tuple_type l ?decl + | ({ Parsetree.ptyp_desc = Ptyp_tuple l; _ } [@if oxcaml]) -> + read_body_of_tuple_type (List.map ~f:snd l) ?decl | { Parsetree.ptyp_desc = Ptyp_variant (l, _, _); ptyp_loc = loc; _ } -> let e = match decl with @@ -513,7 +520,9 @@ and read_body_of_type ?decl y = | Some _ | None -> read_of_poly_variant l y ~loc and tag = [%expr [%e lexer "read_vcase"] buf] in [%expr [%e e] buf [%e tag]] - | { Parsetree.ptyp_desc = Ptyp_var v; _ } when poly -> + | ({ Parsetree.ptyp_desc = Ptyp_var v; _ } [@if not oxcaml]) when poly -> + [%expr [%e evar ("poly_" ^ v)] buf] + | ({ Parsetree.ptyp_desc = Ptyp_var (v, _); _ } [@if oxcaml]) when poly -> [%expr [%e evar ("poly_" ^ v)] buf] | { Parsetree.ptyp_desc = Ptyp_constr (lid, l); _ } -> let e = suffix_lid lid ~suffix:"of_json" @@ -624,16 +633,25 @@ let json_decls_of_type decl y = in write_decl_of_type decl y, read_decl_of_type decl y, json_str decl, recognize, read_tag +let constructor_argument_type ca = ca.Parsetree.pca_type [@@if oxcaml] + +let constructor_argument_type typ = typ [@@if not oxcaml] + let write_case (i, i', l) { Parsetree.pcd_name; pcd_args; _ } = let i, i', lhs, rhs = match pcd_args with | Pcstr_tuple [] | Pcstr_record [] -> i + 1, i', None, [%expr [%e rt "Json_int.write"] buf [%e int i]] - | Pcstr_tuple ([ _ ] as args) -> + | Pcstr_tuple [ arg ] -> let v = fresh_var [] in - i, i' + 1, Some (pvar v), write_tuple_contents [ v ] args ~tag:i' ~poly:true + ( i + , i' + 1 + , Some (pvar v) + , write_tuple_contents [ v ] [ constructor_argument_type arg ] ~tag:i' ~poly:true + ) | Pcstr_tuple args -> let vars = fresh_vars (List.length args) in + let args = List.map ~f:constructor_argument_type args in ( i , i' + 1 , Some (var_ptuple vars) @@ -669,6 +687,7 @@ let read_case ?decl (i, i', l) { Parsetree.pcd_name; pcd_args; _ } = (Exp.construct (label_of_constructor pcd_name) None) :: l ) | Pcstr_tuple pcd_args -> + let pcd_args = List.map ~f:constructor_argument_type pcd_args in let expr = read_tuple_contents ?decl pcd_args ~f in let case = Exp.case [%pat? `NCst [%p pint i']] expr in i, i' + 1, case :: l diff --git a/ppx/ppx_js/lib_internal/ppx_js_internal.ml b/ppx/ppx_js/lib_internal/ppx_js_internal.ml index deadf746fc..0d95f343ac 100644 --- a/ppx/ppx_js/lib_internal/ppx_js_internal.ml +++ b/ppx/ppx_js/lib_internal/ppx_js_internal.ml @@ -23,6 +23,8 @@ open Ast_helper open Asttypes open Parsetree +[@@@ocaml.alert "-prefer_jane_syntax"] + let nolabel = Nolabel exception Syntax_error of Location.Error.t @@ -339,7 +341,7 @@ let method_call ~loc ~apply_loc obj (meth, meth_loc) args = in Exp.apply ~loc:apply_loc - { invoker with pexp_attributes = [ merlin_hide ] } + { invoker with pexp_attributes = invoker.pexp_attributes @ [ merlin_hide ] } ((app_arg obj :: args) @ [ app_arg (Exp.fun_ @@ -550,9 +552,10 @@ let filter_map f l = let rec create_meth_ty exp = match exp.pexp_desc with - | Pexp_fun (label, _, _, body) -> label :: create_meth_ty body + | ((Pexp_fun (label, _, _, body)) [@if not oxcaml]) -> label :: create_meth_ty body | Pexp_function _ -> [ nolabel ] - | Pexp_newtype (_, body) -> create_meth_ty body + | ((Pexp_newtype (_, body)) [@if not oxcaml]) -> create_meth_ty body + | ((Pexp_newtype (_, _, body)) [@if oxcaml]) -> create_meth_ty body | _ -> [] [@@if ast_version < 502] From d9d57baaa4bd2efae3de1f76371352d04e69f759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:42:18 +0200 Subject: [PATCH 18/36] OxCaml support: some runtime changes --- .../js_of_ocaml_compiler_runtime_files.ml | 1 + compiler/lib-runtime-files/tests/all.ml | 2 ++ compiler/tests-check-prim/main.5.2.output | 5 +++++ compiler/tests-check-prim/unix-Unix.5.2.output | 5 +++++ compiler/tests-check-prim/unix-Win32.5.2.output | 5 +++++ runtime/js/dune | 1 + runtime/js/float32.js | 6 ++++++ runtime/js/hash.js | 6 ++++++ runtime/js/sys.js | 7 +++++++ runtime/wasm/float32.wat | 4 ++++ runtime/wasm/hash.wat | 2 +- runtime/wasm/sys.wat | 4 ++++ 12 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 runtime/js/float32.js create mode 100644 runtime/wasm/float32.wat diff --git a/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml b/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml index 41466846a2..abeec1a665 100644 --- a/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml +++ b/compiler/lib-runtime-files/js_of_ocaml_compiler_runtime_files.ml @@ -33,6 +33,7 @@ let runtime = ; graphics ; hash ; ieee_754 + ; float32 ; int64 ; ints ; io diff --git a/compiler/lib-runtime-files/tests/all.ml b/compiler/lib-runtime-files/tests/all.ml index 0896252226..2911e16bf9 100644 --- a/compiler/lib-runtime-files/tests/all.ml +++ b/compiler/lib-runtime-files/tests/all.ml @@ -21,6 +21,7 @@ let%expect_test _ = +dynlink.js +effect.js +fail.js + +float32.js +format.js +fs.js +fs_fake.js @@ -64,6 +65,7 @@ let%expect_test _ = +domain.js +effect.js +fail.js + +float32.js +format.js +fs.js +fs_fake.js diff --git a/compiler/tests-check-prim/main.5.2.output b/compiler/tests-check-prim/main.5.2.output index b3ac67d39d..d28cdca7fb 100644 --- a/compiler/tests-check-prim/main.5.2.output +++ b/compiler/tests-check-prim/main.5.2.output @@ -31,6 +31,9 @@ caml_hash_mix_bigstring From +effect.js: jsoo_effect_not_supported +From +float32.js: +caml_is_boot_compiler + From +fs.js: caml_ba_map_file caml_ba_map_file_bytecode @@ -88,6 +91,7 @@ caml_gr_wait_event caml_gr_window_id From +hash.js: +caml_hash_exn caml_hash_mix_int64 From +ints.js: @@ -127,6 +131,7 @@ caml_format_exception caml_is_special_exception caml_set_static_env caml_sys_const_naked_pointers_checked +caml_sys_const_runtime5 From +toplevel.js: caml_get_section_table diff --git a/compiler/tests-check-prim/unix-Unix.5.2.output b/compiler/tests-check-prim/unix-Unix.5.2.output index 2889e7d976..09db8073fb 100644 --- a/compiler/tests-check-prim/unix-Unix.5.2.output +++ b/compiler/tests-check-prim/unix-Unix.5.2.output @@ -107,6 +107,9 @@ caml_hash_mix_bigstring From +effect.js: jsoo_effect_not_supported +From +float32.js: +caml_is_boot_compiler + From +fs.js: caml_ba_map_file caml_ba_map_file_bytecode @@ -164,6 +167,7 @@ caml_gr_wait_event caml_gr_window_id From +hash.js: +caml_hash_exn caml_hash_mix_int64 From +ints.js: @@ -203,6 +207,7 @@ caml_format_exception caml_is_special_exception caml_set_static_env caml_sys_const_naked_pointers_checked +caml_sys_const_runtime5 From +toplevel.js: caml_get_section_table diff --git a/compiler/tests-check-prim/unix-Win32.5.2.output b/compiler/tests-check-prim/unix-Win32.5.2.output index 931ab94600..6a1d462a4d 100644 --- a/compiler/tests-check-prim/unix-Win32.5.2.output +++ b/compiler/tests-check-prim/unix-Win32.5.2.output @@ -80,6 +80,9 @@ caml_hash_mix_bigstring From +effect.js: jsoo_effect_not_supported +From +float32.js: +caml_is_boot_compiler + From +fs.js: caml_ba_map_file caml_ba_map_file_bytecode @@ -137,6 +140,7 @@ caml_gr_wait_event caml_gr_window_id From +hash.js: +caml_hash_exn caml_hash_mix_int64 From +ints.js: @@ -176,6 +180,7 @@ caml_format_exception caml_is_special_exception caml_set_static_env caml_sys_const_naked_pointers_checked +caml_sys_const_runtime5 From +toplevel.js: caml_get_section_table diff --git a/runtime/js/dune b/runtime/js/dune index 17b7db4117..73f28b7857 100644 --- a/runtime/js/dune +++ b/runtime/js/dune @@ -11,6 +11,7 @@ gc.js graphics.js ieee_754.js + float32.js int64.js io.js jslib.js diff --git a/runtime/js/float32.js b/runtime/js/float32.js new file mode 100644 index 0000000000..a4b14f1feb --- /dev/null +++ b/runtime/js/float32.js @@ -0,0 +1,6 @@ +//Provides: caml_is_boot_compiler +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_is_boot_compiler(_unit) { + return 0; +} diff --git a/runtime/js/hash.js b/runtime/js/hash.js index ae19898dae..09a85dd54e 100644 --- a/runtime/js/hash.js +++ b/runtime/js/hash.js @@ -221,6 +221,12 @@ function caml_hash(count, limit, seed, obj) { return h & 0x3fffffff; } +//Provides: caml_hash_exn +//Requires: caml_hash +//Version: >= 5.2, < 5.3 +//OxCaml +var caml_hash_exn = caml_hash; + //Provides: caml_string_hash //Requires: caml_hash_mix_final, caml_hash_mix_string //Version: >= 5.0 diff --git a/runtime/js/sys.js b/runtime/js/sys.js index 98a9ed5001..86e0f616c6 100644 --- a/runtime/js/sys.js +++ b/runtime/js/sys.js @@ -311,6 +311,13 @@ function caml_sys_isatty(_chan) { return 0; } +//Provides: caml_sys_const_runtime5 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_sys_const_runtime5(_unit) { + return 1; +} + //Provides: caml_runtime_variant //Requires: caml_string_of_jsbytes function caml_runtime_variant(_unit) { diff --git a/runtime/wasm/float32.wat b/runtime/wasm/float32.wat new file mode 100644 index 0000000000..2ba930d0c7 --- /dev/null +++ b/runtime/wasm/float32.wat @@ -0,0 +1,4 @@ +(module + (func (export "caml_is_boot_compiler") (param (ref eq)) (result (ref eq)) + (ref.i31 (i32.const 0))) +) diff --git a/runtime/wasm/hash.wat b/runtime/wasm/hash.wat index a7a78c9e49..cd826e536a 100644 --- a/runtime/wasm/hash.wat +++ b/runtime/wasm/hash.wat @@ -174,7 +174,7 @@ (global $caml_hash_queue (ref $block) (array.new $block (ref.i31 (i32.const 0)) (global.get $HASH_QUEUE_SIZE))) - (func (export "caml_hash") + (func (export "caml_hash") (export "caml_hash_exn") (param $count (ref eq)) (param $limit (ref eq)) (param $seed (ref eq)) (param $obj (ref eq)) (result (ref eq)) (local $sz i32) (local $num i32) (local $h i32) diff --git a/runtime/wasm/sys.wat b/runtime/wasm/sys.wat index ff904edd50..e9a1220071 100644 --- a/runtime/wasm/sys.wat +++ b/runtime/wasm/sys.wat @@ -174,6 +174,10 @@ (param $ch (ref eq)) (result (ref eq)) (return_call $isatty (call $caml_channel_descriptor (local.get $ch)))) + (func (export "caml_sys_const_runtime5") + (param (ref eq)) (result (ref eq)) + (ref.i31 (i32.const 1))) + (func (export "caml_runtime_variant") (param (ref eq)) (result (ref eq)) (@string "")) From 597def4144fa38c84a2aef7d865615accc9e5e1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:42:53 +0200 Subject: [PATCH 19/36] OxCaml support: toplevel It pretends to be 5.2 but uses the new API to reify bytecode --- runtime/js/toplevel.js | 43 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/runtime/js/toplevel.js b/runtime/js/toplevel.js index 3b65660823..09d68857fa 100644 --- a/runtime/js/toplevel.js +++ b/runtime/js/toplevel.js @@ -99,39 +99,32 @@ function jsoo_toplevel_init_reloc(f) { jsoo_toplevel_reloc = f; } -//Provides: caml_reify_bytecode -//Requires: caml_callback -//Requires: caml_string_of_uint8_array, caml_ba_to_typed_array -//Requires: jsoo_toplevel_compile, caml_failwith -//Version: >= 5.2 -function caml_reify_bytecode(code, debug, _digest) { - if (!jsoo_toplevel_compile) { - caml_failwith("Toplevel not initialized (jsoo_toplevel_compile)"); - } - code = caml_string_of_uint8_array(caml_ba_to_typed_array(code)); - return [0, 0, caml_callback(jsoo_toplevel_compile, [code, debug])]; -} - //Provides: caml_reify_bytecode //Requires: caml_callback //Requires: caml_string_of_uint8_array, caml_uint8_array_of_bytes +//Requires: caml_ba_to_typed_array //Requires: jsoo_toplevel_compile, caml_failwith -//Version: < 5.2 function caml_reify_bytecode(code, debug, _digest) { if (!jsoo_toplevel_compile) { caml_failwith("Toplevel not initialized (jsoo_toplevel_compile)"); } - var len = 0; - var all = []; - for (var i = 1; i < code.length; i++) { - var a = caml_uint8_array_of_bytes(code[i]); - all.push(a); - len += a.length; - } - code = new Uint8Array(len); - for (var i = 0, len = 0; i < all.length; i++) { - code.set(all[i], len); - len += all[i].length; + if (code.data) { + //Version: >= 5.2 + code = caml_ba_to_typed_array(code); + } else { + // Oxcaml or version < 5.2 + var len = 0; + var all = []; + for (var i = 1; i < code.length; i++) { + var a = caml_uint8_array_of_bytes(code[i]); + all.push(a); + len += a.length; + } + code = new Uint8Array(len); + for (var i = 0, len = 0; i < all.length; i++) { + code.set(all[i], len); + len += all[i].length; + } } code = caml_string_of_uint8_array(code); return [0, 0, caml_callback(jsoo_toplevel_compile, [code, debug])]; From 126818e9307f642b396f2ee39d378c23caef7455 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:51:04 +0200 Subject: [PATCH 20/36] OxCaml runtime: additional atomic primitives --- compiler/tests-check-prim/main.5.2.output | 8 +++ .../tests-check-prim/unix-Unix.5.2.output | 8 +++ .../tests-check-prim/unix-Win32.5.2.output | 8 +++ runtime/js/domain.js | 49 +++++++++++++ runtime/wasm/domain.wat | 70 +++++++++++++++++++ 5 files changed, 143 insertions(+) diff --git a/compiler/tests-check-prim/main.5.2.output b/compiler/tests-check-prim/main.5.2.output index d28cdca7fb..fa384febeb 100644 --- a/compiler/tests-check-prim/main.5.2.output +++ b/compiler/tests-check-prim/main.5.2.output @@ -28,6 +28,14 @@ caml_bigstring_blit_string_to_ba caml_bigstring_memcmp caml_hash_mix_bigstring +From +domain.js: +caml_atomic_add +caml_atomic_compare_exchange +caml_atomic_land +caml_atomic_lor +caml_atomic_lxor +caml_atomic_sub + From +effect.js: jsoo_effect_not_supported diff --git a/compiler/tests-check-prim/unix-Unix.5.2.output b/compiler/tests-check-prim/unix-Unix.5.2.output index 09db8073fb..d9f415341b 100644 --- a/compiler/tests-check-prim/unix-Unix.5.2.output +++ b/compiler/tests-check-prim/unix-Unix.5.2.output @@ -104,6 +104,14 @@ caml_bigstring_blit_string_to_ba caml_bigstring_memcmp caml_hash_mix_bigstring +From +domain.js: +caml_atomic_add +caml_atomic_compare_exchange +caml_atomic_land +caml_atomic_lor +caml_atomic_lxor +caml_atomic_sub + From +effect.js: jsoo_effect_not_supported diff --git a/compiler/tests-check-prim/unix-Win32.5.2.output b/compiler/tests-check-prim/unix-Win32.5.2.output index 6a1d462a4d..574569f3c1 100644 --- a/compiler/tests-check-prim/unix-Win32.5.2.output +++ b/compiler/tests-check-prim/unix-Win32.5.2.output @@ -77,6 +77,14 @@ caml_bigstring_blit_string_to_ba caml_bigstring_memcmp caml_hash_mix_bigstring +From +domain.js: +caml_atomic_add +caml_atomic_compare_exchange +caml_atomic_land +caml_atomic_lor +caml_atomic_lxor +caml_atomic_sub + From +effect.js: jsoo_effect_not_supported diff --git a/runtime/js/domain.js b/runtime/js/domain.js index 65823adb86..5f9bbb3e19 100644 --- a/runtime/js/domain.js +++ b/runtime/js/domain.js @@ -57,6 +57,15 @@ function caml_atomic_cas_field(b, i, o, n) { return 0; } +//Provides: caml_atomic_compare_exchange +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atomic_compare_exchange(ref, o, n) { + var old = ref[1]; + if (old === o) ref[1] = n; + return old; +} + //Provides: caml_atomic_fetch_add //Version: >= 5 function caml_atomic_fetch_add(ref, i) { @@ -73,6 +82,46 @@ function caml_atomic_fetch_add_field(b, i, n) { return old; } +//Provides: caml_atomic_add +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atomic_add(ref, i) { + ref[1] += i; + return 0; +} + +//Provides: caml_atomic_sub +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atomic_sub(ref, i) { + ref[1] -= i; + return 0; +} + +//Provides: caml_atomic_land +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atomic_land(ref, i) { + ref[1] &= i; + return 0; +} + +//Provides: caml_atomic_lor +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atomic_lor(ref, i) { + ref[1] |= i; + return 0; +} + +//Provides: caml_atomic_lxor +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atomic_lxor(ref, i) { + ref[1] ^= i; + return 0; +} + //Provides: caml_atomic_exchange //Version: >= 5 function caml_atomic_exchange(ref, v) { diff --git a/runtime/wasm/domain.wat b/runtime/wasm/domain.wat index 712486bbdd..446ffa649b 100644 --- a/runtime/wasm/domain.wat +++ b/runtime/wasm/domain.wat @@ -55,6 +55,21 @@ (else (ref.i31 (i32.const 0))))) + (func (export "caml_atomic_compare_exchange") + (param $ref (ref eq)) (param $o (ref eq)) (param $n (ref eq)) + (result (ref eq)) + (local $b (ref $block)) + (local $old (ref eq)) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $old (array.get $block (local.get $b) (i32.const 1))) + (if (result (ref eq)) + (ref.eq (local.get $old) (local.get $o)) + (then + (array.set $block (local.get $b) (i32.const 1) (local.get $n)) + (local.get $old)) + (else + (local.get $old)))) + (func (export "caml_atomic_load") (param (ref eq)) (result (ref eq)) (array.get $block (ref.cast (ref $block) (local.get 0)) (i32.const 1))) @@ -89,6 +104,61 @@ (i31.get_s (ref.cast (ref i31) (local.get $n)))))) (local.get $old)) + (func (export "caml_atomic_add") + (param $ref (ref eq)) (param $i (ref eq)) (result (ref eq)) + (local $b (ref $block)) + (local $old (ref eq)) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $old (array.get $block (local.get $b) (i32.const 1))) + (array.set $block (local.get $b) (i32.const 1) + (ref.i31 (i32.add (i31.get_s (ref.cast (ref i31) (local.get $old))) + (i31.get_s (ref.cast (ref i31) (local.get $i)))))) + (ref.i31 (i32.const 0))) + + (func (export "caml_atomic_sub") + (param $ref (ref eq)) (param $i (ref eq)) (result (ref eq)) + (local $b (ref $block)) + (local $old (ref eq)) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $old (array.get $block (local.get $b) (i32.const 1))) + (array.set $block (local.get $b) (i32.const 1) + (ref.i31 (i32.sub (i31.get_s (ref.cast (ref i31) (local.get $old))) + (i31.get_s (ref.cast (ref i31) (local.get $i)))))) + (ref.i31 (i32.const 0))) + + (func (export "caml_atomic_land") + (param $ref (ref eq)) (param $i (ref eq)) (result (ref eq)) + (local $b (ref $block)) + (local $old (ref eq)) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $old (array.get $block (local.get $b) (i32.const 1))) + (array.set $block (local.get $b) (i32.const 1) + (ref.i31 (i32.and (i31.get_s (ref.cast (ref i31) (local.get $old))) + (i31.get_s (ref.cast (ref i31) (local.get $i)))))) + (ref.i31 (i32.const 0))) + + (func (export "caml_atomic_lor") + (param $ref (ref eq)) (param $i (ref eq)) (result (ref eq)) + (local $b (ref $block)) + (local $old (ref eq)) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $old (array.get $block (local.get $b) (i32.const 1))) + (array.set $block (local.get $b) (i32.const 1) + (ref.i31 (i32.or (i31.get_s (ref.cast (ref i31) (local.get $old))) + (i31.get_s (ref.cast (ref i31) (local.get $i)))))) + (ref.i31 (i32.const 0))) + + (func (export "caml_atomic_lxor") + (param $ref (ref eq)) (param $i (ref eq)) (result (ref eq)) + (local $b (ref $block)) + (local $old (ref eq)) + (local.set $b (ref.cast (ref $block) (local.get $ref))) + (local.set $old (array.get $block (local.get $b) (i32.const 1))) + (array.set $block (local.get $b) (i32.const 1) + (ref.i31 (i32.xor (i31.get_s (ref.cast (ref i31) (local.get $old))) + (i31.get_s (ref.cast (ref i31) (local.get $i)))))) + (ref.i31 (i32.const 0))) + (func (export "caml_atomic_exchange") (param $ref (ref eq)) (param $v (ref eq)) (result (ref eq)) (local $b (ref $block)) From 7b463a56f4cebfe32321248ac5288e4186e096b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 13:05:59 +0200 Subject: [PATCH 21/36] OxCaml runtime: local variant of some primitives --- runtime/js/array.js | 4 ++++ runtime/js/mlBytes.js | 1 + runtime/wasm/array.wat | 9 +++++---- runtime/wasm/string.wat | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/runtime/js/array.js b/runtime/js/array.js index 76e0773a0c..893aa5648c 100644 --- a/runtime/js/array.js +++ b/runtime/js/array.js @@ -18,6 +18,7 @@ ///////////// Array //Provides: caml_array_sub mutable +//Alias: caml_array_sub_local function caml_array_sub(a, i, len) { var a2 = new Array(len + 1); a2[0] = 0; @@ -42,6 +43,7 @@ function caml_uniform_array_sub(a, i, len) { } //Provides: caml_array_append mutable +//Alias: caml_array_append_local function caml_array_append(a1, a2) { var l1 = a1.length, l2 = a2.length; @@ -70,6 +72,7 @@ function caml_uniform_array_append(a1, a2) { } //Provides: caml_array_concat mutable +//Alias: caml_array_concat_local function caml_array_concat(l) { var a = [0]; while (l !== 0) { @@ -228,6 +231,7 @@ function caml_array_create_float(len) { } //Provides: caml_floatarray_create const (const) //Requires: caml_array_bound_error +//Alias: caml_floatarray_create_local function caml_floatarray_create(len) { if (len >>> 0 >= ((0x7fffffff / 8) | 0)) caml_array_bound_error(); var len = (len + 1) | 0; diff --git a/runtime/js/mlBytes.js b/runtime/js/mlBytes.js index 6e724f0ff9..46800322bc 100644 --- a/runtime/js/mlBytes.js +++ b/runtime/js/mlBytes.js @@ -390,6 +390,7 @@ function caml_create_string(_len) { //Provides: caml_create_bytes const //Requires: MlBytes,caml_invalid_argument +//Alias: caml_create_local_bytes function caml_create_bytes(len) { if (len < 0) caml_invalid_argument("Bytes.create"); return new MlBytes(len ? 2 : 9, "", len); diff --git a/runtime/wasm/array.wat b/runtime/wasm/array.wat index 235a9aa52b..7d6be3c173 100644 --- a/runtime/wasm/array.wat +++ b/runtime/wasm/array.wat @@ -62,7 +62,7 @@ (func $caml_floatarray_create (export "caml_make_float_vect") (export "caml_floatarray_create") - (export "caml_array_create_float") + (export "caml_array_create_float") (export "caml_floatarray_create_local") (param $n (ref eq)) (result (ref eq)) (local $sz i32) (local.set $sz (i31.get_s (ref.cast (ref i31) (local.get $n)))) @@ -110,7 +110,7 @@ (struct.get $float 0 (ref.cast (ref $float) (local.get $v)))) (ref.i31 (i32.const 0))) - (func (export "caml_array_sub") + (func (export "caml_array_sub") (export "caml_array_sub_local") (param $a (ref eq)) (param $i (ref eq)) (param $vlen (ref eq)) (result (ref eq)) (local $a1 (ref $block)) (local $a2 (ref $block)) (local $len i32) @@ -163,7 +163,7 @@ (local.get $len)) (local.get $a')) - (func (export "caml_array_append") + (func (export "caml_array_append") (export "caml_array_append_local") (param $va1 (ref eq)) (param $va2 (ref eq)) (result (ref eq)) (local $a1 (ref $block)) (local $a2 (ref $block)) (local $a (ref $block)) (local $fa1 (ref $float_array)) (local $fa2 (ref $float_array)) @@ -235,7 +235,8 @@ (return (local.get $fa)))) (return_call $caml_floatarray_dup (local.get $fa1))) - (func (export "caml_array_concat") (param (ref eq)) (result (ref eq)) + (func (export "caml_array_concat") (export "caml_array_concat_local") + (param (ref eq)) (result (ref eq)) (local $i i32) (local $len i32) (local $l (ref eq)) (local $v (ref eq)) (local $isfloat i32) diff --git a/runtime/wasm/string.wat b/runtime/wasm/string.wat index b594de1206..f1e406f23d 100644 --- a/runtime/wasm/string.wat +++ b/runtime/wasm/string.wat @@ -121,7 +121,7 @@ (@string $Bytes_create "Bytes.create") - (func (export "caml_create_bytes") + (func (export "caml_create_bytes") (export "caml_create_local_bytes") (param $len (ref eq)) (result (ref eq)) (local $l i32) (local.set $l (i31.get_s (ref.cast (ref i31) (local.get $len)))) From ef292063276e117d8b9f47fbb38ae2ed7483851e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 13:33:03 +0200 Subject: [PATCH 22/36] OxCaml runtime: immutable array primitives --- compiler/lib/parse_bytecode.ml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/lib/parse_bytecode.ml b/compiler/lib/parse_bytecode.ml index 8d6f033a95..787e2ce43d 100644 --- a/compiler/lib/parse_bytecode.ml +++ b/compiler/lib/parse_bytecode.ml @@ -1881,6 +1881,8 @@ and compile infos pc state (instrs : instr list) = | "%identity", _ -> true | "caml_ensure_stack_capacity", _ -> true | "caml_process_pending_actions_with_root", _ -> true + | "caml_array_of_iarray", _ -> true + | "caml_iarray_of_array", _ -> true | "caml_make_array", `JavaScript -> true | "caml_array_of_uniform_array", `JavaScript -> true | "caml_js_from_float", `JavaScript -> true From d7c4dfb4cf39785483833b9eb3e5b1b406881e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 10 Jul 2025 21:40:00 +0200 Subject: [PATCH 23/36] OxCaml runtime: obj primitives --- compiler/tests-check-prim/main.5.2.output | 5 +++++ .../tests-check-prim/unix-Unix.5.2.output | 5 +++++ .../tests-check-prim/unix-Win32.5.2.output | 5 +++++ runtime/js/obj.js | 22 +++++++++++++++++++ runtime/wasm/obj.wat | 16 ++++++++++++++ 5 files changed, 53 insertions(+) diff --git a/compiler/tests-check-prim/main.5.2.output b/compiler/tests-check-prim/main.5.2.output index fa384febeb..7e4201ae6a 100644 --- a/compiler/tests-check-prim/main.5.2.output +++ b/compiler/tests-check-prim/main.5.2.output @@ -123,6 +123,11 @@ caml_new_string (deprecated) caml_string_concat caml_to_js_string (deprecated) +From +obj.js: +caml_obj_is_stack +caml_obj_uniquely_reachable_words +caml_succ_scannable_prefix_len + From +runtime_events.js: caml_runtime_events_create_cursor caml_runtime_events_free_cursor diff --git a/compiler/tests-check-prim/unix-Unix.5.2.output b/compiler/tests-check-prim/unix-Unix.5.2.output index d9f415341b..124585a981 100644 --- a/compiler/tests-check-prim/unix-Unix.5.2.output +++ b/compiler/tests-check-prim/unix-Unix.5.2.output @@ -199,6 +199,11 @@ caml_new_string (deprecated) caml_string_concat caml_to_js_string (deprecated) +From +obj.js: +caml_obj_is_stack +caml_obj_uniquely_reachable_words +caml_succ_scannable_prefix_len + From +runtime_events.js: caml_runtime_events_create_cursor caml_runtime_events_free_cursor diff --git a/compiler/tests-check-prim/unix-Win32.5.2.output b/compiler/tests-check-prim/unix-Win32.5.2.output index 574569f3c1..38504f35f0 100644 --- a/compiler/tests-check-prim/unix-Win32.5.2.output +++ b/compiler/tests-check-prim/unix-Win32.5.2.output @@ -172,6 +172,11 @@ caml_new_string (deprecated) caml_string_concat caml_to_js_string (deprecated) +From +obj.js: +caml_obj_is_stack +caml_obj_uniquely_reachable_words +caml_succ_scannable_prefix_len + From +runtime_events.js: caml_runtime_events_create_cursor caml_runtime_events_free_cursor diff --git a/runtime/js/obj.js b/runtime/js/obj.js index c8c96f928c..563dc2f257 100644 --- a/runtime/js/obj.js +++ b/runtime/js/obj.js @@ -64,6 +64,28 @@ function caml_update_dummy_lazy(dummy, newval) { return 0; } +//Provides: caml_obj_is_stack +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_obj_is_stack(_x) { + return 0; +} + +//Provides: caml_succ_scannable_prefix_len +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_succ_scannable_prefix_len(_x) { + return 0; +} + +//Provides: caml_obj_uniquely_reachable_words +//Requires: caml_failwith +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_obj_uniquely_reachable_words(_x) { + caml_failwith("Obj.uniquely_reachable_words is not available in javascript."); +} + //Provides: caml_obj_tag //Requires: caml_is_ml_bytes, caml_is_ml_string function caml_obj_tag(x) { diff --git a/runtime/wasm/obj.wat b/runtime/wasm/obj.wat index 4fc39ee904..ff51b466f7 100644 --- a/runtime/wasm/obj.wat +++ b/runtime/wasm/obj.wat @@ -99,6 +99,22 @@ (global $double_array_tag (export "double_array_tag") i32 (i32.const 254)) (global $custom_tag i32 (i32.const 255)) + (func (export "caml_obj_is_stack") + (param (ref eq)) (result (ref eq)) + (ref.i31 (i32.const 0))) + + (func (export "caml_succ_scannable_prefix_len") + (param (ref eq)) (result (ref eq)) + (ref.i31 (i32.const 0))) + + (@string $unique_words_unsupported + "Obj.uniquely_reachable_words is not available in wasm.") + + (func (export "caml_obj_uniquely_reachable_words") + (param (ref eq)) (result (ref eq)) + (call $caml_failwith (global.get $unique_words_unsupported)) + (ref.i31 (i32.const 0))) + (func $caml_is_closure (export "caml_is_closure") (param $v (ref eq)) (result i32) (i32.or (ref.test (ref $closure) (local.get $v)) From 3dc37b97a88717196ca8e9a91ac4023888af8989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 10 Jul 2025 21:40:00 +0200 Subject: [PATCH 24/36] OxCaml runtime: stub for caml_ml_set_channel_refill --- runtime/wasm/io.wat | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/runtime/wasm/io.wat b/runtime/wasm/io.wat index b74db04e88..f3ec81f89f 100644 --- a/runtime/wasm/io.wat +++ b/runtime/wasm/io.wat @@ -76,6 +76,8 @@ (tag $javascript_exception (param externref))) (import "sys" "caml_handle_sys_error" (func $caml_handle_sys_error (param externref))) + (import "fail" "caml_invalid_argument" + (func $caml_invalid_argument (param (ref eq)))) (import "custom" "custom_compare_id" (func $custom_compare_id (param (ref eq)) (param (ref eq)) (param i32) (result i32))) @@ -1020,4 +1022,13 @@ (ref.i31 (call $caml_getblock_typed_array (local.get $ch) (local.get $d) (local.get $pos) (local.get $len)))) + + (@string $caml_ml_set_channel_refill + "caml_ml_set_channel_refill not implemented") + + (func (export "caml_ml_set_channel_refill") + (param (ref eq)) (param (ref eq)) (result (ref eq)) + (call $caml_invalid_argument + (global.get $caml_ml_set_channel_refill)) + (ref.i31 (i32.const 0))) ) From 75cf4b9eb05cf5f0191ae6deadd6eaaae1013538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 10 Jul 2025 21:40:00 +0200 Subject: [PATCH 25/36] OxCaml: null support --- compiler/lib-wasm/gc_target.ml | 7 ++++ compiler/lib-wasm/generate.ml | 12 ++++-- compiler/lib-wasm/typing.ml | 19 +++++---- compiler/lib-wasm/typing.mli | 1 + compiler/lib/code.ml | 4 ++ compiler/lib/code.mli | 1 + compiler/lib/eval.ml | 9 ++++- compiler/lib/generate.ml | 4 +- compiler/lib/ocaml_compiler.ml | 2 +- compiler/lib/parse_bytecode.ml | 11 ++++- compiler/tests-check-prim/main.5.2.output | 1 - .../tests-check-prim/unix-Unix.5.2.output | 1 - .../tests-check-prim/unix-Win32.5.2.output | 1 - lib/tests/test_poly_compare.ml | 10 ++--- runtime/js/compare.js | 10 +++++ runtime/js/marshal.js | 7 +++- runtime/js/obj.js | 13 +++++- runtime/wasm/compare.wat | 8 ++++ runtime/wasm/marshal.wat | 40 ++++++++++++------- runtime/wasm/obj.wat | 14 +++++++ 20 files changed, 136 insertions(+), 39 deletions(-) diff --git a/compiler/lib-wasm/gc_target.ml b/compiler/lib-wasm/gc_target.ml index eaaa6b6498..b0465beaa9 100644 --- a/compiler/lib-wasm/gc_target.ml +++ b/compiler/lib-wasm/gc_target.ml @@ -1060,6 +1060,13 @@ module Constant = struct | NativeInt i -> let* e = Memory.make_int32 ~kind:`Nativeint (return (W.Const (I32 i))) in return (Const, e) + | Null -> + let* var = + register_import + ~name:"null" + (Global { mut = false; typ = Type.value }) + in + return (Const, W.GlobalGet var) let translate ~unboxed c = match c with diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index 3ae60d0829..9ddb9cd35a 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -235,8 +235,8 @@ module Generate (Target : Target_sig.S) = struct (if negate then Value.phys_neq else Value.phys_eq) (transl_prim_arg ctx ~typ:Top x) (transl_prim_arg ctx ~typ:Top y) - | (Int _ | Number _ | Tuple _ | Bigarray _), _ - | _, (Int _ | Number _ | Tuple _ | Bigarray _) -> + | (Int _ | Number _ | Tuple _ | Bigarray _ | Null), _ + | _, (Int _ | Number _ | Tuple _ | Bigarray _ | Null) -> (* Only Top may contain JavaScript values *) (if negate then Value.phys_neq else Value.phys_eq) (transl_prim_arg ctx ~typ:Top x) @@ -1597,7 +1597,13 @@ module Generate (Target : Target_sig.S) = struct let* indices' = transl_prim_arg ctx indices in let* v' = transl_prim_arg ctx v in return (W.Call (f, [ ta'; indices'; v' ]))) - | _ -> invalid_arity "caml_ba_set_generic" l ~expected:3) + | _ -> invalid_arity "caml_ba_set_generic" l ~expected:3); + register_un_prim "caml_is_null" `Pure ~ret_typ:(Int Normalized) (fun x -> + let* x = x in + let* null = + register_import ~name:"null" (Global { mut = false; typ = Type.value }) + in + return (W.RefEq (x, GlobalGet null))) let unboxed_type ty : W.value_type option = match ty with diff --git a/compiler/lib-wasm/typing.ml b/compiler/lib-wasm/typing.ml index 90b8819c43..65159706da 100644 --- a/compiler/lib-wasm/typing.ml +++ b/compiler/lib-wasm/typing.ml @@ -129,6 +129,7 @@ type typ = overapproximation of the possible values of each of its fields is given by the array of types *) | Bigarray of Bigarray.t + | Null | Bot module Domain = struct @@ -156,11 +157,12 @@ module Domain = struct else Array.init (max l l') ~f:(fun i -> if i < l then if i < l' then join t.(i) t'.(i) else t.(i) else t'.(i))) - | Int _, Tuple _ -> t' - | Tuple _, Int _ -> t + | (Int _ | Null), Tuple _ -> t' + | Tuple _, (Int _ | Null) -> t | Bigarray b, Bigarray b' when Bigarray.equal b b' -> t + | Null, Null -> Null | Top, _ | _, Top -> Top - | (Int _ | Number _ | Tuple _ | Bigarray _), _ -> Top + | (Int _ | Number _ | Tuple _ | Bigarray _ | Null), _ -> Top let join_set ?(others = false) f s = if others then Top else Var.Set.fold (fun x a -> join (f x) a) s Bot @@ -173,7 +175,8 @@ module Domain = struct | Tuple t, Tuple t' -> Array.length t = Array.length t' && Array.for_all2 ~f:equal t t' | Bigarray b, Bigarray b' -> Bigarray.equal b b' - | (Top | Tuple _ | Int _ | Number _ | Bigarray _ | Bot), _ -> false + | Null, Null -> true + | (Top | Tuple _ | Int _ | Number _ | Bigarray _ | Null | Bot), _ -> false let bot = Bot @@ -181,12 +184,12 @@ module Domain = struct let rec depth t = match t with - | Top | Bot | Number _ | Int _ | Bigarray _ -> 0 + | Top | Bot | Number _ | Int _ | Bigarray _ | Null -> 0 | Tuple l -> 1 + Array.fold_left ~f:(fun acc t' -> max (depth t') acc) l ~init:0 let rec truncate depth t = match t with - | Top | Bot | Number _ | Int _ | Bigarray _ -> t + | Top | Bot | Number _ | Int _ | Bigarray _ | Null -> t | Tuple l -> if depth = 0 then Top @@ -225,6 +228,7 @@ module Domain = struct | Boxed -> "boxed" | Unboxed -> "unboxed") | Bigarray b -> Bigarray.print f b + | Null -> Format.fprintf f "null" | Tuple t -> Format.fprintf f @@ -290,6 +294,7 @@ let rec constant_type (c : constant) = | NativeInt _ -> Number (Nativeint, Unboxed) | Float _ -> Number (Float, Unboxed) | Tuple (_, a, _) -> Tuple (Array.map ~f:(fun c' -> Domain.box (constant_type c')) a) + | Null -> Null | _ -> Top let arg_type ~approx arg = @@ -540,7 +545,7 @@ let box_numbers p st types = Var.Set.iter box s) | Expr _ -> () | Phi { known; _ } -> Var.Set.iter box known) - | Number (_, Boxed) | Int _ | Tuple _ | Bigarray _ | Bot -> ()) + | Number (_, Boxed) | Int _ | Tuple _ | Bigarray _ | Null | Bot -> ()) in Code.fold_closures p diff --git a/compiler/lib-wasm/typing.mli b/compiler/lib-wasm/typing.mli index 40b02510e9..978d1386e9 100644 --- a/compiler/lib-wasm/typing.mli +++ b/compiler/lib-wasm/typing.mli @@ -47,6 +47,7 @@ type typ = | Number of boxed_number * boxed_status | Tuple of typ array | Bigarray of Bigarray.t + | Null | Bot val constant_type : Code.constant -> typ diff --git a/compiler/lib/code.ml b/compiler/lib/code.ml index 4399705188..4230974434 100644 --- a/compiler/lib/code.ml +++ b/compiler/lib/code.ml @@ -328,6 +328,7 @@ type constant = | Int64 of Int64.t | NativeInt of Int32.t (* Native int are 32bit on all known backend *) | Tuple of int * constant array * array_or_not + | Null module Constant = struct type t = constant @@ -360,6 +361,7 @@ module Constant = struct b) | Float a, Float b -> Some (Float.ieee_equal (Int64.float_of_bits a) (Int64.float_of_bits b)) + | Null, Null -> Some true | String _, NativeString _ | NativeString _, String _ -> None | Int _, Float _ | Float _, Int _ -> None | Tuple ((0 | 254), _, _), Float_array _ -> None @@ -402,6 +404,7 @@ module Constant = struct | ( (Int _ | Int32 _ | NativeInt _) , (String _ | NativeString _ | Float_array _ | Int64 _ | Tuple (_, _, _)) ) -> Some false + | Null, _ | _, Null -> Some false (* Note: the following cases should not occur when compiling to Javascript *) | Int _, (Int32 _ | NativeInt _) | Int32 _, (Int _ | NativeInt _) @@ -521,6 +524,7 @@ module Print = struct constant f a.(i) done; Format.fprintf f ")") + | Null -> Format.fprintf f "null" let arg f a = match a with diff --git a/compiler/lib/code.mli b/compiler/lib/code.mli index bc9dcab0e8..5e897ebb9e 100644 --- a/compiler/lib/code.mli +++ b/compiler/lib/code.mli @@ -150,6 +150,7 @@ type constant = | Int64 of Int64.t | NativeInt of Int32.t (** Only produced when compiling to WebAssembly. *) | Tuple of int * constant array * array_or_not + | Null module Constant : sig type t = constant diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index 65a3ab3d41..baa62bf074 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -466,6 +466,7 @@ let constant_js_equal a b = Some (Float.ieee_equal (Int64.float_of_bits a) (Int64.float_of_bits b)) | NativeString a, NativeString b -> Some (Native_string.equal a b) | String a, String b when Config.Flag.use_js_string () -> Some (String.equal a b) + | Null, Null -> Some true | Int _, Float _ | Float _, Int _ -> None (* All other values may be distinct objects and thus different by [caml_js_equals]. *) | String _, _ @@ -481,7 +482,9 @@ let constant_js_equal a b = | NativeInt _, _ | _, NativeInt _ | Tuple _, _ - | _, Tuple _ -> None + | _, Tuple _ + | Null, _ + | _, Null -> None (* [eval_prim] does not distinguish the two constants *) let constant_equal a b = @@ -493,10 +496,11 @@ let constant_equal a b = | Int32 a, Int32 b -> Int32.equal a b | NativeInt a, NativeInt b -> Int32.equal a b | Int64 a, Int64 b -> Int64.equal a b + | Null, Null -> true (* We don't need to compare other constants, so let's just return false. *) | Tuple _, Tuple _ -> false | Float_array _, Float_array _ -> false - | (Int _ | Float _ | Int64 _ | Int32 _ | NativeInt _), _ -> false + | (Int _ | Float _ | Int64 _ | Int32 _ | NativeInt _ | Null), _ -> false | (String _ | NativeString _), _ -> false | (Float_array _ | Tuple _), _ -> false @@ -701,6 +705,7 @@ let the_cond_of info x = (fun x -> match Flow.Info.def info x with | Some (Constant (Int x)) -> if Targetint.is_zero x then Zero else Non_zero + | Some (Constant Null) -> Zero | Some (Constant ( Int32 _ diff --git a/compiler/lib/generate.ml b/compiler/lib/generate.ml index bfbd93c5b9..ce21b95584 100644 --- a/compiler/lib/generate.ml +++ b/compiler/lib/generate.ml @@ -536,6 +536,7 @@ let rec constant_rec ~ctx x level instrs = Mlvalue.Block.make ~tag ~args:l, instrs) | Int i -> targetint i, instrs | Int32 i | NativeInt i -> targetint (Targetint.of_int32_exn i), instrs + | Null -> s_var "null", instrs let constant ~ctx x level = let expr, instr = constant_rec ~ctx x level [] in @@ -1390,7 +1391,8 @@ let _ = bool (J.EBin (J.EqEqEq, cx, cy))); register_bin_prim "caml_js_instanceof" `Mutator (fun cx cy _ -> bool (J.EBin (J.InstanceOf, cx, cy))); - register_un_prim "caml_js_typeof" `Mutator (fun cx _ -> J.EUn (J.Typeof, cx)) + register_un_prim "caml_js_typeof" `Mutator (fun cx _ -> J.EUn (J.Typeof, cx)); + register_un_prim "caml_is_null" `Pure (fun cx _ -> J.EBin (EqEqEq, cx, s_var "null")) (****) (* when raising ocaml exception and [improved_stacktrace] is enabled, diff --git a/compiler/lib/ocaml_compiler.ml b/compiler/lib/ocaml_compiler.ml index b9831aa341..aa475e6cdb 100644 --- a/compiler/lib/ocaml_compiler.ml +++ b/compiler/lib/ocaml_compiler.ml @@ -50,7 +50,7 @@ let rec constant_of_const c : Code.constant = Tuple (tag, l, Unknown) | ((Const_base (Const_float32 _ | Const_unboxed_float32 _)) [@if oxcaml]) -> failwith "Float32 unsupported" - | (Const_null [@if oxcaml]) -> failwith "Null unsupported" + | (Const_null [@if oxcaml]) -> Null type module_or_not = | Module diff --git a/compiler/lib/parse_bytecode.ml b/compiler/lib/parse_bytecode.ml index 787e2ce43d..dbee96ebba 100644 --- a/compiler/lib/parse_bytecode.ml +++ b/compiler/lib/parse_bytecode.ml @@ -479,8 +479,16 @@ end = struct let ident_native = ident_of_custom (Obj.repr 0n) + external is_null : Obj.t -> bool = "%is_null" [@@if oxcaml] + + let is_null obj = is_null (Sys.opaque_identity obj) [@@if oxcaml] + + let is_null _ = false [@@if not oxcaml] + let rec parse x = - if Obj.is_block x + if is_null x + then Null + else if Obj.is_block x then let tag = Obj.tag x in if tag = Obj.string_tag @@ -528,6 +536,7 @@ end = struct match target with | `JavaScript -> true | `Wasm -> false) + | Null -> true end let const32 i = Constant (Int (Targetint.of_int32_exn i)) diff --git a/compiler/tests-check-prim/main.5.2.output b/compiler/tests-check-prim/main.5.2.output index 7e4201ae6a..5442aa757f 100644 --- a/compiler/tests-check-prim/main.5.2.output +++ b/compiler/tests-check-prim/main.5.2.output @@ -6,7 +6,6 @@ caml_alloc_dummy_function caml_assume_no_perform caml_continuation_use caml_drop_continuation -caml_int_as_pointer caml_reset_afl_instrumentation debugger diff --git a/compiler/tests-check-prim/unix-Unix.5.2.output b/compiler/tests-check-prim/unix-Unix.5.2.output index 124585a981..a5cb6034c3 100644 --- a/compiler/tests-check-prim/unix-Unix.5.2.output +++ b/compiler/tests-check-prim/unix-Unix.5.2.output @@ -6,7 +6,6 @@ caml_alloc_dummy_function caml_assume_no_perform caml_continuation_use caml_drop_continuation -caml_int_as_pointer caml_reset_afl_instrumentation caml_unix_accept caml_unix_alarm diff --git a/compiler/tests-check-prim/unix-Win32.5.2.output b/compiler/tests-check-prim/unix-Win32.5.2.output index 38504f35f0..8dc26cf84d 100644 --- a/compiler/tests-check-prim/unix-Win32.5.2.output +++ b/compiler/tests-check-prim/unix-Win32.5.2.output @@ -6,7 +6,6 @@ caml_alloc_dummy_function caml_assume_no_perform caml_continuation_use caml_drop_continuation -caml_int_as_pointer caml_reset_afl_instrumentation caml_unix_accept caml_unix_bind diff --git a/lib/tests/test_poly_compare.ml b/lib/tests/test_poly_compare.ml index 734c97c90e..5f9c89cf04 100644 --- a/lib/tests/test_poly_compare.ml +++ b/lib/tests/test_poly_compare.ml @@ -105,7 +105,7 @@ let%expect_test "null/undefined comparison" = assert (s1 = s1); assert (compare s1 s1 = 0); assert (compare s1 s2 = 1); - assert (compare s2 s1 = 1) + assert (compare s2 s1 = -1) let%expect_test "poly compare" = let l = @@ -124,11 +124,11 @@ let%expect_test "poly compare" = List.iter (fun (i, _) -> Printf.printf "%d\n" i) l'; print_endline ""; [%expect {| + 7 1 3 2 0 - 7 6 5 4 @@ -138,6 +138,7 @@ let%expect_test "poly compare" = List.iter (fun (i, _) -> Printf.printf "%d\n" i) l'; print_endline ""; [%expect {| + 7 3 1 2 @@ -145,16 +146,15 @@ let%expect_test "poly compare" = 4 5 6 - 7 |}]; List.iter (fun (i, _) -> Printf.printf "%d\n" i) l''; print_endline ""; [%expect {| + 7 1 3 2 0 4 5 - 6 - 7 |}] + 6 |}] diff --git a/runtime/js/compare.js b/runtime/js/compare.js index aba227499e..dfdd95b442 100644 --- a/runtime/js/compare.js +++ b/runtime/js/compare.js @@ -18,6 +18,7 @@ //Provides: caml_compare_val_tag //Requires: caml_is_ml_string, caml_is_ml_bytes function caml_compare_val_tag(a) { + if (a === null) return 1010; // null_tag if (typeof a === "number") return 1000; // int_tag (we use it for all numbers) else if (caml_is_ml_bytes(a)) @@ -93,6 +94,13 @@ function caml_compare_val(a, b, total) { // tags are different if (tag_a !== tag_b) { + if (tag_a === 1010) { + // Null is less than anything else + return -1; + } + if (tag_b === 1010) { + return 1; + } if (tag_a === 1000) { if (tag_b === 1255) { //immediate can compare against custom @@ -193,6 +201,8 @@ function caml_compare_val(a, b, total) { if (!Number.isNaN(b)) return -1; } break; + case 1010: // Null pointer + return 0; case 1001: // The rest // Here we can be in the following cases: // 1. JavaScript primitive types diff --git a/runtime/js/marshal.js b/runtime/js/marshal.js index c1c68af669..5a3847cbf5 100644 --- a/runtime/js/marshal.js +++ b/runtime/js/marshal.js @@ -44,6 +44,7 @@ var caml_marshal_constants = { CODE_CUSTOM: 0x12, CODE_CUSTOM_LEN: 0x18, CODE_CUSTOM_FIXED: 0x19, + CODE_NULL: 0x1f, }; //Provides: UInt8ArrayReader @@ -495,6 +496,8 @@ function caml_input_value_from_reader(reader) { } if (intern_obj_table) intern_obj_table[obj_counter++] = v; return v; + case 0x1f: //cst.CODE_NULL: + return null; default: caml_failwith("input_value: ill-formed message"); } @@ -687,7 +690,9 @@ var caml_output_val = (function () { } function extern_rec(v) { - if (v.caml_custom) { + if (v === null) { + writer.write(8, 0x1f /*cst.CODE_NULL*/); + } else if (v.caml_custom) { if (memo(v)) return; var name = v.caml_custom; var ops = caml_custom_ops[name]; diff --git a/runtime/js/obj.js b/runtime/js/obj.js index 563dc2f257..51955f4ae7 100644 --- a/runtime/js/obj.js +++ b/runtime/js/obj.js @@ -89,7 +89,8 @@ function caml_obj_uniquely_reachable_words(_x) { //Provides: caml_obj_tag //Requires: caml_is_ml_bytes, caml_is_ml_string function caml_obj_tag(x) { - if (Array.isArray(x) && x[0] === x[0] >>> 0) return x[0]; + if (x === null) return 1010; + else if (Array.isArray(x) && x[0] === x[0] >>> 0) return x[0]; else if (caml_is_ml_bytes(x)) return 252; else if (caml_is_ml_string(x)) return 252; else if (x instanceof Function || typeof x === "function") return 247; @@ -328,3 +329,13 @@ function caml_ml_gc_ramp_up(f) { function caml_ml_gc_ramp_down(_suspended_collection_work) { return 0; } + +//Provides: caml_int_as_pointer +//Requires: caml_failwith +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_int_as_pointer(i) { + // Special-case null pointers for [or_null]. + if (i === 0) return null; + caml_failwith("%int_as_pointer is not supported in javascript."); +} diff --git a/runtime/wasm/compare.wat b/runtime/wasm/compare.wat index a8b92f7e5c..ff4784b9d3 100644 --- a/runtime/wasm/compare.wat +++ b/runtime/wasm/compare.wat @@ -25,6 +25,7 @@ (func $caml_obj_tag (param (ref eq)) (result (ref eq)))) (import "obj" "caml_is_closure" (func $caml_is_closure (param (ref eq)) (result i32))) + (import "obj" "null" (global $null (ref eq))) (import "fail" "caml_invalid_argument" (func $caml_invalid_argument (param (ref eq)))) (import "effect" "caml_is_continuation" @@ -238,6 +239,13 @@ (if (local.get $total) (then (br_if $next_item (ref.eq (local.get $v1) (local.get $v2))))) + (if (ref.eq (local.get $v1) (global.get $null)) + (then + (if (ref.eq (local.get $v2) (global.get $null)) + (then (return (i32.const 0))) + (else (return (i32.const -1)))))) + (if (ref.eq (local.get $v2) (global.get $null)) + (then (return (i32.const 1)))) (drop (block $v1_is_not_int (result (ref eq)) (local.set $i1 (br_on_cast_fail $v1_is_not_int (ref eq) (ref i31) diff --git a/runtime/wasm/marshal.wat b/runtime/wasm/marshal.wat index b25fff016e..bb1784e51b 100644 --- a/runtime/wasm/marshal.wat +++ b/runtime/wasm/marshal.wat @@ -21,6 +21,7 @@ (func $caml_invalid_argument (param (ref eq)))) (import "fail" "caml_raise_end_of_file" (func $caml_raise_end_of_file)) (import "obj" "object_tag" (global $object_tag i32)) + (import "obj" "null" (global $null (ref eq))) (import "obj" "caml_set_oo_id" (func $caml_set_oo_id (param (ref eq)) (result (ref eq)))) (import "string" "caml_string_concat" @@ -190,6 +191,7 @@ (global $CODE_CUSTOM i32 (i32.const 0x12)) (global $CODE_CUSTOM_LEN i32 (i32.const 0x18)) (global $CODE_CUSTOM_FIXED i32 (i32.const 0x19)) + (global $CODE_NULL i32 (i32.const 0x1F)) (type $intern_state (struct @@ -561,20 +563,26 @@ (block $DOUBLE_ARRAY32 (block $CODEPOINTER (block $CUSTOM - (block $default - (br_table $INT8 $INT16 $INT32 $INT64 - $SHARED8 $SHARED16 $SHARED32 - $DOUBLE_ARRAY32 $BLOCK32 $STRING8 - $STRING32 $DOUBLE $DOUBLE - $DOUBLE_ARRAY8 $DOUBLE_ARRAY8 - $DOUBLE_ARRAY32 $CODEPOINTER - $CODEPOINTER $CUSTOM $default - $default $default $default $default - $CUSTOM $CUSTOM $default - (local.get $code))) - ;; default - (call $caml_failwith - (global.get $ill_formed)) + (block $NULL + (block $default + (br_table $INT8 $INT16 $INT32 $INT64 + $SHARED8 $SHARED16 $SHARED32 + $DOUBLE_ARRAY32 $BLOCK32 $STRING8 + $STRING32 $DOUBLE $DOUBLE + $DOUBLE_ARRAY8 $DOUBLE_ARRAY8 + $DOUBLE_ARRAY32 $CODEPOINTER + $CODEPOINTER $CUSTOM $default + $default $default $default $default + $CUSTOM $CUSTOM $default $default + $default $default $default $NULL + $default + (local.get $code))) + ;; default + (call $caml_failwith + (global.get $ill_formed)) + (br $done)) + ;; NULL + (local.set $v (global.get $null)) (br $done)) ;; CUSTOM (local.set $v @@ -1272,6 +1280,10 @@ (local.get $sp))))) (local.set $v (array.get $block (local.get $b) (i32.const 1))) (br $loop))) + (if (ref.eq (local.get $v) (global.get $null)) + (then + (call $write (local.get $s) (global.get $CODE_NULL)) + (br $next_item))) (local.set $pos (call $extern_lookup_position (local.get $s) (local.get $v))) (if (i32.ge_s (local.get $pos) (i32.const 0)) diff --git a/runtime/wasm/obj.wat b/runtime/wasm/obj.wat index ff51b466f7..0280799a88 100644 --- a/runtime/wasm/obj.wat +++ b/runtime/wasm/obj.wat @@ -277,6 +277,8 @@ (local.get $res)) (func (export "caml_obj_tag") (param $v (ref eq)) (result (ref eq)) + (if (ref.eq (local.get $v) (global.get $null)) + (then (return (ref.i31 (i32.const 1010))))) (if (ref.test (ref i31) (local.get $v)) (then (return (ref.i31 (i32.const 1000))))) (drop (block $not_block (result (ref eq)) @@ -577,4 +579,16 @@ (call $caml_callback_1 (local.get $f) (local.get $x)) (local.get $y))) )) + + (type $null (struct)) + (global $null (export "null") (ref eq) (struct.new $null)) + + (@string $int_as_pointer_not_implemented + "caml_int_as_pointer is not supported") + + (func (export "caml_int_as_pointer") (param $x (ref eq)) (result (ref eq)) + (if (i32.eqz (ref.eq (local.get $x) (ref.i31 (i32.const 0)))) + (then + (call $caml_failwith (global.get $int_as_pointer_not_implemented)))) + (global.get $null)) ) From 3a063ec1334593c4a02478c473f79783289b770b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 26 Sep 2025 12:38:26 +0200 Subject: [PATCH 26/36] Allow the ppxlib version used by oxcaml --- dune-project | 12 ++++++------ js_of_ocaml-compiler.opam | 2 +- js_of_ocaml-ppx.opam | 2 +- js_of_ocaml-ppx_deriving_json.opam | 2 +- js_of_ocaml-toplevel.opam | 2 +- js_of_ocaml.opam | 2 +- wasm_of_ocaml-compiler.opam | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dune-project b/dune-project index 6ba3e9d4ee..565a62d5e5 100644 --- a/dune-project +++ b/dune-project @@ -21,7 +21,7 @@ (ocaml (and (>= 4.13) (< 5.5))) (num :with-test) (ppx_expect (and (>= v0.16.1) :with-test)) - (ppxlib (>= 0.35)) + (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (re :with-test) (cmdliner (>= 1.1.0)) (sedlex (>= 3.3)) @@ -63,7 +63,7 @@ (depends (ocaml (>= 4.13)) (js_of_ocaml (= :version)) - (ppxlib (>= 0.35)) + (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) (re (and (>= 1.9.0) :with-test)) @@ -77,7 +77,7 @@ (depends (ocaml (>= 4.13)) (js_of_ocaml (= :version)) - (ppxlib (>= 0.35)) + (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) (re (and (>= 1.9.0) :with-test)) @@ -96,7 +96,7 @@ (graphics :with-test) (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) - (ppxlib (>= 0.35)) + (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (re (and (>= 1.9.0) :with-test)) )) @@ -127,7 +127,7 @@ (js_of_ocaml-compiler (= :version)) (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) - (ppxlib (>= 0.35)) + (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (re (and (>= 1.9.0) :with-test)) )) @@ -141,7 +141,7 @@ (js_of_ocaml (= :version)) (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) - (ppxlib (>= 0.35)) + (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (re :with-test) (cmdliner (>= 1.1.0)) (opam-format :with-test) diff --git a/js_of_ocaml-compiler.opam b/js_of_ocaml-compiler.opam index a1ef027852..b2d59bc463 100644 --- a/js_of_ocaml-compiler.opam +++ b/js_of_ocaml-compiler.opam @@ -16,7 +16,7 @@ depends: [ "ocaml" {>= "4.13" & < "5.5"} "num" {with-test} "ppx_expect" {>= "v0.16.1" & with-test} - "ppxlib" {>= "0.35"} + "ppxlib" {>= "0.35" | = "0.33.0+ox"} "re" {with-test} "cmdliner" {>= "1.1.0"} "sedlex" {>= "3.3"} diff --git a/js_of_ocaml-ppx.opam b/js_of_ocaml-ppx.opam index 73a06dc6ae..c445a24bbc 100644 --- a/js_of_ocaml-ppx.opam +++ b/js_of_ocaml-ppx.opam @@ -15,7 +15,7 @@ depends: [ "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml" {= version} - "ppxlib" {>= "0.35"} + "ppxlib" {>= "0.35" | = "0.33.0+ox"} "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} "re" {>= "1.9.0" & with-test} diff --git a/js_of_ocaml-ppx_deriving_json.opam b/js_of_ocaml-ppx_deriving_json.opam index 73a06dc6ae..c445a24bbc 100644 --- a/js_of_ocaml-ppx_deriving_json.opam +++ b/js_of_ocaml-ppx_deriving_json.opam @@ -15,7 +15,7 @@ depends: [ "dune" {>= "3.20"} "ocaml" {>= "4.13"} "js_of_ocaml" {= version} - "ppxlib" {>= "0.35"} + "ppxlib" {>= "0.35" | = "0.33.0+ox"} "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} "re" {>= "1.9.0" & with-test} diff --git a/js_of_ocaml-toplevel.opam b/js_of_ocaml-toplevel.opam index f28c0052e2..f030a01aba 100644 --- a/js_of_ocaml-toplevel.opam +++ b/js_of_ocaml-toplevel.opam @@ -19,7 +19,7 @@ depends: [ "graphics" {with-test} "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} - "ppxlib" {>= "0.35"} + "ppxlib" {>= "0.35" | = "0.33.0+ox"} "re" {>= "1.9.0" & with-test} "odoc" {with-doc} ] diff --git a/js_of_ocaml.opam b/js_of_ocaml.opam index 5e98d76fed..8b82fb9fdc 100644 --- a/js_of_ocaml.opam +++ b/js_of_ocaml.opam @@ -17,7 +17,7 @@ depends: [ "js_of_ocaml-compiler" {= version} "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} - "ppxlib" {>= "0.35"} + "ppxlib" {>= "0.35" | = "0.33.0+ox"} "re" {>= "1.9.0" & with-test} "odoc" {with-doc} ] diff --git a/wasm_of_ocaml-compiler.opam b/wasm_of_ocaml-compiler.opam index dbf96478a0..5628522b2f 100644 --- a/wasm_of_ocaml-compiler.opam +++ b/wasm_of_ocaml-compiler.opam @@ -17,7 +17,7 @@ depends: [ "js_of_ocaml" {= version} "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} - "ppxlib" {>= "0.35"} + "ppxlib" {>= "0.35" | = "0.33.0+ox"} "re" {with-test} "cmdliner" {>= "1.1.0"} "opam-format" {with-test} From e60e972a773fe86101700a2ded863a8ed741f6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 26 Sep 2025 19:02:38 +0200 Subject: [PATCH 27/36] OxCaml: Float32 support --- compiler/lib-wasm/gc_target.ml | 53 +- compiler/lib-wasm/generate.ml | 515 +++++++++++++++++- compiler/lib-wasm/target_sig.ml | 4 + compiler/lib-wasm/typing.ml | 11 +- compiler/lib-wasm/typing.mli | 2 + compiler/lib-wasm/wasm_ast.ml | 2 +- compiler/lib-wasm/wasm_output.ml | 9 +- compiler/lib-wasm/wat_output.ml | 3 +- compiler/lib/code.ml | 40 +- compiler/lib/code.mli | 1 + compiler/lib/eval.ml | 9 +- compiler/lib/generate.ml | 9 + compiler/lib/ocaml_compiler.ml | 4 +- compiler/lib/parse_bytecode.ml | 5 + compiler/lib/stdlib.ml | 24 + compiler/tests-check-prim/main.5.2.output | 59 ++ .../tests-check-prim/unix-Unix.5.2.output | 59 ++ .../tests-check-prim/unix-Win32.5.2.output | 59 ++ compiler/tests-jsoo/dune | 22 + compiler/tests-jsoo/test_marshal_float32.ml | 32 ++ .../tests-jsoo/test_marshal_float32_js.ml | 32 ++ runtime/js/float32.js | 444 +++++++++++++++ runtime/wasm/bigarray.wat | 207 +++++++ runtime/wasm/custom.wat | 2 + runtime/wasm/float32.wat | 280 ++++++++++ 25 files changed, 1854 insertions(+), 33 deletions(-) create mode 100644 compiler/tests-jsoo/test_marshal_float32.ml create mode 100644 compiler/tests-jsoo/test_marshal_float32_js.ml diff --git a/compiler/lib-wasm/gc_target.ml b/compiler/lib-wasm/gc_target.ml index b0465beaa9..c6d7af085e 100644 --- a/compiler/lib-wasm/gc_target.ml +++ b/compiler/lib-wasm/gc_target.ml @@ -168,6 +168,22 @@ module Type = struct ] }) + let float32_type = + register_type "float32" (fun () -> + let* custom_operations = custom_operations_type in + let* custom = custom_type in + return + { supertype = Some custom + ; final = true + ; typ = + W.Struct + [ { mut = false + ; typ = Value (Ref { nullable = false; typ = Type custom_operations }) + } + ; { mut = false; typ = Value F32 } + ] + }) + let int32_type = register_type "int32" (fun () -> let* custom_operations = custom_operations_type in @@ -884,6 +900,24 @@ module Memory = struct in if_mismatch + let make_float32 e = + let* custom_operations = Type.custom_operations_type in + let* float32_ops = + register_import + ~name:"float32_ops" + (Global + { mut = false; typ = Ref { nullable = false; typ = Type custom_operations } }) + in + let* ty = Type.float32_type in + let* e = e in + return (W.StructNew (ty, [ GlobalGet float32_ops; e ])) + + let box_float32 = make_float32 + + let unbox_float32 e = + let* ty = Type.float32_type in + wasm_struct_get ty (wasm_cast ty e) 1 + let make_int32 ~kind e = let* custom_operations = Type.custom_operations_type in let* int32_ops = @@ -1043,6 +1077,9 @@ module Constant = struct | Float f -> let* ty = Type.float_type in return (Const, W.StructNew (ty, [ Const (F64 (Int64.float_of_bits f)) ])) + | Float32 f -> + let* e = Memory.make_float32 (return (W.Const (F32 (Int64.float_of_bits f)))) in + return (Const, e) | Float_array l -> let l = Array.to_list l in let* ty = Type.float_array_type in @@ -1061,17 +1098,17 @@ module Constant = struct let* e = Memory.make_int32 ~kind:`Nativeint (return (W.Const (I32 i))) in return (Const, e) | Null -> - let* var = - register_import - ~name:"null" - (Global { mut = false; typ = Type.value }) - in - return (Const, W.GlobalGet var) + let* var = + register_import ~name:"null" (Global { mut = false; typ = Type.value }) + in + return (Const, W.GlobalGet var) let translate ~unboxed c = match c with | Code.Int i -> return (W.Const (I32 (Targetint.to_int32 i))) | Float f when unboxed -> return (W.Const (F64 (Int64.float_of_bits f))) + | ((Float32 f) [@if oxcaml]) when unboxed -> + return (W.Const (F32 (Int64.float_of_bits f))) | Int64 i when unboxed -> return (W.Const (I64 i)) | (Int32 i | NativeInt i) when unboxed -> return (W.Const (I32 i)) | _ -> ( @@ -1425,6 +1462,7 @@ module Bigarray = struct , fun x -> let* x = x in return (W.F64PromoteF32 x) ) + | Float32_t -> "dv_get_f32", F32, 2, Fun.id | Float64 -> "dv_get_f64", F64, 3, Fun.id | Int8_signed -> "dv_get_i8", I32, 0, Fun.id | Int8_unsigned -> "dv_get_ui8", I32, 0, Fun.id @@ -1478,6 +1516,7 @@ module Bigarray = struct let* ofs = Arith.(i lsl const (Int32.of_int size)) in match kind with | Float32 + | Float32_t | Float64 | Int8_signed | Int8_unsigned @@ -1510,6 +1549,7 @@ module Bigarray = struct , fun x -> let* x = x in return (W.F32DemoteF64 x) ) + | Float32_t -> "dv_set_f32", F32, 2, Fun.id | Float64 -> "dv_set_f64", F64, 3, Fun.id | Int8_signed | Int8_unsigned -> "dv_set_i8", I32, 0, Fun.id | Int16_signed | Int16_unsigned -> "dv_set_i16", I32, 1, Fun.id @@ -1562,6 +1602,7 @@ module Bigarray = struct in match kind with | Float32 + | Float32_t | Float64 | Int8_signed | Int8_unsigned diff --git a/compiler/lib-wasm/generate.ml b/compiler/lib-wasm/generate.ml index 9ddb9cd35a..832f096c44 100644 --- a/compiler/lib-wasm/generate.ml +++ b/compiler/lib-wasm/generate.ml @@ -72,6 +72,7 @@ module Generate (Target : Target_sig.S) = struct type repr = | Value | Float + | Float32 | Int of normalized | Int32 | Nativeint @@ -81,6 +82,7 @@ module Generate (Target : Target_sig.S) = struct match r with | Value -> None | Float -> Some (Number (Float, Unboxed)) + | Float32 -> Some (Number (Float32, Unboxed)) | Int Normalized -> Some (Int Normalized) | Int Unnormalized -> Some (Int Unnormalized) | Int32 -> Some (Number (Int32, Unboxed)) @@ -91,6 +93,7 @@ module Generate (Target : Target_sig.S) = struct match r with | Value -> Type.value | Float -> F64 + | Float32 -> F32 | Int _ | Int32 | Nativeint -> I32 | Int64 -> I64 @@ -110,20 +113,25 @@ module Generate (Target : Target_sig.S) = struct ; "caml_string_get16", (`Mutator, [ Value; Int Normalized ], Int Normalized) ; "caml_string_get32", (`Mutator, [ Value; Int Normalized ], Int32) ; "caml_string_get64", (`Mutator, [ Value; Int Normalized ], Int64) + ; "caml_string_getf32", (`Mutator, [ Value; Int Normalized ], Float32) ; "caml_bytes_get16", (`Mutator, [ Value; Int Normalized ], Int Normalized) ; "caml_bytes_get32", (`Mutator, [ Value; Int Normalized ], Int32) ; "caml_bytes_get64", (`Mutator, [ Value; Int Normalized ], Int64) + ; "caml_bytes_getf32", (`Mutator, [ Value; Int Normalized ], Float32) ; "caml_bytes_set16", (`Mutator, [ Value; Int Normalized; Int Unnormalized ], Value) ; "caml_bytes_set32", (`Mutator, [ Value; Int Normalized; Int32 ], Value) ; "caml_bytes_set64", (`Mutator, [ Value; Int Normalized; Int64 ], Value) + ; "caml_bytes_setf32", (`Mutator, [ Value; Int Normalized; Float32 ], Value) ; "caml_lxm_next", (`Mutable, [ Value ], Int64) ; "caml_ba_uint8_get16", (`Mutator, [ Value; Int Normalized ], Int Normalized) ; "caml_ba_uint8_get32", (`Mutator, [ Value; Int Normalized ], Int32) ; "caml_ba_uint8_get64", (`Mutator, [ Value; Int Normalized ], Int64) + ; "caml_ba_uint8_getf32", (`Mutator, [ Value; Int Normalized ], Float32) ; ( "caml_ba_uint8_set16" , (`Mutator, [ Value; Int Normalized; Int Unnormalized ], Value) ) ; "caml_ba_uint8_set32", (`Mutator, [ Value; Int Normalized; Int32 ], Value) ; "caml_ba_uint8_set64", (`Mutator, [ Value; Int Normalized; Int64 ], Value) + ; "caml_ba_uint8_setf32", (`Mutator, [ Value; Int Normalized; Float32 ], Value) ; "caml_round_float", (`Pure, [ Float ], Float) ; "caml_nextafter_float", (`Pure, [ Float; Float ], Float) ; "caml_classify_float", (`Pure, [ Float ], Int Normalized) @@ -131,6 +139,13 @@ module Generate (Target : Target_sig.S) = struct ; "caml_erf_float", (`Pure, [ Float ], Float) ; "caml_erfc_float", (`Pure, [ Float ], Float) ; "caml_float_compare", (`Pure, [ Float; Float ], Int Normalized) + ; "caml_round_float32_bytecode", (`Pure, [ Float32 ], Float32) + ; "caml_nextafter_float32_bytecode", (`Pure, [ Float32; Float32 ], Float32) + ; "caml_classify_float32_bytecode", (`Pure, [ Float32 ], Int Normalized) + ; "caml_ldexp_float32_bytecode", (`Pure, [ Float32; Int Normalized ], Float32) + ; "caml_erf_float32_bytecode", (`Pure, [ Float32 ], Float32) + ; "caml_erfc_float32_bytecode", (`Pure, [ Float32 ], Float32) + ; "caml_float32_compare", (`Pure, [ Float32; Float32 ], Int Normalized) ]; h @@ -143,6 +158,15 @@ module Generate (Target : Target_sig.S) = struct let* f = f in return (W.UnOp (F64 op, f)) + let float32_bin_op op f g = + let* f = f in + let* g = g in + return (W.BinOp (F32 op, f, g)) + + let float32_un_op op f = + let* f = f in + return (W.UnOp (F32 op, f)) + let int32_bin_op op f g = let* f = f in let* g = g in @@ -179,6 +203,8 @@ module Generate (Target : Target_sig.S) = struct return (W.Const (I64 0L)) | Int (Unnormalized | Normalized), Number (Float, Unboxed) -> return (W.Const (F64 0.)) + | Int (Unnormalized | Normalized), Number (Float32, Unboxed) -> + return (W.Const (F32 0.)) | _, Int (Normalized | Unnormalized) -> Value.int_val e | Int (Unnormalized | Normalized), _ -> Value.val_int e | Number (_, Unboxed), Number (_, Unboxed) -> e @@ -186,10 +212,12 @@ module Generate (Target : Target_sig.S) = struct | _, Number (Int64, Unboxed) -> Memory.unbox_int64 e | _, Number (Nativeint, Unboxed) -> Memory.unbox_nativeint e | _, Number (Float, Unboxed) -> Memory.unbox_float e + | _, Number (Float32, Unboxed) -> Memory.unbox_float32 e | Number (Int32, Unboxed), _ -> Memory.box_int32 e | Number (Int64, Unboxed), _ -> Memory.box_int64 e | Number (Nativeint, Unboxed), _ -> Memory.box_nativeint e | Number (Float, Unboxed), _ -> Memory.box_float e + | Number (Float32, Unboxed), _ -> Memory.box_float32 e | _ -> e let load_and_box ctx x = convert ~from:(Typing.var_type ctx.types x) ~into:Top (load x) @@ -335,6 +363,10 @@ module Generate (Target : Target_sig.S) = struct let x = transl_prim_arg ctx ~typ:(Number (Float, Unboxed)) x in let y = transl_prim_arg ctx ~typ:(Number (Float, Unboxed)) y in float_bin_op cmp_float x y + | Number (Float32, _), Number (Float32, _) -> + let x = transl_prim_arg ctx ~typ:(Number (Float32, Unboxed)) x in + let y = transl_prim_arg ctx ~typ:(Number (Float32, Unboxed)) y in + float32_bin_op cmp_float x y | _ -> let* f = register_import @@ -346,6 +378,19 @@ module Generate (Target : Target_sig.S) = struct return (W.Call (f, [ x; y ]))) | _ -> invalid_arity name l ~expected:2) + let float_of_float32 f = + let* f = f in + return (W.F64PromoteF32 f) + + let float32_of_float f = + let* f = f in + return (W.F32DemoteF64 f) + + let lift_float_un_op op f = float32_of_float (op (float_of_float32 f)) + + let lift_float_bin_op op f g = + float32_of_float (op (float_of_float32 f) (float_of_float32 g)) + let () = register_bin_prim "caml_floatarray_unsafe_get" @@ -687,7 +732,7 @@ module Generate (Target : Target_sig.S) = struct ~ret_typ:(Int Unnormalized) (fun f -> let* f = f in - return (W.UnOp (I32 (TruncSatF64 S), f))); + return (W.UnOp (I32 (TruncSat (`F64, S)), f))); register_un_prim "caml_float_of_int" `Pure @@ -867,15 +912,382 @@ module Generate (Target : Target_sig.S) = struct ~ret_typ:(Number (Int32, Unboxed)) (fun f -> let* f = f in - return (W.UnOp (I32 (TruncSatF64 S), f))); + return (W.UnOp (I32 (TruncSat (`F64, S)), f))); register_un_prim "caml_int32_to_float" `Pure ~typ:(Number (Int32, Unboxed)) ~ret_typ:(Number (Float, Unboxed)) - (fun n -> - let* n = n in - return (W.UnOp (F64 (Convert (`I32, S)), n))); + (fun n -> float_un_op (Convert (`I32, S)) n); + register_bin_prim + "caml_add_float32" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f g -> float32_bin_op Add f g); + register_bin_prim + "caml_sub_float32" + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + `Pure + (fun f g -> float32_bin_op Sub f g); + register_bin_prim + "caml_mul_float32" + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + `Pure + (fun f g -> float32_bin_op Mul f g); + register_bin_prim + "caml_div_float32" + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + `Pure + (fun f g -> float32_bin_op Div f g); + register_bin_prim + "caml_copysign_float32_bytecode" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f g -> float32_bin_op CopySign f g); + register_un_prim + "caml_signbit_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Int Normalized) + (fun f -> + let* f = f in + Arith.(return (W.UnOp (I32 ReinterpretF, f)) lsr const 31l)); + register_un_prim + "caml_neg_float32" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Neg f); + register_un_prim + "caml_abs_float32" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Abs f); + register_un_prim + "caml_ceil_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Ceil f); + register_un_prim + "caml_floor_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Floor f); + register_un_prim + "caml_trunc_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Trunc f); + register_un_prim + "caml_sqrt_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Sqrt f); + register_bin_prim + "caml_eq_float32" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Int Normalized) + (fun f g -> float32_bin_op Eq f g); + register_bin_prim + "caml_neq_float32" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Int Normalized) + (fun f g -> float32_bin_op Ne f g); + register_bin_prim + "caml_ge_float32" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Int Normalized) + (fun f g -> float32_bin_op Ge f g); + register_bin_prim + "caml_le_float32" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Int Normalized) + (fun f g -> float32_bin_op Le f g); + register_bin_prim + "caml_gt_float32" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Int Normalized) + (fun f g -> float32_bin_op Gt f g); + register_bin_prim + "caml_lt_float32" + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Int Normalized) + `Pure + (fun f g -> float32_bin_op Lt f g); + register_un_prim + "caml_int_of_float32" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Int Unnormalized) + (fun f -> + let* f = f in + return (W.UnOp (I32 (TruncSat (`F32, S)), f))); + register_un_prim + "caml_float32_of_int" + `Pure + ~typ:(Int Normalized) + ~ret_typ:(Number (Float32, Unboxed)) + (fun n -> float32_un_op (Convert (`I32, S)) n); + register_un_prim + "caml_cos_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.cos); + register_un_prim + "caml_sin_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.sin); + register_un_prim + "caml_tan_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.tan); + register_un_prim + "caml_acos_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.acos); + register_un_prim + "caml_asin_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.asin); + register_un_prim + "caml_atan_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.atan); + register_bin_prim + "caml_atan2_float32_bytecode" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_bin_op Math.atan2); + register_un_prim + "caml_cosh_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.cosh); + register_un_prim + "caml_sinh_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.sinh); + register_un_prim + "caml_tanh_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.tanh); + register_un_prim + "caml_acosh_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.acosh); + register_un_prim + "caml_asinh_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.asinh); + register_un_prim + "caml_atanh_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.atanh); + register_un_prim + "caml_cbrt_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.cbrt); + register_un_prim + "caml_exp_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.exp); + register_un_prim + "caml_exp2_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.exp2); + register_un_prim + "caml_log_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.log); + register_un_prim + "caml_expm1_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.expm1); + register_un_prim + "caml_log1p_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.log1p); + register_un_prim + "caml_log2_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.log2); + register_un_prim + "caml_log10_float32_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_un_op Math.log10); + register_bin_prim + "caml_power_float32_bytecode" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_bin_op Math.power); + register_bin_prim + "caml_hypot_float32_bytecode" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_bin_op Math.hypot); + register_bin_prim + "caml_fmod_float32_bytecode" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (lift_float_bin_op Math.fmod); + register_un_prim + "caml_float32_to_bits_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Int32, Unboxed)) + (fun f -> + let* f = f in + return (W.UnOp (I32 ReinterpretF, f))); + register_un_prim + "caml_float32_of_bits_bytecode" + `Pure + ~typ:(Number (Int32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun i -> float32_un_op ReinterpretI i); + register_un_prim + "caml_float_of_float32" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float, Unboxed)) + float_of_float32; + register_un_prim + "caml_float32_of_float" + `Pure + ~typ:(Number (Float, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + float32_of_float; + register_un_prim + "caml_float32_to_int64_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) + (fun f -> + let* f = f in + return (W.UnOp (I64 (TruncSat (`F32, S)), f))); + register_un_prim + "caml_float32_of_int64_bytecode" + `Pure + ~typ:(Number (Int64, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun n -> float32_un_op (Convert (`I64, S)) n); + register_un_prim + "caml_simd_cast_float32_int64_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Int64, Unboxed)) + (fun f -> + let* f = f in + return (W.UnOp (I64 (TruncSat (`F32, S)), W.UnOp (F32 Nearest, f)))); + register_un_prim + "caml_simd_float32_round_current_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Nearest f); + register_un_prim + "caml_simd_float32_round_neg_inf_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Floor f); + register_un_prim + "caml_simd_float32_round_pos_inf_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Ceil f); + register_un_prim + "caml_simd_float32_round_towards_zero_bytecode" + `Pure + ~typ:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f -> float32_un_op Trunc f); + register_bin_prim + "caml_simd_float32_min_bytecode" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f g -> float32_bin_op Min f g); + register_bin_prim + "caml_simd_float32_max_bytecode" + `Pure + ~tx:(Number (Float32, Unboxed)) + ~ty:(Number (Float32, Unboxed)) + ~ret_typ:(Number (Float32, Unboxed)) + (fun f g -> float32_bin_op Max f g); register_un_prim "caml_int32_neg" `Pure @@ -1041,7 +1453,7 @@ module Generate (Target : Target_sig.S) = struct ~ret_typ:(Number (Int64, Unboxed)) (fun f -> let* f = f in - return (W.UnOp (I64 (TruncSatF64 S), f))); + return (W.UnOp (I64 (TruncSat (`F64, S)), f))); register_un_prim "caml_int64_to_float" `Pure @@ -1242,7 +1654,7 @@ module Generate (Target : Target_sig.S) = struct ~ret_typ:(Number (Nativeint, Unboxed)) (fun f -> let* f = f in - return (W.UnOp (I32 (TruncSatF64 S), f))); + return (W.UnOp (I32 (TruncSat (`F64, S)), f))); register_un_prim "caml_nativeint_to_float" `Pure @@ -1539,6 +1951,54 @@ module Generate (Target : Target_sig.S) = struct let* indices' = transl_prim_arg ctx indices in return (W.Call (f, [ ta'; indices' ]))) | _ -> invalid_arity "caml_ba_get_generic" l ~expected:2); + let caml_ba_float32_get_n ~ctx ~context ta indices = + match get_type ctx ta with + | Bigarray { layout; _ } -> + let indices = + List.map ~f:(fun i -> transl_prim_arg ctx ~typ:(Int Normalized) i) indices + in + caml_ba_get ~ctx ~context ~kind:Float32_t ~layout ta indices + | _ -> + let n = List.length indices in + let* f = + register_import + ~name:(Printf.sprintf "caml_ba_float32_get_%d" n) + (Fun + { W.params = + Type.value :: List.init ~len:n ~f:(fun _ : W.value_type -> I32) + ; result = [ F32 ] + }) + in + let* ta' = transl_prim_arg ctx ta in + let* indices' = + expression_list (transl_prim_arg ctx ~typ:(Int Normalized)) indices + in + return (W.Call (f, ta' :: indices')) + in + register_prim + "caml_ba_float32_get_1" + `Mutator + ~ret_typ:(Number (Float32, Unboxed)) + (fun ctx context l -> + match l with + | [ ta; i ] -> caml_ba_float32_get_n ~ctx ~context ta [ i ] + | _ -> invalid_arity "caml_ba_float32_get_1" l ~expected:2); + register_prim + "caml_ba_float32_get_2" + `Mutator + ~ret_typ:(Number (Float32, Unboxed)) + (fun ctx context l -> + match l with + | [ ta; i; j ] -> caml_ba_float32_get_n ~ctx ~context ta [ i; j ] + | _ -> invalid_arity "caml_ba_float32_get_2" l ~expected:3); + register_prim + "caml_ba_float32_get_3" + `Mutator + ~ret_typ:(Number (Float32, Unboxed)) + (fun ctx context l -> + match l with + | [ ta; i; j; k ] -> caml_ba_float32_get_n ~ctx ~context ta [ i; j; k ] + | _ -> invalid_arity "caml_ba_float32_get_3" l ~expected:4); let caml_ba_set ~ctx ~context ~kind ~layout ta indices v = let ta' = transl_prim_arg ctx ta in let v' = transl_prim_arg ctx ~typ:(Typing.bigarray_element_type kind) v in @@ -1598,6 +2058,44 @@ module Generate (Target : Target_sig.S) = struct let* v' = transl_prim_arg ctx v in return (W.Call (f, [ ta'; indices'; v' ]))) | _ -> invalid_arity "caml_ba_set_generic" l ~expected:3); + let caml_ba_float32_set_n ~ctx ~context ta indices v = + match get_type ctx ta with + | Bigarray { layout; _ } -> + let indices = + List.map ~f:(fun i -> transl_prim_arg ctx ~typ:(Int Normalized) i) indices + in + caml_ba_set ~ctx ~context ~kind:Float32_t ~layout ta indices v + | _ -> + let n = List.length indices in + let* f = + register_import + ~name:(Printf.sprintf "caml_ba_float32_set_%d" n) + (Fun + { W.params = + (Type.value :: List.init ~len:n ~f:(fun _ : W.value_type -> I32)) + @ [ F32 ] + ; result = [ Type.value ] + }) + in + let* ta' = transl_prim_arg ctx ta in + let* indices' = + expression_list (transl_prim_arg ctx ~typ:(Int Normalized)) indices + in + let* v' = transl_prim_arg ctx ~typ:(Number (Float32, Unboxed)) v in + return (W.Call (f, ta' :: (indices' @ [ v' ]))) + in + register_prim "caml_ba_float32_set_1" `Mutator ~unbox:true (fun ctx context l -> + match l with + | [ ta; i; v ] -> caml_ba_float32_set_n ~ctx ~context ta [ i ] v + | _ -> invalid_arity "caml_ba_float32_set_1" l ~expected:3); + register_prim "caml_ba_float32_set_2" `Mutator ~unbox:true (fun ctx context l -> + match l with + | [ ta; i; j; v ] -> caml_ba_float32_set_n ~ctx ~context ta [ i; j ] v + | _ -> invalid_arity "caml_ba_float32_set_2" l ~expected:4); + register_prim "caml_ba_float32_set_3" `Mutator ~unbox:true (fun ctx context l -> + match l with + | [ ta; i; j; k; v ] -> caml_ba_float32_set_n ~ctx ~context ta [ i; j; k ] v + | _ -> invalid_arity "caml_ba_float32_set_3" l ~expected:5); register_un_prim "caml_is_null" `Pure ~ret_typ:(Int Normalized) (fun x -> let* x = x in let* null = @@ -1611,6 +2109,7 @@ module Generate (Target : Target_sig.S) = struct Some I32 | Number (Int64, Unboxed) -> Some I64 | Number (Float, Unboxed) -> Some F64 + | Number (Float32, Unboxed) -> Some F32 | _ -> None let box_number_if_needed ctx x e = @@ -2367,7 +2866,7 @@ module Generate (Target : Target_sig.S) = struct (List.exists ~f:(fun ty -> match ty with - | Int32 | Nativeint | Int64 | Float -> true + | Int32 | Nativeint | Int64 | Float | Float32 -> true | Value | Int _ -> false) param_types) (Option.value ~default:Typing.Top (repr_type typ))) diff --git a/compiler/lib-wasm/target_sig.ml b/compiler/lib-wasm/target_sig.ml index 307aba9a9a..f13e4aac5c 100644 --- a/compiler/lib-wasm/target_sig.ml +++ b/compiler/lib-wasm/target_sig.ml @@ -81,6 +81,10 @@ module type S = sig val unbox_float : expression -> expression + val box_float32 : expression -> expression + + val unbox_float32 : expression -> expression + val box_int32 : expression -> expression val unbox_int32 : expression -> expression diff --git a/compiler/lib-wasm/typing.ml b/compiler/lib-wasm/typing.ml index 65159706da..808bf61ec9 100644 --- a/compiler/lib-wasm/typing.ml +++ b/compiler/lib-wasm/typing.ml @@ -39,6 +39,7 @@ type boxed_number = | Int64 | Nativeint | Float + | Float32 type boxed_status = | Boxed @@ -48,6 +49,7 @@ module Bigarray = struct type kind = | Float16 | Float32 + | Float32_t | Float64 | Int8_signed | Int8_unsigned @@ -100,6 +102,7 @@ module Bigarray = struct "bigarray{%s,%s}" (match kind with | Float32 -> "float32" + | Float32_t -> "float32_t" | Float64 -> "float64" | Int8_signed -> "sint8" | Int8_unsigned -> "uint8" @@ -223,7 +226,8 @@ module Domain = struct | Int32 -> "int32" | Int64 -> "int64" | Nativeint -> "nativeint" - | Float -> "float") + | Float -> "float" + | Float32 -> "float32") (match b with | Boxed -> "boxed" | Unboxed -> "unboxed") @@ -293,6 +297,7 @@ let rec constant_type (c : constant) = | Int64 _ -> Number (Int64, Unboxed) | NativeInt _ -> Number (Nativeint, Unboxed) | Float _ -> Number (Float, Unboxed) + | Float32 _ -> Number (Float32, Unboxed) | Tuple (_, a, _) -> Tuple (Array.map ~f:(fun c' -> Domain.box (constant_type c')) a) | Null -> Null | _ -> Top @@ -305,6 +310,7 @@ let arg_type ~approx arg = let bigarray_element_type (kind : Bigarray.kind) = match kind with | Float16 | Float32 | Float64 -> Number (Float, Unboxed) + | Float32_t -> Number (Float32, Unboxed) | Int8_signed | Int8_unsigned | Int16_signed | Int16_unsigned -> Int Normalized | Int -> Int Unnormalized | Int32 -> Number (Int32, Unboxed) @@ -498,7 +504,8 @@ let type_specialized_primitive types global_flow_state name args = | [ Number (Int32, _); Number (Int32, _) ] | [ Number (Int64, _); Number (Int64, _) ] | [ Number (Nativeint, _); Number (Nativeint, _) ] - | [ Number (Float, _); Number (Float, _) ] -> true + | [ Number (Float, _); Number (Float, _) ] + | [ Number (Float32, _); Number (Float32, _) ] -> true | _ -> false) | "caml_ba_get_1" | "caml_ba_get_2" diff --git a/compiler/lib-wasm/typing.mli b/compiler/lib-wasm/typing.mli index 978d1386e9..b14b23232e 100644 --- a/compiler/lib-wasm/typing.mli +++ b/compiler/lib-wasm/typing.mli @@ -10,6 +10,7 @@ type boxed_number = | Int64 | Nativeint | Float + | Float32 type boxed_status = | Boxed @@ -19,6 +20,7 @@ module Bigarray : sig type kind = | Float16 | Float32 + | Float32_t | Float64 | Int8_signed | Int8_unsigned diff --git a/compiler/lib-wasm/wasm_ast.ml b/compiler/lib-wasm/wasm_ast.ml index 889117fceb..ef9f7a78ed 100644 --- a/compiler/lib-wasm/wasm_ast.ml +++ b/compiler/lib-wasm/wasm_ast.ml @@ -83,7 +83,7 @@ type int_un_op = | Ctz | Popcnt | Eqz - | TruncSatF64 of signage + | TruncSat of [ `F32 | `F64 ] * signage | ReinterpretF type int_bin_op = diff --git a/compiler/lib-wasm/wasm_output.ml b/compiler/lib-wasm/wasm_output.ml index 88595ca03a..65014689ca 100644 --- a/compiler/lib-wasm/wasm_output.ml +++ b/compiler/lib-wasm/wasm_output.ml @@ -370,12 +370,15 @@ end = struct | Ctz -> output_byte ch (arith + 1) | Popcnt -> output_byte ch (arith + 2) | Eqz -> output_byte ch comp - | TruncSatF64 signage -> + | TruncSat (size, signage) -> Feature.require nontrapping_fptoint; output_byte ch 0xFC; output_byte ch (trunc + + (match size with + | `F32 -> 0 + | `F64 -> 2) + match signage with | S -> 0 @@ -504,8 +507,8 @@ end = struct | UnOp (op, e') -> ( output_expression st ch e'; match op with - | I32 op -> int_un_op (0x67, 0x45, 2, 0xBC) ch op - | I64 op -> int_un_op (0x79, 0x50, 6, 0xBD) ch op + | I32 op -> int_un_op (0x67, 0x45, 0, 0xBC) ch op + | I64 op -> int_un_op (0x79, 0x50, 4, 0xBD) ch op | F32 op -> output_byte ch (float_un_op (0x8B, 0xB2, 0xBE) op) | F64 op -> output_byte ch (float_un_op (0x99, 0xB7, 0xBF) op)) | BinOp (op, e', e'') -> ( diff --git a/compiler/lib-wasm/wat_output.ml b/compiler/lib-wasm/wat_output.ml index 58d8250c05..602a7e847c 100644 --- a/compiler/lib-wasm/wat_output.ml +++ b/compiler/lib-wasm/wat_output.ml @@ -253,7 +253,8 @@ let int_un_op sz op = | Ctz -> "ctz" | Popcnt -> "popcnt" | Eqz -> "eqz" - | TruncSatF64 s -> signage "trunc_sat_f64" s + | TruncSat (`F32, s) -> signage "trunc_sat_f32" s + | TruncSat (`F64, s) -> signage "trunc_sat_f64" s | ReinterpretF -> "reinterpret_f" ^ sz let int_bin_op _ (op : int_bin_op) = diff --git a/compiler/lib/code.ml b/compiler/lib/code.ml index 4230974434..429a5e9ddd 100644 --- a/compiler/lib/code.ml +++ b/compiler/lib/code.ml @@ -322,6 +322,7 @@ type constant = | String of string | NativeString of Native_string.t | Float of Int64.t + | Float32 of Int64.t | Float_array of Int64.t array | Int of Targetint.t | Int32 of Int32.t @@ -361,9 +362,12 @@ module Constant = struct b) | Float a, Float b -> Some (Float.ieee_equal (Int64.float_of_bits a) (Int64.float_of_bits b)) + | Float32 a, Float32 b -> + Some (Float.ieee_equal (Int64.float_of_bits a) (Int64.float_of_bits b)) | Null, Null -> Some true | String _, NativeString _ | NativeString _, String _ -> None | Int _, Float _ | Float _, Int _ -> None + | Int _, Float32 _ | Float32 _, Int _ -> None | Tuple ((0 | 254), _, _), Float_array _ -> None | Float_array _, Tuple ((0 | 254), _, _) -> None | ( Tuple _ @@ -374,6 +378,7 @@ module Constant = struct | Int32 _ | NativeInt _ | Float _ + | Float32 _ | Float_array _ ) ) -> Some false | ( Float_array _ , ( String _ @@ -383,13 +388,26 @@ module Constant = struct | Int32 _ | NativeInt _ | Float _ + | Float32 _ | Tuple _ ) ) -> Some false | ( String _ - , (Int64 _ | Int _ | Int32 _ | NativeInt _ | Float _ | Tuple _ | Float_array _) ) -> - Some false + , ( Int64 _ + | Int _ + | Int32 _ + | NativeInt _ + | Float _ + | Float32 _ + | Tuple _ + | Float_array _ ) ) -> Some false | ( NativeString _ - , (Int64 _ | Int _ | Int32 _ | NativeInt _ | Float _ | Tuple _ | Float_array _) ) -> - Some false + , ( Int64 _ + | Int _ + | Int32 _ + | NativeInt _ + | Float _ + | Float32 _ + | Tuple _ + | Float_array _ ) ) -> Some false | ( Int64 _ , ( String _ | NativeString _ @@ -397,10 +415,15 @@ module Constant = struct | Int32 _ | NativeInt _ | Float _ + | Float32 _ | Tuple _ | Float_array _ ) ) -> Some false - | Float _, (String _ | NativeString _ | Float_array _ | Int64 _ | Tuple (_, _, _)) -> - Some false + | ( Float _ + , (Float32 _ | String _ | NativeString _ | Float_array _ | Int64 _ | Tuple (_, _, _)) + ) -> Some false + | ( Float32 _ + , (Float _ | String _ | NativeString _ | Float_array _ | Int64 _ | Tuple (_, _, _)) + ) -> Some false | ( (Int _ | Int32 _ | NativeInt _) , (String _ | NativeString _ | Float_array _ | Int64 _ | Tuple (_, _, _)) ) -> Some false @@ -409,8 +432,8 @@ module Constant = struct | Int _, (Int32 _ | NativeInt _) | Int32 _, (Int _ | NativeInt _) | NativeInt _, (Int _ | Int32 _) - | (Int32 _ | NativeInt _), Float _ - | Float _, (Int32 _ | NativeInt _) -> None + | (Int32 _ | NativeInt _), (Float _ | Float32 _) + | (Float _ | Float32 _), (Int32 _ | NativeInt _) -> None end type loc = @@ -497,6 +520,7 @@ module Print = struct | NativeString (Byte s) -> Format.fprintf f "%Sj" s | NativeString (Utf (Utf8 s)) -> Format.fprintf f "%Sj" s | Float fl -> Format.fprintf f "%.12g" (Int64.float_of_bits fl) + | Float32 fl -> Format.fprintf f "%.9g" (Int64.float_of_bits fl) | Float_array a -> Format.fprintf f "[|"; for i = 0 to Array.length a - 1 do diff --git a/compiler/lib/code.mli b/compiler/lib/code.mli index 5e897ebb9e..0eedd6f1aa 100644 --- a/compiler/lib/code.mli +++ b/compiler/lib/code.mli @@ -144,6 +144,7 @@ type constant = | String of string | NativeString of Native_string.t | Float of Int64.t + | Float32 of Int64.t | Float_array of Int64.t array | Int of Targetint.t | Int32 of Int32.t (** Only produced when compiling to WebAssembly. *) diff --git a/compiler/lib/eval.ml b/compiler/lib/eval.ml index baa62bf074..67d7fadeec 100644 --- a/compiler/lib/eval.ml +++ b/compiler/lib/eval.ml @@ -464,10 +464,13 @@ let constant_js_equal a b = | Int i, Int j -> Some (Targetint.equal i j) | Float a, Float b -> Some (Float.ieee_equal (Int64.float_of_bits a) (Int64.float_of_bits b)) + | Float32 a, Float32 b -> + Some (Float.ieee_equal (Int64.float_of_bits a) (Int64.float_of_bits b)) + | Float32 _, Float _ | Float _, Float32 _ -> None | NativeString a, NativeString b -> Some (Native_string.equal a b) | String a, String b when Config.Flag.use_js_string () -> Some (String.equal a b) | Null, Null -> Some true - | Int _, Float _ | Float _, Int _ -> None + | Int _, (Float _ | Float32 _) | (Float _ | Float32 _), Int _ -> None (* All other values may be distinct objects and thus different by [caml_js_equals]. *) | String _, _ | _, String _ @@ -491,6 +494,7 @@ let constant_equal a b = match a, b with | Int i, Int j -> Targetint.equal i j | Float a, Float b -> Int64.equal a b + | Float32 a, Float32 b -> Int64.equal a b | NativeString a, NativeString b -> Native_string.equal a b | String a, String b -> String.equal a b | Int32 a, Int32 b -> Int32.equal a b @@ -500,7 +504,7 @@ let constant_equal a b = (* We don't need to compare other constants, so let's just return false. *) | Tuple _, Tuple _ -> false | Float_array _, Float_array _ -> false - | (Int _ | Float _ | Int64 _ | Int32 _ | NativeInt _ | Null), _ -> false + | (Int _ | Float _ | Float32 _ | Int64 _ | Int32 _ | NativeInt _ | Null), _ -> false | (String _ | NativeString _), _ -> false | (Float_array _ | Tuple _), _ -> false @@ -711,6 +715,7 @@ let the_cond_of info x = ( Int32 _ | NativeInt _ | Float _ + | Float32 _ | Tuple _ | String _ | NativeString _ diff --git a/compiler/lib/generate.ml b/compiler/lib/generate.ml index ce21b95584..83f5fb42b0 100644 --- a/compiler/lib/generate.ml +++ b/compiler/lib/generate.ml @@ -479,6 +479,7 @@ let rec constant_rec ~ctx x level instrs = | Byte x -> Share.get_byte_string str_js_byte x ctx.Ctx.share, instrs | Utf (Utf8 x) -> Share.get_utf_string str_js_utf8 x ctx.Ctx.share, instrs) | Float f -> float_const f, instrs + | Float32 f -> float_const f, instrs | Float_array a -> ( Mlvalue.Array.make ~tag:Obj.double_array_tag @@ -1335,6 +1336,14 @@ let _ = register_bin_prim "caml_le_float" `Pure (fun cx cy _ -> bool (J.EBin (J.Le, cx, cy))); register_bin_prim "caml_gt_float" `Pure (fun cx cy _ -> bool (J.EBin (J.Lt, cy, cx))); register_bin_prim "caml_lt_float" `Pure (fun cx cy _ -> bool (J.EBin (J.Lt, cx, cy))); + register_bin_prim "caml_eq_float32" `Pure (fun cx cy _ -> + bool (J.EBin (J.EqEq, cx, cy))); + register_bin_prim "caml_neq_float32" `Pure (fun cx cy _ -> + bool (J.EBin (J.NotEq, cx, cy))); + register_bin_prim "caml_ge_float32" `Pure (fun cx cy _ -> bool (J.EBin (J.Le, cy, cx))); + register_bin_prim "caml_le_float32" `Pure (fun cx cy _ -> bool (J.EBin (J.Le, cx, cy))); + register_bin_prim "caml_gt_float32" `Pure (fun cx cy _ -> bool (J.EBin (J.Lt, cy, cx))); + register_bin_prim "caml_lt_float32" `Pure (fun cx cy _ -> bool (J.EBin (J.Lt, cx, cy))); register_bin_prim "caml_add_float" `Pure (fun cx cy _ -> J.EBin (J.Plus, cx, cy)); register_bin_prim "caml_sub_float" `Pure (fun cx cy _ -> J.EBin (J.Minus, cx, cy)); register_bin_prim "caml_mul_float" `Pure (fun cx cy _ -> J.EBin (J.Mul, cx, cy)); diff --git a/compiler/lib/ocaml_compiler.ml b/compiler/lib/ocaml_compiler.ml index aa475e6cdb..850d060abd 100644 --- a/compiler/lib/ocaml_compiler.ml +++ b/compiler/lib/ocaml_compiler.ml @@ -28,6 +28,8 @@ let rec constant_of_const c : Code.constant = | Const_base (Const_float s) -> Float (Int64.bits_of_float (float_of_string s)) | ((Const_base (Const_unboxed_float s)) [@if oxcaml]) -> Float (Int64.bits_of_float (float_of_string s)) + | ((Const_base (Const_float32 s | Const_unboxed_float32 s)) [@if oxcaml]) -> + Float32 (Int64.bits_of_float (Float32.of_string s |> Float32.to_float)) | Const_base (Const_int32 i) -> Int32 i | ((Const_base (Const_unboxed_int32 i)) [@if oxcaml]) -> Int32 i | Const_base (Const_int64 i) -> Int64 i @@ -48,8 +50,6 @@ let rec constant_of_const c : Code.constant = | ((Const_mixed_block (tag, _, l)) [@if oxcaml]) -> let l = Array.of_list (List.map l ~f:constant_of_const) in Tuple (tag, l, Unknown) - | ((Const_base (Const_float32 _ | Const_unboxed_float32 _)) [@if oxcaml]) -> - failwith "Float32 unsupported" | (Const_null [@if oxcaml]) -> Null type module_or_not = diff --git a/compiler/lib/parse_bytecode.ml b/compiler/lib/parse_bytecode.ml index dbee96ebba..3f2428b365 100644 --- a/compiler/lib/parse_bytecode.ml +++ b/compiler/lib/parse_bytecode.ml @@ -479,6 +479,8 @@ end = struct let ident_native = ident_of_custom (Obj.repr 0n) + let ident_f32 = ident_of_custom (Obj.repr 0.s) [@@if oxcaml] + external is_null : Obj.t -> bool = "%is_null" [@@if oxcaml] let is_null obj = is_null (Sys.opaque_identity obj) [@@if oxcaml] @@ -503,6 +505,8 @@ end = struct else if tag = Obj.custom_tag then match ident_of_custom x with + | ((Some name) [@if oxcaml]) when same_ident name ident_f32 -> + Float32 (Int64.bits_of_float ((Obj.magic x : float32) |> Float32.to_float)) | Some name when same_ident name ident_32 -> let i : int32 = Obj.magic x in Int32 i @@ -528,6 +532,7 @@ end = struct match c with | String _ | NativeString _ -> false | Float _ -> true + | Float32 _ -> true | Float_array _ -> false | Int64 _ -> false | Tuple _ -> false diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index e0d85f7e31..a7a8a17703 100644 --- a/compiler/lib/stdlib.ml +++ b/compiler/lib/stdlib.ml @@ -356,6 +356,30 @@ module Float = struct external ( >= ) : t -> t -> bool = "%greaterequal" end +module Float32 = struct + type t + + let of_float _ = assert false + + let to_float _ = assert false + + let of_string _ = assert false +end +[@@if not oxcaml] + +module Float32 = struct + type t = float32 + + external of_float : float -> t = "%float32offloat" + + external to_float : t -> float = "%floatoffloat32" + + (* In javascript/wasm, we define float32 parsing as rounding the 64-bit result. + This is not equivalent to native code, which parses to 32 bits directly. *) + let of_string s = float_of_string s |> of_float +end +[@@if oxcaml] + module Bool = struct include Bool diff --git a/compiler/tests-check-prim/main.5.2.output b/compiler/tests-check-prim/main.5.2.output index 5442aa757f..5b2b254be9 100644 --- a/compiler/tests-check-prim/main.5.2.output +++ b/compiler/tests-check-prim/main.5.2.output @@ -39,7 +39,66 @@ From +effect.js: jsoo_effect_not_supported From +float32.js: +caml_abs_float32 +caml_acos_float32_bytecode +caml_acosh_float32_bytecode +caml_add_float32 +caml_asin_float32_bytecode +caml_asinh_float32_bytecode +caml_atan2_float32_bytecode +caml_atan_float32_bytecode +caml_atanh_float32_bytecode +caml_cbrt_float32_bytecode +caml_ceil_float32_bytecode +caml_classify_float32_bytecode +caml_copysign_float32_bytecode +caml_cos_float32_bytecode +caml_cosh_float32_bytecode +caml_div_float32 +caml_erf_float32_bytecode +caml_erfc_float32_bytecode +caml_exp2_float32_bytecode +caml_exp_float32_bytecode +caml_expm1_float32_bytecode +caml_float32_compare +caml_float32_of_bits_bytecode +caml_float32_of_float +caml_float32_of_int +caml_float32_of_int64_bytecode +caml_float32_of_string +caml_float32_to_bits_bytecode +caml_float32_to_int64_bytecode +caml_float_of_float32 +caml_floor_float32_bytecode +caml_fma_float32_bytecode +caml_fmod_float32_bytecode +caml_format_float32 +caml_frexp_float32 +caml_hypot_float32_bytecode +caml_int_of_float32 caml_is_boot_compiler +caml_ldexp_float32_bytecode +caml_log10_float32_bytecode +caml_log1p_float32_bytecode +caml_log2_float32_bytecode +caml_log_float32_bytecode +caml_modf_float32 +caml_mul_float32 +caml_neg_float32 +caml_nextafter_float32_bytecode +caml_power_float32_bytecode +caml_round_float32_bytecode +caml_signbit_float32_bytecode +caml_simd_cast_float32_int64_bytecode +caml_simd_float32_max_bytecode +caml_simd_float32_min_bytecode +caml_sin_float32_bytecode +caml_sinh_float32_bytecode +caml_sqrt_float32_bytecode +caml_sub_float32 +caml_tan_float32_bytecode +caml_tanh_float32_bytecode +caml_trunc_float32_bytecode From +fs.js: caml_ba_map_file diff --git a/compiler/tests-check-prim/unix-Unix.5.2.output b/compiler/tests-check-prim/unix-Unix.5.2.output index a5cb6034c3..dac9cbf7b4 100644 --- a/compiler/tests-check-prim/unix-Unix.5.2.output +++ b/compiler/tests-check-prim/unix-Unix.5.2.output @@ -115,7 +115,66 @@ From +effect.js: jsoo_effect_not_supported From +float32.js: +caml_abs_float32 +caml_acos_float32_bytecode +caml_acosh_float32_bytecode +caml_add_float32 +caml_asin_float32_bytecode +caml_asinh_float32_bytecode +caml_atan2_float32_bytecode +caml_atan_float32_bytecode +caml_atanh_float32_bytecode +caml_cbrt_float32_bytecode +caml_ceil_float32_bytecode +caml_classify_float32_bytecode +caml_copysign_float32_bytecode +caml_cos_float32_bytecode +caml_cosh_float32_bytecode +caml_div_float32 +caml_erf_float32_bytecode +caml_erfc_float32_bytecode +caml_exp2_float32_bytecode +caml_exp_float32_bytecode +caml_expm1_float32_bytecode +caml_float32_compare +caml_float32_of_bits_bytecode +caml_float32_of_float +caml_float32_of_int +caml_float32_of_int64_bytecode +caml_float32_of_string +caml_float32_to_bits_bytecode +caml_float32_to_int64_bytecode +caml_float_of_float32 +caml_floor_float32_bytecode +caml_fma_float32_bytecode +caml_fmod_float32_bytecode +caml_format_float32 +caml_frexp_float32 +caml_hypot_float32_bytecode +caml_int_of_float32 caml_is_boot_compiler +caml_ldexp_float32_bytecode +caml_log10_float32_bytecode +caml_log1p_float32_bytecode +caml_log2_float32_bytecode +caml_log_float32_bytecode +caml_modf_float32 +caml_mul_float32 +caml_neg_float32 +caml_nextafter_float32_bytecode +caml_power_float32_bytecode +caml_round_float32_bytecode +caml_signbit_float32_bytecode +caml_simd_cast_float32_int64_bytecode +caml_simd_float32_max_bytecode +caml_simd_float32_min_bytecode +caml_sin_float32_bytecode +caml_sinh_float32_bytecode +caml_sqrt_float32_bytecode +caml_sub_float32 +caml_tan_float32_bytecode +caml_tanh_float32_bytecode +caml_trunc_float32_bytecode From +fs.js: caml_ba_map_file diff --git a/compiler/tests-check-prim/unix-Win32.5.2.output b/compiler/tests-check-prim/unix-Win32.5.2.output index 8dc26cf84d..609e30356d 100644 --- a/compiler/tests-check-prim/unix-Win32.5.2.output +++ b/compiler/tests-check-prim/unix-Win32.5.2.output @@ -88,7 +88,66 @@ From +effect.js: jsoo_effect_not_supported From +float32.js: +caml_abs_float32 +caml_acos_float32_bytecode +caml_acosh_float32_bytecode +caml_add_float32 +caml_asin_float32_bytecode +caml_asinh_float32_bytecode +caml_atan2_float32_bytecode +caml_atan_float32_bytecode +caml_atanh_float32_bytecode +caml_cbrt_float32_bytecode +caml_ceil_float32_bytecode +caml_classify_float32_bytecode +caml_copysign_float32_bytecode +caml_cos_float32_bytecode +caml_cosh_float32_bytecode +caml_div_float32 +caml_erf_float32_bytecode +caml_erfc_float32_bytecode +caml_exp2_float32_bytecode +caml_exp_float32_bytecode +caml_expm1_float32_bytecode +caml_float32_compare +caml_float32_of_bits_bytecode +caml_float32_of_float +caml_float32_of_int +caml_float32_of_int64_bytecode +caml_float32_of_string +caml_float32_to_bits_bytecode +caml_float32_to_int64_bytecode +caml_float_of_float32 +caml_floor_float32_bytecode +caml_fma_float32_bytecode +caml_fmod_float32_bytecode +caml_format_float32 +caml_frexp_float32 +caml_hypot_float32_bytecode +caml_int_of_float32 caml_is_boot_compiler +caml_ldexp_float32_bytecode +caml_log10_float32_bytecode +caml_log1p_float32_bytecode +caml_log2_float32_bytecode +caml_log_float32_bytecode +caml_modf_float32 +caml_mul_float32 +caml_neg_float32 +caml_nextafter_float32_bytecode +caml_power_float32_bytecode +caml_round_float32_bytecode +caml_signbit_float32_bytecode +caml_simd_cast_float32_int64_bytecode +caml_simd_float32_max_bytecode +caml_simd_float32_min_bytecode +caml_sin_float32_bytecode +caml_sinh_float32_bytecode +caml_sqrt_float32_bytecode +caml_sub_float32 +caml_tan_float32_bytecode +caml_tanh_float32_bytecode +caml_trunc_float32_bytecode From +fs.js: caml_ba_map_file diff --git a/compiler/tests-jsoo/dune b/compiler/tests-jsoo/dune index 5186661bf4..cbaa22152e 100644 --- a/compiler/tests-jsoo/dune +++ b/compiler/tests-jsoo/dune @@ -39,6 +39,26 @@ (preprocess (pps ppx_expect))) +(library + (name jsoo_testsuite_float32_js) + (modules test_marshal_float32_js) + (libraries unix compiler-libs.common js_of_ocaml-compiler) + (enabled_if %{oxcaml_supported}) + (inline_tests + (modes js)) + (preprocess + (pps ppx_expect))) + +(library + (name jsoo_testsuite_float32) + (modules test_marshal_float32) + (libraries unix compiler-libs.common js_of_ocaml-compiler) + (enabled_if %{oxcaml_supported}) + (inline_tests + (modes wasm best)) + (preprocess + (pps ppx_expect))) + (library (name jsoo_testsuite) (modules @@ -49,6 +69,8 @@ test_float16 test_bigarray test_marshal_compressed + test_marshal_float32 + test_marshal_float32_js test_parsing calc_parser calc_lexer)) diff --git a/compiler/tests-jsoo/test_marshal_float32.ml b/compiler/tests-jsoo/test_marshal_float32.ml new file mode 100644 index 0000000000..385e13bd2b --- /dev/null +++ b/compiler/tests-jsoo/test_marshal_float32.ml @@ -0,0 +1,32 @@ +(* In javascript, float32s are represented as floats. + In native code and wasm, float32s are custom blocks containing a float32 field. *) + +external float_of_float32 : float32 -> float = "%floatoffloat32" + +type float64s = + { a : float + ; b : float + } + +let%expect_test ("float64 wasm" [@tags "wasm-only"]) = + let f64 = Marshal.to_string { a = 123.; b = 456. } [] in + Printf.printf "%S" f64; + [%expect + {| "\132\149\166\190\000\000\000\018\000\000\000\001\000\000\000\005\000\000\000\003\014\002\000\000\000\000\000\192^@\000\000\000\000\000\128|@" |}]; + let f64 : float64s = Marshal.from_string f64 0 in + Printf.printf "%f %f" f64.a f64.b; + [%expect {| 123.000000 456.000000 |}] + +type float32s = + { a : float32 + ; b : float32 + } + +let%expect_test ("float32 wasm" [@tags "wasm-only"]) = + let f32 = Marshal.to_string { a = 123.s; b = 456.s } [] in + Printf.printf "%S" f32; + [%expect + {| "\132\149\166\190\000\000\000\021\000\000\000\003\000\000\000\t\000\000\000\t\160\025_f32\000B\246\000\000\025_f32\000C\228\000\000" |}]; + let f32 : float32s = Marshal.from_string f32 0 in + Printf.printf "%f %f" (float_of_float32 f32.a) (float_of_float32 f32.b); + [%expect {| 123.000000 456.000000 |}] diff --git a/compiler/tests-jsoo/test_marshal_float32_js.ml b/compiler/tests-jsoo/test_marshal_float32_js.ml new file mode 100644 index 0000000000..b2047b4fe7 --- /dev/null +++ b/compiler/tests-jsoo/test_marshal_float32_js.ml @@ -0,0 +1,32 @@ +(* In javascript, float32s are represented as floats. + In native code and wasm, float32s are custom blocks containing a float32 field. *) + +external float_of_float32 : float32 -> float = "%floatoffloat32" + +type float64s = + { a : float + ; b : float + } + +let%expect_test ("float64 javascript" [@tags "js-only", "no-wasm"]) = + let f64 = Marshal.to_string { a = 123.; b = 456. } [] in + Printf.printf "%S" f64; + [%expect + {| "\132\149\166\190\000\000\000\n\000\000\000\001\000\000\000\003\000\000\000\003\b\000\000\b\254\000{\001\001\200" |}]; + let f64 : float64s = Marshal.from_string f64 0 in + Printf.printf "%f %f" f64.a f64.b; + [%expect {| 123.000000 456.000000 |}] + +type float32s = + { a : float32 + ; b : float32 + } + +let%expect_test ("float32 javascript" [@tags "js-only", "no-wasm"]) = + let f32 = Marshal.to_string { a = 123.s; b = 456.s } [] in + Printf.printf "%S" f32; + [%expect + {| "\132\149\166\190\000\000\000\006\000\000\000\001\000\000\000\003\000\000\000\003\160\000{\001\001\200" |}]; + let f32 : float32s = Marshal.from_string f32 0 in + Printf.printf "%f %f" (float_of_float32 f32.a) (float_of_float32 f32.b); + [%expect {| 123.000000 456.000000 |}] diff --git a/runtime/js/float32.js b/runtime/js/float32.js index a4b14f1feb..d53ddb378f 100644 --- a/runtime/js/float32.js +++ b/runtime/js/float32.js @@ -1,3 +1,447 @@ +/* + 32-bit floats are represented as javascript numbers, i.e. 64-bit floats. + Each operation is performed in 64-bit precision and then rounded to the + nearest 32-bit float. This is not identical to using true 32-bit operations. + For example, if rounding an exact result to 64 bits places it halfway + between the two nearest 32-bit numbers, rounding it again to 32 bits + may not result in the closest 32-bit number to the exact result. + + Marshalled float32s therefore look like normal floats. This means that + javascript programs are not be able to read float32 data marshalled + by native programs and vice versa. +*/ + +//Provides: caml_float_of_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_float_of_float32(x) { + return x; +} + +//Provides: caml_float32_of_float const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_float32_of_float(x) { + return Math.fround(x); +} + +//Provides: caml_float32_of_int const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_float32_of_int(x) { + return Math.fround(x); +} + +//Provides: caml_int_of_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_int_of_float32(x) { + return x | 0; +} + +//Provides: caml_float32_of_bits_bytecode const +//Requires: caml_int32_float_of_bits +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_float32_of_bits_bytecode = caml_int32_float_of_bits; + +//Provides: caml_float32_to_bits_bytecode const +//Requires: caml_int32_bits_of_float +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_float32_to_bits_bytecode = caml_int32_bits_of_float; + +//Provides: caml_float32_of_int64_bytecode const +//Requires: caml_int64_to_float +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_float32_of_int64_bytecode(x) { + return Math.fround(caml_int64_to_float(x)); +} + +//Provides: caml_float32_to_int64_bytecode const +//Requires: caml_int64_of_float +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_float32_to_int64_bytecode = caml_int64_of_float; + +//Provides: caml_float32_of_string (const) +//Requires: caml_float_of_string +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_float32_of_string(x) { + return Math.fround(caml_float_of_string(x)); +} + +//Provides: caml_format_float32 const +//Requires: caml_format_float +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_format_float32 = caml_format_float; + +//Provides: caml_float32_compare const +//Requires: caml_float_compare +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_float32_compare = caml_float_compare; + +//Provides: caml_add_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_add_float32(x, y) { + return Math.fround(x + y); +} + +//Provides: caml_sub_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_sub_float32(x, y) { + return Math.fround(x - y); +} + +//Provides: caml_mul_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_mul_float32(x, y) { + return Math.fround(x * y); +} + +//Provides: caml_div_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_div_float32(x, y) { + return Math.fround(x / y); +} + +//Provides: caml_fmod_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_fmod_float32_bytecode(x, y) { + return Math.fround(x % y); +} + +//Provides: caml_neg_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_neg_float32(x) { + return -x; // Result is exact +} + +//Provides: caml_abs_float32 const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_abs_float32(x) { + return Math.abs(x); // Result is exact +} + +//Provides: caml_modf_float32 const +//Requires: caml_modf_float +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_modf_float32 = caml_modf_float; // Result is exact + +//Provides: caml_acos_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_acos_float32_bytecode(x) { + return Math.fround(Math.acos(x)); +} + +//Provides: caml_asin_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_asin_float32_bytecode(x) { + return Math.fround(Math.asin(x)); +} + +//Provides: caml_atan_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atan_float32_bytecode(x) { + return Math.fround(Math.atan(x)); +} + +//Provides: caml_atan2_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atan2_float32_bytecode(x, y) { + return Math.fround(Math.atan2(x, y)); +} + +//Provides: caml_ceil_float32_bytecode const +//Alias: caml_simd_float32_round_pos_inf_bytecode +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_ceil_float32_bytecode(x) { + return Math.fround(Math.ceil(x)); +} + +//Provides: caml_cos_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_cos_float32_bytecode(x) { + return Math.fround(Math.cos(x)); +} + +//Provides: caml_exp_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_exp_float32_bytecode(x) { + return Math.fround(Math.exp(x)); +} + +//Provides: caml_floor_float32_bytecode const +//Alias: caml_simd_float32_round_neg_inf_bytecode +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_floor_float32_bytecode(x) { + return Math.fround(Math.floor(x)); +} + +//Provides: caml_log_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_log_float32_bytecode(x) { + return Math.fround(Math.log(x)); +} + +//Provides: caml_power_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_power_float32_bytecode(x, y) { + return Math.fround(Math.pow(x, y)); +} + +//Provides: caml_sin_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_sin_float32_bytecode(x) { + return Math.fround(Math.sin(x)); +} + +//Provides: caml_sqrt_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_sqrt_float32_bytecode(x) { + return Math.fround(Math.sqrt(x)); +} + +//Provides: caml_tan_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_tan_float32_bytecode(x) { + return Math.fround(Math.tan(x)); +} + +//Provides: caml_nextafter_float32_bytecode const +//Requires: caml_int32_bits_of_float, caml_int32_float_of_bits +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_nextafter_float32_bytecode(x, y) { + if (Number.isNaN(x) || Number.isNaN(y)) return Number.NaN; + if (x === y) return y; + if (x === 0) { + if (y < 0) return -Math.pow(2, -149); + else return Math.pow(2, -149); + } + var bits = caml_int32_bits_of_float(x); + if (x < y === x > 0) bits++; + else bits--; + return caml_int32_float_of_bits(bits); +} + +//Provides: caml_trunc_float32_bytecode const +//Alias: caml_simd_float32_round_towards_zero_bytecode +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_trunc_float32_bytecode(x) { + return Math.fround(Math.trunc(x)); +} + +//Provides: caml_classify_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_classify_float32_bytecode(x) { + if (Number.isFinite(x)) { + if (Math.abs(x) >= 1.1754943508222875e-38) return 0; + if (x !== 0) return 1; + return 2; + } + return Number.isNaN(x) ? 4 : 3; +} + +//Provides: caml_ldexp_float32_bytecode const +//Requires: caml_ldexp_float +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_ldexp_float32_bytecode(x, y) { + return Math.fround(caml_ldexp_float(x, y)); +} + +//Provides: caml_frexp_float32 const +//Requires: caml_frexp_float +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_frexp_float32 = caml_frexp_float; // Result is exact + +//Provides: caml_copysign_float32_bytecode const +//Requires: caml_copysign_float +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_copysign_float32_bytecode = caml_copysign_float; // Result is exact + +//Provides: caml_signbit_float32_bytecode const +//Requires: caml_signbit_float +//Version: >= 5.2, < 5.3 +//OxCaml +const caml_signbit_float32_bytecode = caml_signbit_float; + +//Provides: caml_expm1_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_expm1_float32_bytecode(x) { + return Math.fround(Math.expm1(x)); +} + +//Provides: caml_exp2_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_exp2_float32_bytecode(x) { + return Math.fround(Math.pow(2, x)); +} + +//Provides: caml_log1p_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_log1p_float32_bytecode(x) { + return Math.fround(Math.log1p(x)); +} + +//Provides: caml_log2_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_log2_float32_bytecode(x) { + return Math.fround(Math.log2(x)); +} + +//Provides: caml_hypot_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_hypot_float32_bytecode(x, y) { + return Math.fround(Math.hypot(x, y)); +} + +//Provides: caml_log10_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_log10_float32_bytecode(x) { + return Math.fround(Math.log10(x)); +} + +//Provides: caml_cosh_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_cosh_float32_bytecode(x) { + return Math.fround(Math.cosh(x)); +} + +//Provides: caml_acosh_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_acosh_float32_bytecode(x) { + return Math.fround(Math.acosh(x)); +} + +//Provides: caml_sinh_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_sinh_float32_bytecode(x) { + return Math.fround(Math.sinh(x)); +} + +//Provides: caml_asinh_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_asinh_float32_bytecode(x) { + return Math.fround(Math.asinh(x)); +} + +//Provides: caml_tanh_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_tanh_float32_bytecode(x) { + return Math.fround(Math.tanh(x)); +} + +//Provides: caml_atanh_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_atanh_float32_bytecode(x) { + return Math.fround(Math.atanh(x)); +} + +//Provides: caml_round_float32_bytecode const +//Requires: caml_round_float +//Alias: caml_simd_float32_round_current_bytecode +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_round_float32_bytecode(x) { + return Math.fround(caml_round_float(x)); +} + +//Provides: caml_cbrt_float32_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_cbrt_float32_bytecode(x) { + return Math.fround(Math.cbrt(x)); +} + +//Provides: caml_erf_float32_bytecode const +//Requires: caml_erf_float +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_erf_float32_bytecode(x) { + return Math.fround(caml_erf_float(x)); +} + +//Provides: caml_erfc_float32_bytecode const +//Requires: caml_erfc_float +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_erfc_float32_bytecode(x) { + return Math.fround(caml_erfc_float(x)); +} + +//Provides: caml_fma_float32_bytecode const +//Requires: caml_fma_float +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_fma_float32_bytecode(x, y, z) { + return Math.fround(caml_fma_float(x, y, z)); +} + +//Provides: caml_simd_float32_min_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_simd_float32_min_bytecode(x, y) { + return Math.min(x, y); +} + +//Provides: caml_simd_float32_max_bytecode const +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_simd_float32_max_bytecode(x, y) { + return Math.max(x, y); +} + +//Provides: caml_simd_cast_float32_int64_bytecode const +//Requires: caml_int64_of_float +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_simd_cast_float32_int64_bytecode(x) { + return caml_int64_of_float(Math.round(x)); +} + //Provides: caml_is_boot_compiler //Version: >= 5.2, < 5.3 //OxCaml diff --git a/runtime/wasm/bigarray.wat b/runtime/wasm/bigarray.wat index 58d1b3984f..c59c43b0ca 100644 --- a/runtime/wasm/bigarray.wat +++ b/runtime/wasm/bigarray.wat @@ -1244,6 +1244,213 @@ (func (export "caml_ba_dim_3") (param (ref eq)) (result (ref eq)) (return_call $caml_ba_dim (local.get 0) (ref.i31 (i32.const 2)))) + (func $caml_ba_float32_get_at_offset + (param $ba (ref $bigarray)) (param $i i32) (result f32) + (local $view (ref extern)) + (local.set $view (struct.get $bigarray $ba_view (local.get $ba))) + (return + (call $dv_get_f32 + (local.get $view) (i32.shl (local.get $i) (i32.const 2)) + (global.get $littleEndian)))) + + (func $caml_ba_float32_set_at_offset + (param $ba (ref $bigarray)) (param $i i32) (param $v f32) + (local $view (ref extern)) + (local $b (ref $float_array)) (local $l i64) + (local.set $view (struct.get $bigarray $ba_view (local.get $ba))) + (call $dv_set_f32 + (local.get $view) (i32.shl (local.get $i) (i32.const 2)) (local.get $v) + (global.get $littleEndian))) + + (func (export "caml_ba_float32_get_1") + (param (ref eq)) (param $i i32) (result f32) + (local $ba (ref $bigarray)) + (local.set $ba (ref.cast (ref $bigarray) (local.get 0))) + (if (struct.get $bigarray $ba_layout (local.get $ba)) + (then (local.set $i (i32.sub (local.get $i) (i32.const 1))))) + (if (i32.ge_u (local.get $i) + (array.get $int_array (struct.get $bigarray $ba_dim (local.get $ba)) + (i32.const 0))) + (then (call $caml_bound_error))) + (return_call $caml_ba_float32_get_at_offset + (local.get $ba) (local.get $i))) + + (func (export "caml_ba_float32_set_1") + (param (ref eq)) (param $i i32) (param $v f32) (result (ref eq)) + (local $ba (ref $bigarray)) + (local.set $ba (ref.cast (ref $bigarray) (local.get 0))) + (if (struct.get $bigarray $ba_layout (local.get $ba)) + (then (local.set $i (i32.sub (local.get $i) (i32.const 1))))) + (if (i32.ge_u (local.get $i) + (array.get $int_array (struct.get $bigarray $ba_dim (local.get $ba)) + (i32.const 0))) + (then (call $caml_bound_error))) + (call $caml_ba_float32_set_at_offset + (local.get $ba) (local.get $i) (local.get $v)) + (ref.i31 (i32.const 0))) + + (func (export "caml_ba_float32_get_2") + (param $vba (ref eq)) (param $i i32) (param $j i32) (result f32) + (local $ba (ref $bigarray)) + (local $offset i32) + (local $dim (ref $int_array)) + (local.set $ba (ref.cast (ref $bigarray) (local.get $vba))) + (local.set $dim (struct.get $bigarray $ba_dim (local.get $ba))) + (if (struct.get $bigarray $ba_layout (local.get $ba)) + (then + (local.set $i (i32.sub (local.get $i) (i32.const 1))) + (local.set $j (i32.sub (local.get $j) (i32.const 1))) + (local.set $offset + (i32.add + (i32.mul (local.get $j) + (array.get $int_array (local.get $dim) (i32.const 0))) + (local.get $i)))) + (else + (local.set $offset + (i32.add + (i32.mul (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 1))) + (local.get $j))))) + (if (i32.or + (i32.ge_u (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 0))) + (i32.ge_u (local.get $j) + (array.get $int_array (local.get $dim) (i32.const 1)))) + (then + (call $caml_bound_error))) + (return_call $caml_ba_float32_get_at_offset + (local.get $ba) (local.get $offset))) + + (func (export "caml_ba_float32_set_2") + (param $vba (ref eq)) (param $i i32) (param $j i32) (param $v f32) + (result (ref eq)) + (local $ba (ref $bigarray)) + (local $offset i32) + (local $dim (ref $int_array)) + (local.set $ba (ref.cast (ref $bigarray) (local.get $vba))) + (local.set $dim (struct.get $bigarray $ba_dim (local.get $ba))) + (if (struct.get $bigarray $ba_layout (local.get $ba)) + (then + (local.set $i (i32.sub (local.get $i) (i32.const 1))) + (local.set $j (i32.sub (local.get $j) (i32.const 1))) + (local.set $offset + (i32.add + (i32.mul (local.get $j) + (array.get $int_array (local.get $dim) (i32.const 0))) + (local.get $i)))) + (else + (local.set $offset + (i32.add + (i32.mul (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 1))) + (local.get $j))))) + (if (i32.or + (i32.ge_u (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 0))) + (i32.ge_u (local.get $j) + (array.get $int_array (local.get $dim) (i32.const 1)))) + (then + (call $caml_bound_error))) + (call $caml_ba_float32_set_at_offset + (local.get $ba) (local.get $offset) (local.get $v)) + (ref.i31 (i32.const 0))) + + (func (export "caml_ba_float32_get_3") + (param $vba (ref eq)) (param $i i32) (param $j i32) (param $k i32) + (result f32) + (local $ba (ref $bigarray)) + (local $offset i32) + (local $dim (ref $int_array)) + (local.set $ba (ref.cast (ref $bigarray) (local.get $vba))) + (local.set $dim (struct.get $bigarray $ba_dim (local.get $ba))) + (if (struct.get $bigarray $ba_layout (local.get $ba)) + (then + (local.set $i (i32.sub (local.get $i) (i32.const 1))) + (local.set $j (i32.sub (local.get $j) (i32.const 1))) + (local.set $k (i32.sub (local.get $k) (i32.const 1))) + (local.set $offset + (i32.add + (i32.mul + (i32.add + (i32.mul + (local.get $k) + (array.get $int_array (local.get $dim) (i32.const 1))) + (local.get $j)) + (array.get $int_array (local.get $dim) (i32.const 0))) + (local.get $i)))) + (else + (local.set $offset + (i32.add + (i32.mul + (i32.add + (i32.mul + (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 1))) + (local.get $j)) + (array.get $int_array (local.get $dim) (i32.const 2))) + (local.get $k))))) + (if (i32.or + (i32.ge_u (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 0))) + (i32.or + (i32.ge_u (local.get $j) + (array.get $int_array (local.get $dim) (i32.const 1))) + (i32.ge_u (local.get $k) + (array.get $int_array (local.get $dim) (i32.const 2))))) + (then + (call $caml_bound_error))) + (return_call $caml_ba_float32_get_at_offset + (local.get $ba) (local.get $offset))) + + (func (export "caml_ba_float32_set_3") + (param $vba (ref eq)) (param $i i32) (param $j i32) (param $k i32) + (param $v f32) + (result (ref eq)) + (local $ba (ref $bigarray)) + (local $offset i32) + (local $dim (ref $int_array)) + (local.set $ba (ref.cast (ref $bigarray) (local.get $vba))) + (local.set $dim (struct.get $bigarray $ba_dim (local.get $ba))) + (if (struct.get $bigarray $ba_layout (local.get $ba)) + (then + (local.set $i (i32.sub (local.get $i) (i32.const 1))) + (local.set $j (i32.sub (local.get $j) (i32.const 1))) + (local.set $k (i32.sub (local.get $k) (i32.const 1))) + (local.set $offset + (i32.add + (i32.mul + (i32.add + (i32.mul + (local.get $k) + (array.get $int_array (local.get $dim) (i32.const 1))) + (local.get $j)) + (array.get $int_array (local.get $dim) (i32.const 0))) + (local.get $i)))) + (else + (local.set $offset + (i32.add + (i32.mul + (i32.add + (i32.mul + (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 1))) + (local.get $j)) + (array.get $int_array (local.get $dim) (i32.const 2))) + (local.get $k))))) + (if (i32.or + (i32.ge_u (local.get $i) + (array.get $int_array (local.get $dim) (i32.const 0))) + (i32.or + (i32.ge_u (local.get $j) + (array.get $int_array (local.get $dim) (i32.const 1))) + (i32.ge_u (local.get $k) + (array.get $int_array (local.get $dim) (i32.const 2))))) + (then + (call $caml_bound_error))) + (call $caml_ba_float32_set_at_offset + (local.get $ba) (local.get $offset) (local.get $v)) + (ref.i31 (i32.const 0))) + (func $caml_ba_offset (param $b (ref $bigarray)) (param $index (ref $int_array)) (result i32) (local $dim (ref $int_array)) diff --git a/runtime/wasm/custom.wat b/runtime/wasm/custom.wat index 526d36ca62..337d0db4a6 100644 --- a/runtime/wasm/custom.wat +++ b/runtime/wasm/custom.wat @@ -16,6 +16,7 @@ ;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. (module + (import "float32" "float32_ops" (global $float32_ops (ref $custom_operations))) (import "int32" "int32_ops" (global $int32_ops (ref $custom_operations))) (import "int32" "nativeint_ops" (global $nativeint_ops (ref $custom_operations))) @@ -137,6 +138,7 @@ (call $caml_register_custom_operations (global.get $nativeint_ops)) (call $caml_register_custom_operations (global.get $int64_ops)) (call $caml_register_custom_operations (global.get $bigarray_ops)) + (call $caml_register_custom_operations (global.get $float32_ops)) (global.set $initialized (i32.const 1))) (func (export "caml_custom_identifier") (param $v (ref eq)) (result (ref eq)) diff --git a/runtime/wasm/float32.wat b/runtime/wasm/float32.wat index 2ba930d0c7..4ac3827d0f 100644 --- a/runtime/wasm/float32.wat +++ b/runtime/wasm/float32.wat @@ -1,4 +1,284 @@ (module + (import "fail" "caml_failwith" + (func $caml_failwith (param (ref eq)))) + (import "marshal" "caml_serialize_int_4" + (func $caml_serialize_int_4 (param (ref eq)) (param i32))) + (import "marshal" "caml_deserialize_int_4" + (func $caml_deserialize_int_4 (param (ref eq)) (result i32))) + (import "float" "caml_float_of_string" + (func $caml_float_of_string (param (ref eq)) (result (ref eq)))) + (import "float" "caml_format_float" + (func $caml_format_float (param (ref eq)) (param (ref eq)) (result (ref eq)))) + (import "float" "caml_fma_float" + (func $caml_fma_float + (param (ref eq) (ref eq) (ref eq)) (result (ref eq)))) + (import "float" "caml_erf_float" + (func $caml_erf_float (param f64) (result f64))) + (import "float" "caml_erfc_float" + (func $caml_erfc_float (param f64) (result f64))) + (import "float" "caml_frexp_float" + (func $caml_frexp_float (param (ref eq)) (result (ref eq)))) + (import "float" "caml_ldexp_float" + (func $caml_ldexp_float (param f64) (param i32) (result f64))) + (import "bigarray" "caml_ba_uint8_get32" + (func $caml_ba_uint8_get32 (param (ref eq)) (param i32) (result i32))) + (import "bigarray" "caml_ba_uint8_set32" + (func $caml_ba_uint8_set32 (param (ref eq)) (param i32) (param i32) (result (ref eq)))) + (import "string" "caml_string_get32" + (func $caml_string_get32 (param (ref eq)) (param i32) (result i32))) + (import "string" "caml_bytes_get32" + (func $caml_bytes_get32 (param (ref eq)) (param i32) (result i32))) + (import "string" "caml_bytes_set32" + (func $caml_bytes_set32 (param (ref eq)) (param i32) (param i32) (result (ref eq)))) + (import "array" "caml_make_vect" + (func $caml_make_vect (param (ref eq)) (param (ref eq)) (result (ref eq)))) + + (type $float (struct (field f64))) + + (func $box_float (param $f f64) (result (ref eq)) + (struct.new $float (local.get $f))) + + (func $unbox_float (param $f (ref eq)) (result f64) + (struct.get $float 0 (ref.cast (ref $float) (local.get $f)))) + + (type $block (array (mut (ref eq)))) + (type $bytes (array (mut i8))) + (type $compare + (func (param (ref eq)) (param (ref eq)) (param i32) (result i32))) + (type $hash + (func (param (ref eq)) (result i32))) + (type $fixed_length (struct (field $bsize_32 i32) (field $bsize_64 i32))) + (type $serialize + (func (param (ref eq)) (param (ref eq)) (result i32) (result i32))) + (type $deserialize (func (param (ref eq)) (result (ref eq)) (result i32))) + (type $dup (func (param (ref eq)) (result (ref eq)))) + (type $custom_operations + (struct + (field $id (ref $bytes)) + (field $compare (ref null $compare)) + (field $compare_ext (ref null $compare)) + (field $hash (ref null $hash)) + (field $fixed_length (ref null $fixed_length)) + (field $serialize (ref null $serialize)) + (field $deserialize (ref null $deserialize)) + (field $dup (ref null $dup)))) + (type $custom (sub (struct (field (ref $custom_operations))))) + + (global $float32_ops (export "float32_ops") (ref $custom_operations) + (struct.new $custom_operations + (@string "_f32") + (ref.func $float32_cmp) + (ref.null $compare) + (ref.func $float32_hash) + (struct.new $fixed_length (i32.const 4) (i32.const 4)) + (ref.func $float32_serialize) + (ref.func $float32_deserialize) + (ref.func $float32_dup))) + + (type $float32 + (sub final $custom (struct (field (ref $custom_operations)) (field f32)))) + + (func $box_float32 (param $f f32) (result (ref eq)) + (struct.new $float32 (global.get $float32_ops) (local.get $f))) + + (func $unbox_float32 (param $f (ref eq)) (result f32) + (struct.get $float32 1 (ref.cast (ref $float32) (local.get $f)))) + + (func $float32_cmp + (param $v1 (ref eq)) (param $v2 (ref eq)) (param i32) (result i32) + (local $x f32) (local $y f32) + (local.set $x (call $unbox_float32 (local.get $v1))) + (local.set $y (call $unbox_float32 (local.get $v2))) + (i32.add + (i32.sub (f32.gt (local.get $x) (local.get $y)) + (f32.lt (local.get $x) (local.get $y))) + (i32.sub (f32.eq (local.get $x) (local.get $x)) + (f32.eq (local.get $y) (local.get $y))))) + + (func $float32_hash (param $v (ref eq)) (result i32) + (i32.reinterpret_f32 (call $unbox_float32 (local.get $v)))) + + (func $float32_serialize + (param $s (ref eq)) (param $v (ref eq)) (result i32) (result i32) + (call $caml_serialize_int_4 (local.get $s) + (i32.reinterpret_f32 (call $unbox_float32 (local.get $v)))) + (tuple.make 2 (i32.const 4) (i32.const 4))) + + (func $float32_deserialize (param $s (ref eq)) (result (ref eq) i32) + (tuple.make 2 + (call $box_float32 + (f32.reinterpret_i32 (call $caml_deserialize_int_4 (local.get $s)))) + (i32.const 4))) + + (func $float32_dup + (param $v (ref eq)) (result (ref eq)) + (local $d (ref $float32)) + (local.set $d (ref.cast (ref $float32) (local.get $v))) + (struct.new $float32 + (struct.get $float32 0 (local.get $d)) + (struct.get $float32 1 (local.get $d)))) + + (func $caml_float_of_float32 + (param $f32 (ref eq)) (result (ref eq)) + (call $box_float (f64.promote_f32 (call $unbox_float32 (local.get $f32))))) + + (func $caml_float32_of_float + (param $f64 (ref eq)) (result (ref eq)) + (call $box_float32 (f32.demote_f64 (call $unbox_float (local.get $f64))))) + + (func (export "caml_round_float32_bytecode") (param $x f32) (result f32) + (local $y f32) + (if (result f32) (f32.ge (local.get $x) (f32.const 0)) + (then + (local.set $y (f32.floor (local.get $x))) + (if (result f32) + (f32.ge (f32.sub (local.get $x) (local.get $y)) (f32.const 0.5)) + (then (f32.add (local.get $y) (f32.const 1))) + (else (local.get $y)))) + (else + (local.set $y (f32.ceil (local.get $x))) + (if (result f32) + (f32.ge (f32.sub (local.get $y) (local.get $x)) (f32.const 0.5)) + (then (f32.sub (local.get $y) (f32.const 1))) + (else (local.get $y)))))) + + (func (export "caml_float32_of_string") (param $s (ref eq)) (result (ref eq)) + (call $caml_float32_of_float (call $caml_float_of_string (local.get $s)))) + + (func (export "caml_format_float32") + (param $s (ref eq)) (param $f (ref eq)) (result (ref eq)) + (call $caml_format_float + (local.get $s) (call $caml_float_of_float32 (local.get $f)))) + + (func (export "caml_float32_compare") + (param $x f32) (param $y f32) (result i32) + (i32.add + (i32.sub (f32.gt (local.get $x) (local.get $y)) + (f32.lt (local.get $x) (local.get $y))) + (i32.sub (f32.eq (local.get $x) (local.get $x)) + (f32.eq (local.get $y) (local.get $y))))) + + (func (export "caml_modf_float32") (param (ref eq)) (result (ref eq)) + (local $x f32) (local $a f32) (local $i f32) (local $f f32) + (local.set $x (call $unbox_float32 (local.get 0))) + (local.set $a (f32.abs (local.get $x))) + (if (f32.ge (local.get $a) (f32.const 0)) + (then + (if (f32.lt (local.get $a) (f32.const inf)) + (then ;; normal + (local.set $i (f32.floor (local.get $a))) + (local.set $f (f32.sub (local.get $a) (local.get $i))) + (local.set $i (f32.copysign (local.get $i) (local.get $x))) + (local.set $f (f32.copysign (local.get $f) (local.get $x)))) + (else ;; infinity + (local.set $i (local.get $x)) + (local.set $f (f32.copysign (f32.const 0) (local.get $x)))))) + (else ;; zero or nan + (local.set $i (local.get $x)) + (local.set $f (local.get $x)))) + (array.new_fixed $block 3 (ref.i31 (i32.const 0)) + (call $box_float32 (local.get $f)) (call $box_float32 (local.get $i)))) + + (func (export "caml_nextafter_float32_bytecode") + (param $x f32) (param $y f32) (result f32) + (local $i i32) (local $j i32) + (if (f32.ne (local.get $x) (local.get $x)) (then (return (local.get $x)))) + (if (f32.ne (local.get $y) (local.get $y)) (then (return (local.get $y)))) + (if (f32.eq (local.get $x) (local.get $y)) + (then (return (local.get 1)))) + (if (result f32) (f32.eq (local.get $x) (f32.const 0)) + (then + (if (f32.ge (local.get $y) (f32.const 0)) + (then (return (f32.const 0x1p-149))) + (else (return (f32.const -0x1p-149))))) + (else + (local.set $i (i32.reinterpret_f32 (local.get $x))) + (local.set $j (i32.reinterpret_f32 (local.get $y))) + (if (i32.and (i32.lt_s (local.get $i) (local.get $j)) + (i32.lt_u (local.get $i) (local.get $j))) + (then (local.set $i (i32.add (local.get $i) (i32.const 1)))) + (else (local.set $i (i32.sub (local.get $i) (i32.const 1))))) + (return (f32.reinterpret_i32 (local.get $i)))))) + + (func (export "caml_classify_float32_bytecode") + (param $a f32) (result i32) + (local.set $a (f32.abs (local.get $a))) + (if (result i32) (f32.ge (local.get $a) (f32.const 0x1p-126)) + (then + (if (result i32) (f32.lt (local.get $a) (f32.const inf)) + (then (i32.const 0)) ;; normal + (else (i32.const 3)))) ;; infinity + (else + (if (result i32) (f32.eq (local.get $a) (f32.const 0)) + (then (i32.const 2)) ;; zero + (else + (if (result i32) (f32.eq (local.get $a) (local.get $a)) + (then (i32.const 1)) ;; subnormal + (else (i32.const 4)))))))) ;; nan + + (func (export "caml_ldexp_float32_bytecode") + (param $x f32) (param $n i32) (result f32) + (f32.demote_f64 + (call $caml_ldexp_float + (f64.promote_f32 (local.get $x)) (local.get $n)))) + + (func (export "caml_frexp_float32") + (param (ref eq)) (result (ref eq)) + (local $frexp (ref $block)) + (local.set $frexp (ref.cast (ref $block) + (call $caml_frexp_float (call $caml_float_of_float32 (local.get 0))))) + (array.new_fixed $block 3 (ref.i31 (i32.const 0)) + (call $caml_float32_of_float + (array.get $block (local.get $frexp) (i32.const 1))) + (array.get $block (local.get $frexp) (i32.const 2)))) + + (func (export "caml_erf_float32_bytecode") (param $x f32) (result f32) + (f32.demote_f64 (call $caml_erf_float (f64.promote_f32 (local.get $x))))) + + (func (export "caml_erfc_float32_bytecode") (param $x f32) (result f32) + (f32.demote_f64 (call $caml_erfc_float (f64.promote_f32 (local.get $x))))) + + (func (export "caml_fma_float32_bytecode") + (param $x (ref eq)) (param $y (ref eq)) (param $z (ref eq)) + (result (ref eq)) + (call $caml_float32_of_float + (call $caml_fma_float + (call $caml_float_of_float32 (local.get $x)) + (call $caml_float_of_float32 (local.get $y)) + (call $caml_float_of_float32 (local.get $z))))) + + (global $zero (ref eq) + (struct.new $float32 (global.get $float32_ops) (f32.const 0))) + + (func (export "caml_make_unboxed_float32_vect_bytecode") + (param (ref eq)) (result (ref eq)) + (call $caml_make_vect (local.get 0) (global.get $zero))) + + (func (export "caml_ba_uint8_getf32") + (param $a (ref eq)) (param $i i32) (result f32) + (f32.reinterpret_i32 + (call $caml_ba_uint8_get32 (local.get $a) (local.get $i)))) + + (func (export "caml_ba_uint8_setf32") + (param $a (ref eq)) (param $i i32) (param $v f32) (result (ref eq)) + (call $caml_ba_uint8_set32 (local.get $a) (local.get $i) + (i32.reinterpret_f32 (local.get $v)))) + + (func (export "caml_string_getf32") + (param $a (ref eq)) (param $i i32) (result f32) + (f32.reinterpret_i32 + (call $caml_string_get32 (local.get $a) (local.get $i)))) + + (func (export "caml_bytes_getf32") + (param $a (ref eq)) (param $i i32) (result f32) + (f32.reinterpret_i32 + (call $caml_bytes_get32 (local.get $a) (local.get $i)))) + + (func (export "caml_bytes_setf32") + (param $a (ref eq)) (param $i i32) (param $v f32) (result (ref eq)) + (call $caml_bytes_set32 (local.get $a) (local.get $i) + (i32.reinterpret_f32 (local.get $v)))) + (func (export "caml_is_boot_compiler") (param (ref eq)) (result (ref eq)) (ref.i31 (i32.const 0))) ) From 34eb6f3967cc9edf29a47bd4251668f9eec6e730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 1 Oct 2025 20:59:54 +0200 Subject: [PATCH 28/36] Oxcaml runtime: GC primitives --- compiler/tests-check-prim/main.5.2.output | 7 +++++ .../tests-check-prim/unix-Unix.5.2.output | 7 +++++ .../tests-check-prim/unix-Win32.5.2.output | 7 +++++ runtime/js/gc.js | 29 +++++++++++++++++-- 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/compiler/tests-check-prim/main.5.2.output b/compiler/tests-check-prim/main.5.2.output index 5b2b254be9..dcf53c97d8 100644 --- a/compiler/tests-check-prim/main.5.2.output +++ b/compiler/tests-check-prim/main.5.2.output @@ -107,6 +107,13 @@ caml_fs_init jsoo_create_file jsoo_create_file_extern +From +gc.js: +caml_eventlog_pause +caml_eventlog_resume +caml_gc_tweak_get +caml_gc_tweak_list_active +caml_gc_tweak_set + From +graphics.js: caml_gr_arc_aux caml_gr_blit_image diff --git a/compiler/tests-check-prim/unix-Unix.5.2.output b/compiler/tests-check-prim/unix-Unix.5.2.output index dac9cbf7b4..bd813f088a 100644 --- a/compiler/tests-check-prim/unix-Unix.5.2.output +++ b/compiler/tests-check-prim/unix-Unix.5.2.output @@ -183,6 +183,13 @@ caml_fs_init jsoo_create_file jsoo_create_file_extern +From +gc.js: +caml_eventlog_pause +caml_eventlog_resume +caml_gc_tweak_get +caml_gc_tweak_list_active +caml_gc_tweak_set + From +graphics.js: caml_gr_arc_aux caml_gr_blit_image diff --git a/compiler/tests-check-prim/unix-Win32.5.2.output b/compiler/tests-check-prim/unix-Win32.5.2.output index 609e30356d..042813431f 100644 --- a/compiler/tests-check-prim/unix-Win32.5.2.output +++ b/compiler/tests-check-prim/unix-Win32.5.2.output @@ -156,6 +156,13 @@ caml_fs_init jsoo_create_file jsoo_create_file_extern +From +gc.js: +caml_eventlog_pause +caml_eventlog_resume +caml_gc_tweak_get +caml_gc_tweak_list_active +caml_gc_tweak_set + From +graphics.js: caml_gr_arc_aux caml_gr_blit_image diff --git a/runtime/js/gc.js b/runtime/js/gc.js index 183473f4a7..14794b36a4 100644 --- a/runtime/js/gc.js +++ b/runtime/js/gc.js @@ -87,13 +87,15 @@ function caml_memprof_discard(_t) { } //Provides: caml_eventlog_resume -//Version: < 5.0 +//Version: <= 5.2 +//(actually < 5.0, but OxCaml still references it) function caml_eventlog_resume(_unit) { return 0; } //Provides: caml_eventlog_pause -//Version: < 5.0 +//Version: <= 5.2 +//(actually < 5.0, but OxCaml still references it) function caml_eventlog_pause(_unit) { return 0; } @@ -130,3 +132,26 @@ function caml_get_major_bucket(_n) { function caml_get_major_credit(_n) { return 0; } + +//Provides: caml_gc_tweak_get +//Requires: caml_invalid_argument +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_gc_tweak_get(_name) { + caml_invalid_argument("Gc.Tweak: parameter not found"); +} + +//Provides: caml_gc_tweak_set +//Requires: caml_invalid_argument +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_gc_tweak_set(_name, _value) { + caml_invalid_argument("Gc.Tweak: parameter not found"); +} + +//Provides: caml_gc_tweak_list_active +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_gc_tweak_list_active(_unit) { + return 0; +} From 3f1af0d265f2408e2459fad656f77aa06b0f8dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 14:57:07 +0200 Subject: [PATCH 29/36] OxCaml: add tests --- compiler/tests-ocaml/lib-float32/dune | 5 + .../tests-ocaml/lib-float32/test.expected | 93 ++++++++++ compiler/tests-ocaml/lib-float32/test.ml | 150 ++++++++++++++++ compiler/tests-ocaml/lib-or-null/dune | 5 + .../tests-ocaml/lib-or-null/more_tests.ml | 160 ++++++++++++++++++ .../tests-ocaml/lib-or-null/test.expected | 1 + compiler/tests-ocaml/lib-or-null/test.ml | 113 +++++++++++++ 7 files changed, 527 insertions(+) create mode 100644 compiler/tests-ocaml/lib-float32/dune create mode 100644 compiler/tests-ocaml/lib-float32/test.expected create mode 100644 compiler/tests-ocaml/lib-float32/test.ml create mode 100644 compiler/tests-ocaml/lib-or-null/dune create mode 100644 compiler/tests-ocaml/lib-or-null/more_tests.ml create mode 100644 compiler/tests-ocaml/lib-or-null/test.expected create mode 100644 compiler/tests-ocaml/lib-or-null/test.ml diff --git a/compiler/tests-ocaml/lib-float32/dune b/compiler/tests-ocaml/lib-float32/dune new file mode 100644 index 0000000000..9c7b3cb8e1 --- /dev/null +++ b/compiler/tests-ocaml/lib-float32/dune @@ -0,0 +1,5 @@ +(tests + (names test) + (build_if %{oxcaml_supported}) + (libraries stdlib_stable) + (modes js wasm)) diff --git a/compiler/tests-ocaml/lib-float32/test.expected b/compiler/tests-ocaml/lib-float32/test.expected new file mode 100644 index 0000000000..ee7db7c64a --- /dev/null +++ b/compiler/tests-ocaml/lib-float32/test.expected @@ -0,0 +1,93 @@ +001: OK +002: OK +003: OK +004: OK +005: OK +006: OK +007: OK +008: OK +009: OK +010: OK +011: OK +012: OK +013: OK +014: OK +015: OK +016: OK +017: OK +018: OK +019: OK +020: OK +021: OK +022: OK +023: OK +024: OK +025: OK +026: OK +027: OK +028: OK +029: OK +030: OK +031: OK +032: OK +033: OK +034: OK +035: OK +036: OK +037: OK +038: OK +039: OK +040: OK +041: OK +042: OK +043: OK +044: OK +045: OK +046: OK +047: OK +048: OK +049: OK +050: OK +051: OK +052: OK +053: OK +054: OK +055: OK +056: OK +057: OK +058: OK +059: OK +060: OK +061: OK +062: OK +063: OK +064: OK +065: OK +066: OK +067: OK +068: OK +069: OK +070: OK +071: OK +072: OK +073: OK +074: OK +075: OK +076: OK +077: OK +078: OK +079: OK +080: OK +081: OK +082: OK +083: OK +084: OK +085: OK +086: OK +087: OK +088: OK +089: OK +090: OK +091: OK +092: OK +093: OK diff --git a/compiler/tests-ocaml/lib-float32/test.ml b/compiler/tests-ocaml/lib-float32/test.ml new file mode 100644 index 0000000000..4a54bbe281 --- /dev/null +++ b/compiler/tests-ocaml/lib-float32/test.ml @@ -0,0 +1,150 @@ +(* TEST *) + +open Stdlib_stable +open Float32.Operators + +let is_nan2 (x, y) = Float32.is_nan x && Float32.is_nan y + +type test = True of (unit -> bool) + | False of (unit -> bool) + | Equal of ((unit -> float32) * float32) + | Pair of ((unit -> float32 * float32) * (float32 * float32)) + +let cases = [ + ( 1, True (fun () -> Float32.is_finite 1.s)); + ( 2, True (fun () -> Float32.is_finite Float32.pi)); + ( 3, False(fun () -> Float32.is_finite Float32.infinity)); + ( 4, False(fun () -> Float32.is_finite Float32.nan)); + ( 5, True (fun () -> Float32.is_infinite Float32.infinity)); + ( 6, False(fun () -> Float32.is_infinite 1.s)); + ( 7, False(fun () -> Float32.is_infinite Float32.nan)); + ( 8, True (fun () -> Float32.is_nan Float32.nan)); + ( 9, False(fun () -> Float32.is_nan 1.s)); + (10, False(fun () -> Float32.is_nan Float32.neg_infinity)); + (11, True (fun () -> Float32.is_integer 1.s)); + (12, True (fun () -> Float32.is_integer (-1e10s))); + (13, False(fun () -> Float32.is_integer 1.5s)); + (14, False(fun () -> Float32.is_integer Float32.infinity)); + (15, False(fun () -> Float32.is_integer Float32.nan)); + + (16, Equal((fun () -> Float32.trunc 1.5s), 1.s)); + (17, Equal((fun () -> Float32.trunc (-1.5s)), -1.s)); + (18, Equal(Float32.((fun () -> trunc infinity), Float32.infinity))); + (19, Equal(Float32.(((fun () -> trunc neg_infinity), Float32.neg_infinity)))); + (20, True (fun () -> Float32.(is_nan(trunc nan)))); + + (21, Equal((fun () -> Float32.round 0.5s), 1.s)); + (22, Equal((fun () -> Float32.round (-0.5s)), -1.s)); + (23, Equal((fun () -> Float32.round 1.5s), 2.s)); + (24, Equal((fun () -> Float32.round (-1.5s)), -2.s)); + (25, let x = 0x1.000002p+23s in (* x + 0.5 rounds to x +. 1. *) + Equal((fun () -> Float32.round x), x)); + (26, Equal((fun () -> Float32.round (Float32.next_after 0.5s 0.s)), 0.s)); + + (27, Equal(Float32.((fun () -> round infinity), Float32.infinity))); + (28, Equal(Float32.((fun () -> round neg_infinity), Float32.neg_infinity))); + (29, True (fun () -> Float32.(is_nan(round nan)))); + + (30, Equal((fun () -> Float32.next_after 0x1.FFFFFEp-2s 1.s), 0.5s)); + (31, Equal((fun () -> Float32.next_after 0x1.FFFFFEp-2s 0.s), 0x1.FFFFFCp-2s)); + (32, Equal(Float32.((fun () -> next_after 0x1.FFFFFEp-2s infinity), 0.5s))); + (33, Equal(Float32.((fun () -> next_after 0x1.FFFFFEp-2s neg_infinity), 0x1.FFFFFCp-2s))); + (34, Equal((fun () -> Float32.next_after 1.s 1.s), 1.s)); + (35, True (fun () -> Float32.(is_nan(next_after nan 1.s)))); + (36, True (fun () -> Float32.(is_nan(next_after 3.s nan)))); + + (37, Equal(Float32.((fun () -> succ 0x1.FFFFFEp-2s), 0.5s))); + (38, Equal(Float32.((fun () -> pred 0.5s), 0x1.FFFFFEp-2s))); + (39, True (Float32.(fun () -> succ 0.s > 0.s))); + (40, True (Float32.(fun () -> pred 0.s < 0.s))); + (41, Equal(Float32.((fun () -> succ max_float), infinity))); + (42, Equal(Float32.((fun () -> pred (-. max_float)), neg_infinity))); + (43, True (Float32.(fun () -> succ 0.s < min_float))); + (44, Equal(Float32.((fun () -> succ infinity), infinity))); + (45, Equal(Float32.((fun () -> pred neg_infinity), neg_infinity))); + (46, True (Float32.(fun () -> is_nan(succ nan)))); + (47, True (Float32.(fun () -> is_nan(pred nan)))); + + (48, False(fun () -> Float32.sign_bit 1.s)); + (49, True (fun () -> Float32.sign_bit (-1.s))); + (50, False(fun () -> Float32.sign_bit 0.s)); + (51, True (fun () -> Float32.sign_bit (-0.s))); + (52, False(fun () -> Float32.sign_bit Float32.infinity)); + (53, True (fun () -> Float32.sign_bit Float32.neg_infinity)); + + (54, Equal((fun () -> Float32.min 1.s 2.s), 1.s)); + (55, Equal((fun () -> Float32.min 2.s 1.s), 1.s)); + (56, True (fun () -> Float32.(is_nan(min 1.s nan)))); + (57, True (fun () -> Float32.(is_nan(min nan 2.s)))); + (58, True (fun () -> Float32.(is_nan(min nan nan)))); + (59, Equal((fun () -> 1.s /. Float32.min (-0.s) (+0.s)), Float32.neg_infinity)); + (60, Equal((fun () -> 1.s /. Float32.min (+0.s) (-0.s)), Float32.neg_infinity)); + + (61, Equal((fun () -> Float32.max 1.s 2.s), 2.s)); + (62, Equal((fun () -> Float32.max 2.s 1.s), 2.s)); + (63, True (fun () -> Float32.(is_nan(max 1.s nan)))); + (64, True (fun () -> Float32.(is_nan(max nan 2.s)))); + (65, True (fun () -> Float32.(is_nan(max nan nan)))); + (66, Equal((fun () -> 1.s /. Float32.max (-0.s) (+0.s)), Float32.infinity)); + (67, Equal((fun () -> 1.s /. Float32.max (+0.s) (-0.s)), Float32.infinity)); + + (68, Pair ((fun () -> Float32.min_max 1.s 2.s), (1.s, 2.s))); + (69, Pair ((fun () -> Float32.min_max 2.s 1.s), (1.s, 2.s))); + (70, True (fun () -> Float32.(is_nan2(min_max 1.s nan)))); + (71, True (fun () -> Float32.(is_nan2(min_max nan 2.s)))); + (72, True (fun () -> Float32.(is_nan2(min_max nan nan)))); + (73, Pair ((fun () -> let x, y = Float32.min_max (-0.s) (+0.s) in + (1.s /. x, 1.s /. y)), + (Float32.neg_infinity, Float32.infinity))); + (74, Pair ((fun () -> let x, y = Float32.min_max (+0.s) (-0.s) in + (1.s /. x, 1.s /. y)), + (Float32.neg_infinity, Float32.infinity))); + + (75, Equal((fun () -> Float32.min_num 1.s 2.s), 1.s)); + (76, Equal(Float32.((fun () -> min_num 1.s nan), 1.s))); + (77, Equal(Float32.((fun () -> min_num nan 2.s), 2.s))); + (78, True (fun () -> Float32.(is_nan(min_num nan nan)))); + (79, Equal((fun () -> 1.s /. Float32.min_num (-0.s) (+0.s)), Float32.neg_infinity)); + (80, Equal((fun () -> 1.s /. Float32.min_num (+0.s) (-0.s)), Float32.neg_infinity)); + + (81, Equal((fun () -> Float32.max_num 1.s 2.s), 2.s)); + (82, Equal(Float32.((fun () -> max_num 1.s nan), 1.s))); + (83, Equal(Float32.((fun () -> max_num nan 2.s), 2.s))); + (84, True (fun () -> Float32.(is_nan(max_num nan nan)))); + (85, Equal((fun () -> 1.s /. Float32.max_num (-0.s) (+0.s)), Float32.infinity)); + (86, Equal((fun () -> 1.s /. Float32.max_num (+0.s) (-0.s)), Float32.infinity)); + + (87, Pair ((fun () -> Float32.min_max_num 1.s 2.s), (1.s, 2.s))); + (88, Pair ((fun () -> Float32.min_max_num 2.s 1.s), (1.s, 2.s))); + (89, Pair ((fun () -> Float32.(min_max_num 1.s nan)), (1.s, 1.s))); + (90, Pair ((fun () -> Float32.(min_max_num nan 1.s)), (1.s, 1.s))); + (91, True (fun () -> Float32.(is_nan2(min_max_num nan nan)))); + (92, Pair ((fun () -> let x, y = Float32.min_max_num (-0.s) (+0.s) in + (1.s /. x, 1.s /. y)), + (Float32.neg_infinity, Float32.infinity))); + (93, Pair ((fun () -> let x, y = Float32.min_max_num (+0.s) (-0.s) in + (1.s /. x, 1.s /. y)), + (Float32.neg_infinity, Float32.infinity))); +] + +let () = + let f (n, test) = + match test with + | True p -> + Printf.printf "%03d: %s\n%!" n (if p () then "OK" else "FAIL") + | False p -> + Printf.printf "%03d: %s\n%!" n (if p () then "FAIL" else "OK") + | Equal (f, result) -> + let v = f () in + if v = result then + Printf.printf "%03d: OK\n%!" n + else + Printf.printf "%03d: FAIL (%h returned instead of %h)\n%!" n (Float32.to_float v) (Float32.to_float result) + | Pair (f, ((l', r') as result)) -> + let (l, r) as v = f () in + if v = result then + Printf.printf "%03d: OK\n%!" n + else + Printf.printf "%03d: FAIL ((%h, %h) returned instead of (%h, %h))\n%!" n (Float32.to_float l) (Float32.to_float r) (Float32.to_float l') (Float32.to_float r') + in + List.iter f cases diff --git a/compiler/tests-ocaml/lib-or-null/dune b/compiler/tests-ocaml/lib-or-null/dune new file mode 100644 index 0000000000..3786c1708f --- /dev/null +++ b/compiler/tests-ocaml/lib-or-null/dune @@ -0,0 +1,5 @@ +(tests + (names test more_tests) + (build_if %{oxcaml_supported}) + (libraries stdlib_stable) + (modes js wasm)) diff --git a/compiler/tests-ocaml/lib-or-null/more_tests.ml b/compiler/tests-ocaml/lib-or-null/more_tests.ml new file mode 100644 index 0000000000..ed80cf6574 --- /dev/null +++ b/compiler/tests-ocaml/lib-or-null/more_tests.ml @@ -0,0 +1,160 @@ +(* TEST +*) + +let x = Null + +let () = + match x with + | Null -> () + | This _ -> assert false +;; + +let y = This 3 + +let () = + match y with + | This 3 -> () + | _ -> assert false +;; + + +external int_as_pointer : int -> int or_null = "%int_as_pointer" + +let n = int_as_pointer 0 + +let () = + match n with + | Null -> () + | _ -> assert false +;; + +external int_as_int : int -> int or_null = "%opaque" + +let m = int_as_int 5 + +let () = + match m with + | This 5 -> () + | This _ -> assert false + | Null -> assert false +;; + +let x = (Null, This "bar") + +let () = + match x with + | Null, This "foo" -> assert false + | Null, This "bar" -> () + | _, This "bar" -> assert false + | Null, _ -> assert false + | _, _ -> assert false +;; + +let y a = fun () -> This a + +let d = y 5 + +let () = + match d () with + | This 5 -> () + | _ -> assert false +;; + +let z = Marshal.to_bytes (This "foo") [] + +let () = + match Marshal.from_bytes z 0 with + | This "foo" -> () + | This _ -> assert false + | Null -> assert false +;; + +let w = Marshal.to_bytes Null [] + +let () = + match Marshal.from_bytes w 0 with + | Null -> () + | This _ -> assert false +;; + +external evil : 'a or_null -> 'a = "%opaque" + +let e = This (evil Null) + +let () = + match e with + | Null -> () + | This _ -> assert false +;; + +let e' = evil (This 4) + +let () = + match e' with + | 4 -> () + | _ -> assert false +;; + +let f a = fun () -> + match a with + | This x -> x ^ "bar" + | Null -> "foo" +;; + +let g = f (This "xxx") + +let () = + match g () with + | "xxxbar" -> () + | _ -> assert false +;; + +let h = f Null + +let () = + match h () with + | "foo" -> () + | _ -> assert false +;; + +let x = ref Null + +let () = + match !x with + | Null -> () + | _ -> assert false +;; + +let () = x := This "foo" + +let () = + match !x with + | This "foo" -> () + | _ -> assert false +;; + +let () = x := Null + +let () = + match !x with + | Null -> () + | _ -> assert false +;; + +let () = + assert (Null = Null); + assert (This 4 = This 4); + assert (Null <> This 4); + assert (This 8 <> Null); + assert (This 4 <> This 5); +;; + +let () = + assert (compare Null Null = 0); + assert (compare (This 4) (This 4) = 0); + assert (compare Null (This 4) < 0); + assert (compare (This 8) Null > 0); + assert (compare (This 4) (This 5) < 0); + assert (compare (This "abc") (This "xyz") <> 0); + assert (compare (This "xyz") (This "xyz") = 0); +;; diff --git a/compiler/tests-ocaml/lib-or-null/test.expected b/compiler/tests-ocaml/lib-or-null/test.expected new file mode 100644 index 0000000000..d86bac9de5 --- /dev/null +++ b/compiler/tests-ocaml/lib-or-null/test.expected @@ -0,0 +1 @@ +OK diff --git a/compiler/tests-ocaml/lib-or-null/test.ml b/compiler/tests-ocaml/lib-or-null/test.ml new file mode 100644 index 0000000000..00df7b0da0 --- /dev/null +++ b/compiler/tests-ocaml/lib-or-null/test.ml @@ -0,0 +1,113 @@ +(* TEST *) + +open Stdlib_stable + +let strf = Printf.sprintf +let assert_raise_invalid_argument f v = + assert (try ignore (f v); false with Invalid_argument _ -> true); + () + +let test_null_this () = + assert (Or_null.null = Null); + assert (Or_null.this 2 = This 2); + () + +let test_value () = + assert (Or_null.value Null ~default:5 = 5); + assert (Or_null.value (This 3) ~default:5 = 3); + () + +let test_get () = + assert_raise_invalid_argument Or_null.get Null; + assert (Or_null.get (This 2) = 2); + () + +let test_bind () = + assert (Or_null.bind (This 3) (fun x -> This (succ x)) = This 4); + assert (Or_null.bind (This 3) (fun _ -> Null) = Null); + assert (Or_null.bind Null (fun x -> This (succ x)) = Null); + assert (Or_null.bind Null (fun _ -> Null) = Null); + () + +let test_map () = + assert (Or_null.map succ (This 3) = This 4); + assert (Or_null.map succ Null = Null); + () + +let test_fold () = + assert (Or_null.fold ~null:3 ~this:succ (This 1) = 2); + assert (Or_null.fold ~null:3 ~this:succ Null = 3); +(* + assert (Or_null.(fold ~null ~this) (This 1) = (This 1)); + assert (Or_null.(fold ~null ~this) Null = Null); +*) + () + +let test_iter () = + let count = ref 0 in + let set_count x = count := x in + assert (!count = 0); + Or_null.iter set_count (This 2); assert (!count = 2); + Or_null.iter set_count Null; assert (!count = 2); + () + +let test_is_null_this () = + assert (Or_null.is_null Null = true); + assert (Or_null.is_this Null = false); + assert (Or_null.is_null (This 2) = false); + assert (Or_null.is_this (This 2) = true); + () + +let test_equal () = + let eq v0 v1 = (v0 mod 2) = (v1 mod 2) in + let equal = Or_null.equal eq in + assert (not @@ equal (This 2) (This 3)); + assert ( equal (This 2) (This 4)); + assert (not @@ equal (This 2) Null); + assert (not @@ equal Null (This 3)); + assert (not @@ equal Null (This 4)); + assert ( equal Null Null); + () + +let test_compare () = + let compare v0 v1 = - (compare v0 v1) in + let compare = Or_null.compare compare in + assert (compare (This 2) (This 1) = -1); + assert (compare (This 2) (This 2) = 0); + assert (compare (This 2) (This 3) = 1); + assert (compare (This 2) Null = 1); + assert (compare Null (This 1) = -1); + assert (compare Null (This 2) = -1); + assert (compare Null (This 3) = -1); + assert (compare Null Null = 0); + () + +let test_to_option_list_seq () = + assert (Or_null.to_result ~null:6 (This 3) = Ok 3); + assert (Or_null.to_result ~null:6 Null = Error 6); + assert (Or_null.to_list (This 3) = [3]); + assert (Or_null.to_list Null = []); + begin match (Or_null.to_seq (This 3)) () with + | Seq.Cons (3, f) -> assert (f () = Seq.Nil) + | _ -> assert false + end; + assert ((Or_null.to_seq Null) () = Seq.Nil); + () + +let tests () = + test_null_this (); + test_value (); + test_get (); + test_bind (); + test_map (); + test_fold (); + test_iter (); + test_is_null_this (); + test_equal (); + test_compare (); + test_to_option_list_seq (); + () + +let () = + tests (); + print_endline "OK" From bdd1ecbf57de2999ab2c0429cf5d05fb283d79cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 23:43:13 +0200 Subject: [PATCH 30/36] FIX --- lib/lwt/graphics/graphics_js.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/lwt/graphics/graphics_js.ml b/lib/lwt/graphics/graphics_js.ml index 516099a576..c68d08bf2d 100644 --- a/lib/lwt/graphics/graphics_js.ml +++ b/lib/lwt/graphics/graphics_js.ml @@ -28,7 +28,10 @@ end type context = context_ Js.t -let _ = Callback.register_exception "Graphics.Graphic_failure" (Graphic_failure "") +let _ = + (Callback.register_exception + "Graphics.Graphic_failure" [@ocaml.alert "-unsafe_multidomain"]) + (Graphic_failure "") let ( >>= ) = Lwt.bind From ca665c3abe8029d9d5c424bf45dee35e11b7347a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 1 Oct 2025 00:07:02 +0200 Subject: [PATCH 31/36] Keep opam-dune-lint happy --- dune-project | 22 +++++++++++++++++++--- js_of_ocaml-compiler.opam | 1 + js_of_ocaml-lwt.opam | 1 + js_of_ocaml-ppx.opam | 1 + js_of_ocaml-ppx_deriving_json.opam | 1 + js_of_ocaml-toplevel.opam | 1 + js_of_ocaml-tyxml.opam | 1 + js_of_ocaml.opam | 1 + wasm_of_ocaml-compiler.opam | 1 + 9 files changed, 27 insertions(+), 3 deletions(-) diff --git a/dune-project b/dune-project index 565a62d5e5..caaf04e5a1 100644 --- a/dune-project +++ b/dune-project @@ -29,7 +29,9 @@ menhir menhirLib menhirSdk - (yojson (>= 2.1))) + (yojson (>= 2.1)) + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never")))) (depopts ocamlfind) (conflicts @@ -49,7 +51,9 @@ (lwt (and (>= 2.4.4) (<> 5.9.2))) (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) - (re (and (>= 1.9.0) :with-test))) + (re (and (>= 1.9.0) :with-test)) + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never")))) (depopts graphics lwt_log @@ -67,6 +71,8 @@ (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) (re (and (>= 1.9.0) :with-test)) + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never"))) )) (package @@ -81,6 +87,8 @@ (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) (re (and (>= 1.9.0) :with-test)) + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never"))) )) (package @@ -98,6 +106,8 @@ (ppx_expect (and (>= v0.14.2) :with-test)) (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (re (and (>= 1.9.0) :with-test)) + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never"))) )) (package @@ -115,6 +125,8 @@ (num :with-test) (ppx_expect (and (>= v0.14.2) :with-test)) (re (and (>= 1.9.0) :with-test)) + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never"))) )) (package @@ -129,6 +141,8 @@ (ppx_expect (and (>= v0.14.2) :with-test)) (ppxlib (or (>= 0.35) (= 0.33.0+ox))) (re (and (>= 1.9.0) :with-test)) + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never"))) )) (package @@ -150,7 +164,9 @@ menhirLib menhirSdk (yojson (>= 2.1)) - binaryen-bin) + binaryen-bin + ; Keep opam-dune-lint happy + (stdlib_stable (and :with-test (= os "never")))) (depopts ocamlfind) (conflicts diff --git a/js_of_ocaml-compiler.opam b/js_of_ocaml-compiler.opam index b2d59bc463..fffbf03b61 100644 --- a/js_of_ocaml-compiler.opam +++ b/js_of_ocaml-compiler.opam @@ -25,6 +25,7 @@ depends: [ "menhirLib" "menhirSdk" "yojson" {>= "2.1"} + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] depopts: ["ocamlfind"] diff --git a/js_of_ocaml-lwt.opam b/js_of_ocaml-lwt.opam index f40e8cdfa3..e5e5f0d2cc 100644 --- a/js_of_ocaml-lwt.opam +++ b/js_of_ocaml-lwt.opam @@ -20,6 +20,7 @@ depends: [ "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} "re" {>= "1.9.0" & with-test} + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] depopts: ["graphics" "lwt_log"] diff --git a/js_of_ocaml-ppx.opam b/js_of_ocaml-ppx.opam index c445a24bbc..b3e5a5c256 100644 --- a/js_of_ocaml-ppx.opam +++ b/js_of_ocaml-ppx.opam @@ -19,6 +19,7 @@ depends: [ "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} "re" {>= "1.9.0" & with-test} + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] dev-repo: "git+https://github.com/ocsigen/js_of_ocaml.git" diff --git a/js_of_ocaml-ppx_deriving_json.opam b/js_of_ocaml-ppx_deriving_json.opam index c445a24bbc..b3e5a5c256 100644 --- a/js_of_ocaml-ppx_deriving_json.opam +++ b/js_of_ocaml-ppx_deriving_json.opam @@ -19,6 +19,7 @@ depends: [ "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} "re" {>= "1.9.0" & with-test} + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] dev-repo: "git+https://github.com/ocsigen/js_of_ocaml.git" diff --git a/js_of_ocaml-toplevel.opam b/js_of_ocaml-toplevel.opam index f030a01aba..c260edc52a 100644 --- a/js_of_ocaml-toplevel.opam +++ b/js_of_ocaml-toplevel.opam @@ -21,6 +21,7 @@ depends: [ "ppx_expect" {>= "v0.14.2" & with-test} "ppxlib" {>= "0.35" | = "0.33.0+ox"} "re" {>= "1.9.0" & with-test} + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] dev-repo: "git+https://github.com/ocsigen/js_of_ocaml.git" diff --git a/js_of_ocaml-tyxml.opam b/js_of_ocaml-tyxml.opam index 2ce14c0b6b..420bd190c0 100644 --- a/js_of_ocaml-tyxml.opam +++ b/js_of_ocaml-tyxml.opam @@ -22,6 +22,7 @@ depends: [ "num" {with-test} "ppx_expect" {>= "v0.14.2" & with-test} "re" {>= "1.9.0" & with-test} + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] dev-repo: "git+https://github.com/ocsigen/js_of_ocaml.git" diff --git a/js_of_ocaml.opam b/js_of_ocaml.opam index 8b82fb9fdc..654c6adc47 100644 --- a/js_of_ocaml.opam +++ b/js_of_ocaml.opam @@ -19,6 +19,7 @@ depends: [ "ppx_expect" {>= "v0.14.2" & with-test} "ppxlib" {>= "0.35" | = "0.33.0+ox"} "re" {>= "1.9.0" & with-test} + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] dev-repo: "git+https://github.com/ocsigen/js_of_ocaml.git" diff --git a/wasm_of_ocaml-compiler.opam b/wasm_of_ocaml-compiler.opam index 5628522b2f..72dd250568 100644 --- a/wasm_of_ocaml-compiler.opam +++ b/wasm_of_ocaml-compiler.opam @@ -27,6 +27,7 @@ depends: [ "menhirSdk" "yojson" {>= "2.1"} "binaryen-bin" + "stdlib_stable" {with-test & "os" = "never"} "odoc" {with-doc} ] depopts: ["ocamlfind"] From d579095d25adf49f31b4f700f4a1662172550ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 1 Oct 2025 22:15:40 +0200 Subject: [PATCH 32/36] OxCaml: add primitive caml_with_async_exns --- compiler/bin-js_of_ocaml/js_of_ocaml.ml | 2 ++ compiler/bin-jsoo_minify/jsoo_minify.ml | 2 +- compiler/bin-wasm_of_ocaml/wasm_of_ocaml.ml | 2 ++ compiler/lib/stdlib.ml | 4 ++++ compiler/tests-check-prim/main.5.2.output | 1 + compiler/tests-check-prim/unix-Unix.5.2.output | 1 + compiler/tests-check-prim/unix-Win32.5.2.output | 1 + runtime/js/stdlib.js | 8 ++++++++ runtime/wasm/stdlib.wat | 3 +++ 9 files changed, 23 insertions(+), 1 deletion(-) diff --git a/compiler/bin-js_of_ocaml/js_of_ocaml.ml b/compiler/bin-js_of_ocaml/js_of_ocaml.ml index f4203025e8..40ecbf79f3 100644 --- a/compiler/bin-js_of_ocaml/js_of_ocaml.ml +++ b/compiler/bin-js_of_ocaml/js_of_ocaml.ml @@ -43,6 +43,8 @@ let () = | _ -> argv in try + with_async_exns + @@ fun () -> match Cmdliner.Cmd.eval_value ~catch:false diff --git a/compiler/bin-jsoo_minify/jsoo_minify.ml b/compiler/bin-jsoo_minify/jsoo_minify.ml index f636ab50e0..e878479574 100644 --- a/compiler/bin-jsoo_minify/jsoo_minify.ml +++ b/compiler/bin-jsoo_minify/jsoo_minify.ml @@ -92,7 +92,7 @@ let main = Cmdliner.Cmd.v Cmd_arg.info t let (_ : int) = - try Cmdliner.Cmd.eval ~catch:false ~argv:Sys.argv main with + try with_async_exns @@ fun () -> Cmdliner.Cmd.eval ~catch:false ~argv:Sys.argv main with | (Match_failure _ | Assert_failure _ | Not_found) as exc -> let backtrace = Printexc.get_backtrace () in Format.eprintf diff --git a/compiler/bin-wasm_of_ocaml/wasm_of_ocaml.ml b/compiler/bin-wasm_of_ocaml/wasm_of_ocaml.ml index bc87e9ba75..adbd615f24 100644 --- a/compiler/bin-wasm_of_ocaml/wasm_of_ocaml.ml +++ b/compiler/bin-wasm_of_ocaml/wasm_of_ocaml.ml @@ -41,6 +41,8 @@ let () = | _ -> argv in try + with_async_exns + @@ fun () -> match Cmdliner.Cmd.eval_value ~catch:false diff --git a/compiler/lib/stdlib.ml b/compiler/lib/stdlib.ml index a7a8a17703..84ea904363 100644 --- a/compiler/lib/stdlib.ml +++ b/compiler/lib/stdlib.ml @@ -1228,3 +1228,7 @@ module Lexing = struct Printf.sprintf "File \"%s\", line %d, characters %d-%d:\n" file line char1 char2 (* use [char1 + 1] and [char2 + 1] if *not* using Caml mode *) end + +let with_async_exns = Sys.with_async_exns [@@if oxcaml] + +let with_async_exns f = f () [@@if not oxcaml] diff --git a/compiler/tests-check-prim/main.5.2.output b/compiler/tests-check-prim/main.5.2.output index dcf53c97d8..f5d069ee36 100644 --- a/compiler/tests-check-prim/main.5.2.output +++ b/compiler/tests-check-prim/main.5.2.output @@ -202,6 +202,7 @@ caml_runtime_events_user_resolve From +stdlib.js: caml_is_printable caml_maybe_print_stats +caml_with_async_exns From +sys.js: caml_fatal_uncaught_exception diff --git a/compiler/tests-check-prim/unix-Unix.5.2.output b/compiler/tests-check-prim/unix-Unix.5.2.output index bd813f088a..be15b79a27 100644 --- a/compiler/tests-check-prim/unix-Unix.5.2.output +++ b/compiler/tests-check-prim/unix-Unix.5.2.output @@ -278,6 +278,7 @@ caml_runtime_events_user_resolve From +stdlib.js: caml_is_printable caml_maybe_print_stats +caml_with_async_exns From +sys.js: caml_fatal_uncaught_exception diff --git a/compiler/tests-check-prim/unix-Win32.5.2.output b/compiler/tests-check-prim/unix-Win32.5.2.output index 042813431f..5cc50eb69a 100644 --- a/compiler/tests-check-prim/unix-Win32.5.2.output +++ b/compiler/tests-check-prim/unix-Win32.5.2.output @@ -251,6 +251,7 @@ caml_runtime_events_user_resolve From +stdlib.js: caml_is_printable caml_maybe_print_stats +caml_with_async_exns From +sys.js: caml_fatal_uncaught_exception diff --git a/runtime/js/stdlib.js b/runtime/js/stdlib.js index 5bfb024119..2fae5966db 100644 --- a/runtime/js/stdlib.js +++ b/runtime/js/stdlib.js @@ -300,3 +300,11 @@ function caml_is_printable(c) { function caml_maybe_print_stats(_unit) { return 0; } + +//Provides: caml_with_async_exns +//Requires: caml_callback +//Version: >= 5.2, < 5.3 +//OxCaml +function caml_with_async_exns(body_callback) { + return caml_callback(body_callback, [0]); +} diff --git a/runtime/wasm/stdlib.wat b/runtime/wasm/stdlib.wat index 65dfa0c313..e47c64e3d6 100644 --- a/runtime/wasm/stdlib.wat +++ b/runtime/wasm/stdlib.wat @@ -234,4 +234,7 @@ (call $unwrap (call $caml_jsstring_of_string (local.get $msg))))) (call $exit (i32.const 2))))) + + (func (export "caml_with_async_exns") (param $f (ref eq)) (result (ref eq)) + (return_call $caml_callback_1 (local.get $f) (ref.i31 (i32.const 0)))) ) From d901a112beacc4de75efa6470cfce8c2a44b5ba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 23:12:54 +0200 Subject: [PATCH 33/36] OxCaml: check primitives --- compiler/tests-check-prim/dune.inc | 69 +++- compiler/tests-check-prim/gen_dune.ml | 12 +- compiler/tests-check-prim/main.5.2+ox.output | 330 ++++++++++++++++ .../tests-check-prim/unix-Unix.5.2+ox.output | 353 ++++++++++++++++++ 4 files changed, 749 insertions(+), 15 deletions(-) create mode 100644 compiler/tests-check-prim/main.5.2+ox.output create mode 100644 compiler/tests-check-prim/unix-Unix.5.2+ox.output diff --git a/compiler/tests-check-prim/dune.inc b/compiler/tests-check-prim/dune.inc index f587188599..6a8b57aeea 100644 --- a/compiler/tests-check-prim/dune.inc +++ b/compiler/tests-check-prim/dune.inc @@ -2,7 +2,7 @@ (targets main.4.14.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 4.14)(< %{ocaml_version} 5.0))) + (enabled_if (and (>= %{ocaml_version} 4.14)(< %{ocaml_version} 5.0)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -17,7 +17,7 @@ (targets unix-Win32.4.14.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 4.14)(< %{ocaml_version} 5.0)(= %{os_type} Win32))) + (enabled_if (and (>= %{ocaml_version} 4.14)(< %{ocaml_version} 5.0)(= %{os_type} Win32)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -32,7 +32,7 @@ (targets unix-Unix.4.14.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 4.14)(< %{ocaml_version} 5.0)(= %{os_type} Unix))) + (enabled_if (and (>= %{ocaml_version} 4.14)(< %{ocaml_version} 5.0)(= %{os_type} Unix)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -47,7 +47,22 @@ (targets main.5.2.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3))) + (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)(not %{oxcaml_supported}))) + (action + (with-stdout-to + %{targets} + (run + %{bin:js_of_ocaml} + check-runtime + +dynlink.js + +toplevel.js + %{dep:main.bc})))) + +(rule + (targets main.5.2+ox.output) + (mode + (promote (until-clean))) + (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)%{oxcaml_supported})) (action (with-stdout-to %{targets} @@ -62,7 +77,22 @@ (targets unix-Win32.5.2.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)(= %{os_type} Win32))) + (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)(= %{os_type} Win32)(not %{oxcaml_supported}))) + (action + (with-stdout-to + %{targets} + (run + %{bin:js_of_ocaml} + check-runtime + +dynlink.js + +toplevel.js + %{dep:unix.bc})))) + +(rule + (targets unix-Win32.5.2+ox.output) + (mode + (promote (until-clean))) + (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)(= %{os_type} Win32)%{oxcaml_supported})) (action (with-stdout-to %{targets} @@ -77,7 +107,22 @@ (targets unix-Unix.5.2.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)(= %{os_type} Unix))) + (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)(= %{os_type} Unix)(not %{oxcaml_supported}))) + (action + (with-stdout-to + %{targets} + (run + %{bin:js_of_ocaml} + check-runtime + +dynlink.js + +toplevel.js + %{dep:unix.bc})))) + +(rule + (targets unix-Unix.5.2+ox.output) + (mode + (promote (until-clean))) + (enabled_if (and (>= %{ocaml_version} 5.2)(< %{ocaml_version} 5.3)(= %{os_type} Unix)%{oxcaml_supported})) (action (with-stdout-to %{targets} @@ -92,7 +137,7 @@ (targets main.5.3.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.3)(< %{ocaml_version} 5.4))) + (enabled_if (and (>= %{ocaml_version} 5.3)(< %{ocaml_version} 5.4)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -107,7 +152,7 @@ (targets unix-Win32.5.3.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.3)(< %{ocaml_version} 5.4)(= %{os_type} Win32))) + (enabled_if (and (>= %{ocaml_version} 5.3)(< %{ocaml_version} 5.4)(= %{os_type} Win32)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -122,7 +167,7 @@ (targets unix-Unix.5.3.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.3)(< %{ocaml_version} 5.4)(= %{os_type} Unix))) + (enabled_if (and (>= %{ocaml_version} 5.3)(< %{ocaml_version} 5.4)(= %{os_type} Unix)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -137,7 +182,7 @@ (targets main.5.4.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5))) + (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -152,7 +197,7 @@ (targets unix-Win32.5.4.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5)(= %{os_type} Win32))) + (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5)(= %{os_type} Win32)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} @@ -167,7 +212,7 @@ (targets unix-Unix.5.4.output) (mode (promote (until-clean))) - (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5)(= %{os_type} Unix))) + (enabled_if (and (>= %{ocaml_version} 5.4)(< %{ocaml_version} 5.5)(= %{os_type} Unix)(not %{oxcaml_supported}))) (action (with-stdout-to %{targets} diff --git a/compiler/tests-check-prim/gen_dune.ml b/compiler/tests-check-prim/gen_dune.ml index e6cd42c2e4..f657742a30 100644 --- a/compiler/tests-check-prim/gen_dune.ml +++ b/compiler/tests-check-prim/gen_dune.ml @@ -39,7 +39,7 @@ let string_of_os_type = function | Unix -> "Unix" | Win32 -> "Win32" -let rule bc ocaml_version os_type = +let rule bc ocaml_version os_type oxcaml = let vl = [ Printf.sprintf "(>= %%{ocaml_version} %s)" (string_of_version ocaml_version) ] in @@ -53,7 +53,8 @@ let rule bc ocaml_version os_type = | None -> [] | Some os_type -> [ Printf.sprintf "(= %%{os_type} %s)" (string_of_os_type os_type) ] in - let enabled_if = Printf.sprintf "(and %s)" (String.concat "" (vl @ vu @ os)) in + let ox = [ (if oxcaml then "%{oxcaml_supported}" else "(not %{oxcaml_supported})") ] in + let enabled_if = Printf.sprintf "(and %s)" (String.concat "" (vl @ vu @ os @ ox)) in let target = Filename.chop_extension bc @@ -62,6 +63,7 @@ let rule bc ocaml_version os_type = | Some os_type -> "-" ^ string_of_os_type os_type) ^ "." ^ string_of_version ocaml_version + ^ (if oxcaml then "+ox" else "") ^ ".output" in Printf.sprintf @@ -89,6 +91,10 @@ let () = List.iter (fun ocaml_version -> List.iter - (fun (bc, os_type) -> print_endline (rule bc ocaml_version os_type)) + (fun (bc, os_type) -> + print_endline (rule bc ocaml_version os_type false); + match ocaml_version with + | `V5_2 -> print_endline (rule bc ocaml_version os_type true) + | _ -> ()) [ "main.bc", None; "unix.bc", Some Win32; "unix.bc", Some Unix ]) versions diff --git a/compiler/tests-check-prim/main.5.2+ox.output b/compiler/tests-check-prim/main.5.2+ox.output new file mode 100644 index 0000000000..37b0b84f89 --- /dev/null +++ b/compiler/tests-check-prim/main.5.2+ox.output @@ -0,0 +1,330 @@ +Missing +------- + +From main.bc: +caml_alloc_dummy_function +caml_alloc_dummy_mixed +caml_array_get_indexed_by_int32 +caml_array_get_indexed_by_int64 +caml_array_get_indexed_by_nativeint +caml_array_get_local +caml_array_of_iarray +caml_array_set_addr_local +caml_array_set_indexed_by_int32 +caml_array_set_indexed_by_int64 +caml_array_set_indexed_by_nativeint +caml_array_set_local +caml_array_unsafe_get_indexed_by_int32 +caml_array_unsafe_get_indexed_by_int64 +caml_array_unsafe_get_indexed_by_nativeint +caml_array_unsafe_get_local +caml_array_unsafe_set_indexed_by_int32 +caml_array_unsafe_set_indexed_by_int64 +caml_array_unsafe_set_indexed_by_nativeint +caml_array_unsafe_set_local +caml_assume_no_perform +caml_atomic_make +caml_ba_float32_get_1 +caml_ba_float32_get_2 +caml_ba_float32_get_3 +caml_ba_float32_set_1 +caml_ba_float32_set_2 +caml_ba_float32_set_3 +caml_ba_uint8_get16_indexed_by_int32 +caml_ba_uint8_get16_indexed_by_int64 +caml_ba_uint8_get16_indexed_by_nativeint +caml_ba_uint8_get32_indexed_by_int32 +caml_ba_uint8_get32_indexed_by_int64 +caml_ba_uint8_get32_indexed_by_nativeint +caml_ba_uint8_get64_indexed_by_int32 +caml_ba_uint8_get64_indexed_by_int64 +caml_ba_uint8_get64_indexed_by_nativeint +caml_ba_uint8_getf32 +caml_ba_uint8_getf32_indexed_by_int32 +caml_ba_uint8_getf32_indexed_by_int64 +caml_ba_uint8_getf32_indexed_by_nativeint +caml_ba_uint8_set16_indexed_by_int32 +caml_ba_uint8_set16_indexed_by_int64 +caml_ba_uint8_set16_indexed_by_nativeint +caml_ba_uint8_set32_indexed_by_int32 +caml_ba_uint8_set32_indexed_by_int64 +caml_ba_uint8_set32_indexed_by_nativeint +caml_ba_uint8_set64_indexed_by_int32 +caml_ba_uint8_set64_indexed_by_int64 +caml_ba_uint8_set64_indexed_by_nativeint +caml_ba_uint8_setf32 +caml_ba_uint8_setf32_indexed_by_int32 +caml_ba_uint8_setf32_indexed_by_int64 +caml_ba_uint8_setf32_indexed_by_nativeint +caml_bytes_get16_indexed_by_int32 +caml_bytes_get16_indexed_by_int64 +caml_bytes_get16_indexed_by_nativeint +caml_bytes_get32_indexed_by_int32 +caml_bytes_get32_indexed_by_int64 +caml_bytes_get32_indexed_by_nativeint +caml_bytes_get64_indexed_by_int32 +caml_bytes_get64_indexed_by_int64 +caml_bytes_get64_indexed_by_nativeint +caml_bytes_getf32 +caml_bytes_getf32_indexed_by_int32 +caml_bytes_getf32_indexed_by_int64 +caml_bytes_getf32_indexed_by_nativeint +caml_bytes_set16_indexed_by_int32 +caml_bytes_set16_indexed_by_int64 +caml_bytes_set16_indexed_by_nativeint +caml_bytes_set32_indexed_by_int32 +caml_bytes_set32_indexed_by_int64 +caml_bytes_set32_indexed_by_nativeint +caml_bytes_set64_indexed_by_int32 +caml_bytes_set64_indexed_by_int64 +caml_bytes_set64_indexed_by_nativeint +caml_bytes_setf32 +caml_bytes_setf32_indexed_by_int32 +caml_bytes_setf32_indexed_by_int64 +caml_bytes_setf32_indexed_by_nativeint +caml_continuation_use +caml_drop_continuation +caml_floatarray_get_local +caml_floatarray_unsafe_get_local +caml_get_header +caml_iarray_of_array +caml_local_stack_offset +caml_make_array_local +caml_make_local_unboxed_float32_vect +caml_make_local_unboxed_float64_vect +caml_make_local_unboxed_int32_vect +caml_make_local_unboxed_int64_vect +caml_make_local_unboxed_nativeint_vect +caml_make_local_unboxed_vec128_vect +caml_make_local_vect +caml_make_unboxed_float32_vect +caml_make_unboxed_float32_vect_bytecode +caml_make_unboxed_float64_vect +caml_make_unboxed_int32_vect +caml_make_unboxed_int32_vect_bytecode +caml_make_unboxed_int64_vect +caml_make_unboxed_int64_vect_bytecode +caml_make_unboxed_nativeint_vect +caml_make_unboxed_nativeint_vect_bytecode +caml_make_unboxed_vec128_vect +caml_make_unboxed_vec128_vect_bytecode +caml_makearray_dynamic_non_scannable_unboxed_product +caml_makearray_dynamic_scannable_unboxed_product +caml_ml_domain_index +caml_ml_runtime_events_path +caml_no_bytecode_impl +caml_obj_make_forward +caml_reinterpret_tagged_int63_as_unboxed_int64 +caml_reinterpret_unboxed_int64_as_tagged_int63 +caml_reset_afl_instrumentation +caml_simd_bytecode_not_supported +caml_sqrt_float32 +caml_string_get16_indexed_by_int32 +caml_string_get16_indexed_by_int64 +caml_string_get16_indexed_by_nativeint +caml_string_get32_indexed_by_int32 +caml_string_get32_indexed_by_int64 +caml_string_get32_indexed_by_nativeint +caml_string_get64_indexed_by_int32 +caml_string_get64_indexed_by_int64 +caml_string_get64_indexed_by_nativeint +caml_string_getf32 +caml_string_getf32_indexed_by_int32 +caml_string_getf32_indexed_by_int64 +caml_string_getf32_indexed_by_nativeint +caml_unboxed_float32_vect_blit +caml_unboxed_int32_vect_blit +caml_unboxed_int64_vect_blit +caml_unboxed_nativeint_vect_blit +caml_unboxed_vec128_vect_blit +debugger + +Unused +------- + +From +array.js: +caml_check_bound + +From +bigarray.js: +caml_ba_create_from (deprecated) +caml_ba_init + +From +bigstring.js: +caml_bigstring_blit_ba_to_ba +caml_bigstring_blit_ba_to_bytes +caml_bigstring_blit_bytes_to_ba +caml_bigstring_blit_string_to_ba +caml_bigstring_memcmp +caml_hash_mix_bigstring + +From +effect.js: +jsoo_effect_not_supported + +From +fs.js: +caml_ba_map_file +caml_ba_map_file_bytecode +caml_fs_init +jsoo_create_file +jsoo_create_file_extern + +From +graphics.js: +caml_gr_arc_aux +caml_gr_blit_image +caml_gr_clear_graph +caml_gr_close_graph +caml_gr_close_subwindow +caml_gr_create_image +caml_gr_current_x +caml_gr_current_y +caml_gr_display_mode +caml_gr_doc_of_state +caml_gr_draw_arc +caml_gr_draw_char +caml_gr_draw_image +caml_gr_draw_rect +caml_gr_draw_str +caml_gr_draw_string +caml_gr_dump_image +caml_gr_fill_arc +caml_gr_fill_poly +caml_gr_fill_rect +caml_gr_lineto +caml_gr_make_image +caml_gr_moveto +caml_gr_open_graph +caml_gr_open_subwindow +caml_gr_plot +caml_gr_point_color +caml_gr_remember_mode +caml_gr_resize_window +caml_gr_set_color +caml_gr_set_font +caml_gr_set_line_width +caml_gr_set_text_size +caml_gr_set_window_title +caml_gr_sigio_handler +caml_gr_sigio_signal +caml_gr_size_x +caml_gr_size_y +caml_gr_state +caml_gr_state_create +caml_gr_state_get +caml_gr_state_init +caml_gr_state_set +caml_gr_synchronize +caml_gr_text_size +caml_gr_wait_event +caml_gr_window_id + +From +hash.js: +caml_hash_mix_int64 + +From +ints.js: +caml_div +caml_mod + +From +jslib.js: +caml_is_js +caml_trampoline +caml_trampoline_return +caml_wrap_exception + +From +marshal.js: +caml_marshal_constants + +From +mlBytes.js: +caml_array_of_bytes (deprecated) +caml_array_of_string (deprecated) +caml_bytes_of_utf16_jsstring +caml_new_string (deprecated) +caml_string_concat +caml_to_js_string (deprecated) + +From +runtime_events.js: +caml_runtime_events_create_cursor +caml_runtime_events_free_cursor +caml_runtime_events_read_poll +caml_runtime_events_user_resolve + +From +stdlib.js: +caml_is_printable +caml_maybe_print_stats + +From +sys.js: +caml_fatal_uncaught_exception +caml_format_exception +caml_is_special_exception +caml_set_static_env + +From +toplevel.js: +caml_get_section_table +jsoo_get_runtime_aliases +jsoo_toplevel_init_compile +jsoo_toplevel_init_reloc + +From +unix.js: +caml_strerror +caml_unix_access +caml_unix_chdir +caml_unix_chmod +caml_unix_cleanup +caml_unix_close +caml_unix_closedir +caml_unix_fchmod +caml_unix_filedescr_of_fd +caml_unix_findclose +caml_unix_findfirst +caml_unix_findnext +caml_unix_fstat +caml_unix_fstat_64 +caml_unix_fsync +caml_unix_ftruncate +caml_unix_ftruncate_64 +caml_unix_getegid +caml_unix_geteuid +caml_unix_getgid +caml_unix_getpwnam +caml_unix_gettimeofday +caml_unix_getuid +caml_unix_gmtime +caml_unix_has_symlink +caml_unix_inchannel_of_filedescr +caml_unix_inet_addr_of_string +caml_unix_isatty +caml_unix_link +caml_unix_localtime +caml_unix_lookup_file +caml_unix_lseek +caml_unix_lseek_64 +caml_unix_lstat +caml_unix_lstat_64 +caml_unix_mkdir +caml_unix_mktime +caml_unix_open +caml_unix_opendir +caml_unix_outchannel_of_filedescr +caml_unix_read +caml_unix_read_bigarray +caml_unix_readdir +caml_unix_readlink +caml_unix_rename +caml_unix_rewinddir +caml_unix_rmdir +caml_unix_single_write +caml_unix_startup +caml_unix_stat +caml_unix_stat_64 +caml_unix_symlink +caml_unix_time +caml_unix_times +caml_unix_truncate +caml_unix_truncate_64 +caml_unix_unlink +caml_unix_utimes +caml_unix_write +caml_unix_write_bigarray +unix_error_message + +From +zstd.js: +caml_zstd_initialize + diff --git a/compiler/tests-check-prim/unix-Unix.5.2+ox.output b/compiler/tests-check-prim/unix-Unix.5.2+ox.output new file mode 100644 index 0000000000..a69d00e93d --- /dev/null +++ b/compiler/tests-check-prim/unix-Unix.5.2+ox.output @@ -0,0 +1,353 @@ +Missing +------- + +From unix.bc: +caml_alloc_dummy_function +caml_alloc_dummy_mixed +caml_array_get_indexed_by_int32 +caml_array_get_indexed_by_int64 +caml_array_get_indexed_by_nativeint +caml_array_get_local +caml_array_of_iarray +caml_array_set_addr_local +caml_array_set_indexed_by_int32 +caml_array_set_indexed_by_int64 +caml_array_set_indexed_by_nativeint +caml_array_set_local +caml_array_unsafe_get_indexed_by_int32 +caml_array_unsafe_get_indexed_by_int64 +caml_array_unsafe_get_indexed_by_nativeint +caml_array_unsafe_get_local +caml_array_unsafe_set_indexed_by_int32 +caml_array_unsafe_set_indexed_by_int64 +caml_array_unsafe_set_indexed_by_nativeint +caml_array_unsafe_set_local +caml_assume_no_perform +caml_atomic_make +caml_ba_float32_get_1 +caml_ba_float32_get_2 +caml_ba_float32_get_3 +caml_ba_float32_set_1 +caml_ba_float32_set_2 +caml_ba_float32_set_3 +caml_ba_uint8_get16_indexed_by_int32 +caml_ba_uint8_get16_indexed_by_int64 +caml_ba_uint8_get16_indexed_by_nativeint +caml_ba_uint8_get32_indexed_by_int32 +caml_ba_uint8_get32_indexed_by_int64 +caml_ba_uint8_get32_indexed_by_nativeint +caml_ba_uint8_get64_indexed_by_int32 +caml_ba_uint8_get64_indexed_by_int64 +caml_ba_uint8_get64_indexed_by_nativeint +caml_ba_uint8_getf32 +caml_ba_uint8_getf32_indexed_by_int32 +caml_ba_uint8_getf32_indexed_by_int64 +caml_ba_uint8_getf32_indexed_by_nativeint +caml_ba_uint8_set16_indexed_by_int32 +caml_ba_uint8_set16_indexed_by_int64 +caml_ba_uint8_set16_indexed_by_nativeint +caml_ba_uint8_set32_indexed_by_int32 +caml_ba_uint8_set32_indexed_by_int64 +caml_ba_uint8_set32_indexed_by_nativeint +caml_ba_uint8_set64_indexed_by_int32 +caml_ba_uint8_set64_indexed_by_int64 +caml_ba_uint8_set64_indexed_by_nativeint +caml_ba_uint8_setf32 +caml_ba_uint8_setf32_indexed_by_int32 +caml_ba_uint8_setf32_indexed_by_int64 +caml_ba_uint8_setf32_indexed_by_nativeint +caml_bytes_get16_indexed_by_int32 +caml_bytes_get16_indexed_by_int64 +caml_bytes_get16_indexed_by_nativeint +caml_bytes_get32_indexed_by_int32 +caml_bytes_get32_indexed_by_int64 +caml_bytes_get32_indexed_by_nativeint +caml_bytes_get64_indexed_by_int32 +caml_bytes_get64_indexed_by_int64 +caml_bytes_get64_indexed_by_nativeint +caml_bytes_getf32 +caml_bytes_getf32_indexed_by_int32 +caml_bytes_getf32_indexed_by_int64 +caml_bytes_getf32_indexed_by_nativeint +caml_bytes_set16_indexed_by_int32 +caml_bytes_set16_indexed_by_int64 +caml_bytes_set16_indexed_by_nativeint +caml_bytes_set32_indexed_by_int32 +caml_bytes_set32_indexed_by_int64 +caml_bytes_set32_indexed_by_nativeint +caml_bytes_set64_indexed_by_int32 +caml_bytes_set64_indexed_by_int64 +caml_bytes_set64_indexed_by_nativeint +caml_bytes_setf32 +caml_bytes_setf32_indexed_by_int32 +caml_bytes_setf32_indexed_by_int64 +caml_bytes_setf32_indexed_by_nativeint +caml_continuation_use +caml_drop_continuation +caml_floatarray_get_local +caml_floatarray_unsafe_get_local +caml_get_header +caml_iarray_of_array +caml_local_stack_offset +caml_make_array_local +caml_make_local_unboxed_float32_vect +caml_make_local_unboxed_float64_vect +caml_make_local_unboxed_int32_vect +caml_make_local_unboxed_int64_vect +caml_make_local_unboxed_nativeint_vect +caml_make_local_unboxed_vec128_vect +caml_make_local_vect +caml_make_unboxed_float32_vect +caml_make_unboxed_float32_vect_bytecode +caml_make_unboxed_float64_vect +caml_make_unboxed_int32_vect +caml_make_unboxed_int32_vect_bytecode +caml_make_unboxed_int64_vect +caml_make_unboxed_int64_vect_bytecode +caml_make_unboxed_nativeint_vect +caml_make_unboxed_nativeint_vect_bytecode +caml_make_unboxed_vec128_vect +caml_make_unboxed_vec128_vect_bytecode +caml_makearray_dynamic_non_scannable_unboxed_product +caml_makearray_dynamic_scannable_unboxed_product +caml_ml_domain_index +caml_ml_runtime_events_path +caml_no_bytecode_impl +caml_obj_make_forward +caml_reinterpret_tagged_int63_as_unboxed_int64 +caml_reinterpret_unboxed_int64_as_tagged_int63 +caml_reset_afl_instrumentation +caml_simd_bytecode_not_supported +caml_sqrt_float32 +caml_string_get16_indexed_by_int32 +caml_string_get16_indexed_by_int64 +caml_string_get16_indexed_by_nativeint +caml_string_get32_indexed_by_int32 +caml_string_get32_indexed_by_int64 +caml_string_get32_indexed_by_nativeint +caml_string_get64_indexed_by_int32 +caml_string_get64_indexed_by_int64 +caml_string_get64_indexed_by_nativeint +caml_string_getf32 +caml_string_getf32_indexed_by_int32 +caml_string_getf32_indexed_by_int64 +caml_string_getf32_indexed_by_nativeint +caml_unboxed_float32_vect_blit +caml_unboxed_int32_vect_blit +caml_unboxed_int64_vect_blit +caml_unboxed_nativeint_vect_blit +caml_unboxed_vec128_vect_blit +caml_unix_accept +caml_unix_alarm +caml_unix_bind +caml_unix_chown +caml_unix_chroot +caml_unix_clear_close_on_exec +caml_unix_clear_nonblock +caml_unix_connect +caml_unix_dup +caml_unix_dup2 +caml_unix_environment +caml_unix_environment_unsafe +caml_unix_execv +caml_unix_execve +caml_unix_execvp +caml_unix_execvpe +caml_unix_fchown +caml_unix_fork +caml_unix_getaddrinfo +caml_unix_getgroups +caml_unix_gethostbyaddr +caml_unix_gethostbyname +caml_unix_gethostname +caml_unix_getitimer +caml_unix_getlogin +caml_unix_getnameinfo +caml_unix_getpeername +caml_unix_getpid +caml_unix_getppid +caml_unix_getprotobyname +caml_unix_getprotobynumber +caml_unix_getservbyname +caml_unix_getservbyport +caml_unix_getsockname +caml_unix_getsockopt +caml_unix_initgroups +caml_unix_kill +caml_unix_listen +caml_unix_lockf +caml_unix_map_file_bytecode +caml_unix_mkfifo +caml_unix_nice +caml_unix_pipe +caml_unix_putenv +caml_unix_realpath +caml_unix_recv +caml_unix_recvfrom +caml_unix_select +caml_unix_send +caml_unix_sendto +caml_unix_set_close_on_exec +caml_unix_set_nonblock +caml_unix_setgid +caml_unix_setgroups +caml_unix_setitimer +caml_unix_setsid +caml_unix_setsockopt +caml_unix_setuid +caml_unix_shutdown +caml_unix_sigpending +caml_unix_sigprocmask +caml_unix_sigsuspend +caml_unix_sleep +caml_unix_socket +caml_unix_socketpair +caml_unix_spawn +caml_unix_string_of_inet_addr +caml_unix_tcdrain +caml_unix_tcflow +caml_unix_tcflush +caml_unix_tcgetattr +caml_unix_tcsendbreak +caml_unix_tcsetattr +caml_unix_umask +caml_unix_wait +caml_unix_waitpid +debugger + +Unused +------- + +From +array.js: +caml_check_bound + +From +bigarray.js: +caml_ba_create_from (deprecated) +caml_ba_init + +From +bigstring.js: +caml_bigstring_blit_ba_to_ba +caml_bigstring_blit_ba_to_bytes +caml_bigstring_blit_bytes_to_ba +caml_bigstring_blit_string_to_ba +caml_bigstring_memcmp +caml_hash_mix_bigstring + +From +effect.js: +jsoo_effect_not_supported + +From +fs.js: +caml_ba_map_file +caml_ba_map_file_bytecode +caml_fs_init +jsoo_create_file +jsoo_create_file_extern + +From +graphics.js: +caml_gr_arc_aux +caml_gr_blit_image +caml_gr_clear_graph +caml_gr_close_graph +caml_gr_close_subwindow +caml_gr_create_image +caml_gr_current_x +caml_gr_current_y +caml_gr_display_mode +caml_gr_doc_of_state +caml_gr_draw_arc +caml_gr_draw_char +caml_gr_draw_image +caml_gr_draw_rect +caml_gr_draw_str +caml_gr_draw_string +caml_gr_dump_image +caml_gr_fill_arc +caml_gr_fill_poly +caml_gr_fill_rect +caml_gr_lineto +caml_gr_make_image +caml_gr_moveto +caml_gr_open_graph +caml_gr_open_subwindow +caml_gr_plot +caml_gr_point_color +caml_gr_remember_mode +caml_gr_resize_window +caml_gr_set_color +caml_gr_set_font +caml_gr_set_line_width +caml_gr_set_text_size +caml_gr_set_window_title +caml_gr_sigio_handler +caml_gr_sigio_signal +caml_gr_size_x +caml_gr_size_y +caml_gr_state +caml_gr_state_create +caml_gr_state_get +caml_gr_state_init +caml_gr_state_set +caml_gr_synchronize +caml_gr_text_size +caml_gr_wait_event +caml_gr_window_id + +From +hash.js: +caml_hash_mix_int64 + +From +ints.js: +caml_div +caml_mod + +From +jslib.js: +caml_is_js +caml_trampoline +caml_trampoline_return +caml_wrap_exception + +From +marshal.js: +caml_marshal_constants + +From +mlBytes.js: +caml_array_of_bytes (deprecated) +caml_array_of_string (deprecated) +caml_bytes_of_utf16_jsstring +caml_new_string (deprecated) +caml_string_concat +caml_to_js_string (deprecated) + +From +runtime_events.js: +caml_runtime_events_create_cursor +caml_runtime_events_free_cursor +caml_runtime_events_read_poll +caml_runtime_events_user_resolve + +From +stdlib.js: +caml_is_printable +caml_maybe_print_stats + +From +sys.js: +caml_fatal_uncaught_exception +caml_format_exception +caml_is_special_exception +caml_set_static_env + +From +toplevel.js: +caml_get_section_table +jsoo_get_runtime_aliases +jsoo_toplevel_init_compile +jsoo_toplevel_init_reloc + +From +unix.js: +caml_strerror +caml_unix_cleanup +caml_unix_filedescr_of_fd +caml_unix_findclose +caml_unix_findfirst +caml_unix_findnext +caml_unix_startup +unix_error_message + +From +zstd.js: +caml_zstd_initialize + From c1ae6138ca0f5587dd31e3efb31ec09c3290f15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 25 Sep 2025 12:14:21 +0200 Subject: [PATCH 34/36] Default use_js_string to false This is not supported by Oxcaml at the moment --- compiler/bin-js_of_ocaml/check_runtime.ml | 1 + compiler/lib/config.ml | 2 +- compiler/tests-full/dune | 4 ++++ lib/tests/test_fun_call.ml | 4 +--- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compiler/bin-js_of_ocaml/check_runtime.ml b/compiler/bin-js_of_ocaml/check_runtime.ml index 2501c30e6c..0d3f61efec 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.Flag.set "use-js-string" true; Config.set_target `JavaScript; Config.set_effects_backend `Disabled; Linker.reset (); diff --git a/compiler/lib/config.ml b/compiler/lib/config.ml index 65c45d39ca..c897663d36 100644 --- a/compiler/lib/config.ml +++ b/compiler/lib/config.ml @@ -93,7 +93,7 @@ module Flag = struct let safe_string = o ~name:"safestring" ~default:true - let use_js_string = o ~name:"use-js-string" ~default:true + let use_js_string = o ~name:"use-js-string" ~default:false let check_magic = o ~name:"check-magic-number" ~default:true diff --git a/compiler/tests-full/dune b/compiler/tests-full/dune index b609f1bd16..76a8951636 100644 --- a/compiler/tests-full/dune +++ b/compiler/tests-full/dune @@ -12,6 +12,8 @@ var --debuginfo %{lib:stdlib:stdlib.cma} + --enable + use-js-string -o %{targets}))) @@ -53,6 +55,8 @@ --pretty --debuginfo %{dep:shapes.cma} + --enable + use-js-string -o %{targets}))) diff --git a/lib/tests/test_fun_call.ml b/lib/tests/test_fun_call.ml index e236435b38..bddeb09b9a 100644 --- a/lib/tests/test_fun_call.ml +++ b/lib/tests/test_fun_call.ml @@ -422,9 +422,7 @@ let%expect_test _ = let f = Js.wrap_callback (fun s -> print_endline s) in Js.export "f" f; let () = - Js.Unsafe.fun_call - (Js.Unsafe.pure_js_expr "jsoo_exports")##.f - [| Js.Unsafe.coerce (Js.string "hello") |] + Js.Unsafe.fun_call (Js.Unsafe.pure_js_expr "jsoo_exports")##.f [| Obj.magic "hello" |] in (); [%expect {| hello |}] From a1a71c259b7b7607d3e1f27b0779db108bd30d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Tue, 30 Sep 2025 21:40:43 +0200 Subject: [PATCH 35/36] OxCaml: setup CI --- .github/workflows/js_of_ocaml.yml | 20 + .github/workflows/wasm_of_ocaml.yml | 32 +- tools/{ci_setup.ml => ci_setup-generic.ml} | 0 tools/ci_setup-oxcaml.ml | 437 +++++++++++++++++++++ tools/dune | 13 + 5 files changed, 499 insertions(+), 3 deletions(-) rename tools/{ci_setup.ml => ci_setup-generic.ml} (100%) create mode 100644 tools/ci_setup-oxcaml.ml diff --git a/.github/workflows/js_of_ocaml.yml b/.github/workflows/js_of_ocaml.yml index 19c8c76af1..ca1ee401fc 100644 --- a/.github/workflows/js_of_ocaml.yml +++ b/.github/workflows/js_of_ocaml.yml @@ -96,6 +96,13 @@ jobs: skip-effects: true skip-test: true skip-doc: true + - os: ubuntu-latest + os-name: Ubuntu + ocaml-name: "OxCaml" + ocaml-compiler: "ocaml-variants.5.2.0+ox" + skip-effects: false + skip-test: false + skip-doc: true runs-on: ${{ matrix.os }} @@ -140,6 +147,16 @@ jobs: uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} + if: matrix.ocaml-compiler != 'ocaml-variants.5.2.0+ox' + + - name: Set-up OxCaml ${{ matrix.ocaml-compiler }} + uses: ocaml/setup-ocaml@v3 + with: + ocaml-compiler: ${{ matrix.ocaml-compiler }} + opam-repositories: | + default: https://github.com/ocaml/opam-repository.git + ox: https://github.com/oxcaml/opam-repository.git + if: matrix.ocaml-compiler == 'ocaml-variants.5.2.0+ox' # Work-around a race between reinstalling mingw-w64-shims # (because of conf-pkg-config optional dep) and installing other @@ -163,6 +180,9 @@ jobs: # Install the test dependencies if: ${{ !matrix.skip-test }} + - name: Pin js_of_ocaml + run: opam pin . -n --with-version 6.0.1+ox + - run: opam install . # Install the packages (without running the tests) if: ${{ !matrix.skip-test }} diff --git a/.github/workflows/wasm_of_ocaml.yml b/.github/workflows/wasm_of_ocaml.yml index c964c54c30..db1a75c5b9 100644 --- a/.github/workflows/wasm_of_ocaml.yml +++ b/.github/workflows/wasm_of_ocaml.yml @@ -58,6 +58,12 @@ jobs: separate_compilation: false jane_street_tests: true all_jane_street_tests: false + - os: ubuntu-latest + os-name: Ubuntu + ocaml-compiler: "ocaml-variants.5.2.0+ox" + separate_compilation: true + jane_street_tests: true + all_jane_street_tests: true runs-on: ${{ matrix.os }} @@ -82,13 +88,20 @@ jobs: path: wasm_of_ocaml - name: Checkout Jane Street opam repository - if: matrix.jane_street_tests + if: matrix.jane_street_tests && matrix.ocaml-compiler != 'ocaml-variants.5.2.0+ox' uses: actions/checkout@v5 with: repository: janestreet/opam-repository ref: 2819773f29b6f6c14b918eae3cb40c8ff6b22d0e path: janestreet/opam-repository + - name: Checkout OxCaml opam repository + if: matrix.jane_street_tests && matrix.ocaml-compiler == 'ocaml-variants.5.2.0+ox' + uses: actions/checkout@v5 + with: + repository: oxcaml/opam-repository + path: janestreet/opam-repository + - name: Set-up Node.js uses: actions/setup-node@v5 with: @@ -98,6 +111,16 @@ jobs: uses: ocaml/setup-ocaml@v3 with: ocaml-compiler: ${{ matrix.ocaml-compiler }} + if: matrix.ocaml-compiler != 'ocaml-variants.5.2.0+ox' + + - name: Set-up OxCaml ${{ matrix.ocaml-compiler }} + uses: ocaml/setup-ocaml@v3 + with: + ocaml-compiler: ${{ matrix.ocaml-compiler }} + opam-repositories: | + default: https://github.com/ocaml/opam-repository.git + ox: https://github.com/oxcaml/opam-repository.git + if: matrix.ocaml-compiler == 'ocaml-variants.5.2.0+ox' - name: Set-up Binaryen uses: Aandreba/setup-binaryen@v1.0.0 @@ -116,14 +139,17 @@ jobs: - name: Pin wasm_of_ocaml working-directory: ./wasm_of_ocaml - run: opam pin . -n --with-version dev + run: opam pin . -n --with-version 6.0.1+ox + + - name: Pin ppxlib + run: opam pin add ppxlib -n 0.35.0 + if: matrix.ocaml-compiler != 'ocaml-variants.5.2.0+ox' - name: Checkout Jane Street packages if: matrix.jane_street_tests run: | opam repo add js janestreet/opam-repository opam install opam-format - opam pin add ppxlib -n 0.35.0 opam exec -- dune exec --root wasm_of_ocaml tools/ci_setup.exe - name: Pin Jane Street packages diff --git a/tools/ci_setup.ml b/tools/ci_setup-generic.ml similarity index 100% rename from tools/ci_setup.ml rename to tools/ci_setup-generic.ml diff --git a/tools/ci_setup-oxcaml.ml b/tools/ci_setup-oxcaml.ml new file mode 100644 index 0000000000..bff91bb563 --- /dev/null +++ b/tools/ci_setup-oxcaml.ml @@ -0,0 +1,437 @@ +module StringSet = Set.Make (String) + +(****) + +let jane_root, wasmoo_root = + match Sys.argv with + | [| _; jane_root; wasmoo_root |] -> jane_root, wasmoo_root + | _ -> "janestreet", "wasm_of_ocaml" + +let repo = Filename.concat jane_root "opam-repository/packages" + +let roots = + [ "bonsai_web_components" + ; "string_dict" + ; "ppx_html" + ; "bonsai_bench" + ; "float_array" + ; "unboxed" + ] + +let omitted_others = StringSet.of_list [] + +let omitted_js = StringSet.of_list [ "basement"; "sexplib0"; "ppxlib_jane" ] + +let do_pin = + StringSet.of_list + [ "basement" (* https://github.com/janestreet/basement/pull/3 *); "bigstringaf" ] + +let forked_packages = + StringSet.of_list + [ "base" + ; "core" + ; "bonsai_test" + ; "bonsai_web_components" + ; "bonsai_web_test" + ; "virtual_dom" + ] + +let dune_workspace = + {|(lang dune 3.17) +(env + (_ + (env-vars (TESTING_FRAMEWORK inline-test)) + (js_of_ocaml (enabled_if false)) + (ocamlopt_flags -zero-alloc-check none) + (flags :standard -alert -all -warn-error -7-8-27-30-32-34-37-49-52-55 -w -7-27-30-32-34-37-49-52-55-56-58-67-69))) +|} + +let node_wrapper = + [ ( "node_wrapper/dune" + , {|(executable + (public_name node) + (name node_wrapper) + (libraries unix))|} ) + ; "node_wrapper/node_wrapper_per_profile.ml", {|let args = []|} + ; "node_wrapper/dune-project", "(lang dune 3.17)" + ; "node_wrapper/node_wrapper.opam", "" + ] + +let patches = + [ ( "sexp_grammar" + , {| +diff --git a/sexp_grammar_validation.opam b/sexp_grammar_validation.opam +new file mode 100644 +index 0000000..e69de29 +diff --git a/validation/src/dune b/validation/src/dune +index 91933ec..849e4d7 100644 +--- a/validation/src/dune ++++ b/validation/src/dune +@@ -1,5 +1,6 @@ + (library + (name sexp_grammar_validation) ++ (public_name sexp_grammar_validation) + (libraries bignum.bigint core + expect_test_helpers_core.expect_test_helpers_base sexp_grammar) + (preprocess +|} + ) + ; ( "bignum" + , {bignum| +diff --git a/test/src/dune b/test/src/dune +index f93ae3f..3f00557 100644 +--- a/test/src/dune ++++ b/test/src/dune +@@ -2,5 +2,6 @@ + (name bignum_test) + (libraries bigint bignum core expect_test_helpers_core expectable + sexp_grammar_validation zarith) ++ (inline_tests (flags -drop-tag no-js -drop-tag no-wasm -drop-tag 64-bits-only) (modes js wasm)) + (preprocess + (pps ppx_jane))) +diff --git a/test/src/test_bignum.ml b/test/src/test_bignum.ml +index c6d09fb..61b1e5b 100644 +--- a/test/src/test_bignum.ml ++++ b/test/src/test_bignum.ml +@@ -3,6 +3,11 @@ open! Expect_test_helpers_core + open Bignum + open Bignum.For_testing + ++module Zarith = struct ++ module Q = Q ++ module Z = Z ++end ++ + let%expect_test "Bignum.abs" = + let test t = + let t' = require_no_allocation (fun () -> abs t) in +|bignum} + ) + ; ( "bin_prot" + , {bp| +diff --git a/test/dune b/test/dune +index bd88b8d..29b3604 100644 +--- a/test/dune ++++ b/test/dune +@@ -1,15 +1,8 @@ + (library + (name bin_prot_test) + (libraries base base_bigstring bin_prot +- expect_test_helpers_core.expect_test_helpers_base expect_test_patterns ++ expect_test_helpers_core.expect_test_helpers_base ; expect_test_patterns + float_array base.md5 sexplib splittable_random stdio) ++ (inline_tests (flags -drop-tag no-js -drop-tag 64-bits-only -drop-tag 32-bits-only -drop-tag no-wasm) (modes js wasm)) + (preprocess + (pps ppx_jane))) +- +-(rule +- (deps core/blob_stability_tests.ml integers_repr_tests_64bit.ml +- integers_repr_tests_js.ml integers_repr_tests_wasm.ml) +- (action +- (bash +- "diff <(\necho '869e6b3143f14201f406eac9c05c4cdb core/blob_stability_tests.ml'\necho 'a9ed028fa16f307982c196f647d05afa integers_repr_tests_64bit.ml'\necho 'a17ffcd3bf1e15dbca0ee54ec5b95c58 integers_repr_tests_js.ml'\necho 'e747bd85320575c771fc62a0d3085d29 integers_repr_tests_wasm.ml'\n ) <(md5sum %{deps})")) +- (alias runtest)) +diff --git a/test/non_integers_repr.ml b/test/non_integers_repr.ml +index cbb9bd5..b5b5a03 100644 +--- a/test/non_integers_repr.ml ++++ b/test/non_integers_repr.ml +@@ -811,11 +811,12 @@ let%expect_test "Non-integer bin_prot size tests" = + 00 00 00 00 00 00 00 00 -> 0 + |}]; + gen_tests Tests.float_nan; ++ [%expect ++ {| 7f f8 00 00 00 00 00 01 -> NAN |}]; ++(* + Expect_test_patterns.require_match + [%here] +- {| +- 7f f{8,0} 00 00 00 00 00 01 -> NAN (glob) +- |}; ++*) + gen_tests Tests.vec; + [%expect + {| + |bp} + ) + ; ( "base_bigstring" + , {| +diff --git a/test/dune b/test/dune +index 8d23f86..21e83ba 100644 +--- a/test/dune ++++ b/test/dune +@@ -2,5 +2,6 @@ + (name base_bigstring_test) + (libraries base_bigstring core.base_for_tests core expect_test_helpers_core + stdio) ++ (inline_tests (flags -drop-tag no-js -drop-tag 64-bits-only -drop-tag no-wasm) (modes js wasm)) + (preprocess + (pps ppx_jane))) +|} + ) + ; ( "core_kernel" + , {| +diff --git a/version_util/src/dune b/version_util/src/dune +index 4b2b8bb..f7eb7ba 100644 +--- a/version_util/src/dune ++++ b/version_util/src/dune +@@ -10,4 +10,5 @@ + (preprocess + (pps ppx_jane)) + (wasm_of_ocaml +- (javascript_files version_util.js))) ++ (javascript_files version_util.js) ++ (wasm_files version_util.wat))) +|} + ) + ; ( "string_dict" + , {| +diff --git a/test/dune b/test/dune +index b145cb3..e5fc412 100644 +--- a/test/dune ++++ b/test/dune +@@ -1,5 +1,6 @@ + (library + (name string_dict_test) + (libraries base core expect_test_helpers_core string_dict) ++ (inline_tests (flags -drop-tag no-js -drop-tag 64-bits-only -drop-tag no-wasm) (modes js wasm)) + (preprocess + (pps ppx_jane))) +|} + ) + ; ( "zarith_stubs_js" + , {zs| +diff --git a/test/bitwise.ml b/test/bitwise.ml +index 5fd0ddc..4833923 100644 +--- a/test/bitwise.ml ++++ b/test/bitwise.ml +@@ -86,7 +86,7 @@ module Ml_z_popcount = struct + Static.quickcheck ~f:(fun x -> [%message (x : t) (popcount x : int)]) (); + (* Compression rate is low because our quickcheck implementation generates + integers with a bounded bitcount. *) +- [%expect {| ((hash 1e429706c701b111d98b6e6e858bbea4) (uniqueness_rate 42.96875)) |}] ++ [%expect {| ((hash d937e61f530ab9c27544e392922d286d) (uniqueness_rate 42.96875)) |}] + ;; + end + +@@ -102,7 +102,7 @@ module Ml_z_hamdist = struct + (); + (* Compression rate is low because our quickcheck implementation generates + integers with a bounded bitcount. *) +- [%expect {| ((hash 0a270232628736ee7d47c8b403250989) (uniqueness_rate 33.284457)) |}] ++ [%expect {| ((hash 0d36530b39292e2c31f13d10ec004a38) (uniqueness_rate 33.284457)) |}] + ;; + end + +diff --git a/test/dune b/test/dune +index 7996514..d0b463a 100644 +--- a/test/dune ++++ b/test/dune +@@ -1,7 +1,9 @@ + (library + (name zarith_stubs_js_test) +- (libraries zarith core base.md5 zarith_stubs_js) ++ (libraries zarith_wrapper core base.md5 zarith_stubs_js) + (flags :standard -w -60) ++ (inline_tests (flags -drop-tag no-js -drop-tag 64-bits-only -drop-tag no-wasm) (modes js wasm)) ++ (modules (:standard \ zarith)) + (preprocess + (pps ppx_jane))) + +@@ -35,10 +37,16 @@ + (deps implemented_externals.txt tested_externals.txt) + (action + (bash "diff %{deps}")) +- (alias runtest)) ++ (alias runtest-)) + + (rule + (deps implemented_externals.txt zarith_externals.txt) + (action + (bash "diff %{deps}")) +- (alias runtest)) ++ (alias runtest-)) ++ ++(subdir zarith ++ (copy_files (files ../zarith.ml)) ++ (library (name zarith_wrapper) ++ (wrapped false) ++ (libraries zarith))) +|zs} + ) + ] + +let removes = + [ "core/core/test/test_sys.ml" + ; "core/core/test/test_sys.mli" + ; "core/core/test/test_timezone.ml" + ; "core/core/test/test_timezone.mli" + ] +(****) + +let read_opam_file filename = + OpamPp.parse + OpamPp.Op.(OpamFormat.I.file -| OpamPp.map_snd OpamFile.OPAM.pp_raw_fields) + ~pos:{ filename; start = 0, 0; stop = 0, 0 } + (OpamParser.FullPos.file (Filename.concat (Filename.concat repo filename) "opam")) + +let dependencies (_, { OpamFile.OPAM.depends; _ }) = + let open OpamFormula in + depends + |> map (fun (nm, _) -> Atom (nm, None)) + |> of_atom_formula + |> atoms + |> List.map fst + |> List.map OpamPackage.Name.to_string + +let is_jane_street_package (_, (_, opam)) = + let url = OpamUrl.to_string (Option.get (OpamFile.OPAM.get_url opam)) in + String.starts_with ~prefix:"https://github.com/janestreet/" url + +let packages = + repo + |> Sys.readdir + |> Array.to_list + |> List.map (fun s -> + if String.contains s '.' + then String.sub s 0 (String.index s '.'), read_opam_file s + else + ( s + , read_opam_file + (Filename.concat + s + (List.find + (fun f -> String.starts_with ~prefix:s f) + (Array.to_list (Sys.readdir (Filename.concat repo s))))) )) + |> List.filter is_jane_street_package + +let rec traverse visited p = + if StringSet.mem p visited + then visited + else + let visited = StringSet.add p visited in + match List.assoc p packages with + | exception Not_found -> visited + | opam -> + let l = dependencies opam in + List.fold_left traverse visited l + +let is_forked p = StringSet.mem p forked_packages + +let exec_async cmd = + let p = Unix.open_process_out cmd in + fun () -> ignore (Unix.close_process_out p) + +let ( let* ) (f : unit -> 'a) (g : 'a -> unit -> 'b) : unit -> 'b = fun () -> g (f ()) () + +let sync_exec f l = + let l = List.map f l in + List.iter (fun f -> f ()) l + +let pin nm = + exec_async + (Printf.sprintf + "opam pin add -n %s https://github.com/ocaml-wasm/%s.git#wasm-oxcaml" + nm + nm) + +let pin_packages () = sync_exec pin (StringSet.elements do_pin) + +let install_others others = + let others = StringSet.elements (StringSet.diff others omitted_others) in + ignore (Sys.command ("opam install -y " ^ String.concat " " others)) + +let clone ?branch ?(depth = 1) nm src = + exec_async + (Printf.sprintf + "git clone -q --depth %d %s%s %s/lib/%s" + depth + (match branch with + | None -> "" + | Some b -> Printf.sprintf "-b %s " b) + src + jane_root + nm) + +let clone' ?branch ?commit nm src = + match commit with + | None -> clone ?branch nm src + | Some commit -> + let* () = clone ?branch ~depth:100 nm src in + exec_async + (Printf.sprintf "cd %s/lib/%s && git checkout -b wasm %s" jane_root nm commit) + +let () = + let write f contents = + Out_channel.(with_open_bin f @@ fun ch -> output_string ch contents) + in + let copy f f' = + let contents = In_channel.(with_open_bin f @@ input_all) in + Out_channel.(with_open_bin f' @@ fun ch -> output_string ch contents) + in + write (Filename.concat jane_root "dune-workspace") dune_workspace; + Unix.mkdir (Filename.concat jane_root "node_wrapper") 0o755; + List.iter + (fun (f, contents) -> write (Filename.concat jane_root f) contents) + node_wrapper; + copy + (Filename.concat wasmoo_root "tools/node_wrapper.ml") + (Filename.concat jane_root "node_wrapper/node_wrapper.ml") + +let () = + let js, others = + List.fold_left traverse StringSet.empty roots + |> StringSet.partition (fun p -> List.mem_assoc p packages) + in + pin_packages (); + install_others others; + sync_exec (fun () -> clone "ocaml-uri" "https://github.com/mirage/ocaml-uri") [ () ]; + sync_exec (fun () -> exec_async "opam install uri --deps-only") [ () ]; + sync_exec + (fun nm -> + let branch = if is_forked nm then Some "wasm-oxcaml" else Some "with-extensions" in + let commit = + if is_forked nm + then None + else + Some + (let _, opam = List.assoc nm packages in + let url = OpamUrl.to_string (Option.get (OpamFile.OPAM.get_url opam)) in + let tar_file = Filename.basename url in + String.sub tar_file 0 (String.index tar_file '.')) + in + clone' + ?branch + ?commit + nm + (Printf.sprintf + "https://github.com/%s/%s" + (if is_forked nm then "ocaml-wasm" else "janestreet") + nm)) + (StringSet.elements (StringSet.diff js omitted_js)) + +let () = + List.iter + (fun (dir, patch) -> + let p = if Sys.win32 then "patch --binary" else "patch" in + let ch = + Unix.open_process_out + (Printf.sprintf "cd %s/lib/%s && %s -p 1 --" jane_root dir p) + in + let patch = + if Sys.win32 + then String.concat "\r\n" (String.split_on_char '\n' patch) + else patch + in + output_string ch patch; + match Unix.close_process_out ch with + | WEXITED 0 -> () + | e -> + let name, i = + match e with + | WEXITED n -> "exit", n + | WSIGNALED n -> "signal", n + | WSTOPPED n -> "stop", n + in + failwith (Printf.sprintf "%s %d while patching %s" name i dir)) + patches; + List.iter (fun p -> Sys.remove (Printf.sprintf "%s/lib/%s" jane_root p)) removes diff --git a/tools/dune b/tools/dune index 5953c6bc08..0fdd9bee07 100644 --- a/tools/dune +++ b/tools/dune @@ -8,6 +8,19 @@ (modules ci_setup) (libraries opam-format unix)) +(rule + (target ci_setup.ml) + (enabled_if + (not %{oxcaml_supported})) + (action + (copy ci_setup-generic.ml %{target}))) + +(rule + (target ci_setup.ml) + (enabled_if %{oxcaml_supported}) + (action + (copy ci_setup-oxcaml.ml %{target}))) + (executable (name sync_testsuite) (modules sync_testsuite) From ebcbcf55bcf7df373fb962c4d0dfe8001519e07e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 1 Oct 2025 22:36:52 +0200 Subject: [PATCH 36/36] Fix toplevel compilation (missing runtime file) --- toplevel/examples/lwt_toplevel/dune | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/toplevel/examples/lwt_toplevel/dune b/toplevel/examples/lwt_toplevel/dune index b7a8b48fe2..d894138af7 100644 --- a/toplevel/examples/lwt_toplevel/dune +++ b/toplevel/examples/lwt_toplevel/dune @@ -145,6 +145,27 @@ %{target} (run ./effects_flags.exe txt %{profile})))) +(rule + (target javascript_files.txt) + (enabled_if + (and %{oxcaml_supported} %{lib-available:js_of_ocaml-ppx})) + (action + (with-stdout-to + %{target} + (pipe-stdout + (run ocamlfind query -format "%+(jsoo_runtime)" -r ppxlib) + (run grep -v ^$))))) + +(rule + (target javascript_files.txt) + (enabled_if + (not + (and %{oxcaml_supported} %{lib-available:js_of_ocaml-ppx}))) + (action + (with-stdout-to + %{target} + (echo "")))) + (rule (targets toplevel.js) (action @@ -169,6 +190,7 @@ --toplevel --disable shortvar + %{read-strings:javascript_files.txt} %{dep:toplevel.bc} -o %{targets})))