Skip to content

Commit e4ae087

Browse files
committed
lua4jvm: Add debug information generated classes
In other words, stack traces now show function names and line numbers!
1 parent 71dc004 commit e4ae087

File tree

17 files changed

+267
-52
lines changed

17 files changed

+267
-52
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ public static void main(String... args) throws Throwable {
1919
}
2020
vm.globals().set("arg", argTable);
2121

22-
vm.execute(script);
22+
vm.execute(args[0], script);
2323
}
2424
}

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

+16-6
Original file line numberDiff line numberDiff line change
@@ -45,31 +45,41 @@ public LuaTable globals() {
4545
return globals;
4646
}
4747

48-
public LuaModule compile(String chunk) {
48+
public LuaModule compile(String name, String chunk) {
4949
// Tokenize and parse the chunk
5050
var lexer = new LuaLexer(CharStreams.fromString(chunk));
5151
var parser = new LuaParser(new CommonTokenStream(lexer));
5252
var tree = parser.chunk();
5353

5454
// Perform semantic analysis and compile to IR
5555
var rootScope = LuaScope.chunkRoot();
56-
var visitor = new IrCompiler(rootScope);
57-
return new LuaModule(visitor.visitChunk(tree), (LuaLocalVar) rootScope.resolve("_ENV"));
56+
var visitor = new IrCompiler(name, rootScope);
57+
return new LuaModule(name, visitor.visitChunk(tree), (LuaLocalVar) rootScope.resolve("_ENV"));
58+
}
59+
60+
public LuaModule compile(String chunk) {
61+
return compile("unknown", chunk);
5862
}
5963

6064
public LuaFunction load(LuaModule module, LuaTable env) {
6165
// Instantiate the module
6266
var type = LuaType.function(
6367
List.of(new UpvalueTemplate(module.env(), LuaType.TABLE)),
6468
List.of(),
65-
module.root()
69+
module.root(),
70+
module.name(),
71+
"main chunk"
6672
);
6773
return new LuaFunction(this, type, new Object[] {env});
6874
}
6975

70-
public Object execute(String chunk) throws Throwable {
71-
var module = compile(chunk);
76+
public Object execute(String name, String chunk) throws Throwable {
77+
var module = compile(name, chunk);
7278
var func = load(module, globals());
7379
return func.call();
7480
}
81+
82+
public Object execute(String chunk) throws Throwable {
83+
return execute("unknown", chunk);
84+
}
7585
}

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

+14-5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.lang.invoke.MethodHandle;
44
import java.lang.invoke.MethodHandles;
55
import java.lang.invoke.MethodType;
6+
import java.nio.file.Path;
67
import java.util.ArrayList;
78
import java.util.Arrays;
89
import java.util.List;
@@ -66,7 +67,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
6667
// Using hidden classes would be preferable, but JVM hides them from stack frames
6768
// ... which really screws up stack traces of Lua code
6869
// See https://bugs.openjdk.org/browse/JDK-8212620
69-
var implClass = SingleClassLoader.load("unknown", code);
70+
var implClass = SingleClassLoader.load(toClassName(function.type().moduleName()), code);
7071
try {
7172
LOOKUP.findStaticSetter(implClass, ClassData.FIELD_NAME, Object[].class)
7273
.invokeExact(ctx.allClassData());
@@ -100,7 +101,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
100101

101102
var jvmReturnType = ctx.returnType().equals(LuaType.NIL)
102103
? Type.VOID : ctx.returnType().backingType();
103-
var method = LOOKUP.findVirtual(implClass, "call",
104+
var method = LOOKUP.findVirtual(implClass, function.type().functionName(),
104105
MethodType.methodType(jvmReturnType.loadedClass(), jvmArgTypes));
105106

106107
return new CompiledFunction(constructor, method);
@@ -127,8 +128,8 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
127128
private static byte[] generateCode(LuaContext ctx, LuaType.Function type,
128129
LuaType[] argTypes, LuaType[] upvalueTypes) {
129130
// Create class that wraps the method acting as function body
130-
// TODO store name in function if available?
131-
var def = ClassDef.create("unknown", Access.PUBLIC);
131+
var def = ClassDef.create(toClassName(type.moduleName()), Access.PUBLIC);
132+
def.sourceFile(type.moduleName());
132133

133134
// Class data constants
134135
def.addStaticField(Access.PUBLIC, Type.OBJECT.array(1), ClassData.FIELD_NAME);
@@ -155,7 +156,7 @@ private static byte[] generateCode(LuaContext ctx, LuaType.Function type,
155156
// Generate method contents
156157
var jvmReturnType = ctx.returnType().equals(LuaType.NIL)
157158
? Type.VOID : ctx.returnType().backingType();
158-
var method = def.addMethod(jvmReturnType, "call", Access.PUBLIC);
159+
var method = def.addMethod(jvmReturnType, type.functionName(), Access.PUBLIC);
159160

160161
// Add the LuaFunction ('self') argument
161162
// Currently unused, but easier to drop it here than with MethodHandles
@@ -197,5 +198,13 @@ private static byte[] generateCode(LuaContext ctx, LuaType.Function type,
197198

198199
return def.compile(); // Delegate to code4jvm for compilation
199200
}
201+
202+
private static String toClassName(String moduleName) {
203+
// Of course, there is no guarantee that it actually is a path
204+
// But if it is, we'd best use platform path handling to strip the unnecessary parts
205+
var path = Path.of(moduleName);
206+
var fileName = path.getFileName().toString();
207+
return fileName.endsWith(".lua") ? fileName.substring(0, fileName.length() - 4) : fileName;
208+
}
200209

201210
}

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

+32-9
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
import java.util.ArrayDeque;
44
import java.util.ArrayList;
55
import java.util.Deque;
6+
import java.util.IdentityHashMap;
67
import java.util.List;
8+
import java.util.Map;
79
import java.util.Objects;
810
import java.util.stream.Collectors;
911

12+
import org.antlr.v4.runtime.ParserRuleContext;
13+
import org.antlr.v4.runtime.tree.ParseTree;
1014
import org.antlr.v4.runtime.tree.TerminalNode;
1115

16+
import fi.benjami.code4jvm.lua.ir.DebugInfoNode;
1217
import fi.benjami.code4jvm.lua.ir.IrNode;
1318
import fi.benjami.code4jvm.lua.ir.LuaBlock;
1419
import fi.benjami.code4jvm.lua.ir.LuaLocalVar;
15-
import fi.benjami.code4jvm.lua.ir.LuaType;
1620
import fi.benjami.code4jvm.lua.ir.LuaVariable;
1721
import fi.benjami.code4jvm.lua.ir.TableField;
1822
import fi.benjami.code4jvm.lua.ir.expr.ArithmeticExpr;
@@ -91,10 +95,14 @@
9195

9296
public class IrCompiler extends LuaBaseVisitor<IrNode> {
9397

98+
private final String moduleName;
9499
private final Deque<LuaScope> scopes;
95100

96-
public IrCompiler(LuaScope rootScope) {
97-
scopes = new ArrayDeque<>();
101+
private int lastLine;
102+
103+
public IrCompiler(String moduleName, LuaScope rootScope) {
104+
this.moduleName = moduleName;
105+
this.scopes = new ArrayDeque<>();
98106
scopes.push(rootScope);
99107
}
100108

@@ -110,6 +118,19 @@ private void popScope() {
110118
scopes.pop();
111119
}
112120

121+
@Override
122+
public IrNode visit(ParseTree tree) {
123+
if (tree instanceof ParserRuleContext ctx) {
124+
var line = ctx.getStart().getLine();
125+
if (line != lastLine) {
126+
// Line number changed, make sure to record it
127+
assert line > lastLine;
128+
return new DebugInfoNode(line, super.visit(tree));
129+
}
130+
}
131+
return super.visit(tree);
132+
}
133+
113134
@Override
114135
public LuaBlock visitChunk(ChunkContext ctx) {
115136
return visitBlock(ctx.block());
@@ -243,14 +264,16 @@ public IrNode visitFunction(FunctionContext ctx) {
243264
for (var i = 1; i < ctx.Name().size(); i++) {
244265
target = new VariableExpr(new TableField(target, new LuaConstant(ctx.Name(i))));
245266
}
246-
var function = visitFuncbody(ctx.funcbody(), ctx.oopPart != null);
267+
// TODO incorporate the full name as part of function name
268+
var function = visitFuncbody(ctx.Name(ctx.Name().size() - 1).getText(), ctx.funcbody(), ctx.oopPart != null);
247269
return new SetVariablesStmt(List.of(target.source()), List.of(function), false);
248270
}
249271

250272
@Override
251273
public IrNode visitLocalFunction(LocalFunctionContext ctx) {
252-
var function = visitFuncbody(ctx.funcbody(), false);
253-
return new SetVariablesStmt(List.of(currentScope().declare(ctx.Name().getText())), List.of(function), false);
274+
var name = ctx.Name().getText();
275+
var function = visitFuncbody(name, ctx.funcbody(), false);
276+
return new SetVariablesStmt(List.of(currentScope().declare(name)), List.of(function), false);
254277
}
255278

256279
@Override
@@ -498,10 +521,10 @@ public IrNode visitArgs(ArgsContext ctx) {
498521

499522
@Override
500523
public IrNode visitFunctiondef(FunctiondefContext ctx) {
501-
return visitFuncbody(ctx.funcbody(), false);
524+
return visitFuncbody("anonymous", ctx.funcbody(), false);
502525
}
503526

504-
public IrNode visitFuncbody(FuncbodyContext ctx, boolean addSelfArg) {
527+
public IrNode visitFuncbody(String name, FuncbodyContext ctx, boolean addSelfArg) {
505528
pushScope(new LuaScope(currentScope(), true));
506529
var scope = currentScope();
507530
List<LuaLocalVar> args;
@@ -528,7 +551,7 @@ public IrNode visitFuncbody(FuncbodyContext ctx, boolean addSelfArg) {
528551
}
529552
var body = visitBlock(ctx.block());
530553
popScope();
531-
return new FunctionDeclExpr(scope.upvalues(), args, body);
554+
return new FunctionDeclExpr(moduleName, name, scope.upvalues(), args, body);
532555
}
533556

534557
@Override

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

+1
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.Arrays;
45
import java.util.IdentityHashMap;
56
import java.util.List;
67
import java.util.Map;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package fi.benjami.code4jvm.lua.ir;
2+
3+
import fi.benjami.code4jvm.Value;
4+
import fi.benjami.code4jvm.block.Block;
5+
import fi.benjami.code4jvm.lua.compiler.LuaContext;
6+
import fi.benjami.code4jvm.statement.DebugInfo;
7+
8+
/**
9+
* Emits debug information such as line numbers to generated bytecode.
10+
* Debug info nodes should not otherwise affect execution of code!
11+
*
12+
*/
13+
public record DebugInfoNode(
14+
int lineNumber,
15+
IrNode node
16+
) implements IrNode {
17+
18+
@Override
19+
public Value emit(LuaContext ctx, Block block) {
20+
block.add(DebugInfo.lineNumber(lineNumber));
21+
return node.emit(ctx, block);
22+
}
23+
24+
@Override
25+
public LuaType outputType(LuaContext ctx) {
26+
return node.outputType(ctx);
27+
}
28+
29+
@Override
30+
public boolean hasReturn() {
31+
return node.hasReturn();
32+
}
33+
34+
@Override
35+
public IrNode concreteNode() {
36+
return node.concreteNode();
37+
}
38+
39+
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,8 @@ public interface IrNode {
1313
default boolean hasReturn() {
1414
return false;
1515
}
16+
17+
default IrNode concreteNode() {
18+
return this;
19+
}
1620
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package fi.benjami.code4jvm.lua.ir;
22

33
public record LuaModule(
4+
String name,
45
LuaBlock root,
56
LuaLocalVar env
67
) {}

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

+16-3
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,17 @@ class Function implements LuaType {
8787
private final List<UpvalueTemplate> upvalues;
8888
private final List<LuaLocalVar> acceptedArgs;
8989
private final LuaBlock body;
90+
private final String moduleName;
91+
private final String funcName;
9092

9193
private final Map<FunctionCompiler.CacheKey, CompiledFunction> specializations;
9294

93-
private Function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body) {
95+
private Function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body, String moduleName, String funcName) {
9496
this.upvalues = upvalues;
9597
this.acceptedArgs = args;
9698
this.body = body;
99+
this.moduleName = moduleName;
100+
this.funcName = funcName;
97101
this.specializations = new HashMap<>();
98102
}
99103

@@ -116,6 +120,14 @@ public Map<FunctionCompiler.CacheKey, CompiledFunction> specializations() {
116120
public boolean isVarargs() {
117121
return !acceptedArgs.isEmpty() && acceptedArgs.get(acceptedArgs.size() - 1) == LuaLocalVar.VARARGS;
118122
}
123+
124+
public String moduleName() {
125+
return moduleName;
126+
}
127+
128+
public String functionName() {
129+
return funcName;
130+
}
119131

120132
@Override
121133
public String name() {
@@ -254,14 +266,15 @@ public static Tuple tuple(LuaType... types) {
254266
return new Tuple(types);
255267
}
256268

257-
public static Function function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body) {
269+
public static Function function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body,
270+
String moduleName, String name) {
258271
if (!body.hasReturn()) {
259272
// If the function doesn't always return, insert return nil at end
260273
var nodes = new ArrayList<>(body.nodes());
261274
nodes.add(new ReturnStmt(List.of()));
262275
body = new LuaBlock(nodes);
263276
}
264-
return new Function(upvalues, args, body);
277+
return new Function(upvalues, args, body, moduleName, name);
265278
}
266279

267280
public static Shape shape() {

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/expr/FunctionDeclExpr.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@
1616
import fi.benjami.code4jvm.statement.ArrayAccess;
1717

1818
public record FunctionDeclExpr(
19+
/**
20+
* Name of the module this function declaration belongs to.
21+
*/
22+
String moduleName,
23+
24+
/**
25+
* Name of this function.
26+
*/
27+
String name,
28+
1929
List<Upvalue> upvalues,
2030

2131
/**
@@ -56,7 +66,7 @@ public LuaType.Function outputType(LuaContext ctx) {
5666
var upvalueTemplates = upvalues.stream()
5767
.map(upvalue -> new UpvalueTemplate(upvalue.inside(), ctx.variableType(upvalue.outside())))
5868
.toList();
59-
return ctx.cached(this, LuaType.function(upvalueTemplates, arguments, body));
69+
return ctx.cached(this, LuaType.function(upvalueTemplates, arguments, body, moduleName, name));
6070
}
6171

6272
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/stmt/IteratorForStmt.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public Value emit(LuaContext ctx, Block block) {
4848
// Before loop body, call the iterable to (hopefully) produce an array of:
4949
// iterator function, state, initial value for control variable, (TODO closing value)
5050
// TODO Java iterable interop?
51-
var first = iterable.get(0);
51+
var first = iterable.get(0).concreteNode();
5252
Value iterator;
5353
if (first instanceof FunctionCallExpr call) {
5454
ctx.setAllowSpread(true);

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/runtime/MultiVals.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
public class MultiVals {
1616

1717
public static boolean canReturnMultiVal(IrNode source) {
18-
return source instanceof FunctionCallExpr
19-
|| source instanceof VariableExpr expr && expr.source() == LuaLocalVar.VARARGS;
18+
var concrete = source.concreteNode();
19+
return concrete instanceof FunctionCallExpr
20+
|| concrete instanceof VariableExpr expr && expr.source() == LuaLocalVar.VARARGS;
2021
}
2122

2223
// Called from generated code

0 commit comments

Comments
 (0)