Skip to content

Commit be57b4e

Browse files
authored
Optimize Lua array reads and div/mod emission (#1288)
* Optimize Lua array reads and div-mod intrinsics Remove typed primitive array normalization, invert legacy assertions to require raw reads, and keep erased-generic normalization intact. Emit raw div/mod primitives directly as Lua operators without helper definitions. * Track Lua numeric intrinsics by IM identity
1 parent 48b3084 commit be57b4e

9 files changed

Lines changed: 200 additions & 233 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ExprTranslation.java

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -731,9 +731,8 @@ && isCalledOnDynamicRef(e)
731731
}
732732

733733
ImExpr receiver = leftExpr == null ? null : leftExpr.imTranslateExpr(t, f);
734-
boolean normalizeAtBoundary = directFunc != null && isLuaExternalBoundary(directFunc);
735734
FunctionSignature selectedSignature = t.isLuaTarget() ? e.attrFunctionSignature() : null;
736-
ImExprs imArgs = translateExprs(arguments, t, f, normalizeAtBoundary, selectedSignature);
735+
ImExprs imArgs = translateExprs(arguments, t, f, selectedSignature);
737736

738737
if (calledFunc instanceof TupleDef) {
739738
// creating a new tuple...
@@ -857,16 +856,11 @@ private static boolean isCalledOnDynamicRef(FunctionCall e) {
857856
}
858857

859858
private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFunction f) {
860-
return translateExprs(arguments, t, f, false);
859+
return translateExprs(arguments, t, f, null);
861860
}
862861

863862
private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFunction f,
864-
boolean externalBoundary) {
865-
return translateExprs(arguments, t, f, externalBoundary, null);
866-
}
867-
868-
private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFunction f,
869-
boolean externalBoundary, @Nullable FunctionSignature selectedSignature) {
863+
@Nullable FunctionSignature selectedSignature) {
870864
ImExprs result = ImExprs();
871865
for (int i = 0; i < arguments.size(); i++) {
872866
Expr e = arguments.get(i);
@@ -876,9 +870,6 @@ private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFu
876870
ImExpr translated = expectedType != null && isCompositeExpectedTypeExpression(e)
877871
? translateWithExpectedType(e, t, f, expectedType)
878872
: e.imTranslateExpr(t, f);
879-
if (externalBoundary) {
880-
translated = wrapLuaAtExternalBoundary(e, t, translated);
881-
}
882873
result.add(translated);
883874
}
884875
return result;
@@ -888,41 +879,6 @@ static boolean isCompositeExpectedTypeExpression(Expr e) {
888879
return e instanceof ExprIfElse || e instanceof ExprUnary || e instanceof ExprStatementsBlock;
889880
}
890881

891-
private static boolean isLuaExternalBoundary(ImFunction function) {
892-
return function.isNative() || function.isBj() || function.isExtern();
893-
}
894-
895-
private static ImExpr wrapLuaAtExternalBoundary(Expr source, ImTranslator t, ImExpr translated) {
896-
WurstType actualType = source.attrTypRaw();
897-
// Ordinary Wurst locals and literals already have their normal Lua
898-
// representation. Only values which can lose their primitive default
899-
// in Lua need normalization: raw array reads crossing into untyped
900-
// code. Erased generic values are normalized by wrapTranslation when
901-
// a concrete primitive context consumes them.
902-
if (!(translated instanceof ImVarArrayAccess)) {
903-
return translated;
904-
}
905-
WurstType normalized = actualType.normalize();
906-
ImFunction ensureType = null;
907-
if (normalized instanceof WurstTypeInt) {
908-
ensureType = t.ensureIntFunc;
909-
} else if (normalized instanceof WurstTypeBool) {
910-
ensureType = t.ensureBoolFunc;
911-
} else if (normalized instanceof WurstTypeReal) {
912-
ensureType = t.ensureRealFunc;
913-
} else if (normalized instanceof WurstTypeString) {
914-
ensureType = t.ensureStrFunc;
915-
}
916-
if (ensureType == null) {
917-
return translated;
918-
}
919-
if (ensureType == t.ensureBoolFunc) {
920-
return ImOperatorCall(WurstOperator.EQ, ImExprs(
921-
translated, ImBoolVal(true)));
922-
}
923-
return ImFunctionCall(source, ensureType, ImTypeArguments(), ImExprs(translated), false, CallType.NORMAL);
924-
}
925-
926882
private static boolean isPrimitiveType(WurstType type) {
927883
WurstType normalized = type.normalize();
928884
return normalized instanceof WurstTypeInt
@@ -944,7 +900,7 @@ public static ImExpr translateIntern(ExprNewObject e, ImTranslator t, ImFunction
944900
ImTypeArguments typeArgs = getFunctionCallTypeArguments(t, sig, e, imClass.getTypeVariables());
945901
FunctionSignature selectedSignature = t.isLuaTarget() ? sig : null;
946902
return ImFunctionCall(e, constructorImFunc, typeArgs,
947-
translateExprs(e.getArgs(), t, f, false, selectedSignature), false, CallType.NORMAL);
903+
translateExprs(e.getArgs(), t, f, selectedSignature), false, CallType.NORMAL);
948904
}
949905

950906
public static ImExprOpt translate(NoExpr e, ImTranslator translator, ImFunction f) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/ImTranslator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@ public <T extends Element> T canonical(T copy) {
191191
@Nullable public ImFunction ensureRealFunc = null;
192192
@Nullable public ImFunction ensureStrFunc = null;
193193
@Nullable public ImFunction stringConcatFunc = null;
194+
// Exact synthetic nodes owned by LuaNativeLowering; backend intrinsic recognition must use identity.
195+
@Nullable public ImFunction luaRawFloorDivIntFunc = null;
196+
@Nullable public ImFunction luaRawFmodIntFunc = null;
197+
@Nullable public ImFunction luaRawFmodRealFunc = null;
194198

195199
private final Map<ImVar, VarsForTupleResult> varsForTupleVar = new Object2ObjectLinkedOpenHashMap<>();
196200

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/imtranslation/LuaNativeLowering.java

Lines changed: 12 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,7 @@ public static void transform(ImProg prog, ImTranslator translator) {
126126
}
127127

128128
lowerStringConcatenation(prog, translator);
129-
lowerDivMod(prog);
130-
lowerPrimitiveArrayBoundaryEnsure(prog, translator);
129+
lowerDivMod(prog, translator);
131130

132131
// Maps original BJ function → replacement (IS_NATIVE stub or nil-safety wrapper).
133132
// Populated lazily during the traversal.
@@ -268,8 +267,8 @@ public void visit(ImOperatorCall call) {
268267
* handler's "was this an intentional abort" check. Leave that one
269268
* expression untouched so the existing recognition still fires.
270269
*/
271-
private static void lowerDivMod(ImProg prog) {
272-
DivModFunctions funcs = new DivModFunctions();
270+
private static void lowerDivMod(ImProg prog, ImTranslator translator) {
271+
DivModFunctions funcs = new DivModFunctions(translator);
273272
prog.accept(new Element.DefaultVisitor() {
274273
@Override
275274
public void visit(ImOperatorCall call) {
@@ -352,119 +351,13 @@ private static int stacktraceParamIndex(ImFunction f) {
352351
return -1;
353352
}
354353

355-
/**
356-
* Normalizes primitive array reads which can cross the Lua/Wurst boundary.
357-
* Arrays can be visible to foreign Lua/Jass code, so a present value can
358-
* be malformed even though the array metatable supplies defaults for
359-
* missing keys. Lvalue writes remain raw; only rvalue reads are wrapped.
360-
*/
361-
private static void lowerPrimitiveArrayBoundaryEnsure(ImProg prog, ImTranslator translator) {
362-
prog.accept(new Element.DefaultVisitor() {
363-
@Override
364-
public void visit(ImVarArrayAccess access) {
365-
super.visit(access);
366-
if (access.isUsedAsLValue() || isAlreadyNormalized(access, translator)
367-
|| isAlreadyNormalizedAccess(access, translator)) {
368-
return;
369-
}
370-
replaceWithEnsure(access, access.attrTrace(), translator);
371-
}
372-
373-
@Override
374-
public void visit(ImFunctionCall call) {
375-
super.visit(call);
376-
ImFunction function = call.getFunc();
377-
if (!isExternalBoundary(function)) {
378-
return;
379-
}
380-
for (ImExpr argument : new ArrayList<>(call.getArguments())) {
381-
if (!(argument instanceof ImVarArrayAccess)
382-
|| isAlreadyNormalized(argument, translator)) {
383-
continue;
384-
}
385-
replaceWithEnsure((ImVarArrayAccess) argument, call.attrTrace(), translator);
386-
}
387-
}
388-
});
389-
}
390-
391-
private static void replaceWithEnsure(ImVarArrayAccess access, de.peeeq.wurstscript.ast.Element trace,
392-
ImTranslator translator) {
393-
ImFunction ensure = ensureFunctionFor(access.attrTyp(), translator);
394-
if (ensure == null) {
395-
return;
396-
}
397-
ImExpr normalized;
398-
if (ensure == translator.ensureBoolFunc) {
399-
normalized = JassIm.ImOperatorCall(WurstOperator.EQ,
400-
JassIm.ImExprs(access.copy(), JassIm.ImBoolVal(true)));
401-
} else {
402-
normalized = callWithStacktrace(trace, ensure, JassIm.ImExprs(access.copy()));
403-
}
404-
access.replaceBy(normalized);
405-
}
406-
407-
private static boolean isExternalBoundary(ImFunction function) {
408-
return !function.getName().startsWith("__wurst_")
409-
&& (function.isNative() || function.isBj() || function.isExtern());
410-
}
411-
412-
private static boolean isAlreadyNormalized(ImExpr argument, ImTranslator translator) {
413-
if (argument instanceof ImFunctionCall
414-
&& (((ImFunctionCall) argument).getFunc() == translator.ensureIntFunc
415-
|| ((ImFunctionCall) argument).getFunc() == translator.ensureBoolFunc
416-
|| ((ImFunctionCall) argument).getFunc() == translator.ensureRealFunc
417-
|| ((ImFunctionCall) argument).getFunc() == translator.ensureStrFunc)) {
418-
return true;
419-
}
420-
if (argument instanceof ImOperatorCall) {
421-
ImOperatorCall operator = (ImOperatorCall) argument;
422-
return operator.getOp() == WurstOperator.EQ
423-
&& operator.getArguments().size() == 2
424-
&& operator.getArguments().get(1) instanceof ImBoolVal
425-
&& ((ImBoolVal) operator.getArguments().get(1)).getValB();
426-
}
427-
return false;
428-
}
429-
430-
private static boolean isAlreadyNormalizedAccess(ImVarArrayAccess access, ImTranslator translator) {
431-
Element parent = access.getParent();
432-
Element owner = parent == null ? null : parent.getParent();
433-
if (owner instanceof ImFunctionCall) {
434-
ImFunction function = ((ImFunctionCall) owner).getFunc();
435-
return function == translator.ensureIntFunc || function == translator.ensureBoolFunc
436-
|| function == translator.ensureRealFunc || function == translator.ensureStrFunc;
437-
}
438-
if (!(owner instanceof ImOperatorCall)) {
439-
return false;
440-
}
441-
ImOperatorCall operator = (ImOperatorCall) owner;
442-
return operator.getOp() == WurstOperator.EQ
443-
&& operator.getArguments().size() == 2
444-
&& operator.getArguments().get(0) == access
445-
&& operator.getArguments().get(1) instanceof ImBoolVal
446-
&& ((ImBoolVal) operator.getArguments().get(1)).getValB();
447-
}
448-
449-
private static ImFunction ensureFunctionFor(ImType type, ImTranslator translator) {
450-
if (TypesHelper.isIntType(type)) {
451-
return translator.ensureIntFunc;
452-
} else if (TypesHelper.isBoolType(type)) {
453-
return translator.ensureBoolFunc;
454-
} else if (TypesHelper.isRealType(type)) {
455-
return translator.ensureRealFunc;
456-
} else if (TypesHelper.isStringType(type)) {
457-
return translator.ensureStrFunc;
458-
}
459-
return null;
460-
}
461-
462354
/**
463355
* Lazily builds (and memoizes) the div/mod helper functions and the tiny
464356
* raw-Lua-primitive natives they delegate to (Wurst's IM has no
465357
* floor-division/fmod operator of its own).
466358
*/
467359
private static final class DivModFunctions {
360+
private final ImTranslator translator;
468361
private final List<ImFunction> created = new ArrayList<>();
469362
private ImFunction rawFloorDivInt;
470363
private ImFunction rawFmodInt;
@@ -473,6 +366,10 @@ private static final class DivModFunctions {
473366
private ImFunction modInt;
474367
private ImFunction modReal;
475368

369+
private DivModFunctions(ImTranslator translator) {
370+
this.translator = translator;
371+
}
372+
476373
List<ImFunction> createdFunctions() {
477374
return created;
478375
}
@@ -508,6 +405,7 @@ ImFunction jassModInt() {
508405
private ImFunction rawFloorDivInt() {
509406
if (rawFloorDivInt == null) {
510407
rawFloorDivInt = rawNative("__wurst_rawFloorDivInt", TypesHelper.imInt());
408+
translator.luaRawFloorDivIntFunc = rawFloorDivInt;
511409
created.add(rawFloorDivInt);
512410
}
513411
return rawFloorDivInt;
@@ -516,6 +414,7 @@ private ImFunction rawFloorDivInt() {
516414
private ImFunction rawFmodInt() {
517415
if (rawFmodInt == null) {
518416
rawFmodInt = rawNative("__wurst_rawFmodInt", TypesHelper.imInt());
417+
translator.luaRawFmodIntFunc = rawFmodInt;
519418
created.add(rawFmodInt);
520419
}
521420
return rawFmodInt;
@@ -524,12 +423,13 @@ private ImFunction rawFmodInt() {
524423
private ImFunction rawFmodReal() {
525424
if (rawFmodReal == null) {
526425
rawFmodReal = rawNative("__wurst_rawFmodReal", TypesHelper.imReal());
426+
translator.luaRawFmodRealFunc = rawFmodReal;
527427
created.add(rawFmodReal);
528428
}
529429
return rawFmodReal;
530430
}
531431

532-
/** A native leaf with two params and a return, all of the same primitive type. Body supplied by LuaNatives. */
432+
/** A native leaf with two params and a return, translated as a Lua backend intrinsic. */
533433
private static ImFunction rawNative(String name, ImType numType) {
534434
ImVar a = JassIm.ImVar(SYNTHETIC_TRACE, numType.copy(), "a", false);
535435
ImVar b = JassIm.ImVar(SYNTHETIC_TRACE, numType.copy(), "b", false);

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/ExprTranslation.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,12 +136,24 @@ public static LuaExpr translate(ImFunctionCall e, LuaTranslator tr) {
136136
}
137137
}
138138

139-
LuaFunction f = tr.luaFunc.getFor(e.getFunc());
140139
// Use the immutable ImFunction name rather than f.getName(), because f is a cached
141140
// LuaFunction object shared across all call sites of this native. The setName() calls
142141
// below mutate it, so f.getName() changes after the first translation and can no longer
143142
// be relied upon for sentinel checks.
144143
String imFuncName = e.getFunc().getName();
144+
if (isRawNumericIntrinsic(e.getFunc(), tr)) {
145+
if (e.getArguments().size() != 2) {
146+
throw new CompileError(e.attrTrace().attrSource(),
147+
imFuncName + " expects exactly two arguments");
148+
}
149+
LuaExpr left = e.getArguments().get(0).translateToLua(tr);
150+
LuaExpr right = e.getArguments().get(1).translateToLua(tr);
151+
if (e.getFunc() == tr.imTr.luaRawFloorDivIntFunc) {
152+
return LuaAst.LuaExprBinary(left, LuaAst.LuaOpFloorDiv(), right);
153+
}
154+
return LuaAst.LuaExprFunctionCallByName("math.fmod", LuaAst.LuaExprlist(left, right));
155+
}
156+
LuaFunction f = tr.luaFunc.getFor(e.getFunc());
145157
if ("I2S".equals(imFuncName) && isIntentionalThreadAbortCall(e)) {
146158
return LuaAst.LuaExprFunctionCallByName("error", LuaAst.LuaExprlist(
147159
LuaAst.LuaExprStringVal(WURST_ABORT_THREAD_SENTINEL),
@@ -156,6 +168,12 @@ public static LuaExpr translate(ImFunctionCall e, LuaTranslator tr) {
156168
return LuaAst.LuaExprFunctionCall(f, tr.translateExprList(e.getArguments()));
157169
}
158170

171+
static boolean isRawNumericIntrinsic(ImFunction function, LuaTranslator tr) {
172+
return function == tr.imTr.luaRawFloorDivIntFunc
173+
|| function == tr.imTr.luaRawFmodIntFunc
174+
|| function == tr.imTr.luaRawFmodRealFunc;
175+
}
176+
159177
private static boolean isIntentionalThreadAbortCall(ImFunctionCall e) {
160178
if (e.getArguments().size() != 1) {
161179
return false;
@@ -445,16 +463,7 @@ public static LuaExpr translate(ImVarAccess e, LuaTranslator tr) {
445463
return LuaAst.LuaExprVarAccess(tr.luaVar.getFor(e.getVar()));
446464
}
447465

448-
/**
449-
* Primitive-typed array reads are wrapped in a type-normalizing helper
450-
* call at the IM level, before the optimizer runs (see
451-
* LuaNativeLowering#lowerPrimitiveArrayEnsure), by rewriting the read into
452-
* a call against ImTranslator#ensureIntFunc and friends - so by the time
453-
* an ImVarArrayAccess reaches this method, it is already either a
454-
* genuine lvalue/raw access or an access whose type never needed
455-
* wrapping (e.g. class/handle-typed arrays, which default to nil the
456-
* same way an untouched Lua table key already does).
457-
*/
466+
/** Primitive-typed arrays carry their Wurst defaults through metatables, so every read is raw. */
458467
public static LuaExpr translate(ImVarArrayAccess e, LuaTranslator tr) {
459468
return translateArrayAccessRaw(e, tr);
460469
}

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaAssertions.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,11 @@ public void visit(LuaTableNamedField f) {
7676
@Override
7777
public void visit(LuaExprFunctionCallByName call) {
7878
super.visit(call);
79-
check("call to", call.getFuncName());
79+
// Backend-owned qualified standard-library calls are valid Lua expressions,
80+
// though they are deliberately not valid single identifiers.
81+
if (!"math.fmod".equals(call.getFuncName())) {
82+
check("call to", call.getFuncName());
83+
}
8084
}
8185
});
8286
if (!invalid.isEmpty()) {

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaNatives.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -138,24 +138,6 @@ public class LuaNatives {
138138
f.getBody().add(LuaAst.LuaLiteral("return math.ceil(x)"));
139139
});
140140

141-
addNative("__wurst_rawFloorDivInt", f -> {
142-
f.getParams().add(LuaAst.LuaVariable("a", LuaAst.LuaNoExpr()));
143-
f.getParams().add(LuaAst.LuaVariable("b", LuaAst.LuaNoExpr()));
144-
f.getBody().add(LuaAst.LuaLiteral("return a // b"));
145-
});
146-
147-
addNative("__wurst_rawFmodInt", f -> {
148-
f.getParams().add(LuaAst.LuaVariable("a", LuaAst.LuaNoExpr()));
149-
f.getParams().add(LuaAst.LuaVariable("b", LuaAst.LuaNoExpr()));
150-
f.getBody().add(LuaAst.LuaLiteral("return math.fmod(a, b)"));
151-
});
152-
153-
addNative("__wurst_rawFmodReal", f -> {
154-
f.getParams().add(LuaAst.LuaVariable("a", LuaAst.LuaNoExpr()));
155-
f.getParams().add(LuaAst.LuaVariable("b", LuaAst.LuaNoExpr()));
156-
f.getBody().add(LuaAst.LuaLiteral("return math.fmod(a, b)"));
157-
});
158-
159141
addNative(Arrays.asList("__wurst_rawToNumberInt", "__wurst_rawToNumberReal"), f -> {
160142
f.getParams().add(LuaAst.LuaVariable("x", LuaAst.LuaNoExpr()));
161143
f.getBody().add(LuaAst.LuaLiteral("return tonumber(x)"));

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstscript/translation/lua/translation/LuaTranslator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,9 @@ private void translateFunc(ImFunction f) {
725725
// do not translate blizzard functions
726726
return;
727727
}
728+
if (f.isNative() && ExprTranslation.isRawNumericIntrinsic(f, this)) {
729+
return;
730+
}
728731
LuaFunction lf = luaFunc.getFor(f);
729732
if (f.isNative()) {
730733
LuaNatives.get(lf);

0 commit comments

Comments
 (0)