diff --git a/src/generators/genlua.ml b/src/generators/genlua.ml index 2f3668ab85b..d6de383b51c 100644 --- a/src/generators/genlua.ml +++ b/src/generators/genlua.ml @@ -1023,7 +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 "use._bitop"; + add_feature ctx "op_bitwise"; spr ctx "_hx_bit.bnot("; gen_value ctx e; spr ctx ")"; @@ -1572,7 +1572,7 @@ and gen_paren_tbinop ctx e = gen_value ctx ee and gen_bitop ctx op e1 e2 = - add_feature ctx "use._bitop"; + add_feature ctx "op_bitwise"; print ctx "_hx_bit.%s(" (match op with | Ast.OpXor -> "bxor" | Ast.OpAnd -> "band" @@ -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 = {};"; @@ -2223,13 +2223,10 @@ 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; - (* 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/src/optimization/dce.ml b/src/optimization/dce.ml index f64d46aa6ea..fff35f77416 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 "op_bitwise"; + | OpXor | OpOr | OpAnd | OpShl | OpShr -> + check_and_add_feature dce "op_bitwise"; | 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 "op_bitwise"; + expr dce e; | TCall(({ eexpr = TField(ef, fa) } as e2), el ) -> mark_t dce e2.epos e2.etype; expr_field dce ef fa true; 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 6f92506620d..53ebdcb1b6a 100644 --- a/std/lua/Boot.hx +++ b/std/lua/Boot.hx @@ -25,13 +25,28 @@ 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; - 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 @@ -194,11 +209,75 @@ class Boot { + (if (h < 10) "0" + h else "" + h) + ":" + (if (mi < 10) "0" + mi else "" + mi) + ":" + (if (s < 10) "0" + s else "" + s); } + + extern inline static function prepareForBitwise(v:Float) { + if (v > 2251798999999999) { + return v * 2; + } + return v; + } + + extern inline static function clampNativeOperator(v:Float):Int { + v = prepareForBitwise(v); + return lua.Syntax.code("({0} & 0x7FFFFFFF) - ({0} & 0x80000000)", v); + } + + 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)); + } + + extern inline static function clampModulo(v:Float):Int { + v = lua.Syntax.modulo(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("op_bitwise") + public static function clampInt32(v:Float) { + #if (lua_ver >= 5.3) + return clampWrapper(clampNativeOperator)(v); + #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; + #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); + } + }; + + // set implementation so that future calls don't have to perform detection + untyped lua.Boot.clampInt32 = clampImpl; + + return clampImpl(v); + #end } /** @@ -316,4 +395,5 @@ class Boot { return null; } + #end } 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); + } } 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 fbbaf1272f5..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/bit 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 without bit, bit32, or native bit ops: 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; 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; 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