From 500fdc38435fbdc1ef58a205810368d1f7463ad8 Mon Sep 17 00:00:00 2001 From: bensku Date: Sun, 11 Aug 2024 19:24:54 +0300 Subject: [PATCH] lua4jvm: Split Lua numbers to floats and ints according to 5.4 spec This might improve performance. It certainly makes using e.g. Lua tables from Java code easier - things no longer blow up if you accidentally use ints instead of doubles. Same goes for Java FFI. Further testing needed. Many of the existing tests inject doubles from Java to Lua, which doesn't necessarily mean that the int paths work! --- .../code4jvm/lua/compiler/IrCompiler.java | 4 +- .../fi/benjami/code4jvm/lua/ir/LuaType.java | 27 ++----- .../code4jvm/lua/ir/LuaTypeSupport.java | 12 ++- .../code4jvm/lua/ir/expr/ArithmeticExpr.java | 75 +++++++++++++++++-- .../code4jvm/lua/ir/expr/LengthExpr.java | 8 +- .../code4jvm/lua/ir/expr/LuaConstant.java | 2 + .../code4jvm/lua/ir/expr/NegateExpr.java | 10 ++- .../lua/ir/expr/StringConcatExpr.java | 2 +- .../benjami/code4jvm/lua/linker/BinaryOp.java | 37 +++++---- .../code4jvm/lua/runtime/LuaTable.java | 47 +++++++----- .../code4jvm/lua/runtime/TableAccess.java | 2 +- .../code4jvm/lua/test/FunctionTest.java | 4 +- .../benjami/code4jvm/lua/test/TableTest.java | 10 +-- .../code4jvm/lua/test/UnaryOpTest.java | 12 +-- .../code4jvm/lua/test/VariableTest.java | 4 +- 15 files changed, 163 insertions(+), 93 deletions(-) diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/IrCompiler.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/IrCompiler.java index e935e21..86d9f2d 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/IrCompiler.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/IrCompiler.java @@ -447,8 +447,8 @@ public IrNode visitStringConcat(StringConcatContext ctx) { @Override public IrNode visitNumberLiteral(NumberLiteralContext ctx) { - // TODO non-decimal numbers - return new LuaConstant(Double.valueOf(ctx.Numeral().getText())); + var value = Double.valueOf(ctx.Numeral().getText()); + return new LuaConstant(value.intValue() == value ? value.intValue() : value); } @Override diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaType.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaType.java index 0cfc5fe..1b016bf 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaType.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaType.java @@ -6,15 +6,12 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import fi.benjami.code4jvm.Type; import fi.benjami.code4jvm.Value; import fi.benjami.code4jvm.lua.compiler.CompiledFunction; import fi.benjami.code4jvm.lua.compiler.CompiledShape; import fi.benjami.code4jvm.lua.compiler.FunctionCompiler; -import fi.benjami.code4jvm.lua.compiler.LuaContext; -import fi.benjami.code4jvm.lua.compiler.ShapeGenerator; import fi.benjami.code4jvm.lua.compiler.ShapeTypes; import fi.benjami.code4jvm.lua.ir.stmt.ReturnStmt; import fi.benjami.code4jvm.lua.runtime.LuaFunction; @@ -232,7 +229,8 @@ public boolean equals(Object obj) { // Lua standard types static final LuaType NIL = new Simple("nil", Type.OBJECT); static final LuaType BOOLEAN = new Simple("boolean", Type.BOOLEAN); - static final LuaType NUMBER = new Simple("number", Type.DOUBLE); + static final LuaType INTEGER = new Simple("number", Type.INT); + static final LuaType FLOAT = new Simple("number", Type.DOUBLE); static final LuaType STRING = new Simple("string", Type.STRING); static final LuaType TABLE = new Simple("table", LuaTable.TYPE); // TODO userdata, thread @@ -281,23 +279,6 @@ public static Shape shape() { return new Shape(); } - public static List readList(String str) { - var types = new ArrayList(); - for (var i = 0; i < str.length(); i++) { - types.add(switch (str.charAt(i)) { - case 'V' -> LuaType.NIL; - case 'B' -> LuaType.BOOLEAN; - case 'N' -> LuaType.NUMBER; - case 'S' -> LuaType.STRING; - case 'U' -> LuaType.UNKNOWN; - case 'T' -> throw new UnsupportedOperationException("todo"); - case 'F' -> throw new UnsupportedOperationException("todo"); - default -> throw new IllegalArgumentException("unknown type: " + str.charAt(i)); - }); - } - return types; - } - /** * Name of type for Lua code. * @return Lua type name. @@ -317,4 +298,8 @@ public static List readList(String str) { default boolean isAssignableFrom(LuaType other) { return this == LuaType.UNKNOWN || equals(other); } + + default boolean isNumber() { + return this == LuaType.INTEGER || this == LuaType.FLOAT; + } } diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaTypeSupport.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaTypeSupport.java index 3323bea..5d4950f 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaTypeSupport.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaTypeSupport.java @@ -10,8 +10,10 @@ class LuaTypeSupport { public static final Map TYPE_TO_TYPE = Map.of( Type.BOOLEAN, LuaType.BOOLEAN, Type.of(Boolean.class), LuaType.BOOLEAN, - Type.DOUBLE, LuaType.NUMBER, - Type.of(Double.class), LuaType.NUMBER, + Type.INT, LuaType.INTEGER, + Type.of(Integer.class), LuaType.INTEGER, + Type.DOUBLE, LuaType.FLOAT, + Type.of(Double.class), LuaType.FLOAT, Type.STRING, LuaType.STRING, LuaTable.TYPE, LuaType.TABLE ); @@ -19,8 +21,10 @@ class LuaTypeSupport { public static final Map, LuaType> CLASS_TO_TYPE = Map.of( boolean.class, LuaType.BOOLEAN, Boolean.class, LuaType.BOOLEAN, - double.class, LuaType.NUMBER, - Double.class, LuaType.NUMBER, + int.class, LuaType.INTEGER, + Integer.class, LuaType.INTEGER, + double.class, LuaType.FLOAT, + Double.class, LuaType.FLOAT, String.class, LuaType.STRING, LuaTable.class, LuaType.TABLE ); diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/ArithmeticExpr.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/ArithmeticExpr.java index fca67ab..9c908de 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/ArithmeticExpr.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/ArithmeticExpr.java @@ -3,6 +3,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.List; import java.util.function.BiFunction; import fi.benjami.code4jvm.Expression; @@ -38,7 +39,10 @@ public record ArithmeticExpr( public enum Kind { POWER(MATH_POW::call, "power", "__pow"), MULTIPLY(Arithmetic::multiply, "multiply", "__mul"), - DIVIDE(Arithmetic::divide, "divide", "__div"), + DIVIDE((lhs, rhs) -> { + // Lua uses float division unless integer division is explicitly request (see below) + return Arithmetic.divide(lhs.cast(Type.DOUBLE), rhs.cast(Type.DOUBLE)); + }, "divide", "__div"), FLOOR_DIVIDE(FLOOR_DIV::call, "floorDivide", "__idiv"), MODULO((lhs, rhs) -> (block -> { // Lua expects modulo to be always positive; Java's remainder can return negative values @@ -53,16 +57,27 @@ public enum Kind { Kind(BiFunction directEmitter, String methodName, String metamethod) { this.directEmitter = directEmitter; - MethodHandle fastPath; + var intReturnType = methodName == "power" || methodName.equals("divide") ? double.class : int.class; + MethodHandle doublePath, intPath; try { // Drop the call target argument, it is not needed - fastPath = MethodHandles.dropArguments(LOOKUP.findStatic(ArithmeticExpr.class, methodName, + doublePath = MethodHandles.dropArguments(LOOKUP.findStatic(ArithmeticExpr.class, methodName, MethodType.methodType(double.class, double.class, double.class)), 0, Object.class); + intPath = MethodHandles.dropArguments(LOOKUP.findStatic(ArithmeticExpr.class, methodName, + MethodType.methodType(intReturnType, int.class, int.class)), 0, Object.class); } catch (NoSuchMethodException | IllegalAccessException e) { throw new AssertionError(e); } - this.callTarget = BinaryOp.newTarget(Double.class, fastPath, metamethod, - (a, b) -> new LuaException("attempted to perform arithmetic on non-number values")); + // If we have any doubles at all, take the double path + var paths = List.of( + new BinaryOp.Path(Integer.class, Integer.class, intPath), + new BinaryOp.Path(Double.class, Double.class, doublePath), + new BinaryOp.Path(Integer.class, Double.class, MethodHandles.explicitCastArguments(doublePath, MethodType.methodType(double.class, Object.class, int.class, double.class))), + new BinaryOp.Path(Double.class, Integer.class, MethodHandles.explicitCastArguments(doublePath, MethodType.methodType(double.class, Object.class, double.class, int.class))) + ); + this.callTarget = BinaryOp.newTarget(paths, metamethod, + (a, b) -> new LuaException("cannot " + methodName + " " + + LuaType.of(a).name() + " and " + LuaType.of(b).name())); } } @@ -102,11 +117,44 @@ private static double subtract(double lhs, double rhs) { return lhs - rhs; } + @SuppressWarnings("unused") + private static double power(int lhs, int rhs) { + return Math.pow(lhs, rhs); + } + + @SuppressWarnings("unused") + private static int multiply(int lhs, int rhs) { + return lhs * rhs; + } + + private static double divide(int lhs, int rhs) { + return ((double) lhs) / ((double) rhs); + } + + public static int floorDivide(int lhs, int rhs) { + return (int) Math.floor(divide(lhs, rhs)); + } + + @SuppressWarnings("unused") + private static int modulo(int lhs, int rhs) { + return Math.abs(lhs % rhs); + } + + @SuppressWarnings("unused") + private static int add(int lhs, int rhs) { + return lhs + rhs; + } + + @SuppressWarnings("unused") + private static int subtract(int lhs, int rhs) { + return lhs - rhs; + } + @Override public Value emit(LuaContext ctx, Block block) { var lhsValue = lhs.emit(ctx, block); var rhsValue = rhs.emit(ctx, block); - if (outputType(ctx).equals(LuaType.NUMBER)) { + if (outputType(ctx).isNumber()) { // Both arguments are known to be numbers; emit arithmetic operation directly return block.add(kind.directEmitter.apply(lhsValue, rhsValue)); } else { @@ -117,8 +165,19 @@ public Value emit(LuaContext ctx, Block block) { @Override public LuaType outputType(LuaContext ctx) { - return lhs.outputType(ctx).equals(LuaType.NUMBER) && rhs.outputType(ctx).equals(LuaType.NUMBER) - ? LuaType.NUMBER : LuaType.UNKNOWN; + var lhsOut = lhs.outputType(ctx); + var rhsOut = rhs.outputType(ctx); + if (lhsOut.isNumber() && rhsOut.isNumber()) { + if (kind == Kind.POWER || kind == Kind.DIVIDE) { + // Lua spec says that these always produce floats + return LuaType.FLOAT; + } else if (lhsOut == LuaType.INTEGER && rhsOut == LuaType.INTEGER) { + return LuaType.INTEGER; // Both sides are integers + } + return LuaType.FLOAT; // Float on at least one side + } else { + return LuaType.UNKNOWN; + } } } diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LengthExpr.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LengthExpr.java index dd50eab..3bb7a0c 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LengthExpr.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LengthExpr.java @@ -25,10 +25,8 @@ public record LengthExpr(IrNode expr) implements IrNode { static { var lookup = MethodHandles.lookup(); try { - TABLE_LENGTH = MethodHandles.dropArguments(lookup.findVirtual(LuaTable.class, "arraySize", MethodType.methodType(int.class)) - .asType(MethodType.methodType(double.class, LuaTable.class)), 0, Object.class); - STRING_LENGTH = MethodHandles.dropArguments(lookup.findVirtual(String.class, "length", MethodType.methodType(int.class)) - .asType(MethodType.methodType(double.class, String.class)), 0, Object.class); + TABLE_LENGTH = MethodHandles.dropArguments(lookup.findVirtual(LuaTable.class, "arraySize", MethodType.methodType(int.class)), 0, Object.class); + STRING_LENGTH = MethodHandles.dropArguments(lookup.findVirtual(String.class, "length", MethodType.methodType(int.class)), 0, Object.class); } catch (NoSuchMethodException | IllegalAccessException e) { throw new AssertionError(e); } @@ -50,7 +48,7 @@ public Value emit(LuaContext ctx, Block block) { @Override public LuaType outputType(LuaContext ctx) { // We can't do type analysis through metatables (yet) - return expr.outputType(ctx).equals(LuaType.STRING) ? LuaType.NUMBER : LuaType.UNKNOWN; + return expr.outputType(ctx).equals(LuaType.STRING) ? LuaType.INTEGER : LuaType.UNKNOWN; } } diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LuaConstant.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LuaConstant.java index 0817b07..29267c0 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LuaConstant.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/LuaConstant.java @@ -27,6 +27,8 @@ public Value emit(LuaContext ctx, Block block) { return Constant.nullValue(Type.OBJECT); } else if (value instanceof Boolean bool) { return Constant.of(bool); + } else if (value instanceof Integer num) { + return Constant.of(num); } else if (value instanceof Double num) { return Constant.of(num); } else if (value instanceof String str) { diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/NegateExpr.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/NegateExpr.java index cd9259d..9d075ed 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/NegateExpr.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/NegateExpr.java @@ -41,7 +41,7 @@ private static double negate(Object callable, double value) { @Override public Value emit(LuaContext ctx, Block block) { var value = expr.emit(ctx, block); - if (outputType(ctx).equals(LuaType.NUMBER)) { + if (outputType(ctx).isNumber()) { return block.add(Arithmetic.negate(value)); } else { return block.add(LuaLinker.setupCall(ctx, CallSiteOptions.nonFunction(ctx.owner(), LuaType.UNKNOWN, LuaType.UNKNOWN), TARGET, value)); @@ -50,8 +50,14 @@ public Value emit(LuaContext ctx, Block block) { @Override public LuaType outputType(LuaContext ctx) { + var exprType = expr.outputType(ctx); + if (exprType == LuaType.INTEGER) { + return LuaType.INTEGER; + } else if (exprType == LuaType.FLOAT) { + return LuaType.FLOAT; + } // We can't do type analysis through metatables (yet) - return expr.outputType(ctx).equals(LuaType.NUMBER) ? LuaType.NUMBER : LuaType.UNKNOWN; + return LuaType.UNKNOWN; } } diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/StringConcatExpr.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/StringConcatExpr.java index 845f0f2..d73ac3b 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/StringConcatExpr.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/StringConcatExpr.java @@ -35,7 +35,7 @@ public record StringConcatExpr( throw new AssertionError(); } - TARGET = BinaryOp.newTarget(String.class, CONCAT_TWO, "__concat", + TARGET = BinaryOp.newTarget(List.of(new BinaryOp.Path(String.class, String.class, CONCAT_TWO)), "__concat", (a, b) -> new LuaException("attempted to concatenate non-string values")); } diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/BinaryOp.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/BinaryOp.java index 257bf00..2df52e6 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/BinaryOp.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/linker/BinaryOp.java @@ -3,6 +3,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; +import java.util.List; import java.util.function.BiFunction; import fi.benjami.code4jvm.lua.ir.LuaType; @@ -19,8 +20,8 @@ */ public class BinaryOp { - private static final boolean checkTypes(Class expected, Object callable, Object lhs, Object rhs) { - return lhs != null && lhs.getClass() == expected && rhs != null && rhs.getClass() == expected; + private static final boolean checkTypes(Class expectedLhs, Class expectedRhs, Object callable, Object lhs, Object rhs) { + return lhs != null && lhs.getClass() == expectedLhs && rhs != null && rhs.getClass() == expectedRhs; } @SuppressWarnings("unused") // MethodHandle @@ -37,7 +38,7 @@ private static final boolean checkLhsMetamethod(String metamethod, Object callab var lookup = MethodHandles.lookup(); try { CHECK_TYPES = lookup.findStatic(BinaryOp.class, "checkTypes", - MethodType.methodType(boolean.class, Class.class, Object.class, Object.class, Object.class)); + MethodType.methodType(boolean.class, Class.class, Class.class, Object.class, Object.class, Object.class)); CHECK_LHS_METAMETHOD = lookup.findStatic(BinaryOp.class, "checkLhsMetamethod", MethodType.methodType(boolean.class, String.class, Object.class, Object.class)); } catch (NoSuchMethodException | IllegalAccessException e) { @@ -45,30 +46,36 @@ private static final boolean checkLhsMetamethod(String metamethod, Object callab } } + public record Path( + Class lhsType, + Class rhsType, + MethodHandle target + ) {} + /** * Produces a dynamic call target for a binary operation call site. - * @param expectedType Type that the fast path supports. - * @param fastPath Fast path that is entered if both sides are of expected - * type. The fast path should accept the call target as first parameter, - * LHS as second and RHS as third. + * @param fastPaths Fast paths, to be evaluated in order. * @param metamethod Name of the metamethod call if metatables are present. * @param errorHandler Called when either value has invalid type and * metamethods are not found. Returns a Lua exception that is thrown. * @return Call target. */ - public static DynamicTarget newTarget(Class expectedType, MethodHandle fastPath, String metamethod, + public static DynamicTarget newTarget(List fastPaths, String metamethod, BiFunction errorHandler) { - assert !expectedType.isPrimitive(); // LHS and RHS will be in their boxed forms - assert !expectedType.equals(LuaType.class); // This is currently unnecessary for Lua return (meta, args) -> { assert args.length == 2; var lhs = args[0]; var rhs = args[1]; - if (checkTypes(expectedType, null, lhs, rhs)) { - // Fast path, e.g. arithmetic operation on numbers or string concatenation on strings - var guard = CHECK_TYPES.bindTo(expectedType); - return new LuaCallTarget(fastPath, guard); - } else if (lhs instanceof LuaTable table + for (var path : fastPaths) { + if (checkTypes(path.lhsType, path.rhsType, null, lhs, rhs)) { + // Fast path, e.g. arithmetic operation on numbers or string concatenation on strings + var guard = MethodHandles.insertArguments(CHECK_TYPES, 0, path.lhsType, path.rhsType); + return new LuaCallTarget(path.target, guard); + } + } + + // None of the fast paths matched + if (lhs instanceof LuaTable table && table.metatable() != null && table.metatable().get(metamethod) != null) { // Slower path, call LHS metamethod diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/LuaTable.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/LuaTable.java index e78f169..537530e 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/LuaTable.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/LuaTable.java @@ -37,6 +37,16 @@ private static int hash(Object key) { return key == null ? 0 : (hash = key.hashCode()) ^ (hash >>> 16); } + static Object normalizeKey(Object key) { + if (key instanceof Double num) { + var intVal = num.intValue(); + if (intVal == num) { + return intVal; + } + } + return key; + } + int getSlot(Object key) { if (keys == EMPTY) { return -1; @@ -55,9 +65,8 @@ int getSlot(Object key) { } int getArrayIndex(Object key) { - if (key instanceof Double num) { - var index = num.intValue(); - if (index == num.doubleValue() && index < arrayCapacity) { + if (key instanceof Integer index) { + if (index < arrayCapacity) { return index; } } @@ -76,6 +85,7 @@ Object getAt(int slot) { } public Object getRaw(Object key) { + key = normalizeKey(key); var arrayIndex = getArrayIndex(key); if (arrayIndex != -1) { return table[arrayIndex]; @@ -142,16 +152,16 @@ void setAt(int slot, Object key, Object value) { } public void setRaw(Object key, Object value) { - if (key instanceof Double num) { + key = normalizeKey(key); + if (key instanceof Integer index) { // The logic here is subtly different from getArrayIndex() // We allow appending to array (with a few gaps) even if it is full - var integer = num.intValue(); - if (integer == num.doubleValue() && integer < arrayCapacity + 3) { - if (integer >= arrayCapacity) { + if (index < arrayCapacity + 3) { + if (index >= arrayCapacity) { enlargeArray(); } - table[integer] = value; - arraySize = Math.max(arraySize, integer + 1); + table[index] = value; + arraySize = Math.max(arraySize, index + 1); return; } } @@ -191,7 +201,7 @@ public void set(Object key, Object value) { } } - int getFreeSlot(Object key) { + private int getFreeSlot(Object key) { if (keys == EMPTY) { return -1; } @@ -251,11 +261,10 @@ private void enlargeArray() { // Scan rest of the table to find if any keys should be moved to array part for (var i = 0; i < keys.length; i++) { var key = keys[i]; - if (key instanceof Double d) { - var integer = d.intValue(); - if (integer == d.doubleValue() && integer < newCapacity) { + if (key instanceof Integer index) { + if (index < newCapacity) { // Move to array part - newTable[integer] = table[arrayCapacity + i]; + newTable[index] = table[arrayCapacity + i]; table[arrayCapacity + i] = null; keys[i] = null; } @@ -320,7 +329,7 @@ public Object[] next(Object prevKey) { if (prevKey == null) { if (arraySize != 0) { // First call, array has at least one member - return new Object[] {1d, getArray(1)}; + return new Object[] {1, getArray(1)}; } else { // First call, no array members -> return "first" table member for (var i = arrayCapacity; i < table.length; i++) { @@ -332,11 +341,11 @@ public Object[] next(Object prevKey) { } int slot; - if (prevKey instanceof Double index) { + if (prevKey instanceof Integer index) { // Iterate the array in order as long as we have elements if (index < arraySize - 1) { var newIndex = index + 1; - return new Object[] {newIndex, getArray((int) newIndex)}; + return new Object[] {newIndex, getArray(newIndex)}; } else { slot = 0; // First entry after array part } @@ -407,7 +416,7 @@ private boolean nextArray() { // Reached array end // ... but if there were previously gaps, some hash table entries // might also need to be visible to array iterators - var nextEntry = getRaw((double) index); + var nextEntry = getRaw(index); if (nextEntry != null) { return true; } // else: REALLY reached array end @@ -436,7 +445,7 @@ private boolean nextTable() { } public Object key() { - return array ? (double) index : keys[index - arrayCapacity]; + return array ? index : keys[index - arrayCapacity]; } public Object value() { diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/TableAccess.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/TableAccess.java index 359de85..65f2985 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/TableAccess.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/TableAccess.java @@ -67,7 +67,7 @@ private static LuaCallTarget resolveConstantGet(LuaCallSite meta, Object[] args) return new LuaCallTarget(GET); } - var key = args[1]; + var key = LuaTable.normalizeKey(args[1]); // Low-level table APIs don't do this for us if (args[0] instanceof LuaTable table) { var metatable = table.metatable(); var arrayIndex = table.getArrayIndex(key); diff --git a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/FunctionTest.java b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/FunctionTest.java index a8699a9..4c69a24 100644 --- a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/FunctionTest.java +++ b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/FunctionTest.java @@ -58,7 +58,7 @@ public void upvalues() throws Throwable { var a = new LuaLocalVar("a"); var b = new LuaLocalVar("b"); var type = LuaType.function( - List.of(new UpvalueTemplate(a, LuaType.NUMBER)), + List.of(new UpvalueTemplate(a, LuaType.FLOAT)), List.of(b), new LuaBlock(List.of(new ReturnStmt(List.of( new ArithmeticExpr(new VariableExpr(a), ArithmeticExpr.Kind.ADD, new VariableExpr(b)) @@ -107,7 +107,7 @@ public void declareFunction() throws Throwable { var insideB = new LuaLocalVar("b"); var c = new LuaLocalVar("c"); var type = LuaType.function( - List.of(new UpvalueTemplate(a, LuaType.NUMBER)), + List.of(new UpvalueTemplate(a, LuaType.FLOAT)), List.of(b), new LuaBlock(List.of( new ReturnStmt(List.of(new FunctionDeclExpr( diff --git a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/TableTest.java b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/TableTest.java index fe17fb8..983fd1d 100644 --- a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/TableTest.java +++ b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/TableTest.java @@ -269,7 +269,7 @@ public void tableIterators() { } } while (prevKey != null); - var keys = Set.of(1d, "foo", "bar", "baz"); + var keys = Set.of(1, "foo", "bar", "baz"); var values = Set.of("test", 1d, 2d, 3d); assertEquals(keys, itKeys); @@ -298,7 +298,7 @@ public void arrayIterator() { itVals.add(it.value()); } - var keys = Set.of(1d, 2d); + var keys = Set.of(1, 2); var values = Set.of("test", "second"); assertEquals(keys, itKeys); @@ -317,7 +317,7 @@ public void arrayIterator() { itVals.add(it.value()); } - var keys = Set.of(1d, 2d, 3d, 4d); + var keys = Set.of(1, 2, 3, 4); var values = Set.of("test", "second", "later!", "third"); assertEquals(keys, itKeys); @@ -337,7 +337,7 @@ public void fakeArray() { { var it = table.arrayIterator(); assertTrue(it.next()); - assertEquals(1d, it.key()); + assertEquals(1, it.key()); assertEquals("test", "test"); assertFalse(it.next()); } @@ -356,7 +356,7 @@ public void fakeArray() { itVals.add(it.value()); } - var keys = Set.of(1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d, 10d); + var keys = Set.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); var values = Set.of("test", "second", 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d); assertEquals(keys, itKeys); diff --git a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/UnaryOpTest.java b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/UnaryOpTest.java index 7af9dba..eed7db2 100644 --- a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/UnaryOpTest.java +++ b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/UnaryOpTest.java @@ -48,8 +48,8 @@ public void negateMetatable() throws Throwable { @Test public void stringLength() throws Throwable { - assertEquals(5d, vm.execute("return #\"12345\"")); - assertEquals(5d, vm.execute(""" + assertEquals(5, vm.execute("return #\"12345\"")); + assertEquals(5, vm.execute(""" str = "12345" return #str """)); @@ -58,9 +58,9 @@ public void stringLength() throws Throwable { @Test public void tableLength() throws Throwable { // Array length - assertEquals(0d, vm.execute("return #{}")); - assertEquals(5d, vm.execute("return #{1, 2, 3, false, true}")); - assertEquals(0d, vm.execute("return #{foo = 1}")); + assertEquals(0, vm.execute("return #{}")); + assertEquals(5, vm.execute("return #{1, 2, 3, false, true}")); + assertEquals(0, vm.execute("return #{foo = 1}")); // Metatables var metaTbl = new LuaTable(); @@ -76,6 +76,6 @@ public void tableLength() throws Throwable { assertEquals("nope!", vm.execute("return #tbl")); metaTbl.set("__len", null); - assertEquals(0d, vm.execute("return #tbl")); + assertEquals(0, vm.execute("return #tbl")); } } diff --git a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/VariableTest.java b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/VariableTest.java index 0028ed9..ec3a0e2 100644 --- a/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/VariableTest.java +++ b/lua4jvm/src/test/java/fi/benjami/code4jvm/lua/test/VariableTest.java @@ -188,8 +188,8 @@ public void writeTable() throws Throwable { false), new SetVariablesStmt( List.of( - new TableField(new VariableExpr(c), new LuaConstant(1d, LuaType.NUMBER)), - new TableField(new VariableExpr(c), new LuaConstant(2d, LuaType.NUMBER)) + new TableField(new VariableExpr(c), new LuaConstant(1d, LuaType.FLOAT)), + new TableField(new VariableExpr(c), new LuaConstant(2d, LuaType.FLOAT)) ), List.of(new VariableExpr(a), new VariableExpr(b)), false),