From ed41b8c94eaaa09970c4a622ba48bc1138adea86 Mon Sep 17 00:00:00 2001 From: Thomas Gazagnaire Date: Thu, 30 Jul 2026 11:48:29 -0700 Subject: [PATCH] Avoid int literals that overflow a 31-bit target wasm_of_ocaml compiles OCaml int to 31 bits, where 0x7fffffff and 0x40000000 do not fit: Warning [integer-overflow]: integer 0x40000000 (1073741824) truncated to 0xc0000000 (-1073741824); the generated code might be incorrect. Neither site is wrong. In the emulation modules 0x40000000 truncates to the 31-bit sign bit, which is what without_bit_sign masks off, so the code is correct on the backend that selects them. optint_native is only reached when Sys.word_size = 64, and its neighbouring constants already wrap on a narrower int without warning. Build the emulation masks from wider literals so nothing is truncated at compile time, and derive int32_max from uint32_max rather than laundering a literal that cannot fit. Values are unchanged on 63-bit native, 32-bit js_of_ocaml and 31-bit wasm_of_ocaml. --- src/int63_emul.ml | 6 +++++- src/optint_emul.ml | 6 +++++- src/optint_native.ml | 6 +++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/int63_emul.ml b/src/int63_emul.ml index 3811a48..2684a08 100644 --- a/src/int63_emul.ml +++ b/src/int63_emul.ml @@ -99,7 +99,11 @@ let to_unsigned_int x = then to_int x else invalid_arg "Int63.to_unsigned_int: %Lx can not fit into a 31 bits unsigned integer" x -let without_bit_sign (x:int) = if x >= 0 then x else x land (lnot 0x40000000) +(* [0x40000000] does not fit a 31-bit [int], where it is the sign bit; build it + from an [int64] so that nothing is truncated at compile time. *) +let sign_bit = Int64.to_int 0x40000000L + +let without_bit_sign (x:int) = if x >= 0 then x else x land (lnot sign_bit) let of_unsigned_int x = if x < 0 diff --git a/src/optint_emul.ml b/src/optint_emul.ml index 28e4500..86d1a5d 100644 --- a/src/optint_emul.ml +++ b/src/optint_emul.ml @@ -10,7 +10,11 @@ let of_int64 = Int64.to_int32 let pp ppf (x:t) = Format.fprintf ppf "%ld" x -let without_bit_sign (x:int) = if x >= 0 then x else x land (lnot 0x40000000) +(* [0x40000000] does not fit a 31-bit [int], where it is the sign bit; build it + from an [int32] so that nothing is truncated at compile time. *) +let sign_bit = Int32.to_int 0x40000000l + +let without_bit_sign (x:int) = if x >= 0 then x else x land (lnot sign_bit) let invalid_arg fmt = Format.kasprintf invalid_arg fmt diff --git a/src/optint_native.ml b/src/optint_native.ml index 21f3f3b..766ca7c 100644 --- a/src/optint_native.ml +++ b/src/optint_native.ml @@ -53,11 +53,15 @@ let equal : int -> int -> bool = fun a b -> a = b let invalid_arg fmt = Format.kasprintf invalid_arg fmt +(* Reached only when [Sys.word_size = 64], so these masks assume an [int] wider + than 32 bits: [int32_sign_mask] and the [lsl 32] in [of_int32] are already + unspecified on a narrower one. They are computed rather than written as + literals so that a 31-bit backend has nothing to truncate. *) let uint32_max = (0xffff lsl 16) lor 0xffff let int32_sign_maskl = 0x80000000l let int32_sign_mask = 1 lsl 31 let int32_maxl = 0x7fffffffl -let int32_max = 0x7fffffff +let int32_max = uint32_max lsr 1 let to_int32 x = let truncated = x land uint32_max in