From eb53a6743aa401afb53e935720a16b06efc22e03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 06:15:14 +0000 Subject: [PATCH 1/5] Initial plan From 98d82e8d4722eda6b758e6b0b17663736db50f74 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 07:28:38 +0000 Subject: [PATCH 2/5] Refactor Int32 to use Int32Native pattern matching Int64 architecture - Create std/haxe/numeric/Int32Native.hx as cross-platform abstract(Int) with masking - Create JVM, HL, C++, Neko target overrides in _std directories - Refactor std/haxe/Int32.hx to thin @:op layer delegating to Int32Native - Add comprehensive TestInt32 unit test class - All tests passing on interp, JS, JVM, Neko, Python, PHP Co-authored-by: Simn <634365+Simn@users.noreply.github.com> --- std/cpp/_std/haxe/numeric/Int32Native.hx | 77 +++++++ std/haxe/Int32.hx | 189 ++++++----------- std/haxe/numeric/Int32Native.hx | 141 +++++++++++++ std/hl/_std/haxe/numeric/Int32Native.hx | 77 +++++++ std/jvm/_std/haxe/numeric/Int32Native.hx | 77 +++++++ std/neko/_std/haxe/numeric/Int32Native.hx | 80 ++++++++ tests/unit/src/unit/TestInt32.hx | 236 ++++++++++++++++++++++ tests/unit/src/unit/TestMain.hx | 1 + 8 files changed, 753 insertions(+), 125 deletions(-) create mode 100644 std/cpp/_std/haxe/numeric/Int32Native.hx create mode 100644 std/haxe/numeric/Int32Native.hx create mode 100644 std/hl/_std/haxe/numeric/Int32Native.hx create mode 100644 std/jvm/_std/haxe/numeric/Int32Native.hx create mode 100644 std/neko/_std/haxe/numeric/Int32Native.hx create mode 100644 tests/unit/src/unit/TestInt32.hx diff --git a/std/cpp/_std/haxe/numeric/Int32Native.hx b/std/cpp/_std/haxe/numeric/Int32Native.hx new file mode 100644 index 00000000000..726a99e61ba --- /dev/null +++ b/std/cpp/_std/haxe/numeric/Int32Native.hx @@ -0,0 +1,77 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe.numeric; + +/** + C++-specific Int32Native. On C++, Int is natively 32-bit, + so all operations are trivial with no masking needed. +**/ +typedef Int32Native = Int32NativeImpl; + +@:coreApi(check = Off) +private abstract Int32NativeImpl(Int) from Int to Int { + public static inline function neg(x:Int32Native):Int32Native + return cast(-(x : Int)); + + public static inline function add(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) + (b : Int)); + + public static inline function sub(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) - (b : Int)); + + public static inline function mul(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) * (b : Int)); + + public static inline function complement(a:Int32Native):Int32Native + return cast ~(a : Int); + + public static inline function and(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) & (b : Int)); + + public static inline function or(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) | (b : Int)); + + public static inline function xor(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) ^ (b : Int)); + + public static inline function shl(a:Int32Native, b:Int):Int32Native + return cast((a : Int) << b); + + public static inline function shr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >> b); + + public static inline function ushr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >>> b); + + public static function ucompare(a:Int32Native, b:Int32Native):Int { + if ((a : Int) < 0) + return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; + return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); + } + + public inline function toFloat():Float + return this; + + public static inline function clamp(x:Int):Int32Native + return cast x; +} diff --git a/std/haxe/Int32.hx b/std/haxe/Int32.hx index 615bea1860a..00e280fe7a1 100644 --- a/std/haxe/Int32.hx +++ b/std/haxe/Int32.hx @@ -22,78 +22,71 @@ package haxe; +import haxe.numeric.Int32Native; + /** - Int32 provides a 32-bit integer with consistent overflow behavior across - all platforms. + A cross-platform signed 32-bit integer with consistent overflow behavior. + + This abstract defines the operator overloads and public API surface. + The actual implementation is in `haxe.numeric.Int32Native`, which can be + shadowed by platform-specific `_std` directories for native support. **/ @:transitive -abstract Int32(Int) from Int to Int { - @:op(-A) private inline function negate():Int32 - return clamp(~this + 1); +abstract Int32(Int32Native) from Int to Int { + private inline function new(x:Int32Native) + this = x; - @:op(++A) private inline function preIncrement():Int32 - return this = clamp(++this); + @:op(-A) private static inline function neg(x:Int32):Int32 + return Int32Native.neg(x); + + @:op(++A) private inline function preIncrement():Int32 { + this = Int32Native.add(this, 1); + return cast this; + } @:op(A++) private inline function postIncrement():Int32 { - var ret = this++; - this = clamp(this); + var ret = this; + this = Int32Native.add(this, 1); return ret; } - @:op(--A) private inline function preDecrement():Int32 - return this = clamp(--this); + @:op(--A) private inline function preDecrement():Int32 { + this = Int32Native.sub(this, 1); + return cast this; + } @:op(A--) private inline function postDecrement():Int32 { - var ret = this--; - this = clamp(this); + var ret = this; + this = Int32Native.sub(this, 1); return ret; } @:op(A + B) private static inline function add(a:Int32, b:Int32):Int32 - return clamp((a : Int) + (b : Int)); + return Int32Native.add(a, b); @:op(A + B) @:commutative private static inline function addInt(a:Int32, b:Int):Int32 - return clamp((a : Int) + (b : Int)); + return Int32Native.add(a, b); @:op(A + B) @:commutative private static function addFloat(a:Int32, b:Float):Float; @:op(A - B) private static inline function sub(a:Int32, b:Int32):Int32 - return clamp((a : Int) - (b : Int)); + return Int32Native.sub(a, b); @:op(A - B) private static inline function subInt(a:Int32, b:Int):Int32 - return clamp((a : Int) - (b : Int)); + return Int32Native.sub(a, b); @:op(A - B) private static inline function intSub(a:Int, b:Int32):Int32 - return clamp((a : Int) - (b : Int)); + return Int32Native.sub(a, b); @:op(A - B) private static function subFloat(a:Int32, b:Float):Float; @:op(A - B) private static function floatSub(a:Float, b:Int32):Float; - #if (js || php || python || lua) - #if js - // on JS we want to try using Math.imul, but we have to assign that function to Int32.mul only once, - // or else V8 will deoptimize it, so we need to be a bit funky with this. - // See https://github.com/HaxeFoundation/haxe/issues/5367 for benchmarks. - @:op(A * B) inline static function mul(a:Int32, b:Int32):Int32 - return _mul(a, b); - - static var _mul:Int32->Int32->Int32 = untyped if (Math.imul != null) - Math.imul - else - function(a:Int32, b:Int32):Int32 return clamp((a : Int) * ((b : Int) & 0xFFFF) + clamp((a : Int) * ((b : Int) >>> 16) << 16)); - #else - @:op(A * B) private static function mul(a:Int32, b:Int32):Int32 - return clamp((a : Int) * ((b : Int) & 0xFFFF) + clamp((a : Int) * ((b : Int) >>> 16) << 16)); - #end + @:op(A * B) private static inline function mul(a:Int32, b:Int32):Int32 + return Int32Native.mul(a, b); @:op(A * B) @:commutative private static inline function mulInt(a:Int32, b:Int):Int32 - return mul(a, b); - #else - @:op(A * B) private static function mul(a:Int32, b:Int32):Int32; - - @:op(A * B) @:commutative private static function mulInt(a:Int32, b:Int):Int32; - #end + return Int32Native.mul(a, b); @:op(A * B) @:commutative private static function mulFloat(a:Int32, b:Float):Float; @@ -169,114 +162,60 @@ abstract Int32(Int) from Int to Int { @:op(A >= B) private static function floatGte(a:Float, b:Int32):Bool; - #if (lua || python || php) @:op(~A) private static inline function complement(a:Int32):Int32 - #if lua return lua.Boot.clampInt32(~a); #else return clamp(~a); #end - #else - @:op(~A) private function complement():Int32; - #end - - @:op(A & B) private static function and(a:Int32, b:Int32):Int32; - - @:op(A & B) @:commutative private static function andInt(a:Int32, b:Int):Int32; + return Int32Native.complement(a); - #if (lua || python || php) - @:op(A | B) private static #if (python || php) inline #end function or(a:Int32, b:Int32):Int32 - return clamp((a : Int) | (b : Int)); + @:op(A & B) private static inline function and(a:Int32, b:Int32):Int32 + return Int32Native.and(a, b); - @:op(A | B) @:commutative private #if (python || php) inline #end static function orInt(a:Int32, b:Int):Int32 - return clamp((a : Int) | b); - #else - @:op(A | B) private static function or(a:Int32, b:Int32):Int32; + @:op(A & B) @:commutative private static inline function andInt(a:Int32, b:Int):Int32 + return Int32Native.and(a, b); - @:op(A | B) @:commutative private static function orInt(a:Int32, b:Int):Int32; - #end + @:op(A | B) private static inline function or(a:Int32, b:Int32):Int32 + return Int32Native.or(a, b); - #if (lua || python || php) - @:op(A ^ B) private static #if (python || php) inline #end function xor(a:Int32, b:Int32):Int32 - return clamp((a : Int) ^ (b : Int)); + @:op(A | B) @:commutative private static inline function orInt(a:Int32, b:Int):Int32 + return Int32Native.or(a, b); - @:op(A ^ B) @:commutative private static #if (python || php) inline #end function xorInt(a:Int32, b:Int):Int32 - return clamp((a : Int) ^ b); - #else - @:op(A ^ B) private static function xor(a:Int32, b:Int32):Int32; + @:op(A ^ B) private static inline function xor(a:Int32, b:Int32):Int32 + return Int32Native.xor(a, b); - @:op(A ^ B) @:commutative private static function xorInt(a:Int32, b:Int):Int32; - #end + @:op(A ^ B) @:commutative private static inline function xorInt(a:Int32, b:Int):Int32 + return Int32Native.xor(a, b); - #if (lua || python || php) - @:op(A >> B) private static #if (python || php) inline #end function shr(a:Int32, b:Int32):Int32 - return clamp((a : Int) >> (b : Int)); + @:op(A >> B) private static inline function shr(a:Int32, b:Int32):Int32 + return Int32Native.shr(a, (b : Int)); - @:op(A >> B) private static #if (python || php) inline #end function shrInt(a:Int32, b:Int):Int32 - return clamp((a : Int) >> b); + @:op(A >> B) private static inline function shrInt(a:Int32, b:Int):Int32 + return Int32Native.shr(a, b); - @:op(A >> B) private static #if (python || php) inline #end function intShr(a:Int, b:Int32):Int32 - return clamp(a >> (b : Int)); - #else - @:op(A >> B) private static function shr(a:Int32, b:Int32):Int32; + @:op(A >> B) private static inline function intShr(a:Int, b:Int32):Int32 + return Int32Native.shr(a, (b : Int)); - @:op(A >> B) private static function shrInt(a:Int32, b:Int):Int32; + @:op(A >>> B) private static inline function ushr(a:Int32, b:Int32):Int32 + return Int32Native.ushr(a, (b : Int)); - @:op(A >> B) private static function intShr(a:Int, b:Int32):Int32; - #end + @:op(A >>> B) private static inline function ushrInt(a:Int32, b:Int):Int32 + return Int32Native.ushr(a, b); - @:op(A >>> B) private static function ushr(a:Int32, b:Int32):Int32; + @:op(A >>> B) private static inline function intUshr(a:Int, b:Int32):Int32 + return Int32Native.ushr(a, (b : Int)); - @:op(A >>> B) private static function ushrInt(a:Int32, b:Int):Int32; - - @:op(A >>> B) private static function intUshr(a:Int, b:Int32):Int32; - - #if (php || python || lua) - // PHP may be 64-bit, so shifts must be clamped @:op(A << B) private static inline function shl(a:Int32, b:Int32):Int32 - return clamp((a : Int) << (b : Int)); + return Int32Native.shl(a, (b : Int)); @:op(A << B) private static inline function shlInt(a:Int32, b:Int):Int32 - return clamp((a : Int) << b); + return Int32Native.shl(a, b); @:op(A << B) private static inline function intShl(a:Int, b:Int32):Int32 - return clamp(a << (b : Int)); - #else - @:op(A << B) private static function shl(a:Int32, b:Int32):Int32; - - @:op(A << B) private static function shlInt(a:Int32, b:Int):Int32; - - @:op(A << B) private static function intShl(a:Int, b:Int32):Int32; - #end + return Int32Native.shl(a, (b : Int)); @:to private inline function toFloat():Float - return this; + return (this : Int); /** Compare `a` and `b` in unsigned mode. **/ - public static function ucompare(a:Int32, b:Int32):Int { - if (a < 0) - return b < 0 ? (~b - ~a) : 1; - return b < 0 ? -1 : (a - b); - } - - #if php - static var extraBits:Int = php.Const.PHP_INT_SIZE * 8 - 32; - #end - - #if !lua - inline - #end - static function clamp(x:Int):Int { - // force to-int conversion on platforms that require it - #if js - return x | 0; - #elseif php - // we might be on 64-bit php, so sign extend from 32-bit - return (x << extraBits) >> extraBits; - #elseif python - return (python.Syntax.code("{0} % {1}", (x + python.Syntax.opPow(2, 31)), python.Syntax.opPow(2, 32)) : Int) - python.Syntax.opPow(2, 31); - #elseif lua - return lua.Boot.clampInt32(x); - #else - return (x); - #end - } + public static inline function ucompare(a:Int32, b:Int32):Int + return Int32Native.ucompare(a, b); } diff --git a/std/haxe/numeric/Int32Native.hx b/std/haxe/numeric/Int32Native.hx new file mode 100644 index 00000000000..810dbce6e1b --- /dev/null +++ b/std/haxe/numeric/Int32Native.hx @@ -0,0 +1,141 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe.numeric; + +/** + Cross-platform 32-bit integer native implementation. + Provides the actual arithmetic and bitwise operations with appropriate + masking for platforms where Int is wider than 32 bits. + + Targets with native 32-bit Int (C++, JVM, HL) shadow this file via + their `_std` directories to avoid unnecessary masking. +**/ +typedef Int32Native = Int32NativeImpl; + +private abstract Int32NativeImpl(Int) from Int to Int { + public static inline function neg(x:Int32Native):Int32Native + return clamp(~(x : Int) + 1); + + public static inline function add(a:Int32Native, b:Int32Native):Int32Native + return clamp((a : Int) + (b : Int)); + + public static inline function sub(a:Int32Native, b:Int32Native):Int32Native + return clamp((a : Int) - (b : Int)); + + #if (js || php || python || lua) + #if js + // On JS we want to try using Math.imul, but we have to assign that function + // to _mul only once, or else V8 will deoptimize it. + // See https://github.com/HaxeFoundation/haxe/issues/5367 for benchmarks. + public static inline function mul(a:Int32Native, b:Int32Native):Int32Native + return _mul(a, b); + + static var _mul:Int32Native->Int32Native->Int32Native = untyped if (Math.imul != null) + Math.imul + else + function(a:Int32Native, b:Int32Native):Int32Native return clamp((a : Int) * ((b : Int) & 0xFFFF) + clamp((a : Int) * ((b : Int) >>> 16) << 16)); + #else + public static function mul(a:Int32Native, b:Int32Native):Int32Native + return clamp((a : Int) * ((b : Int) & 0xFFFF) + clamp((a : Int) * ((b : Int) >>> 16) << 16)); + #end + #else + public static inline function mul(a:Int32Native, b:Int32Native):Int32Native + return clamp((a : Int) * (b : Int)); + #end + + #if (lua || python || php) + public static #if (python || php) inline #end function complement(a:Int32Native):Int32Native + #if lua return lua.Boot.clampInt32(~(a : Int)); #else return clamp(~(a : Int)); #end + #else + public static inline function complement(a:Int32Native):Int32Native + return cast ~(a : Int); + #end + + public static inline function and(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) & (b : Int)); + + #if (lua || python || php) + public static #if (python || php) inline #end function or(a:Int32Native, b:Int32Native):Int32Native + return clamp((a : Int) | (b : Int)); + #else + public static inline function or(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) | (b : Int)); + #end + + #if (lua || python || php) + public static #if (python || php) inline #end function xor(a:Int32Native, b:Int32Native):Int32Native + return clamp((a : Int) ^ (b : Int)); + #else + public static inline function xor(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) ^ (b : Int)); + #end + + #if (php || python || lua) + public static inline function shl(a:Int32Native, b:Int):Int32Native + return clamp((a : Int) << b); + #else + public static inline function shl(a:Int32Native, b:Int):Int32Native + return cast((a : Int) << b); + #end + + #if (lua || python || php) + public static #if (python || php) inline #end function shr(a:Int32Native, b:Int):Int32Native + return clamp((a : Int) >> b); + #else + public static inline function shr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >> b); + #end + + public static inline function ushr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >>> b); + + public static function ucompare(a:Int32Native, b:Int32Native):Int { + if ((a : Int) < 0) + return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; + return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); + } + + public inline function toFloat():Float + return this; + + #if php + static var extraBits:Int = php.Const.PHP_INT_SIZE * 8 - 32; + #end + + #if !lua + inline + #end + public static function clamp(x:Int):Int32Native { + #if js + return cast(x | 0); + #elseif php + return cast((x << extraBits) >> extraBits); + #elseif python + return cast(((python.Syntax.code("{0} % {1}", (x + python.Syntax.opPow(2, 31)), python.Syntax.opPow(2, 32)) : Int) - python.Syntax.opPow(2, 31))); + #elseif lua + return cast lua.Boot.clampInt32(x); + #else + return cast x; + #end + } +} diff --git a/std/hl/_std/haxe/numeric/Int32Native.hx b/std/hl/_std/haxe/numeric/Int32Native.hx new file mode 100644 index 00000000000..1e73cda5cd7 --- /dev/null +++ b/std/hl/_std/haxe/numeric/Int32Native.hx @@ -0,0 +1,77 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe.numeric; + +/** + HashLink-specific Int32Native. On HL, Int is natively 32-bit, + so all operations are trivial with no masking needed. +**/ +typedef Int32Native = Int32NativeImpl; + +@:coreApi(check = Off) +private abstract Int32NativeImpl(Int) from Int to Int { + public static inline function neg(x:Int32Native):Int32Native + return cast(-(x : Int)); + + public static inline function add(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) + (b : Int)); + + public static inline function sub(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) - (b : Int)); + + public static inline function mul(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) * (b : Int)); + + public static inline function complement(a:Int32Native):Int32Native + return cast ~(a : Int); + + public static inline function and(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) & (b : Int)); + + public static inline function or(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) | (b : Int)); + + public static inline function xor(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) ^ (b : Int)); + + public static inline function shl(a:Int32Native, b:Int):Int32Native + return cast((a : Int) << b); + + public static inline function shr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >> b); + + public static inline function ushr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >>> b); + + public static function ucompare(a:Int32Native, b:Int32Native):Int { + if ((a : Int) < 0) + return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; + return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); + } + + public inline function toFloat():Float + return this; + + public static inline function clamp(x:Int):Int32Native + return cast x; +} diff --git a/std/jvm/_std/haxe/numeric/Int32Native.hx b/std/jvm/_std/haxe/numeric/Int32Native.hx new file mode 100644 index 00000000000..a09194f5a4b --- /dev/null +++ b/std/jvm/_std/haxe/numeric/Int32Native.hx @@ -0,0 +1,77 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe.numeric; + +/** + JVM-specific Int32Native. On JVM, Int is natively 32-bit, + so all operations are trivial with no masking needed. +**/ +typedef Int32Native = Int32NativeImpl; + +@:coreApi(check = Off) +private abstract Int32NativeImpl(Int) from Int to Int { + public static inline function neg(x:Int32Native):Int32Native + return cast(-(x : Int)); + + public static inline function add(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) + (b : Int)); + + public static inline function sub(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) - (b : Int)); + + public static inline function mul(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) * (b : Int)); + + public static inline function complement(a:Int32Native):Int32Native + return cast ~(a : Int); + + public static inline function and(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) & (b : Int)); + + public static inline function or(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) | (b : Int)); + + public static inline function xor(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) ^ (b : Int)); + + public static inline function shl(a:Int32Native, b:Int):Int32Native + return cast((a : Int) << b); + + public static inline function shr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >> b); + + public static inline function ushr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >>> b); + + public static function ucompare(a:Int32Native, b:Int32Native):Int { + if ((a : Int) < 0) + return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; + return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); + } + + public inline function toFloat():Float + return this; + + public static inline function clamp(x:Int):Int32Native + return cast x; +} diff --git a/std/neko/_std/haxe/numeric/Int32Native.hx b/std/neko/_std/haxe/numeric/Int32Native.hx new file mode 100644 index 00000000000..e708c2916e0 --- /dev/null +++ b/std/neko/_std/haxe/numeric/Int32Native.hx @@ -0,0 +1,80 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe.numeric; + +/** + Neko-specific Int32Native. Neko's Int type is only 31 bits, + so full 32-bit overflow behavior cannot be guaranteed. + This provides best-effort semantics within the 31-bit constraint. + Values near the 32-bit boundaries may not behave identically to + targets with native 32-bit integers. +**/ +typedef Int32Native = Int32NativeImpl; + +@:coreApi(check = Off) +private abstract Int32NativeImpl(Int) from Int to Int { + public static inline function neg(x:Int32Native):Int32Native + return cast(~(x : Int) + 1); + + public static inline function add(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) + (b : Int)); + + public static inline function sub(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) - (b : Int)); + + public static inline function mul(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) * (b : Int)); + + public static inline function complement(a:Int32Native):Int32Native + return cast ~(a : Int); + + public static inline function and(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) & (b : Int)); + + public static inline function or(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) | (b : Int)); + + public static inline function xor(a:Int32Native, b:Int32Native):Int32Native + return cast((a : Int) ^ (b : Int)); + + public static inline function shl(a:Int32Native, b:Int):Int32Native + return cast((a : Int) << b); + + public static inline function shr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >> b); + + public static inline function ushr(a:Int32Native, b:Int):Int32Native + return cast((a : Int) >>> b); + + public static function ucompare(a:Int32Native, b:Int32Native):Int { + if ((a : Int) < 0) + return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; + return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); + } + + public inline function toFloat():Float + return this; + + public static inline function clamp(x:Int):Int32Native + return cast x; +} diff --git a/tests/unit/src/unit/TestInt32.hx b/tests/unit/src/unit/TestInt32.hx new file mode 100644 index 00000000000..6bfaf3466f6 --- /dev/null +++ b/tests/unit/src/unit/TestInt32.hx @@ -0,0 +1,236 @@ +package unit; + +import haxe.Int32; + +class TestInt32 extends Test { + // --- Constants --- + static var MAX:Int32 = 0x7fffffff; + static var MIN:Int32 = 0x80000000; + static var ZERO:Int32 = 0; + static var ONE:Int32 = 1; + static var NEG_ONE:Int32 = -1; + + // --- Overflow behavior --- + function testOverflowAdd() { + eq((MAX + ONE : Int32), MIN); + eq((MIN + NEG_ONE : Int32), MAX); + eq((MAX + MIN : Int32), NEG_ONE); + } + + function testOverflowSub() { + eq((MIN - ONE : Int32), MAX); + eq((MAX - MIN : Int32), NEG_ONE); + eq((MIN - 1 : Int32), MAX); + } + + function testOverflowMul() { + eq((MAX * MAX : Int32), ONE); + eq((MAX * 2 : Int32), cast(-2, Int32)); + var minVal:Int32 = MIN; + eq((MAX * minVal : Int32), MIN); + } + + function testOverflowNeg() { + // Two's complement: -MIN overflows back to MIN + eq((-MIN : Int32), MIN); + // Normal negation + eq((-ONE : Int32), NEG_ONE); + eq((-NEG_ONE : Int32), ONE); + eq((-ZERO : Int32), ZERO); + } + + // --- Increment/Decrement --- + function testPreIncrement() { + var a:Int32 = MAX; + eq(++a, MIN); + eq(a, MIN); + } + + function testPostIncrement() { + var a:Int32 = MAX; + eq(a++, MAX); + eq(a, MIN); + } + + function testPreDecrement() { + var a:Int32 = MIN; + eq(--a, MAX); + eq(a, MAX); + } + + function testPostDecrement() { + var a:Int32 = MIN; + eq(a--, MIN); + eq(a, MAX); + } + + // --- Bitwise operations --- + function testComplement() { + eq((~ZERO : Int32), NEG_ONE); + eq((~NEG_ONE : Int32), ZERO); + eq((~MAX : Int32), MIN); + eq((~MIN : Int32), MAX); + } + + function testBitwiseAnd() { + eq((MAX & MIN : Int32), ZERO); + eq((NEG_ONE & MAX : Int32), MAX); + eq((NEG_ONE & MIN : Int32), MIN); + } + + function testBitwiseOr() { + var expected:Int32 = NEG_ONE; + eq((MAX | MIN : Int32), expected); + eq((ZERO | MAX : Int32), MAX); + } + + function testBitwiseXor() { + var expected:Int32 = NEG_ONE; + eq((MAX ^ MIN : Int32), expected); + eq((MAX ^ MAX : Int32), ZERO); + eq((MIN ^ MIN : Int32), ZERO); + } + + // --- Shift operations --- + function testShiftLeft() { + var one:Int32 = 1; + eq((one << 31 : Int32), MIN); + eq((MIN << 1 : Int32), ZERO); + var v:Int32 = 0xFF; + eq((v << 8 : Int32), cast(0xFF00, Int32)); + } + + function testShiftRight() { + eq((MIN >> 1 : Int32), cast(0xc0000000, Int32)); + eq((NEG_ONE >> 1 : Int32), NEG_ONE); // sign extension + eq((MAX >> 1 : Int32), cast(0x3fffffff, Int32)); + } + + function testUnsignedShiftRight() { + eq((MIN >>> 1 : Int32), cast(0x40000000, Int32)); + eq((NEG_ONE >>> 1 : Int32), MAX); + } + + // --- Unsigned comparison --- + function testUcompare() { + // 0 < MAX (unsigned) + t(Int32.ucompare(ZERO, MAX) < 0); + // MAX < MIN (unsigned, since MIN = 0x80000000 is large unsigned) + t(Int32.ucompare(MAX, MIN) < 0); + // MIN < NEG_ONE (unsigned, 0x80000000 < 0xFFFFFFFF) + t(Int32.ucompare(MIN, NEG_ONE) < 0); + // Equal values + eq(Int32.ucompare(MAX, MAX), 0); + eq(Int32.ucompare(MIN, MIN), 0); + eq(Int32.ucompare(ZERO, ZERO), 0); + } + + // --- Comparison operators --- + function testComparison() { + t(MIN < MAX); + t(MAX > MIN); + t(MIN <= MIN); + t(MAX >= MAX); + t(ZERO == ZERO); + t(ONE != ZERO); + } + + // --- Mixed-type operations --- + function testMixedIntOps() { + // Int32 + Int + eq((MAX + 1 : Int32), MIN); + // Int32 - Int + eq((MIN - 1 : Int32), MAX); + // Int - Int32 + eq((0 - ONE : Int32), NEG_ONE); + } + + function testMixedFloatOps() { + // Int32 + Float returns Float + var result:Float = MAX + 0.5; + feq(result, 2147483647.5); + // Int32 * Float returns Float + var result2:Float = ONE * 2.5; + feq(result2, 2.5); + } + + // --- Conversion --- + function testToFloat() { + var f:Float = MAX; + feq(f, 2147483647.0); + var f2:Float = MIN; + feq(f2, -2147483648.0); + } + + function testFromInt() { + var a:Int32 = 42; + eq((a : Int), 42); + var b:Int32 = -42; + eq((b : Int), -42); + } + + // --- Division (returns Float) --- + function testDivision() { + var ten:Int32 = 10; + var three:Int32 = 3; + feq((ten : Float) / (three : Float), 10.0 / 3.0); + } + + // --- Modulus --- + function testModulus() { + var ten:Int32 = 10; + var three:Int32 = 3; + eq((ten % three : Int32), cast(1, Int32)); + var negTen:Int32 = -10; + eq((negTen % three : Int32), cast(-1, Int32)); + } + + // --- Specific regression tests --- + function testTwosComplementOverflow_Issue7491() { + // https://github.com/HaxeFoundation/haxe/pull/7491 + var min:Int32 = MIN; + eq(-min, min); // two's complement overflow + eq(-2147483643, cast(5 + -min, Int)); // order of ops and negate + eq(2147483643, cast(-(5 + min), Int)); // static analyzer issue + } + + function testArrayIndexWithInt32() { + #if !cpp + var a = [1]; + var next = 0; + + var i32:Int32 = MAX - 1; + i32 |= ((a[next] << 32) | 1); + eq(i32, MAX); + + var i32:Int32 = ((a[next] << 33) | 3); + i32 >>= 1; + eq((i32 : Int), 1); + + var i32:Int32 = 2; + i32 ^= (((a[next] << 32) | 1) : Int32); + eq((i32 : Int), 3); + + var i32:Int32 = 2; + var c = ~(((a[next] << 32) | 1) : Int32); + eq(c, cast(0xfffffffe, Int32)); + #end + } + + // --- Arithmetic identity tests --- + function testArithmeticIdentities() { + var a:Int32 = 12345; + eq((a + ZERO : Int32), a); + eq((a - ZERO : Int32), a); + eq((a * ONE : Int32), a); + eq(a, a); + } + + function testBitwiseIdentities() { + var a:Int32 = 0xDEADBEEF; + eq((a & NEG_ONE : Int32), a); + eq((a | ZERO : Int32), a); + eq((a ^ ZERO : Int32), a); + eq((a ^ a : Int32), ZERO); + } +} diff --git a/tests/unit/src/unit/TestMain.hx b/tests/unit/src/unit/TestMain.hx index 8cc773e8283..e9ed4c285d1 100644 --- a/tests/unit/src/unit/TestMain.hx +++ b/tests/unit/src/unit/TestMain.hx @@ -45,6 +45,7 @@ function main() { new TestMisc(), new TestJson(), new TestResource(), + new TestInt32(), new TestInt64(), new TestReflect(), new TestSerialize(), From e5914c287c3512ffccb54142bb7c280986f5393f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 07:30:04 +0000 Subject: [PATCH 3/5] Address code review: add comments for Neko negation and C++ test skip Co-authored-by: Simn <634365+Simn@users.noreply.github.com> --- std/neko/_std/haxe/numeric/Int32Native.hx | 2 ++ tests/unit/src/unit/TestInt32.hx | 1 + 2 files changed, 3 insertions(+) diff --git a/std/neko/_std/haxe/numeric/Int32Native.hx b/std/neko/_std/haxe/numeric/Int32Native.hx index e708c2916e0..0507d0ae45f 100644 --- a/std/neko/_std/haxe/numeric/Int32Native.hx +++ b/std/neko/_std/haxe/numeric/Int32Native.hx @@ -33,6 +33,8 @@ typedef Int32Native = Int32NativeImpl; @:coreApi(check = Off) private abstract Int32NativeImpl(Int) from Int to Int { + // Two's complement negation. Best-effort on Neko where values + // near 32-bit boundaries may overflow the 31-bit Int range. public static inline function neg(x:Int32Native):Int32Native return cast(~(x : Int) + 1); diff --git a/tests/unit/src/unit/TestInt32.hx b/tests/unit/src/unit/TestInt32.hx index 6bfaf3466f6..567f19585bb 100644 --- a/tests/unit/src/unit/TestInt32.hx +++ b/tests/unit/src/unit/TestInt32.hx @@ -195,6 +195,7 @@ class TestInt32 extends Test { } function testArrayIndexWithInt32() { + // C++ handles array indexing with Int32 differently due to native type handling #if !cpp var a = [1]; var next = 0; From 4f7b59438ae8fb35954cd503af3e97e6862859b9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 08:12:05 +0000 Subject: [PATCH 4/5] Address PR review: fix test #if, remove Neko override, remove target docs and @:coreApi - Move #if !cpp to cover entire testArrayIndexWithInt32 function (avoids utest "no assertions") - Remove Neko-specific Int32Native override (cross-platform version works correctly via Float auto-promotion) - Remove doc comments from target-specific Int32Native files (docs belong on main Int32 type) - Remove unnecessary @:coreApi(check = Off) from private types (Implied check handles these) - Add platform representation docs to main Int32.hx Co-authored-by: Simn <634365+Simn@users.noreply.github.com> --- std/cpp/_std/haxe/numeric/Int32Native.hx | 5 -- std/haxe/Int32.hx | 6 ++ std/haxe/numeric/Int32Native.hx | 8 --- std/hl/_std/haxe/numeric/Int32Native.hx | 5 -- std/jvm/_std/haxe/numeric/Int32Native.hx | 5 -- std/neko/_std/haxe/numeric/Int32Native.hx | 82 ----------------------- tests/unit/src/unit/TestInt32.hx | 6 +- 7 files changed, 9 insertions(+), 108 deletions(-) delete mode 100644 std/neko/_std/haxe/numeric/Int32Native.hx diff --git a/std/cpp/_std/haxe/numeric/Int32Native.hx b/std/cpp/_std/haxe/numeric/Int32Native.hx index 726a99e61ba..e482c6a42e4 100644 --- a/std/cpp/_std/haxe/numeric/Int32Native.hx +++ b/std/cpp/_std/haxe/numeric/Int32Native.hx @@ -22,13 +22,8 @@ package haxe.numeric; -/** - C++-specific Int32Native. On C++, Int is natively 32-bit, - so all operations are trivial with no masking needed. -**/ typedef Int32Native = Int32NativeImpl; -@:coreApi(check = Off) private abstract Int32NativeImpl(Int) from Int to Int { public static inline function neg(x:Int32Native):Int32Native return cast(-(x : Int)); diff --git a/std/haxe/Int32.hx b/std/haxe/Int32.hx index 00e280fe7a1..0c348e24831 100644 --- a/std/haxe/Int32.hx +++ b/std/haxe/Int32.hx @@ -30,6 +30,12 @@ import haxe.numeric.Int32Native; This abstract defines the operator overloads and public API surface. The actual implementation is in `haxe.numeric.Int32Native`, which can be shadowed by platform-specific `_std` directories for native support. + + On targets with native 32-bit Int (C++, JVM, HL), Int32 maps directly + to the platform Int with no overhead. On scripting targets (JS, PHP, Python, + Lua), operations are clamped to 32-bit range after each computation. On Neko, + values exceeding 31-bit range are auto-promoted to Float by the VM while + preserving correct 32-bit arithmetic. **/ @:transitive abstract Int32(Int32Native) from Int to Int { diff --git a/std/haxe/numeric/Int32Native.hx b/std/haxe/numeric/Int32Native.hx index 810dbce6e1b..189df0f0cbf 100644 --- a/std/haxe/numeric/Int32Native.hx +++ b/std/haxe/numeric/Int32Native.hx @@ -22,14 +22,6 @@ package haxe.numeric; -/** - Cross-platform 32-bit integer native implementation. - Provides the actual arithmetic and bitwise operations with appropriate - masking for platforms where Int is wider than 32 bits. - - Targets with native 32-bit Int (C++, JVM, HL) shadow this file via - their `_std` directories to avoid unnecessary masking. -**/ typedef Int32Native = Int32NativeImpl; private abstract Int32NativeImpl(Int) from Int to Int { diff --git a/std/hl/_std/haxe/numeric/Int32Native.hx b/std/hl/_std/haxe/numeric/Int32Native.hx index 1e73cda5cd7..e482c6a42e4 100644 --- a/std/hl/_std/haxe/numeric/Int32Native.hx +++ b/std/hl/_std/haxe/numeric/Int32Native.hx @@ -22,13 +22,8 @@ package haxe.numeric; -/** - HashLink-specific Int32Native. On HL, Int is natively 32-bit, - so all operations are trivial with no masking needed. -**/ typedef Int32Native = Int32NativeImpl; -@:coreApi(check = Off) private abstract Int32NativeImpl(Int) from Int to Int { public static inline function neg(x:Int32Native):Int32Native return cast(-(x : Int)); diff --git a/std/jvm/_std/haxe/numeric/Int32Native.hx b/std/jvm/_std/haxe/numeric/Int32Native.hx index a09194f5a4b..e482c6a42e4 100644 --- a/std/jvm/_std/haxe/numeric/Int32Native.hx +++ b/std/jvm/_std/haxe/numeric/Int32Native.hx @@ -22,13 +22,8 @@ package haxe.numeric; -/** - JVM-specific Int32Native. On JVM, Int is natively 32-bit, - so all operations are trivial with no masking needed. -**/ typedef Int32Native = Int32NativeImpl; -@:coreApi(check = Off) private abstract Int32NativeImpl(Int) from Int to Int { public static inline function neg(x:Int32Native):Int32Native return cast(-(x : Int)); diff --git a/std/neko/_std/haxe/numeric/Int32Native.hx b/std/neko/_std/haxe/numeric/Int32Native.hx deleted file mode 100644 index 0507d0ae45f..00000000000 --- a/std/neko/_std/haxe/numeric/Int32Native.hx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C)2005-2019 Haxe Foundation - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - */ - -package haxe.numeric; - -/** - Neko-specific Int32Native. Neko's Int type is only 31 bits, - so full 32-bit overflow behavior cannot be guaranteed. - This provides best-effort semantics within the 31-bit constraint. - Values near the 32-bit boundaries may not behave identically to - targets with native 32-bit integers. -**/ -typedef Int32Native = Int32NativeImpl; - -@:coreApi(check = Off) -private abstract Int32NativeImpl(Int) from Int to Int { - // Two's complement negation. Best-effort on Neko where values - // near 32-bit boundaries may overflow the 31-bit Int range. - public static inline function neg(x:Int32Native):Int32Native - return cast(~(x : Int) + 1); - - public static inline function add(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) + (b : Int)); - - public static inline function sub(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) - (b : Int)); - - public static inline function mul(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) * (b : Int)); - - public static inline function complement(a:Int32Native):Int32Native - return cast ~(a : Int); - - public static inline function and(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) & (b : Int)); - - public static inline function or(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) | (b : Int)); - - public static inline function xor(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) ^ (b : Int)); - - public static inline function shl(a:Int32Native, b:Int):Int32Native - return cast((a : Int) << b); - - public static inline function shr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >> b); - - public static inline function ushr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >>> b); - - public static function ucompare(a:Int32Native, b:Int32Native):Int { - if ((a : Int) < 0) - return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; - return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); - } - - public inline function toFloat():Float - return this; - - public static inline function clamp(x:Int):Int32Native - return cast x; -} diff --git a/tests/unit/src/unit/TestInt32.hx b/tests/unit/src/unit/TestInt32.hx index 567f19585bb..d2176f321ce 100644 --- a/tests/unit/src/unit/TestInt32.hx +++ b/tests/unit/src/unit/TestInt32.hx @@ -194,9 +194,9 @@ class TestInt32 extends Test { eq(2147483643, cast(-(5 + min), Int)); // static analyzer issue } + // C++ handles array indexing with Int32 differently due to native type handling + #if !cpp function testArrayIndexWithInt32() { - // C++ handles array indexing with Int32 differently due to native type handling - #if !cpp var a = [1]; var next = 0; @@ -215,8 +215,8 @@ class TestInt32 extends Test { var i32:Int32 = 2; var c = ~(((a[next] << 32) | 1) : Int32); eq(c, cast(0xfffffffe, Int32)); - #end } + #end // --- Arithmetic identity tests --- function testArithmeticIdentities() { From 6dd76419717fd7520ef72992c70ca1116a251183 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:42:58 +0000 Subject: [PATCH 5/5] Fix CPPIA neg overflow, deduplicate Int32Native with Int32Direct, remove @:coreApi from Int64Native, add issue regression tests Co-authored-by: Simn <634365+Simn@users.noreply.github.com> --- std/cpp/_std/haxe/numeric/Int32Native.hx | 49 +------------- std/cpp/_std/haxe/numeric/Int64Native.hx | 1 - std/haxe/numeric/Int32Direct.hx | 81 ++++++++++++++++++++++++ std/hl/_std/haxe/numeric/Int32Native.hx | 49 +------------- std/hl/_std/haxe/numeric/Int64Native.hx | 1 - std/jvm/_std/haxe/numeric/Int32Native.hx | 49 +------------- std/jvm/_std/haxe/numeric/Int64Native.hx | 1 - tests/unit/src/unit/TestInt32.hx | 27 ++++++++ 8 files changed, 111 insertions(+), 147 deletions(-) create mode 100644 std/haxe/numeric/Int32Direct.hx diff --git a/std/cpp/_std/haxe/numeric/Int32Native.hx b/std/cpp/_std/haxe/numeric/Int32Native.hx index e482c6a42e4..a3fe93955e5 100644 --- a/std/cpp/_std/haxe/numeric/Int32Native.hx +++ b/std/cpp/_std/haxe/numeric/Int32Native.hx @@ -22,51 +22,4 @@ package haxe.numeric; -typedef Int32Native = Int32NativeImpl; - -private abstract Int32NativeImpl(Int) from Int to Int { - public static inline function neg(x:Int32Native):Int32Native - return cast(-(x : Int)); - - public static inline function add(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) + (b : Int)); - - public static inline function sub(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) - (b : Int)); - - public static inline function mul(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) * (b : Int)); - - public static inline function complement(a:Int32Native):Int32Native - return cast ~(a : Int); - - public static inline function and(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) & (b : Int)); - - public static inline function or(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) | (b : Int)); - - public static inline function xor(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) ^ (b : Int)); - - public static inline function shl(a:Int32Native, b:Int):Int32Native - return cast((a : Int) << b); - - public static inline function shr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >> b); - - public static inline function ushr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >>> b); - - public static function ucompare(a:Int32Native, b:Int32Native):Int { - if ((a : Int) < 0) - return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; - return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); - } - - public inline function toFloat():Float - return this; - - public static inline function clamp(x:Int):Int32Native - return cast x; -} +typedef Int32Native = Int32Direct; diff --git a/std/cpp/_std/haxe/numeric/Int64Native.hx b/std/cpp/_std/haxe/numeric/Int64Native.hx index 7c26585d8c7..ed63c3b232a 100644 --- a/std/cpp/_std/haxe/numeric/Int64Native.hx +++ b/std/cpp/_std/haxe/numeric/Int64Native.hx @@ -98,7 +98,6 @@ private extern class CppInt64Helper { } #end -@:coreApi(check = Off) #if cppia extern #end diff --git a/std/haxe/numeric/Int32Direct.hx b/std/haxe/numeric/Int32Direct.hx new file mode 100644 index 00000000000..c8d07cd6017 --- /dev/null +++ b/std/haxe/numeric/Int32Direct.hx @@ -0,0 +1,81 @@ +/* + * Copyright (C)2005-2019 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +package haxe.numeric; + +/** + Shared Int32Native implementation for targets where `Int` is natively + 32-bit (C++, JVM, HashLink). No masking is needed; operations are identity + casts on the underlying type. + + This type is used internally via `typedef Int32Native = Int32Direct` + in the target-specific overrides. +**/ +abstract Int32Direct(Int) from Int to Int { + public static inline function neg(x:Int32Direct):Int32Direct + // Use ~x+1 (two's complement) rather than unary minus. + // On CPPIA, unary minus on Int can return a value wider than 32 bits, + // while bitwise NOT and addition stay within the native 32-bit int range. + return cast(~(x : Int) + 1); + + public static inline function add(a:Int32Direct, b:Int32Direct):Int32Direct + return cast((a : Int) + (b : Int)); + + public static inline function sub(a:Int32Direct, b:Int32Direct):Int32Direct + return cast((a : Int) - (b : Int)); + + public static inline function mul(a:Int32Direct, b:Int32Direct):Int32Direct + return cast((a : Int) * (b : Int)); + + public static inline function complement(a:Int32Direct):Int32Direct + return cast ~(a : Int); + + public static inline function and(a:Int32Direct, b:Int32Direct):Int32Direct + return cast((a : Int) & (b : Int)); + + public static inline function or(a:Int32Direct, b:Int32Direct):Int32Direct + return cast((a : Int) | (b : Int)); + + public static inline function xor(a:Int32Direct, b:Int32Direct):Int32Direct + return cast((a : Int) ^ (b : Int)); + + public static inline function shl(a:Int32Direct, b:Int):Int32Direct + return cast((a : Int) << b); + + public static inline function shr(a:Int32Direct, b:Int):Int32Direct + return cast((a : Int) >> b); + + public static inline function ushr(a:Int32Direct, b:Int):Int32Direct + return cast((a : Int) >>> b); + + public static function ucompare(a:Int32Direct, b:Int32Direct):Int { + if ((a : Int) < 0) + return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; + return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); + } + + public inline function toFloat():Float + return this; + + public static inline function clamp(x:Int):Int32Direct + return cast x; +} diff --git a/std/hl/_std/haxe/numeric/Int32Native.hx b/std/hl/_std/haxe/numeric/Int32Native.hx index e482c6a42e4..a3fe93955e5 100644 --- a/std/hl/_std/haxe/numeric/Int32Native.hx +++ b/std/hl/_std/haxe/numeric/Int32Native.hx @@ -22,51 +22,4 @@ package haxe.numeric; -typedef Int32Native = Int32NativeImpl; - -private abstract Int32NativeImpl(Int) from Int to Int { - public static inline function neg(x:Int32Native):Int32Native - return cast(-(x : Int)); - - public static inline function add(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) + (b : Int)); - - public static inline function sub(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) - (b : Int)); - - public static inline function mul(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) * (b : Int)); - - public static inline function complement(a:Int32Native):Int32Native - return cast ~(a : Int); - - public static inline function and(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) & (b : Int)); - - public static inline function or(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) | (b : Int)); - - public static inline function xor(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) ^ (b : Int)); - - public static inline function shl(a:Int32Native, b:Int):Int32Native - return cast((a : Int) << b); - - public static inline function shr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >> b); - - public static inline function ushr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >>> b); - - public static function ucompare(a:Int32Native, b:Int32Native):Int { - if ((a : Int) < 0) - return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; - return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); - } - - public inline function toFloat():Float - return this; - - public static inline function clamp(x:Int):Int32Native - return cast x; -} +typedef Int32Native = Int32Direct; diff --git a/std/hl/_std/haxe/numeric/Int64Native.hx b/std/hl/_std/haxe/numeric/Int64Native.hx index a42374695c3..7d3fb69a7a2 100644 --- a/std/hl/_std/haxe/numeric/Int64Native.hx +++ b/std/hl/_std/haxe/numeric/Int64Native.hx @@ -25,7 +25,6 @@ package haxe.numeric; #if (hl_ver >= version("1.12.0") && !hl_legacy32) typedef Int64Native = Int64NativeImpl; -@:coreApi(check = Off) private abstract Int64NativeImpl(hl.I64) from hl.I64 to hl.I64 { static var MASK:hl.I64 = { var v:hl.I64 = 0xFFFF; diff --git a/std/jvm/_std/haxe/numeric/Int32Native.hx b/std/jvm/_std/haxe/numeric/Int32Native.hx index e482c6a42e4..a3fe93955e5 100644 --- a/std/jvm/_std/haxe/numeric/Int32Native.hx +++ b/std/jvm/_std/haxe/numeric/Int32Native.hx @@ -22,51 +22,4 @@ package haxe.numeric; -typedef Int32Native = Int32NativeImpl; - -private abstract Int32NativeImpl(Int) from Int to Int { - public static inline function neg(x:Int32Native):Int32Native - return cast(-(x : Int)); - - public static inline function add(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) + (b : Int)); - - public static inline function sub(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) - (b : Int)); - - public static inline function mul(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) * (b : Int)); - - public static inline function complement(a:Int32Native):Int32Native - return cast ~(a : Int); - - public static inline function and(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) & (b : Int)); - - public static inline function or(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) | (b : Int)); - - public static inline function xor(a:Int32Native, b:Int32Native):Int32Native - return cast((a : Int) ^ (b : Int)); - - public static inline function shl(a:Int32Native, b:Int):Int32Native - return cast((a : Int) << b); - - public static inline function shr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >> b); - - public static inline function ushr(a:Int32Native, b:Int):Int32Native - return cast((a : Int) >>> b); - - public static function ucompare(a:Int32Native, b:Int32Native):Int { - if ((a : Int) < 0) - return (b : Int) < 0 ? (~(b : Int) - ~(a : Int)) : 1; - return (b : Int) < 0 ? -1 : ((a : Int) - (b : Int)); - } - - public inline function toFloat():Float - return this; - - public static inline function clamp(x:Int):Int32Native - return cast x; -} +typedef Int32Native = Int32Direct; diff --git a/std/jvm/_std/haxe/numeric/Int64Native.hx b/std/jvm/_std/haxe/numeric/Int64Native.hx index 60dfad843f7..71090f8f028 100644 --- a/std/jvm/_std/haxe/numeric/Int64Native.hx +++ b/std/jvm/_std/haxe/numeric/Int64Native.hx @@ -24,7 +24,6 @@ package haxe.numeric; typedef Int64Native = Int64NativeImpl; -@:coreApi(check = Off) private abstract Int64NativeImpl(jvm.Int64) from jvm.Int64 to jvm.Int64 { public var high(get, set):haxe.Int32; diff --git a/tests/unit/src/unit/TestInt32.hx b/tests/unit/src/unit/TestInt32.hx index d2176f321ce..78d0911bef6 100644 --- a/tests/unit/src/unit/TestInt32.hx +++ b/tests/unit/src/unit/TestInt32.hx @@ -194,6 +194,33 @@ class TestInt32 extends Test { eq(2147483643, cast(-(5 + min), Int)); // static analyzer issue } + // https://github.com/HaxeFoundation/haxe/issues/10780 + // C++ inconsistent overflow behavior for Int32 with multiplication + function testMulOverflow_Issue10780() { + var a:Int32 = 257; + var b:Int32 = 0x01010101; + // 257 * 0x01010101 = 0x102020201 overflows 32-bit to 0x02020201 = 33686017 + eq((a * b : Int), 0x02020201); + } + + // https://github.com/HaxeFoundation/haxe/issues/10995 + // Python: wrong result when XOR with dynamic shift amount + function testXorWithDynamicShift_Issue10995() { + var changeBit = 31; + var result:Int32 = MIN ^ (1 << changeBit); + eq((result : Int), 0); + } + + // https://github.com/HaxeFoundation/haxe/issues/5938 + // Python: |= not clamping result to 32 bits + function testOrEqualsNotClamping_Issue5938() { + var i32:Int32 = 15459750; + var arr = [178, 0, 0]; + var next = 0; + i32 |= (arr[next] << 24); + eq((i32 : Int), -1293163098); + } + // C++ handles array indexing with Int32 differently due to native type handling #if !cpp function testArrayIndexWithInt32() {