Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -731,9 +731,8 @@ && isCalledOnDynamicRef(e)
}

ImExpr receiver = leftExpr == null ? null : leftExpr.imTranslateExpr(t, f);
boolean normalizeAtBoundary = directFunc != null && isLuaExternalBoundary(directFunc);
FunctionSignature selectedSignature = t.isLuaTarget() ? e.attrFunctionSignature() : null;
ImExprs imArgs = translateExprs(arguments, t, f, normalizeAtBoundary, selectedSignature);
ImExprs imArgs = translateExprs(arguments, t, f, selectedSignature);

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

private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFunction f) {
return translateExprs(arguments, t, f, false);
return translateExprs(arguments, t, f, null);
}

private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFunction f,
boolean externalBoundary) {
return translateExprs(arguments, t, f, externalBoundary, null);
}

private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFunction f,
boolean externalBoundary, @Nullable FunctionSignature selectedSignature) {
@Nullable FunctionSignature selectedSignature) {
ImExprs result = ImExprs();
for (int i = 0; i < arguments.size(); i++) {
Expr e = arguments.get(i);
Expand All @@ -876,9 +870,6 @@ private static ImExprs translateExprs(List<Expr> arguments, ImTranslator t, ImFu
ImExpr translated = expectedType != null && isCompositeExpectedTypeExpression(e)
? translateWithExpectedType(e, t, f, expectedType)
: e.imTranslateExpr(t, f);
if (externalBoundary) {
translated = wrapLuaAtExternalBoundary(e, t, translated);
}
result.add(translated);
}
return result;
Expand All @@ -888,41 +879,6 @@ static boolean isCompositeExpectedTypeExpression(Expr e) {
return e instanceof ExprIfElse || e instanceof ExprUnary || e instanceof ExprStatementsBlock;
}

private static boolean isLuaExternalBoundary(ImFunction function) {
return function.isNative() || function.isBj() || function.isExtern();
}

private static ImExpr wrapLuaAtExternalBoundary(Expr source, ImTranslator t, ImExpr translated) {
WurstType actualType = source.attrTypRaw();
// Ordinary Wurst locals and literals already have their normal Lua
// representation. Only values which can lose their primitive default
// in Lua need normalization: raw array reads crossing into untyped
// code. Erased generic values are normalized by wrapTranslation when
// a concrete primitive context consumes them.
if (!(translated instanceof ImVarArrayAccess)) {
return translated;
}
WurstType normalized = actualType.normalize();
ImFunction ensureType = null;
if (normalized instanceof WurstTypeInt) {
ensureType = t.ensureIntFunc;
} else if (normalized instanceof WurstTypeBool) {
ensureType = t.ensureBoolFunc;
} else if (normalized instanceof WurstTypeReal) {
ensureType = t.ensureRealFunc;
} else if (normalized instanceof WurstTypeString) {
ensureType = t.ensureStrFunc;
}
if (ensureType == null) {
return translated;
}
if (ensureType == t.ensureBoolFunc) {
return ImOperatorCall(WurstOperator.EQ, ImExprs(
translated, ImBoolVal(true)));
}
return ImFunctionCall(source, ensureType, ImTypeArguments(), ImExprs(translated), false, CallType.NORMAL);
}

private static boolean isPrimitiveType(WurstType type) {
WurstType normalized = type.normalize();
return normalized instanceof WurstTypeInt
Expand All @@ -944,7 +900,7 @@ public static ImExpr translateIntern(ExprNewObject e, ImTranslator t, ImFunction
ImTypeArguments typeArgs = getFunctionCallTypeArguments(t, sig, e, imClass.getTypeVariables());
FunctionSignature selectedSignature = t.isLuaTarget() ? sig : null;
return ImFunctionCall(e, constructorImFunc, typeArgs,
translateExprs(e.getArgs(), t, f, false, selectedSignature), false, CallType.NORMAL);
translateExprs(e.getArgs(), t, f, selectedSignature), false, CallType.NORMAL);
}

public static ImExprOpt translate(NoExpr e, ImTranslator translator, ImFunction f) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ public <T extends Element> T canonical(T copy) {
@Nullable public ImFunction ensureRealFunc = null;
@Nullable public ImFunction ensureStrFunc = null;
@Nullable public ImFunction stringConcatFunc = null;
// Exact synthetic nodes owned by LuaNativeLowering; backend intrinsic recognition must use identity.
@Nullable public ImFunction luaRawFloorDivIntFunc = null;
@Nullable public ImFunction luaRawFmodIntFunc = null;
@Nullable public ImFunction luaRawFmodRealFunc = null;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,7 @@ public static void transform(ImProg prog, ImTranslator translator) {
}

lowerStringConcatenation(prog, translator);
lowerDivMod(prog);
lowerPrimitiveArrayBoundaryEnsure(prog, translator);
lowerDivMod(prog, translator);

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

/**
* Normalizes primitive array reads which can cross the Lua/Wurst boundary.
* Arrays can be visible to foreign Lua/Jass code, so a present value can
* be malformed even though the array metatable supplies defaults for
* missing keys. Lvalue writes remain raw; only rvalue reads are wrapped.
*/
private static void lowerPrimitiveArrayBoundaryEnsure(ImProg prog, ImTranslator translator) {
prog.accept(new Element.DefaultVisitor() {
@Override
public void visit(ImVarArrayAccess access) {
super.visit(access);
if (access.isUsedAsLValue() || isAlreadyNormalized(access, translator)
|| isAlreadyNormalizedAccess(access, translator)) {
return;
}
replaceWithEnsure(access, access.attrTrace(), translator);
}

@Override
public void visit(ImFunctionCall call) {
super.visit(call);
ImFunction function = call.getFunc();
if (!isExternalBoundary(function)) {
return;
}
for (ImExpr argument : new ArrayList<>(call.getArguments())) {
if (!(argument instanceof ImVarArrayAccess)
|| isAlreadyNormalized(argument, translator)) {
continue;
}
replaceWithEnsure((ImVarArrayAccess) argument, call.attrTrace(), translator);
}
}
});
}

private static void replaceWithEnsure(ImVarArrayAccess access, de.peeeq.wurstscript.ast.Element trace,
ImTranslator translator) {
ImFunction ensure = ensureFunctionFor(access.attrTyp(), translator);
if (ensure == null) {
return;
}
ImExpr normalized;
if (ensure == translator.ensureBoolFunc) {
normalized = JassIm.ImOperatorCall(WurstOperator.EQ,
JassIm.ImExprs(access.copy(), JassIm.ImBoolVal(true)));
} else {
normalized = callWithStacktrace(trace, ensure, JassIm.ImExprs(access.copy()));
}
access.replaceBy(normalized);
}

private static boolean isExternalBoundary(ImFunction function) {
return !function.getName().startsWith("__wurst_")
&& (function.isNative() || function.isBj() || function.isExtern());
}

private static boolean isAlreadyNormalized(ImExpr argument, ImTranslator translator) {
if (argument instanceof ImFunctionCall
&& (((ImFunctionCall) argument).getFunc() == translator.ensureIntFunc
|| ((ImFunctionCall) argument).getFunc() == translator.ensureBoolFunc
|| ((ImFunctionCall) argument).getFunc() == translator.ensureRealFunc
|| ((ImFunctionCall) argument).getFunc() == translator.ensureStrFunc)) {
return true;
}
if (argument instanceof ImOperatorCall) {
ImOperatorCall operator = (ImOperatorCall) argument;
return operator.getOp() == WurstOperator.EQ
&& operator.getArguments().size() == 2
&& operator.getArguments().get(1) instanceof ImBoolVal
&& ((ImBoolVal) operator.getArguments().get(1)).getValB();
}
return false;
}

private static boolean isAlreadyNormalizedAccess(ImVarArrayAccess access, ImTranslator translator) {
Element parent = access.getParent();
Element owner = parent == null ? null : parent.getParent();
if (owner instanceof ImFunctionCall) {
ImFunction function = ((ImFunctionCall) owner).getFunc();
return function == translator.ensureIntFunc || function == translator.ensureBoolFunc
|| function == translator.ensureRealFunc || function == translator.ensureStrFunc;
}
if (!(owner instanceof ImOperatorCall)) {
return false;
}
ImOperatorCall operator = (ImOperatorCall) owner;
return operator.getOp() == WurstOperator.EQ
&& operator.getArguments().size() == 2
&& operator.getArguments().get(0) == access
&& operator.getArguments().get(1) instanceof ImBoolVal
&& ((ImBoolVal) operator.getArguments().get(1)).getValB();
}

private static ImFunction ensureFunctionFor(ImType type, ImTranslator translator) {
if (TypesHelper.isIntType(type)) {
return translator.ensureIntFunc;
} else if (TypesHelper.isBoolType(type)) {
return translator.ensureBoolFunc;
} else if (TypesHelper.isRealType(type)) {
return translator.ensureRealFunc;
} else if (TypesHelper.isStringType(type)) {
return translator.ensureStrFunc;
}
return null;
}

/**
* Lazily builds (and memoizes) the div/mod helper functions and the tiny
* raw-Lua-primitive natives they delegate to (Wurst's IM has no
* floor-division/fmod operator of its own).
*/
private static final class DivModFunctions {
private final ImTranslator translator;
private final List<ImFunction> created = new ArrayList<>();
private ImFunction rawFloorDivInt;
private ImFunction rawFmodInt;
Expand All @@ -473,6 +366,10 @@ private static final class DivModFunctions {
private ImFunction modInt;
private ImFunction modReal;

private DivModFunctions(ImTranslator translator) {
this.translator = translator;
}

List<ImFunction> createdFunctions() {
return created;
}
Expand Down Expand Up @@ -508,6 +405,7 @@ ImFunction jassModInt() {
private ImFunction rawFloorDivInt() {
if (rawFloorDivInt == null) {
rawFloorDivInt = rawNative("__wurst_rawFloorDivInt", TypesHelper.imInt());
translator.luaRawFloorDivIntFunc = rawFloorDivInt;
created.add(rawFloorDivInt);
}
return rawFloorDivInt;
Expand All @@ -516,6 +414,7 @@ private ImFunction rawFloorDivInt() {
private ImFunction rawFmodInt() {
if (rawFmodInt == null) {
rawFmodInt = rawNative("__wurst_rawFmodInt", TypesHelper.imInt());
translator.luaRawFmodIntFunc = rawFmodInt;
created.add(rawFmodInt);
}
return rawFmodInt;
Expand All @@ -524,12 +423,13 @@ private ImFunction rawFmodInt() {
private ImFunction rawFmodReal() {
if (rawFmodReal == null) {
rawFmodReal = rawNative("__wurst_rawFmodReal", TypesHelper.imReal());
translator.luaRawFmodRealFunc = rawFmodReal;
created.add(rawFmodReal);
}
return rawFmodReal;
}

/** A native leaf with two params and a return, all of the same primitive type. Body supplied by LuaNatives. */
/** A native leaf with two params and a return, translated as a Lua backend intrinsic. */
private static ImFunction rawNative(String name, ImType numType) {
ImVar a = JassIm.ImVar(SYNTHETIC_TRACE, numType.copy(), "a", false);
ImVar b = JassIm.ImVar(SYNTHETIC_TRACE, numType.copy(), "b", false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,24 @@ public static LuaExpr translate(ImFunctionCall e, LuaTranslator tr) {
}
}

LuaFunction f = tr.luaFunc.getFor(e.getFunc());
// Use the immutable ImFunction name rather than f.getName(), because f is a cached
// LuaFunction object shared across all call sites of this native. The setName() calls
// below mutate it, so f.getName() changes after the first translation and can no longer
// be relied upon for sentinel checks.
String imFuncName = e.getFunc().getName();
if (isRawNumericIntrinsic(e.getFunc(), tr)) {
if (e.getArguments().size() != 2) {
throw new CompileError(e.attrTrace().attrSource(),
imFuncName + " expects exactly two arguments");
}
LuaExpr left = e.getArguments().get(0).translateToLua(tr);
LuaExpr right = e.getArguments().get(1).translateToLua(tr);
if (e.getFunc() == tr.imTr.luaRawFloorDivIntFunc) {
return LuaAst.LuaExprBinary(left, LuaAst.LuaOpFloorDiv(), right);
}
return LuaAst.LuaExprFunctionCallByName("math.fmod", LuaAst.LuaExprlist(left, right));
Comment thread
Frotty marked this conversation as resolved.
}
LuaFunction f = tr.luaFunc.getFor(e.getFunc());
if ("I2S".equals(imFuncName) && isIntentionalThreadAbortCall(e)) {
return LuaAst.LuaExprFunctionCallByName("error", LuaAst.LuaExprlist(
LuaAst.LuaExprStringVal(WURST_ABORT_THREAD_SENTINEL),
Expand All @@ -156,6 +168,12 @@ public static LuaExpr translate(ImFunctionCall e, LuaTranslator tr) {
return LuaAst.LuaExprFunctionCall(f, tr.translateExprList(e.getArguments()));
}

static boolean isRawNumericIntrinsic(ImFunction function, LuaTranslator tr) {
return function == tr.imTr.luaRawFloorDivIntFunc
|| function == tr.imTr.luaRawFmodIntFunc
|| function == tr.imTr.luaRawFmodRealFunc;
}

private static boolean isIntentionalThreadAbortCall(ImFunctionCall e) {
if (e.getArguments().size() != 1) {
return false;
Expand Down Expand Up @@ -445,16 +463,7 @@ public static LuaExpr translate(ImVarAccess e, LuaTranslator tr) {
return LuaAst.LuaExprVarAccess(tr.luaVar.getFor(e.getVar()));
}

/**
* Primitive-typed array reads are wrapped in a type-normalizing helper
* call at the IM level, before the optimizer runs (see
* LuaNativeLowering#lowerPrimitiveArrayEnsure), by rewriting the read into
* a call against ImTranslator#ensureIntFunc and friends - so by the time
* an ImVarArrayAccess reaches this method, it is already either a
* genuine lvalue/raw access or an access whose type never needed
* wrapping (e.g. class/handle-typed arrays, which default to nil the
* same way an untouched Lua table key already does).
*/
/** Primitive-typed arrays carry their Wurst defaults through metatables, so every read is raw. */
public static LuaExpr translate(ImVarArrayAccess e, LuaTranslator tr) {
return translateArrayAccessRaw(e, tr);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ public void visit(LuaTableNamedField f) {
@Override
public void visit(LuaExprFunctionCallByName call) {
super.visit(call);
check("call to", call.getFuncName());
// Backend-owned qualified standard-library calls are valid Lua expressions,
// though they are deliberately not valid single identifiers.
if (!"math.fmod".equals(call.getFuncName())) {
check("call to", call.getFuncName());
}
}
});
if (!invalid.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,24 +138,6 @@ public class LuaNatives {
f.getBody().add(LuaAst.LuaLiteral("return math.ceil(x)"));
});

addNative("__wurst_rawFloorDivInt", f -> {
f.getParams().add(LuaAst.LuaVariable("a", LuaAst.LuaNoExpr()));
f.getParams().add(LuaAst.LuaVariable("b", LuaAst.LuaNoExpr()));
f.getBody().add(LuaAst.LuaLiteral("return a // b"));
});

addNative("__wurst_rawFmodInt", f -> {
f.getParams().add(LuaAst.LuaVariable("a", LuaAst.LuaNoExpr()));
f.getParams().add(LuaAst.LuaVariable("b", LuaAst.LuaNoExpr()));
f.getBody().add(LuaAst.LuaLiteral("return math.fmod(a, b)"));
});

addNative("__wurst_rawFmodReal", f -> {
f.getParams().add(LuaAst.LuaVariable("a", LuaAst.LuaNoExpr()));
f.getParams().add(LuaAst.LuaVariable("b", LuaAst.LuaNoExpr()));
f.getBody().add(LuaAst.LuaLiteral("return math.fmod(a, b)"));
});

addNative(Arrays.asList("__wurst_rawToNumberInt", "__wurst_rawToNumberReal"), f -> {
f.getParams().add(LuaAst.LuaVariable("x", LuaAst.LuaNoExpr()));
f.getBody().add(LuaAst.LuaLiteral("return tonumber(x)"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,9 @@ private void translateFunc(ImFunction f) {
// do not translate blizzard functions
return;
}
if (f.isNative() && ExprTranslation.isRawNumericIntrinsic(f, this)) {
return;
}
LuaFunction lf = luaFunc.getFor(f);
if (f.isNative()) {
LuaNatives.get(lf);
Expand Down
Loading
Loading