Skip to content

Commit 88033d3

Browse files
committed
luaj4jvm: Add support for static invokedynamic call sites
Finally! Originally abandoned for its complexity, this was surprisingly manageable task. Of course, most of the linker has been rewritten since then... Other related changes: * Codified the ad-hoc compiler passes (assertions only, for now) * Added flag system for variables and converted mutability tracking to it * LuaLocalVars must NOT be mutated after IR_GEN pass, because the further passes can and will run more than once * Disabled upvalue type tracking, it is unsafe with mutable upvalues * Upcoming VARIABLE_TRACKING pass will allow re-enabling it
1 parent 805e0e9 commit 88033d3

21 files changed

+308
-158
lines changed

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/LuaVm.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.antlr.v4.runtime.Recognizer;
1212
import org.antlr.v4.runtime.misc.ParseCancellationException;
1313

14+
import fi.benjami.code4jvm.lua.compiler.CompilerPass;
1415
import fi.benjami.code4jvm.lua.compiler.IrCompiler;
1516
import fi.benjami.code4jvm.lua.compiler.LuaScope;
1617
import fi.benjami.code4jvm.lua.compiler.LuaSyntaxException;
@@ -87,7 +88,10 @@ public void syntaxError(Recognizer<?, ?> recognizer,
8788
// Perform semantic analysis and compile to IR
8889
var rootScope = LuaScope.chunkRoot();
8990
var visitor = new IrCompiler(name, rootScope);
90-
return new LuaModule(name, visitor.visitChunk(tree), (LuaLocalVar) rootScope.resolve("_ENV"));
91+
CompilerPass.setCurrent(CompilerPass.IR_GEN);
92+
var root = visitor.visitChunk(tree);
93+
CompilerPass.setCurrent(null);
94+
return new LuaModule(name, root, (LuaLocalVar) rootScope.resolve("_ENV"));
9195
}
9296

9397
public LuaModule compile(String chunk) {
@@ -96,10 +100,9 @@ public LuaModule compile(String chunk) {
96100

97101
public LuaFunction load(LuaModule module, LuaTable env) {
98102
// Instantiate the module
99-
module.env().markMutable(); // Initial assignment by VM
100103
var type = LuaType.function(
101104
// TODO _ENV mutability tracking
102-
List.of(new UpvalueTemplate(module.env(), module.env().mutable() ? LuaType.UNKNOWN : LuaType.TABLE, module.env().mutable())),
105+
List.of(new UpvalueTemplate(module.env(), LuaType.TABLE)),
103106
List.of(),
104107
module.root(),
105108
module.name(),
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package fi.benjami.code4jvm.lua.compiler;
2+
3+
public enum CompilerPass {
4+
5+
/**
6+
* In this phase, lua4jvm IR is generated.
7+
*/
8+
IR_GEN,
9+
10+
/**
11+
* In this phase, variables are traced to determine their mutability and
12+
* other properties. TODO not yet implemented
13+
*/
14+
VARIABLE_TRACING,
15+
16+
/**
17+
* In analysis phase, types that can be statically inferred are inferred
18+
* to generate better code later.
19+
*/
20+
TYPE_ANALYSIS,
21+
22+
/**
23+
* Code generation. In this pass, code4jvm IR is generated. Actual bytecode
24+
* generation is done later by code4jvm, which has its own set of internal
25+
* passes.
26+
*/
27+
CODEGEN
28+
;
29+
30+
private static final ThreadLocal<CompilerPass> current = new ThreadLocal<>();
31+
32+
public static void setCurrent(CompilerPass pass) {
33+
current.set(pass);
34+
}
35+
36+
public boolean active() {
37+
return current.get() == this;
38+
}
39+
40+
public boolean inactive() {
41+
return current.get() != this;
42+
}
43+
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/FunctionCompiler.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import fi.benjami.code4jvm.lua.ir.LuaType;
1919
import fi.benjami.code4jvm.lua.ir.UpvalueTemplate;
2020
import fi.benjami.code4jvm.lua.linker.LuaLinker;
21+
import fi.benjami.code4jvm.lua.runtime.LuaBox;
2122
import fi.benjami.code4jvm.lua.runtime.LuaFunction;
2223
import fi.benjami.code4jvm.statement.Return;
2324
import fi.benjami.code4jvm.typedef.ClassDef;
@@ -35,7 +36,9 @@ public record CacheKey(
3536
List<LuaType> argTypes,
3637
List<LuaType> upvalueTypes,
3738
boolean truncateReturn
38-
) {}
39+
) {
40+
41+
}
3942

4043
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
4144

@@ -60,8 +63,13 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
6063

6164
// Compile and load the function code, or use something that is already cached
6265
var compiledFunc = function.type().specializations().computeIfAbsent(cacheKey, t -> {
66+
CompilerPass.setCurrent(CompilerPass.TYPE_ANALYSIS);
6367
var ctx = LuaContext.forFunction(function.owner(), function.type(), truncateReturn, argTypes);
64-
var code = generateCode(ctx, function.type(), argTypes, upvalueTypes);
68+
69+
CompilerPass.setCurrent(CompilerPass.CODEGEN);
70+
var code = generateCode(ctx, function.type(), argTypes, upvalueTypes, function.upvalues());
71+
CompilerPass.setCurrent(null);
72+
6573
try {
6674
// Load the class with single-use class loader
6775
// Using hidden classes would be preferable, but JVM hides them from stack frames
@@ -126,7 +134,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
126134
}
127135

128136
private static byte[] generateCode(LuaContext ctx, LuaType.Function type,
129-
LuaType[] argTypes, LuaType[] upvalueTypes) {
137+
LuaType[] argTypes, LuaType[] upvalueTypes, Object[] upvalues) {
130138
// Create class that wraps the method acting as function body
131139
var def = ClassDef.create(toClassName(type.moduleName()), Access.PUBLIC);
132140
def.sourceFile(type.moduleName());
@@ -188,9 +196,11 @@ private static byte[] generateCode(LuaContext ctx, LuaType.Function type,
188196
// Read upvalues from fields to local variables
189197
for (var i = 0; i < upvalueTypes.length; i++) {
190198
var template = type.upvalues().get(i);
199+
// If upvalue is mutable, its actual type is unknown - and JVM type is LuaBox
200+
var actualType = upvalues[i] instanceof LuaBox ? LuaBox.TYPE : upvalueTypes[i].backingType();
191201
var value = method.add(template.variable().name(), method.self()
192-
.getField(upvalueTypes[i].backingType(), template.variable().name()));
193-
ctx.addUpvalue(template.variable(), value);
202+
.getField(actualType, template.variable().name()));
203+
ctx.addUpvalue(template.variable(), value, upvalues[i]);
194204
}
195205

196206
// Emit Lua code as JVM bytecode

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/LuaContext.java

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fi.benjami.code4jvm.lua.compiler;
22

33
import java.util.ArrayList;
4+
import java.util.EnumSet;
45
import java.util.IdentityHashMap;
56
import java.util.List;
67
import java.util.Map;
@@ -23,6 +24,7 @@ public static LuaContext forFunction(LuaVm vm, LuaType.Function type, boolean tr
2324
var ctx = new LuaContext(vm, truncateReturn);
2425
for (var upvalue : type.upvalues()) {
2526
ctx.recordType(upvalue.variable(), upvalue.type());
27+
ctx.setFlag(upvalue.variable(), VariableFlag.ASSIGNED); // Compiler generates code to assign upvalues
2628
}
2729

2830
// Add types of function arguments
@@ -34,11 +36,13 @@ public static LuaContext forFunction(LuaVm vm, LuaType.Function type, boolean tr
3436
}
3537
var acceptedArgs = type.acceptedArgs();
3638
for (var i = 0; i < normalArgs; i++) {
39+
var arg = acceptedArgs.get(i);
3740
if (argTypes.length > i) {
38-
ctx.recordType(acceptedArgs.get(i), argTypes[i]);
41+
ctx.recordType(arg, argTypes[i]);
3942
} else {
40-
ctx.recordType(acceptedArgs.get(i), LuaType.NIL);
43+
ctx.recordType(arg, LuaType.NIL);
4144
}
45+
ctx.setFlag(arg, VariableFlag.ASSIGNED); // JVM assigns arguments to these
4246
}
4347

4448
// Compute types of local variables and the return type
@@ -60,7 +64,14 @@ public static LuaContext forFunction(LuaVm vm, LuaType.Function type, boolean tr
6064
/**
6165
* Local variables that are, in fact, upvalues.
6266
*/
63-
private final Map<LuaLocalVar, Variable> upvalues;
67+
private final Map<LuaLocalVar, Object> upvalues;
68+
69+
/**
70+
* Flags for local variables. This is used to circumvent the issue the
71+
* fact that local variables cannot be mutated, since the same objects may
72+
* be used for multiple compilation runs.
73+
*/
74+
private final Map<LuaLocalVar, EnumSet<VariableFlag>> variableFlags;
6475

6576
/**
6677
* Data given to JVM when the function is loaded as a hidden class.
@@ -91,6 +102,7 @@ public LuaContext(LuaVm owner, boolean truncateReturn) {
91102
this.typeTable = new IdentityHashMap<>();
92103
this.variables = new IdentityHashMap<>();
93104
this.upvalues = new IdentityHashMap<>();
105+
this.variableFlags = new IdentityHashMap<>();
94106
this.classData = new ArrayList<>();
95107
this.cache = new IdentityHashMap<>();
96108
this.truncateReturn = truncateReturn;
@@ -124,15 +136,26 @@ public void addFunctionArg(LuaLocalVar arg, Variable variable) {
124136
variables.put(arg, variable);
125137
}
126138

127-
public void addUpvalue(LuaLocalVar arg, Variable variable) {
128-
variables.put(arg, variable);
129-
upvalues.put(arg, variable);
139+
/**
140+
* Adds an upvalue to local variables at this context.
141+
* @param localVar Lua local variable that represents the upvalue.
142+
* @param jvmVar JVM variable that represents it in currently compiled method.
143+
* @param value Known value of the upvalue at compilation time. This can be
144+
* used by compiler if it is known to be constant.
145+
*/
146+
public void addUpvalue(LuaLocalVar localVar, Variable jvmVar, Object value) {
147+
variables.put(localVar, jvmVar);
148+
upvalues.put(localVar, value);
130149
}
131150

132151
public boolean isUpvalue(LuaLocalVar localVar) {
133152
return upvalues.containsKey(localVar);
134153
}
135154

155+
public Object getUpvalue(LuaLocalVar localVar) {
156+
return upvalues.get(localVar);
157+
}
158+
136159
public LuaType variableType(LuaVariable variable) {
137160
if (variable instanceof LuaLocalVar) {
138161
assert typeTable.containsKey(variable) : variable;
@@ -156,13 +179,28 @@ public Variable resolveLocalVar(LuaLocalVar variable) {
156179
var backingVar = variables.get(variable);
157180
if (backingVar == null) {
158181
var type = typeTable.get(variable);
159-
var useBox = variable.upvalue() && variable.mutable();
182+
var useBox = variable.upvalue() && hasFlag(variable, VariableFlag.MUTABLE);
160183
backingVar = Variable.create(useBox ? LuaBox.TYPE : type.backingType(), variable.name());
161184
variables.put(variable, backingVar);
162185
}
163186
return backingVar;
164187
}
165188

189+
public boolean hasFlag(LuaLocalVar variable, VariableFlag flag) {
190+
assert flag.lockedPass == null || flag.lockedPass.inactive();
191+
var set = variableFlags.get(variable);
192+
return set != null ? set.contains(flag) : false;
193+
}
194+
195+
public void setFlag(LuaLocalVar variable, VariableFlag flag) {
196+
var set = variableFlags.get(variable);
197+
if (set == null) {
198+
set = EnumSet.of(flag);
199+
} else {
200+
set.add(flag);
201+
}
202+
}
203+
166204
public void returnTypes(LuaType... types) {
167205
if (returnTypes == null) {
168206
returnTypes = types;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package fi.benjami.code4jvm.lua.compiler;
2+
3+
import fi.benjami.code4jvm.lua.ir.LuaLocalVar;
4+
5+
/**
6+
* Flags that can be assigned to {@link LuaLocalVar local variables} in
7+
* {@link LuaContext compiler contexts}. Thus, these are unique per compilation
8+
* run.
9+
*/
10+
public enum VariableFlag {
11+
12+
/**
13+
* Variable is assigned to once or more.
14+
*/
15+
ASSIGNED(null),
16+
17+
/**
18+
* Variable is mutable; that is, it is assigned to at least twice.
19+
*/
20+
MUTABLE(CompilerPass.TYPE_ANALYSIS)
21+
;
22+
23+
/**
24+
* The flag must not be checked during this pass, but can be set.
25+
* Null to disable this check. When assertions are enabled,
26+
* {@link LuaContext#hasFlag(LuaLocalVar, VariableFlag)} checks this.
27+
*
28+
* <p>Locked passes are used to ensure that flags are not being mutated
29+
* and read at the same time. If unintentional, such actions could lead to
30+
* subtle but nasty bugs.
31+
*/
32+
public final CompilerPass lockedPass;
33+
34+
VariableFlag(CompilerPass lockedPass) {
35+
this.lockedPass = lockedPass;
36+
}
37+
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/debug/LinkerTrace.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,6 @@ public class LinkerTrace {
99
public Object callable;
1010

1111
public Object currentPrototype;
12+
13+
public int stableTargets;
1214
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaLocalVar.java

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fi.benjami.code4jvm.lua.ir;
22

33
import fi.benjami.code4jvm.Type;
4+
import fi.benjami.code4jvm.lua.compiler.CompilerPass;
45

56
public final class LuaLocalVar implements LuaVariable {
67

@@ -9,7 +10,6 @@ public final class LuaLocalVar implements LuaVariable {
910
public static final LuaLocalVar VARARGS = new LuaLocalVar("...");
1011

1112
private final String name;
12-
private int mutationSites;
1313
private boolean upvalue;
1414

1515
public LuaLocalVar(String name) {
@@ -20,28 +20,18 @@ public String name() {
2020
return name;
2121
}
2222

23-
@Override
24-
public void markMutable() {
25-
mutationSites++;
26-
}
27-
28-
/**
29-
* Whether or not this local variable is ever assigned to after its initial
30-
* assignment. This includes mutations by blocks that inherit it as upvalue
31-
* (to be precise, Lua upvalues are essentially external local variables).
32-
*/
33-
public boolean mutable() {
34-
return mutationSites > 1;
35-
}
23+
// The below mutations are safe, because they're done in IR compilation phase that is run once only
3624

3725
public void markUpvalue() {
26+
assert CompilerPass.IR_GEN.active();
3827
upvalue = true;
3928
}
4029

4130
/**
4231
* Whether or not this local variable is an upvalue for some block.
4332
*/
4433
public boolean upvalue() {
34+
assert CompilerPass.IR_GEN.inactive();
4535
return upvalue;
4636
}
4737

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaType.java

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -140,28 +140,6 @@ public Type backingType() {
140140
public String toString() {
141141
return "LuaType.Function[upvalues=" + upvalues + ", acceptedArgs=" + acceptedArgs + ", body=" + body + "]";
142142
}
143-
144-
@Override
145-
public int hashCode() {
146-
return Objects.hash(acceptedArgs, body, specializations, upvalues);
147-
}
148-
149-
@Override
150-
public boolean equals(Object obj) {
151-
if (this == obj) {
152-
return true;
153-
}
154-
if (obj == null) {
155-
return false;
156-
}
157-
if (getClass() != obj.getClass()) {
158-
return false;
159-
}
160-
Function other = (Function) obj;
161-
return Objects.equals(acceptedArgs, other.acceptedArgs) && Objects.equals(body, other.body)
162-
&& Objects.equals(specializations, other.specializations)
163-
&& Objects.equals(upvalues, other.upvalues);
164-
}
165143

166144
}
167145

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaVariable.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,4 @@
22

33
public sealed interface LuaVariable permits LuaLocalVar, TableField {
44

5-
void markMutable();
65
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/TableField.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,4 @@ public record TableField(
55
IrNode field
66
) implements LuaVariable {
77

8-
@Override
9-
public void markMutable() {
10-
// Do nothing, table fields are always mutable
11-
}
128
}

0 commit comments

Comments
 (0)