Skip to content

Commit

Permalink
lua4jvm: Add debug information generated classes
Browse files Browse the repository at this point in the history
In other words, stack traces now show function names and line numbers!
  • Loading branch information
bensku committed Aug 3, 2024
1 parent 71dc004 commit e4ae087
Show file tree
Hide file tree
Showing 17 changed files with 267 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ public static void main(String... args) throws Throwable {
}
vm.globals().set("arg", argTable);

vm.execute(script);
vm.execute(args[0], script);
}
}
22 changes: 16 additions & 6 deletions lua4jvm/src/main/java/fi/benjami/code4jvm/lua/LuaVm.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,31 +45,41 @@ public LuaTable globals() {
return globals;
}

public LuaModule compile(String chunk) {
public LuaModule compile(String name, String chunk) {
// Tokenize and parse the chunk
var lexer = new LuaLexer(CharStreams.fromString(chunk));
var parser = new LuaParser(new CommonTokenStream(lexer));
var tree = parser.chunk();

// Perform semantic analysis and compile to IR
var rootScope = LuaScope.chunkRoot();
var visitor = new IrCompiler(rootScope);
return new LuaModule(visitor.visitChunk(tree), (LuaLocalVar) rootScope.resolve("_ENV"));
var visitor = new IrCompiler(name, rootScope);
return new LuaModule(name, visitor.visitChunk(tree), (LuaLocalVar) rootScope.resolve("_ENV"));
}

public LuaModule compile(String chunk) {
return compile("unknown", chunk);
}

public LuaFunction load(LuaModule module, LuaTable env) {
// Instantiate the module
var type = LuaType.function(
List.of(new UpvalueTemplate(module.env(), LuaType.TABLE)),
List.of(),
module.root()
module.root(),
module.name(),
"main chunk"
);
return new LuaFunction(this, type, new Object[] {env});
}

public Object execute(String chunk) throws Throwable {
var module = compile(chunk);
public Object execute(String name, String chunk) throws Throwable {
var module = compile(name, chunk);
var func = load(module, globals());
return func.call();
}

public Object execute(String chunk) throws Throwable {
return execute("unknown", chunk);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -66,7 +67,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
// Using hidden classes would be preferable, but JVM hides them from stack frames
// ... which really screws up stack traces of Lua code
// See https://bugs.openjdk.org/browse/JDK-8212620
var implClass = SingleClassLoader.load("unknown", code);
var implClass = SingleClassLoader.load(toClassName(function.type().moduleName()), code);
try {
LOOKUP.findStaticSetter(implClass, ClassData.FIELD_NAME, Object[].class)
.invokeExact(ctx.allClassData());
Expand Down Expand Up @@ -100,7 +101,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,

var jvmReturnType = ctx.returnType().equals(LuaType.NIL)
? Type.VOID : ctx.returnType().backingType();
var method = LOOKUP.findVirtual(implClass, "call",
var method = LOOKUP.findVirtual(implClass, function.type().functionName(),
MethodType.methodType(jvmReturnType.loadedClass(), jvmArgTypes));

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

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

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

return def.compile(); // Delegate to code4jvm for compilation
}

private static String toClassName(String moduleName) {
// Of course, there is no guarantee that it actually is a path
// But if it is, we'd best use platform path handling to strip the unnecessary parts
var path = Path.of(moduleName);
var fileName = path.getFileName().toString();
return fileName.endsWith(".lua") ? fileName.substring(0, fileName.length() - 4) : fileName;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;

import fi.benjami.code4jvm.lua.ir.DebugInfoNode;
import fi.benjami.code4jvm.lua.ir.IrNode;
import fi.benjami.code4jvm.lua.ir.LuaBlock;
import fi.benjami.code4jvm.lua.ir.LuaLocalVar;
import fi.benjami.code4jvm.lua.ir.LuaType;
import fi.benjami.code4jvm.lua.ir.LuaVariable;
import fi.benjami.code4jvm.lua.ir.TableField;
import fi.benjami.code4jvm.lua.ir.expr.ArithmeticExpr;
Expand Down Expand Up @@ -91,10 +95,14 @@

public class IrCompiler extends LuaBaseVisitor<IrNode> {

private final String moduleName;
private final Deque<LuaScope> scopes;

public IrCompiler(LuaScope rootScope) {
scopes = new ArrayDeque<>();
private int lastLine;

public IrCompiler(String moduleName, LuaScope rootScope) {
this.moduleName = moduleName;
this.scopes = new ArrayDeque<>();
scopes.push(rootScope);
}

Expand All @@ -110,6 +118,19 @@ private void popScope() {
scopes.pop();
}

@Override
public IrNode visit(ParseTree tree) {
if (tree instanceof ParserRuleContext ctx) {
var line = ctx.getStart().getLine();
if (line != lastLine) {
// Line number changed, make sure to record it
assert line > lastLine;
return new DebugInfoNode(line, super.visit(tree));
}
}
return super.visit(tree);
}

@Override
public LuaBlock visitChunk(ChunkContext ctx) {
return visitBlock(ctx.block());
Expand Down Expand Up @@ -243,14 +264,16 @@ public IrNode visitFunction(FunctionContext ctx) {
for (var i = 1; i < ctx.Name().size(); i++) {
target = new VariableExpr(new TableField(target, new LuaConstant(ctx.Name(i))));
}
var function = visitFuncbody(ctx.funcbody(), ctx.oopPart != null);
// TODO incorporate the full name as part of function name
var function = visitFuncbody(ctx.Name(ctx.Name().size() - 1).getText(), ctx.funcbody(), ctx.oopPart != null);
return new SetVariablesStmt(List.of(target.source()), List.of(function), false);
}

@Override
public IrNode visitLocalFunction(LocalFunctionContext ctx) {
var function = visitFuncbody(ctx.funcbody(), false);
return new SetVariablesStmt(List.of(currentScope().declare(ctx.Name().getText())), List.of(function), false);
var name = ctx.Name().getText();
var function = visitFuncbody(name, ctx.funcbody(), false);
return new SetVariablesStmt(List.of(currentScope().declare(name)), List.of(function), false);
}

@Override
Expand Down Expand Up @@ -498,10 +521,10 @@ public IrNode visitArgs(ArgsContext ctx) {

@Override
public IrNode visitFunctiondef(FunctiondefContext ctx) {
return visitFuncbody(ctx.funcbody(), false);
return visitFuncbody("anonymous", ctx.funcbody(), false);
}

public IrNode visitFuncbody(FuncbodyContext ctx, boolean addSelfArg) {
public IrNode visitFuncbody(String name, FuncbodyContext ctx, boolean addSelfArg) {
pushScope(new LuaScope(currentScope(), true));
var scope = currentScope();
List<LuaLocalVar> args;
Expand All @@ -528,7 +551,7 @@ public IrNode visitFuncbody(FuncbodyContext ctx, boolean addSelfArg) {
}
var body = visitBlock(ctx.block());
popScope();
return new FunctionDeclExpr(scope.upvalues(), args, body);
return new FunctionDeclExpr(moduleName, name, scope.upvalues(), args, body);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fi.benjami.code4jvm.lua.compiler;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package fi.benjami.code4jvm.lua.ir;

import fi.benjami.code4jvm.Value;
import fi.benjami.code4jvm.block.Block;
import fi.benjami.code4jvm.lua.compiler.LuaContext;
import fi.benjami.code4jvm.statement.DebugInfo;

/**
* Emits debug information such as line numbers to generated bytecode.
* Debug info nodes should not otherwise affect execution of code!
*
*/
public record DebugInfoNode(
int lineNumber,
IrNode node
) implements IrNode {

@Override
public Value emit(LuaContext ctx, Block block) {
block.add(DebugInfo.lineNumber(lineNumber));
return node.emit(ctx, block);
}

@Override
public LuaType outputType(LuaContext ctx) {
return node.outputType(ctx);
}

@Override
public boolean hasReturn() {
return node.hasReturn();
}

@Override
public IrNode concreteNode() {
return node.concreteNode();
}

}
4 changes: 4 additions & 0 deletions lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/IrNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ public interface IrNode {
default boolean hasReturn() {
return false;
}

default IrNode concreteNode() {
return this;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package fi.benjami.code4jvm.lua.ir;

public record LuaModule(
String name,
LuaBlock root,
LuaLocalVar env
) {}
19 changes: 16 additions & 3 deletions lua4jvm/src/main/java/fi/benjami/code4jvm/lua/ir/LuaType.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,17 @@ class Function implements LuaType {
private final List<UpvalueTemplate> upvalues;
private final List<LuaLocalVar> acceptedArgs;
private final LuaBlock body;
private final String moduleName;
private final String funcName;

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

private Function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body) {
private Function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body, String moduleName, String funcName) {
this.upvalues = upvalues;
this.acceptedArgs = args;
this.body = body;
this.moduleName = moduleName;
this.funcName = funcName;
this.specializations = new HashMap<>();
}

Expand All @@ -116,6 +120,14 @@ public Map<FunctionCompiler.CacheKey, CompiledFunction> specializations() {
public boolean isVarargs() {
return !acceptedArgs.isEmpty() && acceptedArgs.get(acceptedArgs.size() - 1) == LuaLocalVar.VARARGS;
}

public String moduleName() {
return moduleName;
}

public String functionName() {
return funcName;
}

@Override
public String name() {
Expand Down Expand Up @@ -254,14 +266,15 @@ public static Tuple tuple(LuaType... types) {
return new Tuple(types);
}

public static Function function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body) {
public static Function function(List<UpvalueTemplate> upvalues, List<LuaLocalVar> args, LuaBlock body,
String moduleName, String name) {
if (!body.hasReturn()) {
// If the function doesn't always return, insert return nil at end
var nodes = new ArrayList<>(body.nodes());
nodes.add(new ReturnStmt(List.of()));
body = new LuaBlock(nodes);
}
return new Function(upvalues, args, body);
return new Function(upvalues, args, body, moduleName, name);
}

public static Shape shape() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@
import fi.benjami.code4jvm.statement.ArrayAccess;

public record FunctionDeclExpr(
/**
* Name of the module this function declaration belongs to.
*/
String moduleName,

/**
* Name of this function.
*/
String name,

List<Upvalue> upvalues,

/**
Expand Down Expand Up @@ -56,7 +66,7 @@ public LuaType.Function outputType(LuaContext ctx) {
var upvalueTemplates = upvalues.stream()
.map(upvalue -> new UpvalueTemplate(upvalue.inside(), ctx.variableType(upvalue.outside())))
.toList();
return ctx.cached(this, LuaType.function(upvalueTemplates, arguments, body));
return ctx.cached(this, LuaType.function(upvalueTemplates, arguments, body, moduleName, name));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public Value emit(LuaContext ctx, Block block) {
// Before loop body, call the iterable to (hopefully) produce an array of:
// iterator function, state, initial value for control variable, (TODO closing value)
// TODO Java iterable interop?
var first = iterable.get(0);
var first = iterable.get(0).concreteNode();
Value iterator;
if (first instanceof FunctionCallExpr call) {
ctx.setAllowSpread(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
public class MultiVals {

public static boolean canReturnMultiVal(IrNode source) {
return source instanceof FunctionCallExpr
|| source instanceof VariableExpr expr && expr.source() == LuaLocalVar.VARARGS;
var concrete = source.concreteNode();
return concrete instanceof FunctionCallExpr
|| concrete instanceof VariableExpr expr && expr.source() == LuaLocalVar.VARARGS;
}

// Called from generated code
Expand Down
Loading

0 comments on commit e4ae087

Please sign in to comment.