Improve codegen readability

This commit is contained in:
Astrash
2023-09-11 19:09:08 +10:00
parent e177c9e792
commit 002da30fd5
2 changed files with 100 additions and 44 deletions

View File

@@ -0,0 +1,22 @@
package com.dfsek.terra.addons.terrascript.codegen.asm;
import org.objectweb.asm.Opcodes;
public enum OpcodeAlias {
CMP_GREATER_THAN(Opcodes.IFGT),
CMP_GREATER_EQUALS(Opcodes.IFGE),
CMP_LESS_THAN(Opcodes.IFLT),
CMP_LESS_EQUALS(Opcodes.IFLE),
CMP_EQUALS(Opcodes.IFEQ),
CMP_NOT_EQUALS(Opcodes.IFNE),
BOOL_FALSE(Opcodes.IFEQ),
BOOL_TRUE(Opcodes.IFNE),
INTEGERS_EQUAL(Opcodes.IF_ICMPEQ),
INTEGERS_NOT_EQUAL(Opcodes.IF_ICMPNE),
;
public final int opcode;
OpcodeAlias(int opcode) { this.opcode = opcode; }
}

View File

@@ -46,6 +46,16 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.BOOL_FALSE;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_GREATER_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_GREATER_THAN;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_LESS_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_LESS_THAN;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.CMP_NOT_EQUALS;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.BOOL_TRUE;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.INTEGERS_EQUAL;
import static com.dfsek.terra.addons.terrascript.codegen.asm.OpcodeAlias.INTEGERS_NOT_EQUAL;
import static com.dfsek.terra.addons.terrascript.util.ASMUtil.dynamicName; import static com.dfsek.terra.addons.terrascript.util.ASMUtil.dynamicName;
public class TerraScriptClassGenerator { public class TerraScriptClassGenerator {
@@ -162,7 +172,7 @@ public class TerraScriptClassGenerator {
@Override @Override
public Void visitBinaryTypedExpr(Binary expr) { public Void visitBinaryTypedExpr(Binary expr) {
switch(expr.operator) { switch(expr.operator) {
case EQUALS, NOT_EQUALS, BOOLEAN_AND, BOOLEAN_OR, GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> pushComparisonResult(expr); case EQUALS, NOT_EQUALS, BOOLEAN_AND, BOOLEAN_OR, GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> pushComparisonBool(expr);
case ADD -> { case ADD -> {
pushBinaryOperands(expr); pushBinaryOperands(expr);
switch(expr.type) { switch(expr.type) {
@@ -175,7 +185,6 @@ public class TerraScriptClassGenerator {
case SUBTRACT -> binaryInsn(expr, Opcodes.DSUB); case SUBTRACT -> binaryInsn(expr, Opcodes.DSUB);
case MULTIPLY -> binaryInsn(expr, Opcodes.DMUL); case MULTIPLY -> binaryInsn(expr, Opcodes.DMUL);
case DIVIDE -> binaryInsn(expr, Opcodes.DDIV); case DIVIDE -> binaryInsn(expr, Opcodes.DDIV);
// case MODULO ->
default -> throw new RuntimeException("Unhandled binary operator " + expr.operator); default -> throw new RuntimeException("Unhandled binary operator " + expr.operator);
} }
return null; return null;
@@ -190,7 +199,7 @@ public class TerraScriptClassGenerator {
@Override @Override
public Void visitLiteralTypedExpr(Literal expr) { public Void visitLiteralTypedExpr(Literal expr) {
switch (expr.type) { switch (expr.type) {
case BOOLEAN -> method.visitInsn((boolean) expr.value ? Opcodes.ICONST_1 : Opcodes.ICONST_0); case BOOLEAN -> { if ((boolean) expr.value) pushTrue(); else pushFalse(); }
case NUMBER, STRING -> method.visitLdcInsn(expr.value); case NUMBER, STRING -> method.visitLdcInsn(expr.value);
} }
return null; return null;
@@ -265,6 +274,11 @@ public class TerraScriptClassGenerator {
return null; return null;
} }
/**
* Writes function as a private static method of the current class
* @param stmt
* @return
*/
@Override @Override
public Void visitFunctionDeclarationTypedStmt(FunctionDeclaration stmt) { public Void visitFunctionDeclarationTypedStmt(FunctionDeclaration stmt) {
List<Type> parameterTypes = stmt.parameters.stream().map(Pair::getRight).toList(); List<Type> parameterTypes = stmt.parameters.stream().map(Pair::getRight).toList();
@@ -281,7 +295,7 @@ public class TerraScriptClassGenerator {
int lvidx = 0; int lvidx = 0;
for (Pair<String, Type> parameter : stmt.parameters) { for (Pair<String, Type> parameter : stmt.parameters) {
funcGenerator.lvTable.put(parameter.getLeft(), lvidx); funcGenerator.lvTable.put(parameter.getLeft(), lvidx);
lvidx += switch(parameter.getRight()) { lvidx += switch(parameter.getRight()) { // Increment by how many slots data type takes
case NUMBER -> 2; case NUMBER -> 2;
case STRING, BOOLEAN -> 1; case STRING, BOOLEAN -> 1;
default -> throw new RuntimeException("Unable to register local variable index for parameter, unknown parameter type '" + parameter.getRight() + "'"); default -> throw new RuntimeException("Unable to register local variable index for parameter, unknown parameter type '" + parameter.getRight() + "'");
@@ -332,7 +346,7 @@ public class TerraScriptClassGenerator {
conditionalStmt(elseIfClause.getLeft(), elseIfClause.getRight(), endIf); conditionalStmt(elseIfClause.getLeft(), elseIfClause.getRight(), endIf);
} }
stmt.elseBody.ifPresent(b -> b.accept(this)); stmt.elseBody.ifPresent(b -> b.accept(this));
method.visitLabel(endIf); label(endIf);
return null; return null;
} }
@@ -343,15 +357,15 @@ public class TerraScriptClassGenerator {
Label loopEnd = new Label(); Label loopEnd = new Label();
stmt.initializer.accept(this); stmt.initializer.accept(this);
method.visitJumpInsn(Opcodes.GOTO, loopBody); // Skip over incrementer on first loop jump(loopBody); // Skip over incrementer on first loop
method.visitLabel(loopStart); label(loopStart);
stmt.incrementer.accept(this); stmt.incrementer.accept(this);
method.visitLabel(loopBody); label(loopBody);
loopStack.push(Pair.of(loopStart, loopEnd)); loopStack.push(Pair.of(loopStart, loopEnd));
conditionalStmt(stmt.condition, stmt.body, loopStart); conditionalStmt(stmt.condition, stmt.body, loopStart);
loopStack.pop(); loopStack.pop();
method.visitLabel(loopEnd); label(loopEnd);
return null; return null;
} }
@@ -360,11 +374,11 @@ public class TerraScriptClassGenerator {
Label loopStart = new Label(); Label loopStart = new Label();
Label loopEnd = new Label(); Label loopEnd = new Label();
method.visitLabel(loopStart); label(loopStart);
loopStack.push(Pair.of(loopStart, loopEnd)); loopStack.push(Pair.of(loopStart, loopEnd));
conditionalStmt(stmt.condition, stmt.body, loopStart); conditionalStmt(stmt.condition, stmt.body, loopStart);
loopStack.pop(); loopStack.pop();
method.visitLabel(loopEnd); label(loopEnd);
return null; return null;
} }
@@ -375,13 +389,13 @@ public class TerraScriptClassGenerator {
@Override @Override
public Void visitBreakTypedStmt(Break stmt) { public Void visitBreakTypedStmt(Break stmt) {
method.visitJumpInsn(Opcodes.GOTO, loopStack.getFirst().getRight()); jump(loopStack.getFirst().getRight());
return null; return null;
} }
@Override @Override
public Void visitContinueTypedStmt(Continue stmt) { public Void visitContinueTypedStmt(Continue stmt) {
method.visitJumpInsn(Opcodes.GOTO, loopStack.getFirst().getLeft()); jump(loopStack.getFirst().getLeft());
return null; return null;
} }
@@ -402,15 +416,15 @@ public class TerraScriptClassGenerator {
private void invertBool() { private void invertBool() {
Label invertToFalse = new Label(); Label invertToFalse = new Label();
Label finished = new Label(); Label finished = new Label();
method.visitJumpInsn(Opcodes.IFNE, invertToFalse); jumpIf(BOOL_TRUE, invertToFalse);
method.visitInsn(Opcodes.ICONST_1); pushFalse();
method.visitJumpInsn(Opcodes.GOTO, finished); jump(finished);
method.visitLabel(invertToFalse); label(invertToFalse);
method.visitInsn(Opcodes.ICONST_0); pushFalse();
method.visitLabel(finished); label(finished);
} }
private void pushBinaryOperands(Binary expr) { private void pushBinaryOperands(Binary expr) {
@@ -427,11 +441,11 @@ public class TerraScriptClassGenerator {
* Pushes boolean on to the stack based on comparison result * Pushes boolean on to the stack based on comparison result
* @param condition * @param condition
*/ */
private void pushComparisonResult(TypedExpr condition) { private void pushComparisonBool(TypedExpr condition) {
Label trueFinished = new Label(); Label trueFinished = new Label();
conditionalRunnable(condition, () -> method.visitInsn(Opcodes.ICONST_1), trueFinished); conditionalRunnable(condition, this::pushTrue, trueFinished);
method.visitInsn(Opcodes.ICONST_0); pushFalse();
method.visitLabel(trueFinished); label(trueFinished);
} }
/** /**
@@ -444,57 +458,77 @@ public class TerraScriptClassGenerator {
conditionalRunnable(condition, () -> stmt.accept(this), exit); conditionalRunnable(condition, () -> stmt.accept(this), exit);
} }
private void conditionalRunnable(TypedExpr condition, Runnable trueSection, Label trueFinished) { private void pushTrue() {
method.visitInsn(Opcodes.ICONST_1);
}
private void pushFalse() {
method.visitInsn(Opcodes.ICONST_0);
}
private void jumpIf(OpcodeAlias insn, Label label) {
method.visitJumpInsn(insn.opcode, label);
}
private void jump(Label label) {
method.visitJumpInsn(Opcodes.GOTO, label);
}
private void label(Label label) {
method.visitLabel(label);
}
private void conditionalRunnable(TypedExpr condition, Runnable trueBlock, Label trueFinished) {
Label exit = new Label(); // If the first conditional is false, jump over statement and don't execute it Label exit = new Label(); // If the first conditional is false, jump over statement and don't execute it
if (condition instanceof Binary binaryCondition) { if (condition instanceof Binary binaryCondition) {
switch(binaryCondition.operator) { switch(binaryCondition.operator) {
case BOOLEAN_AND -> { case BOOLEAN_AND -> {
// Operands assumed booleans // Operands assumed booleans
binaryCondition.left.accept(this); binaryCondition.left.accept(this);
method.visitJumpInsn(Opcodes.IFEQ, exit); // If left is false, short circuit, don't evaluate right jumpIf(BOOL_FALSE, exit); // If left is false, short circuit, don't evaluate right
binaryCondition.right.accept(this); binaryCondition.right.accept(this);
method.visitJumpInsn(Opcodes.IFEQ, exit); jumpIf(BOOL_FALSE, exit);
} }
case BOOLEAN_OR -> { case BOOLEAN_OR -> {
Label skipRight = new Label(); Label skipRight = new Label();
// Operands assumed booleans // Operands assumed booleans
binaryCondition.left.accept(this); binaryCondition.left.accept(this);
method.visitJumpInsn(Opcodes.IFNE, skipRight); // If left is true, skip evaluating right jumpIf(BOOL_TRUE, skipRight);
binaryCondition.right.accept(this); binaryCondition.right.accept(this);
method.visitJumpInsn(Opcodes.IFEQ, exit); jumpIf(BOOL_FALSE, exit);
method.visitLabel(skipRight); label(skipRight);
} }
case EQUALS -> { case EQUALS -> {
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
pushBinaryOperands(binaryCondition); pushBinaryOperands(binaryCondition);
method.visitJumpInsn(Opcodes.IF_ICMPNE, exit); jumpIf(INTEGERS_NOT_EQUAL, exit);
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles } else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
pushBinaryOperands(binaryCondition); pushBinaryOperands(binaryCondition);
method.visitInsn(Opcodes.DCMPG); method.visitInsn(Opcodes.DCMPG);
method.visitJumpInsn(Opcodes.IFNE, exit); jumpIf(CMP_NOT_EQUALS, exit);
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) { } else if (binaryOperandsSameType(Type.STRING, binaryCondition)) {
pushBinaryOperands(binaryCondition); pushBinaryOperands(binaryCondition);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false); method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
method.visitJumpInsn(Opcodes.IFEQ, exit); jumpIf(BOOL_FALSE, exit);
} else throw new CompilerBugException(); } else throw new CompilerBugException();
} }
case NOT_EQUALS -> { case NOT_EQUALS -> {
if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers if (binaryOperandsSameType(Type.BOOLEAN, binaryCondition)) { // Operands assumed integers
pushBinaryOperands(binaryCondition); pushBinaryOperands(binaryCondition);
method.visitJumpInsn(Opcodes.IF_ICMPEQ, exit); jumpIf(INTEGERS_EQUAL, exit);
} else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles } else if (binaryOperandsSameType(Type.NUMBER, binaryCondition)) { // Operands assumed doubles
pushBinaryOperands(binaryCondition); pushBinaryOperands(binaryCondition);
method.visitInsn(Opcodes.DCMPG); method.visitInsn(Opcodes.DCMPG);
method.visitJumpInsn(Opcodes.IFEQ, exit); jumpIf(CMP_EQUALS, exit);
} else if (binaryOperandsSameType(Type.STRING, binaryCondition)) { // Operands assumed references } else if (binaryOperandsSameType(Type.STRING, binaryCondition)) { // Operands assumed references
pushBinaryOperands(binaryCondition); pushBinaryOperands(binaryCondition);
method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false); method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
invertBool(); invertBool();
method.visitJumpInsn(Opcodes.IFEQ, exit); jumpIf(CMP_EQUALS, exit);
} else throw new CompilerBugException(); } else throw new CompilerBugException();
} }
case GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> { case GREATER, GREATER_EQUALS, LESS, LESS_EQUALS -> {
@@ -507,11 +541,11 @@ public class TerraScriptClassGenerator {
default -> throw new IllegalStateException(); default -> throw new IllegalStateException();
}); });
method.visitJumpInsn(switch(binaryCondition.operator) { jumpIf(switch(binaryCondition.operator) {
case GREATER -> Opcodes.IFLE; case GREATER -> CMP_LESS_EQUALS;
case GREATER_EQUALS -> Opcodes.IFLT; case GREATER_EQUALS -> CMP_LESS_THAN;
case LESS -> Opcodes.IFGE; case LESS -> CMP_GREATER_EQUALS;
case LESS_EQUALS -> Opcodes.IFGT; case LESS_EQUALS -> CMP_GREATER_THAN;
default -> throw new IllegalStateException(); default -> throw new IllegalStateException();
}, exit); }, exit);
} }
@@ -520,11 +554,11 @@ public class TerraScriptClassGenerator {
} else { } else {
// Assume condition returns bool // Assume condition returns bool
condition.accept(this); condition.accept(this);
method.visitJumpInsn(Opcodes.IFEQ, exit); jumpIf(BOOL_FALSE, exit);
} }
trueSection.run(); trueBlock.run();
method.visitJumpInsn(Opcodes.GOTO, trueFinished); // Jump to end of statement after execution jump(trueFinished); // Jump to end of statement after execution
method.visitLabel(exit); label(exit);
} }
private String getFunctionDescriptor(List<Type> parameters, Type returnType) { private String getFunctionDescriptor(List<Type> parameters, Type returnType) {