Skip to content
Open
11 changes: 4 additions & 7 deletions src/generators/genlua.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1129,7 +1129,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 ")";
Expand Down Expand Up @@ -1619,7 +1619,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"
Expand Down Expand Up @@ -2245,21 +2245,18 @@ 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_print, _hx_apply_self, _hx_box_mr, _hx_bit_clamp, _hx_table, _hx_bit_raw, _hx_dyn_add, _hx_wrap_if_string_field, _hx_handle_error, _hx_luv";
println ctx "local _hx_bind, _hx_bit, _hx_staticToInstance, _hx_funcToField, _hx_anonToField, _hx_print, _hx_apply_self, _hx_box_mr, _hx_table, _hx_bit_raw, _hx_dyn_add, _hx_wrap_if_string_field, _hx_handle_error, _hx_luv";
println ctx "local _hx_pcall_default = {};";
println ctx "local _hx_pcall_break = {};";

List.iter (transform_multireturn ctx) com.types;
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;
Expand Down
6 changes: 6 additions & 0 deletions src/optimization/dce.ml
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,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
| _ ->
Expand Down Expand Up @@ -744,6 +747,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;
Expand Down
2 changes: 1 addition & 1 deletion std/lua/Bit.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
88 changes: 84 additions & 4 deletions std/lua/Boot.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<T, S>(f:T->S):Null<T->S> {
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
}

/**
Expand Down Expand Up @@ -316,4 +395,5 @@ class Boot {

return null;
}
#end
}
9 changes: 9 additions & 0 deletions std/lua/Syntax.hx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
12 changes: 6 additions & 6 deletions std/lua/_lua/_hx_bit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ elseif _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 _hx_c.__lua_Boot.clampInt32(_hx_bit_raw.bnot(...)) end
_hx_bit.bxor = function(...) return _hx_c.__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 _hx_c.__lua_Boot.clampInt32(_hx_bit_raw.bor(...)) end
_hx_bit.band = function(...) return _hx_c.__lua_Boot.clampInt32(_hx_bit_raw.band(...)) end
_hx_bit.arshift = function(...) return _hx_c.__lua_Boot.clampInt32(_hx_bit_raw.arshift(...)) end
_hx_bit.lshift = function(...) return _hx_c.__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')
Expand Down
49 changes: 0 additions & 49 deletions std/lua/_lua/_hx_bit_clamp.lua

This file was deleted.

2 changes: 1 addition & 1 deletion std/lua/_std/Array.hx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class Array<T> {
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;
Expand Down
22 changes: 13 additions & 9 deletions tests/misc/lua/projects/Issue11013/Main.hx
Original file line number Diff line number Diff line change
@@ -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);
}
}
3 changes: 2 additions & 1 deletion tests/misc/lua/projects/Issue11013/compile.hxml.stdout
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Success
Success
Success
Loading