From 6dd79a49150d2c2b5a062de1b9a0acd7f7df58cf Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Wed, 11 Feb 2026 13:58:14 +0000 Subject: [PATCH 01/13] [lua] Port _hx_bit_clamp to haxe code We can now rely on normal dce so that the code for this function isn't generated unless required. It also provides more control over what code is generated. This also allows compile time abstractions that aren't supported on lua, like inline function calls, which reduce duplicate code. --- src/generators/genlua.ml | 5 +-- std/lua/Boot.hx | 77 +++++++++++++++++++++++++++++++++- std/lua/_lua/_hx_bit.lua | 12 +++--- std/lua/_lua/_hx_bit_clamp.lua | 49 ---------------------- 4 files changed, 82 insertions(+), 61 deletions(-) delete mode 100644 std/lua/_lua/_hx_bit_clamp.lua diff --git a/src/generators/genlua.ml b/src/generators/genlua.ml index 2f3668ab85b..9f1ca238ff9 100644 --- a/src/generators/genlua.ml +++ b/src/generators/genlua.ml @@ -2215,7 +2215,7 @@ let generate com = List.iter (generate_type_forward ctx) com.types; newline ctx; (* Generate some dummy placeholders for utility libs that may be required*) - println ctx "local _hx_bind, _hx_bit, _hx_staticToInstance, _hx_funcToField, _hx_anonToField, _hx_maxn, _hx_print, _hx_apply_self, _hx_box_mr, _hx_bit_clamp, _hx_table, _hx_bit_raw"; + println ctx "local _hx_bind, _hx_bit, _hx_staticToInstance, _hx_funcToField, _hx_anonToField, _hx_maxn, _hx_print, _hx_apply_self, _hx_box_mr, _hx_table, _hx_bit_raw"; println ctx "local _hx_pcall_default = {};"; println ctx "local _hx_pcall_break = {};"; @@ -2227,9 +2227,6 @@ let generate com = print_file (find_file "lua/_lua/_hx_bit.lua"); end; - (* integer clamping is always required, and will use bit ops if available *) - print_file (find_file "lua/_lua/_hx_bit_clamp.lua"); - (* Array is required, always patch it *) println ctx "_hx_array_mt.__index = Array.prototype"; newline ctx; diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index 6f92506620d..a2d86def583 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -194,11 +194,84 @@ class Boot { + (if (h < 10) "0" + h else "" + h) + ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s); } + // wrap with common functionality for all implementations + extern inline static function clampWrapper(inner:Float->Null):Float->Null { + return function (v:Float) { + if (v <= Max_Int32 && v >= Min_Int32) + return v > 0 ? Math.floor(v) : Math.ceil(v); + + if (inline std.Math.isNaN(v) || !inline std.Math.isFinite(v)) + return null; + + return inner(v); + }; + } + + extern inline static function prepareForBitwise(v:Float) { + if (v > 2251798999999999) { + return v * 2; + } + return v; + } + + // ideally, these should be extern inline because + // we don't want it to outside of it being inlined in testFunctionSupport, + // but that prevents use as closures, even if inlined + inline static function clampNativeOperator(v:Float) { + v = prepareForBitwise(v); + return lua.Syntax.code("({0} & 0x7FFFFFFF) - ({0} & 0x80000000)", v); + } + + inline static function clampHxBit(v:Float):Int { + v = prepareForBitwise(v); + final band:(Float, Float) -> Int = untyped _hx_bit_raw.band; + return band(v, Max_Int32) - cast Math.abs(band(v, -Min_Int32)); + } + + inline static function clampModulo(v:Float):Int { + v %= 4294967296; + if (v >= 2147483648) { + v -= 4294967296; + } + return cast v; + } + + /** + Test whether the syntax used in function `f` is supported on the current lua version. + + If it is, return it, otherwise return null. + **/ + extern inline static function testFunctionSupport(f:T->S):NullS> { + final result = Lua.pcall(Lua.load, lua.Syntax.code("[[return {0}]]", f)); + if (result.status && result.value != null) { + final fn:() -> (T->S) = result.value; + return fn(); + } + return null; + } + /** A 32 bit clamp function for numbers **/ - public inline static function clampInt32(x:Float) { - return untyped _hx_bit_clamp(x); + @:ifFeature("use._bitop") + public static function clampInt32(v:Float) { + final clampImpl = { + // Try native Lua 5.3+ bit operators first (preferred over bit32/bit library) + final nativeOperators = testFunctionSupport(clampWrapper(clampNativeOperator)); + if (nativeOperators != null) { + nativeOperators; + } else if (untyped _hx_bit_raw != null) { + clampWrapper(clampHxBit); + } else { + // Fallback for Lua without bit, bit32, or native bit ops: wrap using modulo + clampWrapper(clampModulo); + } + }; + + // set implementation so that future calls don't have to perform detection + untyped lua.Boot.clampInt32 = clampImpl; + + return clampImpl(v); } /** diff --git a/std/lua/_lua/_hx_bit.lua b/std/lua/_lua/_hx_bit.lua index 19e80c810ff..06f19acee09 100644 --- a/std/lua/_lua/_hx_bit.lua +++ b/std/lua/_lua/_hx_bit.lua @@ -3,13 +3,13 @@ if _G.bit32 or pcall(require, 'bit32') then _hx_bit_raw = _G.bit32 or require('bit32') _hx_bit = setmetatable({}, { __index = _hx_bit_raw }) -- bit32 operations require manual clamping - _hx_bit.bnot = function(...) return _hx_bit_clamp(_hx_bit_raw.bnot(...)) end - _hx_bit.bxor = function(...) return _hx_bit_clamp(_hx_bit_raw.bxor(...)) end + _hx_bit.bnot = function(...) return __lua_Boot.clampInt32(_hx_bit_raw.bnot(...)) end + _hx_bit.bxor = function(...) return __lua_Boot.clampInt32(_hx_bit_raw.bxor(...)) end -- see https://github.com/HaxeFoundation/haxe/issues/8849 - _hx_bit.bor = function(...) return _hx_bit_clamp(_hx_bit_raw.bor(...)) end - _hx_bit.band = function(...) return _hx_bit_clamp(_hx_bit_raw.band(...)) end - _hx_bit.arshift = function(...) return _hx_bit_clamp(_hx_bit_raw.arshift(...)) end - _hx_bit.lshift = function(...) return _hx_bit_clamp(_hx_bit_raw.lshift(...)) end + _hx_bit.bor = function(...) return __lua_Boot.clampInt32(_hx_bit_raw.bor(...)) end + _hx_bit.band = function(...) return __lua_Boot.clampInt32(_hx_bit_raw.band(...)) end + _hx_bit.arshift = function(...) return __lua_Boot.clampInt32(_hx_bit_raw.arshift(...)) end + _hx_bit.lshift = function(...) return __lua_Boot.clampInt32(_hx_bit_raw.lshift(...)) end elseif _G.bit or pcall(require, 'bit') then -- if we do not have bit32, fallback to bit, default on luajit _hx_bit_raw = _G.bit or require('bit') diff --git a/std/lua/_lua/_hx_bit_clamp.lua b/std/lua/_lua/_hx_bit_clamp.lua deleted file mode 100644 index 8f4b1b1ef3f..00000000000 --- a/std/lua/_lua/_hx_bit_clamp.lua +++ /dev/null @@ -1,49 +0,0 @@ --- Try native Lua 5.3+ bit operators first (preferred over bit32 library) -local _hx_bit_clamp_native = (function() - local ok, fn = pcall(load, [[ - return function(v) - if v <= 2147483647 and v >= -2147483648 then - if v > 0 then return _G.math.floor(v) - else return _G.math.ceil(v) - end - end - if v > 2251798999999999 then v = v*2 end - if (v ~= v or math.abs(v) == _G.math.huge) then return nil end - return (v & 0x7FFFFFFF) - (v & 0x80000000) - end - ]]) - if ok and fn then return fn() end - return nil -end)() - -if _hx_bit_clamp_native then - _hx_bit_clamp = _hx_bit_clamp_native -elseif _hx_bit_raw then - _hx_bit_clamp = function(v) - if v <= 2147483647 and v >= -2147483648 then - if v > 0 then return _G.math.floor(v) - else return _G.math.ceil(v) - end - end - if v > 2251798999999999 then v = v*2 end; - if (v ~= v or math.abs(v) == _G.math.huge) then return nil end - return _hx_bit_raw.band(v, 2147483647 ) - math.abs(_hx_bit_raw.band(v, 2147483648)) - end -else - -- Fallback for Lua 5.1/5.2 without bit library: wrap using modulo - _hx_bit_clamp = function(v) - if v <= 2147483647 and v >= -2147483648 then - if v > 0 then return _G.math.floor(v) - else return _G.math.ceil(v) - end - end - if (v ~= v or math.abs(v) == _G.math.huge) then return nil end - if v > 0 then v = _G.math.floor(v) - else v = _G.math.ceil(v) end - v = v % 4294967296 - if v >= 2147483648 then - v = v - 4294967296 - end - return v - end -end; From 615bea9796e1d091dbc318cfdc8a0da8b0501ac6 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 12 Feb 2026 04:25:23 +0000 Subject: [PATCH 02/13] [lua] Replace Std.int in Array with Math.floor This is a simpler operation which makes more sense here given the range of values this.length can have. Array fails to be dce'd in hello world, so removing this avoids clamp from being included. --- std/lua/_std/Array.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/lua/_std/Array.hx b/std/lua/_std/Array.hx index cb235f0ee57..b0b3276a8d7 100644 --- a/std/lua/_std/Array.hx +++ b/std/lua/_std/Array.hx @@ -62,7 +62,7 @@ class Array { public function reverse():Void { var tmp:T; var i = 0; - while (i < Std.int(this.length / 2)) { + while (i < Math.floor(this.length / 2)) { tmp = this[i]; this[i] = this[this.length - i - 1]; this[this.length - i - 1] = tmp; From 8b98c95a5f0f05e5790aa54c36b2a3c7b5017254 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 12 Feb 2026 04:04:39 +0000 Subject: [PATCH 03/13] [lua] Use inline var for Max_Int32 and Min_Int32 In clampInt32, these are used before static init runs, which means they are set to nil --- std/lua/Boot.hx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index a2d86def583..3f1ec000abd 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -30,8 +30,8 @@ class Boot { static var _:Dynamic; static var _fid = 0; - static var Max_Int32 = 2147483647; - static var Min_Int32 = -2147483648; + static inline var Max_Int32 = 2147483647; + static inline var Min_Int32 = -2147483648; // A max stack size to respect for unpack operations From 202c9793d714d6db64e73cfc038a19e7d9cfcf98 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 12 Feb 2026 03:05:05 +0000 Subject: [PATCH 04/13] [lua] Optimise clampInt32 for lua_ver flags If lua 5.3+ is being targetted, we know that native bitwise operators are available, so we can use them. On lua 5.2, we know that bit32 is available so we don't need to generate a fallback for it being null. --- std/lua/Boot.hx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index 3f1ec000abd..4a7f7e6902f 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -255,16 +255,21 @@ class Boot { **/ @:ifFeature("use._bitop") public static function clampInt32(v:Float) { + #if (lua_ver >= 5.3) + return clampWrapper(clampNativeOperator); + #else final clampImpl = { // Try native Lua 5.3+ bit operators first (preferred over bit32/bit library) final nativeOperators = testFunctionSupport(clampWrapper(clampNativeOperator)); if (nativeOperators != null) { nativeOperators; - } else if (untyped _hx_bit_raw != null) { - clampWrapper(clampHxBit); - } else { + #if !(lua_ver >= 5.2) // lua 5.2 definitely has bit32 + } else if (untyped _hx_bit_raw == null) { // Fallback for Lua without bit, bit32, or native bit ops: wrap using modulo clampWrapper(clampModulo); + #end + } else { + clampWrapper(clampHxBit); } }; @@ -272,6 +277,7 @@ class Boot { untyped lua.Boot.clampInt32 = clampImpl; return clampImpl(v); + #end } /** From aac5a246ede32d55f0866f55e69306c274dddcf1 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Thu, 12 Feb 2026 04:46:22 +0000 Subject: [PATCH 05/13] [lua] Fix unfinished comment --- std/lua/Boot.hx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index 4a7f7e6902f..0c6f75bcd5b 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -214,9 +214,9 @@ class Boot { return v; } - // ideally, these should be extern inline because - // we don't want it to outside of it being inlined in testFunctionSupport, - // but that prevents use as closures, even if inlined + // Ideally, these should be extern inline because they should only be used + // via inlining (we also don't want clampNativeOperator outside testFunctionSupport) + // however, extern inline prevents closures, even if they are immediately inlined inline static function clampNativeOperator(v:Float) { v = prepareForBitwise(v); return lua.Syntax.code("({0} & 0x7FFFFFFF) - ({0} & 0x80000000)", v); From 7920ae4d275504f2dcfe9a6786ed2dcd95d129b3 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Fri, 13 Feb 2026 02:45:27 +0000 Subject: [PATCH 06/13] [lua] Move use._bitop check to dce Previously use._bitop was being set during the generator phase, at which point dce already occurred so @:ifFeature on lua.Boot.clampInt32 didn't work. This patch moves use._bitop detection to the dce phase, which means it will be set in time to make sure clampInt32 is included when needed. --- src/generators/genlua.ml | 2 -- src/optimization/dce.ml | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/generators/genlua.ml b/src/generators/genlua.ml index 9f1ca238ff9..fe6005f7c72 100644 --- a/src/generators/genlua.ml +++ b/src/generators/genlua.ml @@ -1023,7 +1023,6 @@ and gen_expr ?(local=true) ctx e = begin spr ctx "not "; gen_value ctx e; | TUnop (NegBits,unop_flag,e) -> - add_feature ctx "use._bitop"; spr ctx "_hx_bit.bnot("; gen_value ctx e; spr ctx ")"; @@ -1572,7 +1571,6 @@ and gen_paren_tbinop ctx e = gen_value ctx ee and gen_bitop ctx op e1 e2 = - add_feature ctx "use._bitop"; print ctx "_hx_bit.%s(" (match op with | Ast.OpXor -> "bxor" | Ast.OpAnd -> "band" diff --git a/src/optimization/dce.ml b/src/optimization/dce.ml index f64d46aa6ea..6f8b53b43a0 100644 --- a/src/optimization/dce.ml +++ b/src/optimization/dce.ml @@ -549,6 +549,9 @@ and check_op dce op = match op with check_and_add_feature dce "binop_%"; | OpUShr -> check_and_add_feature dce "binop_>>>"; + check_and_add_feature dce "use._bitop"; + | OpXor | OpOr | OpAnd | OpShl | OpShr -> + check_and_add_feature dce "use._bitop"; | OpAssignOp op -> check_op dce op | _ -> @@ -708,6 +711,9 @@ and expr dce e = check_op dce op; expr dce e1; expr dce e2; + | TUnop(NegBits,flag,e) -> + check_and_add_feature dce "use._bitop"; + expr dce e; | TCall(({ eexpr = TField(ef, fa) } as e2), el ) -> mark_t dce e2.epos e2.etype; expr_field dce ef fa true; From 3d62b56a0dc3fa349ee5f412b856693f24c740f3 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Fri, 13 Feb 2026 03:00:46 +0000 Subject: [PATCH 07/13] [lua] Fix incorrect bitwise and operand -Min_Int32 is "optimised" into -2147483648 due to overflow --- std/lua/Boot.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index 0c6f75bcd5b..772edc99824 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -225,7 +225,7 @@ class Boot { inline static function clampHxBit(v:Float):Int { v = prepareForBitwise(v); final band:(Float, Float) -> Int = untyped _hx_bit_raw.band; - return band(v, Max_Int32) - cast Math.abs(band(v, -Min_Int32)); + return band(v, Max_Int32) - cast Math.abs(band(v, 2147483648)); } inline static function clampModulo(v:Float):Int { From 2708cdee4240b1f24e36ea9a5bd530ec1f9b1e0f Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Fri, 13 Feb 2026 03:01:17 +0000 Subject: [PATCH 08/13] [lua] Fix typo in lua 5.3 optimised clamp --- std/lua/Boot.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index 772edc99824..21f24a48f06 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -256,7 +256,7 @@ class Boot { @:ifFeature("use._bitop") public static function clampInt32(v:Float) { #if (lua_ver >= 5.3) - return clampWrapper(clampNativeOperator); + return clampWrapper(clampNativeOperator)(v); #else final clampImpl = { // Try native Lua 5.3+ bit operators first (preferred over bit32/bit library) From 852692223c0b4e3aecc05a65f8b95074e66ebcd2 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Fri, 13 Feb 2026 12:18:54 +0000 Subject: [PATCH 09/13] [dce] Rename use._bitop to op_bitwise --- src/generators/genlua.ml | 2 +- src/optimization/dce.ml | 6 +++--- std/lua/Bit.hx | 2 +- std/lua/Boot.hx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/generators/genlua.ml b/src/generators/genlua.ml index fe6005f7c72..7ad136a1751 100644 --- a/src/generators/genlua.ml +++ b/src/generators/genlua.ml @@ -2221,7 +2221,7 @@ let generate com = List.iter (generate_type ctx) com.types; (* If bit ops are manually imported include the haxe wrapper for them *) - if has_feature ctx "use._bitop" then begin + if has_feature ctx "op_bitwise" then begin print_file (find_file "lua/_lua/_hx_bit.lua"); end; diff --git a/src/optimization/dce.ml b/src/optimization/dce.ml index 6f8b53b43a0..fff35f77416 100644 --- a/src/optimization/dce.ml +++ b/src/optimization/dce.ml @@ -549,9 +549,9 @@ and check_op dce op = match op with check_and_add_feature dce "binop_%"; | OpUShr -> check_and_add_feature dce "binop_>>>"; - check_and_add_feature dce "use._bitop"; + check_and_add_feature dce "op_bitwise"; | OpXor | OpOr | OpAnd | OpShl | OpShr -> - check_and_add_feature dce "use._bitop"; + check_and_add_feature dce "op_bitwise"; | OpAssignOp op -> check_op dce op | _ -> @@ -712,7 +712,7 @@ and expr dce e = expr dce e1; expr dce e2; | TUnop(NegBits,flag,e) -> - check_and_add_feature dce "use._bitop"; + check_and_add_feature dce "op_bitwise"; expr dce e; | TCall(({ eexpr = TField(ef, fa) } as e2), el ) -> mark_t dce e2.epos e2.etype; diff --git a/std/lua/Bit.hx b/std/lua/Bit.hx index bb2771ce102..39eec89374a 100644 --- a/std/lua/Bit.hx +++ b/std/lua/Bit.hx @@ -36,6 +36,6 @@ extern class Bit { static function arshift(x:Float, places:Int):Int; static function mod(numerator:Float, denominator:Float):Int; static function __init__():Void { - untyped _hx_bit = __define_feature__("use._bitop", _hx_bit); + untyped _hx_bit = __define_feature__("op_bitwise", _hx_bit); } } diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index 21f24a48f06..c81a14041bf 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -253,7 +253,7 @@ class Boot { /** A 32 bit clamp function for numbers **/ - @:ifFeature("use._bitop") + @:ifFeature("op_bitwise") public static function clampInt32(v:Float) { #if (lua_ver >= 5.3) return clampWrapper(clampNativeOperator)(v); From 836c177af8a5dc7c5c878fcac95b9cc6c635bb32 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sat, 14 Feb 2026 23:21:04 +0000 Subject: [PATCH 10/13] [tests] Add underflow test for fallback lua clamp The clamp port caused a regression in underflow, but it wasn't caught due to this missing test case. --- tests/misc/lua/projects/Issue11013/Main.hx | 22 +++++++++++-------- .../projects/Issue11013/compile.hxml.stdout | 3 ++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/tests/misc/lua/projects/Issue11013/Main.hx b/tests/misc/lua/projects/Issue11013/Main.hx index bbca5eb500b..aee484692f7 100644 --- a/tests/misc/lua/projects/Issue11013/Main.hx +++ b/tests/misc/lua/projects/Issue11013/Main.hx @@ -1,15 +1,19 @@ class Main { - static function main() { - // Test Int32 wrapping without bit32/bit library available. - // require, bit32, and bit are set to nil before running, so they cannot be used - var max:haxe.Int32 = 2147483647; - var one:haxe.Int32 = 1; - var result:Int = max + one; - var expected = -2147483648; - if (result == -2147483648) { + static function assertEquals(expected, actual) { + if (actual == expected) { Sys.println("Success"); } else { - Sys.println('Expected $expected but got $result'); + Sys.println('Expected $expected but got $actual'); } } + + static function main() { + // Test Int32 wrapping without bit32/bit library available. + // require, bit32, and bit are set to nil before running, so they cannot be used + final max:haxe.Int32 = 2147483647; + final min:haxe.Int32 = -2147483648; + final one:haxe.Int32 = 1; + assertEquals(min, max + one); + assertEquals(max, min - one); + } } diff --git a/tests/misc/lua/projects/Issue11013/compile.hxml.stdout b/tests/misc/lua/projects/Issue11013/compile.hxml.stdout index 51da4200abb..4d50a1a89dd 100644 --- a/tests/misc/lua/projects/Issue11013/compile.hxml.stdout +++ b/tests/misc/lua/projects/Issue11013/compile.hxml.stdout @@ -1 +1,2 @@ -Success \ No newline at end of file +Success +Success From f28c5d0cd9b923270a6136a6d2e3e018929fc427 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sat, 14 Feb 2026 23:33:38 +0000 Subject: [PATCH 11/13] [lua] Use native modulo for fallback clamp Haxe modulo generates as math.fmod, which rounds towards 0 instead of minus infinity. This breaks underflow. This requires adding lua.Syntax.modulo. Documentation is taken from: https://www.lua.org/manual/5.5/manual.html#3.4.1 --- std/lua/Boot.hx | 2 +- std/lua/Syntax.hx | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index c81a14041bf..fd3857c2c0c 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -229,7 +229,7 @@ class Boot { } inline static function clampModulo(v:Float):Int { - v %= 4294967296; + v = lua.Syntax.modulo(v, 4294967296); if (v >= 2147483648) { v -= 4294967296; } diff --git a/std/lua/Syntax.hx b/std/lua/Syntax.hx index e2d9ebccead..903e80559f5 100644 --- a/std/lua/Syntax.hx +++ b/std/lua/Syntax.hx @@ -53,4 +53,13 @@ extern class Syntax { The same as `lua.Syntax.code` except this one does not provide code interpolation. **/ static function plainCode(code:String):Dynamic; + + /** + Generates a native modulo expression. + + Modulo is defined as the remainder of a division that rounds the quotient towards minus infinity (floor division). + **/ + inline static function modulo(a:Float, b:Float):Float { + return code("({0} % {1})", a, b); + } } From c744e742f74c8498eaf6b50a6d84b1d4ae9f51b1 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sun, 15 Feb 2026 12:29:05 +0000 Subject: [PATCH 12/13] [lua] Restore generator time op_bitwise checks Otherwise, the _hx_bit file will always be omitted when dce is off. --- src/generators/genlua.ml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/generators/genlua.ml b/src/generators/genlua.ml index 7ad136a1751..d6de383b51c 100644 --- a/src/generators/genlua.ml +++ b/src/generators/genlua.ml @@ -1023,6 +1023,7 @@ and gen_expr ?(local=true) ctx e = begin spr ctx "not "; gen_value ctx e; | TUnop (NegBits,unop_flag,e) -> + add_feature ctx "op_bitwise"; spr ctx "_hx_bit.bnot("; gen_value ctx e; spr ctx ")"; @@ -1571,6 +1572,7 @@ and gen_paren_tbinop ctx e = gen_value ctx ee and gen_bitop ctx op e1 e2 = + add_feature ctx "op_bitwise"; print ctx "_hx_bit.%s(" (match op with | Ast.OpXor -> "bxor" | Ast.OpAnd -> "band" From 1bb5d6a7494e8ca93242b7c71b662cfd52ec2ab1 Mon Sep 17 00:00:00 2001 From: Tobiasz Laskowski Date: Sun, 15 Feb 2026 12:40:43 +0000 Subject: [PATCH 13/13] [lua] Use macro for clampWrapper This allows the incomplete clamp implementations to be marked as extern inline, which prevents them from being generated outside of clampInt32. This is important especially for clampNativeOperator, which does not parse on lua < 5.3 so it cannot be generated outside testFunctionSupport. @:noPackageRestrict is needed here for the macro to work within the lua.Boot module. --- std/lua/Boot.hx | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/std/lua/Boot.hx b/std/lua/Boot.hx index fd3857c2c0c..53ebdcb1b6a 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -25,7 +25,22 @@ package lua; import haxe.SysTools; @:dox(hide) +@:noPackageRestrict class Boot { + // wrap with common clamping functionality for all implementations + macro static function clampWrapper(inner:haxe.macro.Expr):haxe.macro.Expr { + return macro function(v:Float):Int { + if (v <= Max_Int32 && v >= Min_Int32) + return v > 0 ? std.Math.floor(v) : std.Math.ceil(v); + + if (inline std.Math.isNaN(v) || !inline std.Math.isFinite(v)) + return null; + + return $inner(v); + }; + } + + #if !macro // Used temporarily for bind() static var _:Dynamic; static var _fid = 0; @@ -194,18 +209,6 @@ class Boot { + (if (h < 10) "0" + h else "" + h) + ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s); } - // wrap with common functionality for all implementations - extern inline static function clampWrapper(inner:Float->Null):Float->Null { - return function (v:Float) { - if (v <= Max_Int32 && v >= Min_Int32) - return v > 0 ? Math.floor(v) : Math.ceil(v); - - if (inline std.Math.isNaN(v) || !inline std.Math.isFinite(v)) - return null; - - return inner(v); - }; - } extern inline static function prepareForBitwise(v:Float) { if (v > 2251798999999999) { @@ -214,21 +217,18 @@ class Boot { return v; } - // Ideally, these should be extern inline because they should only be used - // via inlining (we also don't want clampNativeOperator outside testFunctionSupport) - // however, extern inline prevents closures, even if they are immediately inlined - inline static function clampNativeOperator(v:Float) { + extern inline static function clampNativeOperator(v:Float):Int { v = prepareForBitwise(v); return lua.Syntax.code("({0} & 0x7FFFFFFF) - ({0} & 0x80000000)", v); } - inline static function clampHxBit(v:Float):Int { + extern inline static function clampHxBit(v:Float):Int { v = prepareForBitwise(v); final band:(Float, Float) -> Int = untyped _hx_bit_raw.band; return band(v, Max_Int32) - cast Math.abs(band(v, 2147483648)); } - inline static function clampModulo(v:Float):Int { + extern inline static function clampModulo(v:Float):Int { v = lua.Syntax.modulo(v, 4294967296); if (v >= 2147483648) { v -= 4294967296; @@ -395,4 +395,5 @@ class Boot { return null; } + #end }