Compare commits

...

12 Commits

Author SHA1 Message Date
c124587983 git: ignore idea designer file 2023-03-23 03:35:11 +01:00
07e5a338a4 GenASM: Fix segfaults when calling malloc
This was a tricky one. In order to call malloc the stack needs to be 16 byte aligned. GenASM up until this point was 8 byte aligned. This commit changes the code generation so that it is guaranteed that the stack is 16 byte aligned before visiting a child node. We need to do this before visiting *any* child node because we can not know whether somewhere downstream some child node calls malloc.

To make it possible for a visitor method to determine if it needs to align the stack it has to assume that the stack is currently aligned. This is the other reason we ensure that the stack is aligned before visiting any child node.

On top of that some visitor methods did not clean up after themselves. I am not sure why this did not result in segfaults already, but I changed the code so that each visitor method leaves the stack in the same state after completion.
2023-03-23 03:21:10 +01:00
76419d86bb GenASM: Remove a few warnings 2023-03-23 02:59:01 +01:00
f55f2661de Tests: Fix failing test because the error message changed. 2023-03-23 00:32:50 +01:00
06609ae899 GenASM: Get number of local variables from the "localVariables" attribute of FunctionDefinition. 2023-03-22 23:40:02 +01:00
c5c01041e4 ContextAnalysis: Track local variables and function parameters separately.
This enables us to add all local variable Definitions to the DAST so that downstream visitors don't need to compute the local variables of a function again.
2023-03-22 23:37:43 +01:00
9751a1da2f Eval: Add the corresponding named type to a struct Value. 2023-03-22 00:14:24 +01:00
534b507f7a Eval: Make a NullExpression evaluate to a Value with Type "NulLValue" instead of returning null. 2023-03-22 00:13:23 +01:00
cce58b6e38 ContextAnalysis: Make naught comparable to struct again. 2023-03-21 22:44:21 +01:00
8b17ced533 Build: Set Java to verison 17 2023-03-21 22:43:40 +01:00
bacc40d844 GenASM: Make sure the stack is 16 byte aligned before calling malloc or free.
The GenASM visitor only uses quad mnemonics so each manipulation to the stack is done in 8 byte chunks. This means our stack is always 8 byte aligned which is fine when only calling KLang code. libc functions like 'malloc' or 'free' require the stack to be 16 byte aligned which isn't automatically guaranteed when being 8 byte aligned. This is why we need to be careful when manipulating the stack (e.g. pushq, popq, subq x, %rsp). By assuming that we are stack aligned at the beginning of execution and by tracking the number of pushq operations we can deduce whether we misaligned the stack and align it again by adding another 8 byte to the stack.

This re-alignment of the stack is done directly before entering a function body or calling malloc. Since there are no stack manipulations involved when generating the code to calling 'free' there is reason to check for alignment there.
2023-03-21 22:21:14 +01:00
f38bd3d69e GenASM: Use leave instead of mov and pop for returning from a function. 2023-03-21 00:22:11 +01:00
23 changed files with 517 additions and 242 deletions

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@ src/main/antlr4/de/hsrm/compiler/Klang/.antlr
# build output
out
src/test/test
src/test/test
/.idea/uiDesigner.xml

2
.idea/compiler.xml generated
View File

@@ -10,7 +10,7 @@
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="klang" target="11" />
<module name="klang" target="17" />
</bytecodeTargetLevel>
</component>
</project>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_11">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_17">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">

View File

@@ -40,7 +40,7 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<release>11</release>
<release>17</release>
</configuration>
</plugin>
<!-- Plugin to compile the g4 files ahead of the java files

View File

@@ -8,10 +8,12 @@ import de.hsrm.compiler.Klang.nodes.loops.ForLoop;
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
import de.hsrm.compiler.Klang.nodes.statements.*;
import de.hsrm.compiler.Klang.types.Type;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.*;
public class ContextAnalysis extends KlangBaseVisitor<Node> {
Map<String, VariableDeclaration> params = new HashMap<>();
Map<String, VariableDeclaration> vars = new HashMap<>();
Map<String, FunctionDefinition> functionDefs;
Map<String, StructDefinition> structDefs;
@@ -199,8 +201,8 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
if (vars.get(variableName) != null) {
var error = "Redeclaration of variable with name \"" + variableName + "\".";
if (vars.get(variableName) != null || params.get(variableName) != null) {
var error = "Redeclaration of variable or parameter with name \"" + variableName + "\".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
@@ -236,31 +238,27 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override
public Node visitVariable_assignment(KlangParser.Variable_assignmentContext ctx) {
String name = ctx.IDENT().getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
var name = ctx.IDENT().getText();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
VariableDeclaration var = this.vars.get(name);
if (var == null) {
String error = "Variable with name \"" + name + "\" not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var variableOrParameter = getVariableOrParameter(name, line, col);
// Evaluate the expression
Expression expression = (Expression) this.visit(ctx.expression());
var expression = (Expression) visit(ctx.expression());
// Make sure expression can be assigned to the variable
try {
expression.type.combine(var.type);
expression.type.combine(variableOrParameter.type);
} catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
}
// Since we assigned a value to this variable, we can consider it initialized
var.initialized = true;
variableOrParameter.initialized = true;
// Create a new node and add the type of the expression to it
Node result = new VariableAssignment(name, expression);
var result = new VariableAssignment(name, expression);
result.line = line;
result.col = col;
return result;
@@ -272,8 +270,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
ReturnStatement result = new ReturnStatement(expression);
// Check if this expression is a tail recursion
if (expression instanceof FunctionCall) {
var funCall = (FunctionCall) expression;
if (expression instanceof FunctionCall funCall) {
if (funCall.name.equals(this.currentFunctionDefinitionName)) {
// Flag this function call
funCall.isTailRecursive = true;
@@ -288,46 +285,24 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override
public Node visitField_assignment(KlangParser.Field_assignmentContext ctx) {
String varName = ctx.IDENT(0).getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
String[] path = new String[ctx.IDENT().size() - 1];
var varName = ctx.IDENT(0).getText();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
var path = createStructPath(ctx.IDENT());
for (int i = 1; i < ctx.IDENT().size(); i++) {
path[i - 1] = ctx.IDENT(i).getText();
}
var variableOrParameter = getVariableOrParameter(varName, line, col);
ensureReferencesStruct(variableOrParameter, line, col);
var fieldType = drillType(variableOrParameter, path, line, col);
// Get the referenced variable, make sure it is defined
var variableDef = this.vars.get(varName);
if (variableDef == null) {
String error = "Variable with name " + varName + " not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
// Make sure it references a struct
if (variableDef.type.isPrimitiveType()) {
String error = "Variable must reference a struct but references " + variableDef.type.getName() + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
// Get the type of the result of this expression
String structName = variableDef.type.getName();
Type fieldType;
try {
fieldType = Helper.drillType(this.structDefs, structName, path, 0);
} catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
}
// Get the expression and make sure the type combines properly
Expression expression = (Expression) this.visit(ctx.expression());
// Visit the expression and make sure the type combines properly
var expression = (Expression) visit(ctx.expression());
try {
fieldType.combine(expression.type);
} catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
}
Node result = new FieldAssignment(varName, structName, path, expression);
var result = new FieldAssignment(varName, variableOrParameter.type.getName(), path, expression);
result.col = col;
result.line = line;
return result;
@@ -338,23 +313,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
var baseName = ctx.IDENT(0).getText();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
// Create a list of member names. This excludes
// the first entry as it is the base name.
var path = new ArrayList<String>();
for (int i = 1; i < ctx.IDENT().size(); i++) {
path.add(ctx.IDENT(i).getText());
}
var path = createStructPath(ctx.IDENT());
// Determine if the base name points to an enum or a variable
var enumDef = enumDefs.get(baseName);
if (enumDef != null) {
if (path.size() != 1) {
if (path.length != 1) {
var error = "Illegal access to enum " + enumDef.name + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var enumValueName = path.get(0);
var enumValueName = path[0];
var enumValue = Arrays.stream(enumDef.enums)
.filter(e -> e.value.equals(enumValueName))
.findFirst()
@@ -371,29 +340,11 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
return enumAccessExpression;
}
// Get the referenced variable, make sure it is defined
var variableDef = vars.get(baseName);
if (variableDef == null) {
var error = "Variable with name " + baseName + " not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var variableOrParameter = getVariableOrParameter(baseName, line, col);
ensureReferencesStruct(variableOrParameter, line, col);
var resultType = drillType(variableOrParameter, path, line, col);
// Make sure it references a struct
if (variableDef.type.isPrimitiveType()) {
var error = "Variable must reference a struct but references " + variableDef.type.getName() + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
// Get the type of the result of this expression
var structName = variableDef.type.getName();
Type resultType;
try {
resultType = Helper.drillType(structDefs, structName, path.toArray(new String[0]), 0);
} catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
}
var memberAccessExpression = new MemberAccessExpression(baseName, structName, path.toArray(new String[0]));
var memberAccessExpression = new MemberAccessExpression(baseName, variableOrParameter.type.getName(), path);
memberAccessExpression.type = resultType;
memberAccessExpression.line = line;
memberAccessExpression.col = col;
@@ -711,24 +662,20 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override
public Node visitVariable(KlangParser.VariableContext ctx) {
String name = ctx.IDENT().getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
var variableName = ctx.IDENT().getText();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
VariableDeclaration var = this.vars.get(name);
if (var == null) {
String error = "Variable with name \"" + name + "\" not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var variableOrParameter = getVariableOrParameter(variableName, line, col);
// Make sure the variable has been initialized before it can be used
if (!var.initialized) {
String error = "Variable with name \"" + name + "\" has not been initialized.";
if (!variableOrParameter.initialized) {
var error = "Variable with name \"" + variableName + "\" has not been initialized.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
Variable result = new Variable(ctx.IDENT().getText());
result.type = var.type;
var result = new Variable(ctx.IDENT().getText());
result.type = variableOrParameter.type;
result.line = line;
result.col = col;
return result;
@@ -839,23 +786,23 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
// Create a new set for the variables of the current function
// Create a new set for the variables and parameters of the current function
// this will be filled in the variable declaration visitor as well
vars = new HashMap<>();
params = new HashMap<>();
// Process the parameter list by visiting every parameter in it
var paramCount = ctx.params.parameter().size();
var params = new Parameter[paramCount];
var functionParameters = new Parameter[paramCount]; // the list of parameters that get passed to FunctionDefinition
for (int i = 0; i < paramCount; i++) {
// Add the parameter to the list of parameters
var param = (Parameter) visit(ctx.params.parameter(i));
params[i] = param;
functionParameters[i] = (Parameter) visit(ctx.params.parameter(i));
// add the param as a variable
var var = new VariableDeclaration(param.name);
var.initialized = true; // parameters can always be considered initialized
var.type = param.type;
vars.put(param.name, var);
// add the param as a variable to the global parameter map so that
// child nodes can access them.
var param = new VariableDeclaration(functionParameters[i].name);
param.initialized = true; // parameters can always be considered initialized
param.type = functionParameters[i].type;
params.put(param.name, param);
}
// Visit the block, make sure that a return value is guaranteed
@@ -867,7 +814,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var functionDef = new FunctionDefinition(name, params, (Block) block);
var functionDef = new FunctionDefinition(name, functionParameters, vars.values().toArray(new VariableDeclaration[0]), (Block) block);
functionDef.type = returnType;
functionDef.line = ctx.start.getLine();
functionDef.col = ctx.start.getCharPositionInLine();
@@ -988,19 +935,52 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
@Override
public Node visitDestroy_statement(KlangParser.Destroy_statementContext ctx) {
String name = ctx.IDENT().getText();
int line = ctx.start.getLine();
int col = ctx.start.getCharPositionInLine();
VariableDeclaration var = this.vars.get(name);
if (var == null) {
String error = "Variable with name \"" + name + "\" not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
var varName = ctx.IDENT().getText();
var line = ctx.start.getLine();
var col = ctx.start.getCharPositionInLine();
Node result = new DestructorCall(name);
var variableOrParameter = getVariableOrParameter(varName, line, col);
ensureReferencesStruct(variableOrParameter, line, col);
var result = new DestructorCall(varName);
result.line = line;
result.col = col;
return result;
}
private VariableDeclaration getVariableOrParameter(String name, int line, int col) {
var variable = vars.get(name);
var parameter = params.get(name);
if (variable == null && parameter == null) {
var error = "Variable or parameter with name \"" + name + "\" not defined.";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
return variable != null ? variable : parameter;
}
private void ensureReferencesStruct(VariableDeclaration variableOrParameter, int line, int col) {
if (variableOrParameter.type.isPrimitiveType() || enumDefs.containsKey(variableOrParameter.type.getName())) {
var error = "Variable or parameter must reference a struct but references " + variableOrParameter.type.getName() + ".";
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
}
}
private Type drillType(VariableDeclaration variableOrParameter, String[] path, int line, int col) {
try {
var structName = variableOrParameter.type.getName();
return Helper.drillType(structDefs, structName, path, 0);
} catch (Exception e) {
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
}
}
private String[] createStructPath(List<TerminalNode> identifiers) {
// Create a list of member names. This excludes the first entry as it is the base name.
var path = new String[identifiers.size() - 1];
for (int i = 1; i < identifiers.size(); i++) {
path[i - 1] = identifiers.get(i).getText();
}
return path;
}
}

View File

@@ -210,7 +210,7 @@ public class GetDefinitions extends KlangBaseVisitor<Node> {
parameters.put(paramName, parameter);
}
var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null);
var functionDef = new FunctionDefinition(funcName, parameters.values().toArray(new Parameter[0]), null, null);
functionDef.type = Type.getByName(ctx.returnType.type().getText());
functionDef.line = line;
functionDef.col = col;

View File

@@ -172,6 +172,10 @@ public class ASM {
mnemonics.add(new Cqto());
}
public void leave() {
mnemonics.add(new Leave());
}
public void ret() {
mnemonics.add(new Ret());
}

View File

@@ -0,0 +1,8 @@
package de.hsrm.compiler.Klang.asm.mnemonics;
public class Leave extends NoOperandMnemonic {
@Override
public String toAsm() {
return "leave";
}
}

View File

@@ -1,17 +1,20 @@
package de.hsrm.compiler.Klang.nodes;
import de.hsrm.compiler.Klang.nodes.statements.VariableDeclaration;
import de.hsrm.compiler.Klang.visitors.Visitor;
public class FunctionDefinition extends Node {
public String name;
public Parameter[] parameters;
public VariableDeclaration[] localVariables;
public Block block;
public FunctionDefinition(String name, Parameter[] parameters, Block block) {
public FunctionDefinition(String name, Parameter[] parameters, VariableDeclaration[] localVariables, Block block) {
this.name = name;
this.parameters = parameters;
this.block = block;
this.localVariables = localVariables;
}
@Override

View File

@@ -1,11 +1,6 @@
package de.hsrm.compiler.Klang.types;
import de.hsrm.compiler.Klang.Value;
import de.hsrm.compiler.Klang.nodes.EnumDefinition;
import de.hsrm.compiler.Klang.nodes.FunctionDefinition;
import de.hsrm.compiler.Klang.nodes.StructDefinition;
import java.util.Map;
public class NamedType extends Type {
public String name;
@@ -21,7 +16,7 @@ public class NamedType extends Type {
@Override
public Type combine(Type that) {
if(this.equals(that)) {
if(this.equals(that) || (that instanceof NullType)) {
return this;
}
@@ -49,8 +44,7 @@ public class NamedType extends Type {
return true;
}
if (that instanceof NamedType) {
var thatType = (NamedType) that;
if (that instanceof NamedType thatType) {
return getName().equals(thatType.getName());
}

View File

@@ -10,6 +10,8 @@ import de.hsrm.compiler.Klang.nodes.loops.DoWhileLoop;
import de.hsrm.compiler.Klang.nodes.loops.ForLoop;
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
import de.hsrm.compiler.Klang.nodes.statements.*;
import de.hsrm.compiler.Klang.types.NamedType;
import de.hsrm.compiler.Klang.types.NullType;
import de.hsrm.compiler.Klang.types.Type;
public class EvalVisitor implements Visitor<Value> {
@@ -400,7 +402,7 @@ public class EvalVisitor implements Visitor<Value> {
@Override
public Value visit(Block e) {
for (var stmt : e.statements) {
Value result = stmt.welcome(this);
var result = stmt.welcome(this);
if (result != null) {
return result;
}
@@ -428,7 +430,7 @@ public class EvalVisitor implements Visitor<Value> {
this.env = newEnv;
// Execute
Value result = func.block.welcome(this);
var result = func.block.welcome(this);
// Das alte env wiederherstellen
this.env = oldEnv;
@@ -502,12 +504,17 @@ public class EvalVisitor implements Visitor<Value> {
struct.put(structDef.fields[i].name, arg);
}
return new Value(struct);
var structValue = new Value(struct);
structValue.type = structDef.type;
return structValue;
}
@Override
public Value visit(NullExpression e) {
return null;
var nullValue = new Value(null);
nullValue.type = new NullType();
return nullValue;
}
@Override

View File

@@ -13,16 +13,16 @@ import de.hsrm.compiler.Klang.types.Type;
import java.util.*;
public class GenASM implements Visitor<Void> {
private class FloatWriter {
private StringBuilder sb = new StringBuilder();
private static class FloatWriter {
private final StringBuilder sb = new StringBuilder();
private int id = -1;
public String getFloat(double d) {
Long longBits = Double.doubleToRawLongBits(d);
String binary = Long.toBinaryString(longBits);
long longBits = Double.doubleToRawLongBits(d);
StringBuilder binary = new StringBuilder(Long.toBinaryString(longBits));
int padCount = 64 - binary.length();
while (padCount > 0) {
binary = "0" + binary;
binary.insert(0, "0");
padCount--;
}
String upper = binary.substring(0, 32);
@@ -59,49 +59,19 @@ public class GenASM implements Visitor<Void> {
}
}
private ASM asm;
private FloatWriter fw = new FloatWriter();
private String mainName;
private final ASM asm;
private final FloatWriter fw = new FloatWriter();
private final String mainName;
Map<String, Integer> env = new HashMap<>();
Map<String, StructDefinition> structs;
Set<String> vars;
String[] registers = { "%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9" };
String[] floatRegisters = { "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" };
private int lCount = 0; // Invariant: lCount is used
private int currentFunctionStartLabel = 0;
private Parameter[] currentFunctionParams;
private boolean prepareRegisters(Expression lhs, Expression rhs) {
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
if (lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm2", "%xmm0");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else if (lhsIsFloat && !rhsIsFloat) {
lhs.welcome(this);
rhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm1");
return true;
} else if (!lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm1");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else {
lhs.welcome(this);
asm.push("q", "%rax");
rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
return false;
}
}
private int lCount = 0;
private int currentFunctionStartLabel = 0;
private long bytesToClearFromTheStack = 0;
private Parameter[] currentFunctionParams;
public GenASM(String mainName, Map<String, StructDefinition> structs) {
this.mainName = mainName;
@@ -109,10 +79,6 @@ public class GenASM implements Visitor<Void> {
this.asm = new ASM();
}
public GenASM(Map<String, StructDefinition> structs) {
this("main", structs);
}
public String toAsm() {
return asm.toAsm();
}
@@ -326,10 +292,19 @@ public class GenASM implements Visitor<Void> {
@Override
public Void visit(ModuloExpression e) {
e.lhs.welcome(this);
// A single pushq brings the stack out of 16 byte alignment,
// so we need to increase the stack by another 8 bytes
asm.sub("q", "$8", "%rsp");
asm.push("q", "%rax");
e.rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
// Which we remove afterwards
asm.add("q", "$8", "%rsp");
asm.cqto();
asm.idiv("q", "%rbx");
asm.mov("q", "%rdx", "%rax");
@@ -357,7 +332,7 @@ public class GenASM implements Visitor<Void> {
// Werte LHS aus
// Wenn LHS != 0 bedeutet das true
// also können wir direkt sagen dass das Ergebnis true ist
// also können wir direkt sagen, dass das Ergebnis true ist
e.lhs.welcome(this);
asm.cmp("q", 0, "%rax");
asm.jne(lblTrue);
@@ -375,11 +350,11 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", 1, "%rax");
asm.jmp(lblEnd);
// Die Expressoin wertet zu false aus
// Die Expression wertet zu false aus
asm.label(lblFalse);
asm.mov("q", 0, "%rax");
// Das hier ist das ende
// Das hier ist das Ende
asm.label(lblEnd);
return null;
}
@@ -392,7 +367,7 @@ public class GenASM implements Visitor<Void> {
// Werte LHS aus
// Wenn LHS == 0, bedeutet das false
// also können wir direkt sagen dass das Ergebnis false ist
// also können wir direkt sagen, dass das Ergebnis false ist
e.lhs.welcome(this);
asm.cmp("q", 0, "%rax");
asm.je(lblFalse);
@@ -410,11 +385,11 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", 1, "%rax");
asm.jmp(lblEnd);
// Die Expressoin wertet zu false aus
// Die Expression wertet zu false aus
asm.label(lblFalse);
asm.mov("q", 0, "%rax");
// Das hier ist das ende
// Das hier ist das Ende
asm.label(lblEnd);
return null;
}
@@ -424,9 +399,9 @@ public class GenASM implements Visitor<Void> {
int lblFalse = ++lCount;
int lblEnd = ++lCount;
// Werte LHS aus
// Wenn LHS != 0 bedeutet das true, also jumpe zum false Teil
// Wenn nicht, falle durch zum true Teil
// Werte LHS aus.
// Wenn LHS != 0 bedeutet das true, also springe zum false Teil.
// Wenn nicht, falle durch zum true Teil.
e.lhs.welcome(this);
asm.cmp("q", 0, "%rax");
asm.jne(lblFalse);
@@ -551,8 +526,15 @@ public class GenASM implements Visitor<Void> {
@Override
public Void visit(ReturnStatement e) {
e.expression.welcome(this);
asm.mov("q", "%rbp", "%rsp");
asm.pop("q", "%rbp");
// The ReturnStatement visitor is kindly removing the
// stack space that was allocated by the FunctionDefinition
// before executing the function body.
if (bytesToClearFromTheStack > 0) {
asm.add("q", "$" + bytesToClearFromTheStack, "%rsp");
}
asm.leave();
asm.ret();
return null;
}
@@ -574,32 +556,27 @@ public class GenASM implements Visitor<Void> {
e.name = "main_by_user";
}
int lblStart = ++lCount;
this.currentFunctionStartLabel = lblStart;
this.currentFunctionParams = e.parameters;
var lblStart = ++lCount;
currentFunctionStartLabel = lblStart;
currentFunctionParams = e.parameters;
asm.functionHead(e.name);
asm.push("q", "%rbp");
asm.mov("q", "%rsp", "%rbp");
asm.label(lblStart);
// hole die anzahl der lokalen variablen
this.vars = new TreeSet<String>();
HashMap<String, Type> types = new HashMap<String, Type>();
GetVars getvars = new GetVars(vars, types);
getvars.visit(e);
// Create a new environment
env = new HashMap<>();
// Erzeuge ein environment
this.env = new HashMap<String, Integer>();
// Remember the offsets of the arguments that were placed on the stack.
var offset = 16; // Variables that were passed via the stack are located above our BSP
var ri = 0;
var fi = 0;
// Merke dir die offsets der parameter, die direkt auf den stack gelegt wurden
int offset = 16; // Per Stack übergebene Parameter liegen über unserem BSP
int ri = 0;
int fi = 0;
// Per stack übergebene variablen in env registrieren
// Add arguments that were passed via the stack to the environment
var registerParameters = new ArrayList<Parameter>();
for (int i = 0; i < e.parameters.length; i++) {
if (e.parameters[i].type.equals(Type.getFloatType())) {
if (fi >= this.floatRegisters.length) {
if (fi >= floatRegisters.length) {
// parameter is on stack already
env.put(e.parameters[i].name, offset);
offset += 8;
@@ -609,7 +586,7 @@ public class GenASM implements Visitor<Void> {
fi++;
}
} else {
if (ri >= this.registers.length) {
if (ri >= registers.length) {
// parameter is on stack already
env.put(e.parameters[i].name, offset);
offset += 8;
@@ -624,29 +601,58 @@ public class GenASM implements Visitor<Void> {
offset = 0;
ri = 0;
fi = 0;
// Push arguments that were passed via registers onto the stack
// and add them to the environment.
for (var param: registerParameters) {
if (param.type.equals(Type.getFloatType())) {
asm.mov("q", this.floatRegisters[fi], "%rax");
asm.mov("q", floatRegisters[fi], "%rax");
asm.push("q", "%rax");
offset -= 8;
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
env.put(param.name, offset); // negative, liegt unter aktuellem BP
fi++;
} else {
asm.push("q", this.registers[ri]);
asm.push("q", registers[ri]);
offset -= 8;
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
env.put(param.name, offset); // negative, liegt unter aktuellem BP
ri++;
}
}
// Reserviere Platz auf dem Stack für jede lokale variable
for (String lok_var : vars) {
offset -= 8;
asm.push("q", 0);
this.env.put(lok_var, offset);
// Reserve memory on the stack for the local variables.
if (e.localVariables.length > 0) {
// Each variable is at most 8 bytes in size.
asm.sub("q", "$" + (8 * e.localVariables.length), "%rsp");
// Save the offsets (they are relative to rbp)
for (var localVariable : e.localVariables) {
offset -= 8;
env.put(localVariable.name, offset);
}
}
// Check the stack alignment and correct if necessary
// The padding needs to happen after the register parameters
// and local variables were pushed to the stack because otherwise
// the relative offsets to rbp will be wrong.
if ((registerParameters.size() + e.localVariables.length) % 2 != 0) {
// Since each variable is 8 bytes and the stack is 16 byte aligned
// we need to add 8 bytes to the stack to make it aligned
// if there is an odd number of parameters and local variables.
asm.sub("q", "$8", "%rsp");
}
e.block.welcome(this);
// I need to clear the stack here so that the top most element is the old rbp that
// ret uses to return to the caller but code that gets generated here will never be
// reached since the block node is guaranteed to contain a return node that results
// in a ret command being executed before this code would be reached.
// As a workaround (and a dirty, dirty hack) I indicate to the return node visitor
// how many bytes need to be cleared from the stack.
var wasStackPadded = (registerParameters.size() + e.localVariables.length) % 2 != 0;
bytesToClearFromTheStack = 8L * (registerParameters.size() + e.localVariables.length + (wasStackPadded ? 1 : 0));
return null;
}
@@ -665,7 +671,7 @@ public class GenASM implements Visitor<Void> {
asm.push("q", "%rax");
}
// push args into local var locations, last arg is ontop of the stack
// push args into local var locations, last arg is on top of the stack
for (int i = e.arguments.length - 1; i >= 0; i--) {
asm.pop("q", this.env.get(this.currentFunctionParams[i].name) + "(%rbp)");
}
@@ -674,6 +680,9 @@ public class GenASM implements Visitor<Void> {
return null;
}
// This holds the indices into the arguments array for
// arguments that need to be passed to the callee via the stack
var stackIndices = new ArrayList<Integer>();
if (e.arguments.length > 0) {
// Mapping arguments index -> xmm registers index
@@ -684,9 +693,6 @@ public class GenASM implements Visitor<Void> {
int[] rIdxs = new int[this.registers.length];
int ri = -1;
// Mapping arguments index -> stack
ArrayList<Integer> stackIdxs = new ArrayList<Integer>();
// Go through arguments
// sort them into the memory regions they go when being passed to functions
for (int i = 0; i < e.arguments.length; i++) {
@@ -698,7 +704,7 @@ public class GenASM implements Visitor<Void> {
xmmIdxs[fi] = i;
} else {
// Float onto stack
stackIdxs.add(i);
stackIndices.add(i);
}
} else {
if (ri < this.registers.length - 1) {
@@ -707,14 +713,39 @@ public class GenASM implements Visitor<Void> {
rIdxs[ri] = i;
} else {
// bool/int onto stack
stackIdxs.add(i);
stackIndices.add(i);
}
}
}
// Welcome the arguments in order, push everything onto the stack
for (var arg : e.arguments) {
// Make sure that the stack is aligned after all arguments
// have been pushed onto the stack. Keep in mind that we
// may remove the top n elements from our stack so they form
// the base of the callee's stack.
if ((e.arguments.length - stackIndices.size()) % 2 != 0) {
asm.sub("q", "$8", "%rsp");
}
// Welcome the arguments in order, push everything onto the stack.
// We can assume that we are currently 16 byte aligned which holds
// true until after the result of the first argument is pushed onto
// the stack. After that we have to pad the stack for every argument
// to ensure that the stack is aligned before welcoming the argument.
// We get rid of the padding directly after welcome() to
// "tightly pack" the arguments on the stack. This way the usual
// 8 byte offsets are kept intact.
for (int i = 0; i < e.arguments.length; i++) {
if (i % 2 == 0) {
asm.sub("q", "$8", "%rsp");
}
var arg = e.arguments[i];
arg.welcome(this);
if (i % 2 == 0) {
asm.add("q", "$8", "%rsp");
}
if (arg.type.equals(Type.getFloatType())) {
asm.mov("q", "%xmm0", "%rax");
asm.push("q", "%rax");
@@ -737,22 +768,38 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", rspOffset, "%rsp", this.registers[i]);
}
// Move everything else from a higher stack position to our stack frame start
// Reorder the remaining n arguments that did not fit in a register,
// so that the remaining n arguments occupy the last n spots on our current stack
// This does not manipulate the stack in any way. It just reorders it.
int stackStartOffset = ((e.arguments.length) * 8);
for (int i = stackIdxs.size() - 1; i >= 0; i--) {
for (int i = stackIndices.size() - 1; i >= 0; i--) {
stackStartOffset -= 8;
int indexInArguments = stackIdxs.get(i);
int indexInArguments = stackIndices.get(i);
int rspOffset = (((e.arguments.length - indexInArguments) - 1) * 8);
asm.mov("q", rspOffset, "%rsp", "%rax");
asm.mov("q", "%rax", stackStartOffset, "%rsp");
}
// Rescue RSP
asm.add("q", stackStartOffset, "%rsp");
// The top n elements of the stack prepared in the step earlier will now become
// the base of the callee's stack by shrinking the stack by the size of the
// n arguments that did not fit in registers.
// This must only be done if there were any arguments that did not fit in the registers.
if (!stackIndices.isEmpty()) {
asm.add("q", stackStartOffset, "%rsp");
}
}
// We rename a function name if it is "main"
asm.call(e.name.equals("main") ? "main_by_user": e.name);
var mainName = e.name.equals("main") ? "main_by_user": e.name;
asm.call(mainName);
// Remove the arguments that remained on the stack and the stack padding if there was any
var wasStackPadded = (e.arguments.length - stackIndices.size()) % 2 != 0;
var bytesToRemove = 8 * (e.arguments.length - stackIndices.size() + (wasStackPadded ? 1 : 0));
if (bytesToRemove > 0) {
asm.add("q", "$" + bytesToRemove, "%rsp");
}
return null;
}
@@ -840,10 +887,29 @@ public class GenASM implements Visitor<Void> {
@Override
public Void visit(ConstructorCall e) {
// Make sure the stack is aligned before calling malloc
if (e.args.length % 2 != 0) {
// an odd number of arguments means we called pushq an odd number of times
// which results in an unaligned stack. Subtract 8 from the stack pointer to
// re-align the stack
asm.sub("q", "$8", "%rsp");
}
// push arguments onto the stack
for (var arg: e.args) {
for (int i = 0; i < e.args.length; i++) {
// if you want to know why this is done go and read visit(FunctionCall)
if (i % 2 != 0) {
asm.sub("q", "$8", "%rsp");
}
var arg = e.args[i];
arg.welcome(this);
if (i % 2 != 0) {
asm.add("q", "$8", "%rsp");
}
// move float values from xmm0 to rax first
if (arg.type.equals(Type.getFloatType())) {
asm.mov("q", "%xmm0", "%rax");
@@ -857,11 +923,16 @@ public class GenASM implements Visitor<Void> {
asm.mov("l", Helper.getFieldSizeBytes(structDef), "%edi");
asm.call("malloc@PLT");
// push args into struct memory, last arg is ontop of the stack
// push args into struct memory, last arg is on top of the stack
for (int i = e.args.length - 1; i >= 0; i--) {
asm.pop("q", Helper.getFieldOffset(structDef, i) + "(%rax)");
}
// Get rid of the stack alignment if there was any
if (e.args.length % 2 != 0) {
asm.add("q", "$8", "%rsp");
}
return null;
}
@@ -886,7 +957,7 @@ public class GenASM implements Visitor<Void> {
e.expression.welcome(this);
// Move it from xmm0 rax if its a flaot
// Move it from xmm0 rax if it's a float
if (e.expression.type.equals(Type.getFloatType())) {
asm.mov("q", "%xmm0", "%rax");
}
@@ -904,11 +975,49 @@ public class GenASM implements Visitor<Void> {
asm.mov("q", Helper.getFieldOffset(structDef, e.path[i]), "%rax", "%rax");
}
// pop the expression that is ontop of the stack into the field of the struct that has to be updated
// pop the expression that is on top of the stack into the field of the struct that has to be updated
asm.pop("q", Helper.getFieldOffset(structDef, fieldNameToUpdate) + "(%rax)");
asm.mov("q", 0 , "%rax"); // clear rax since an assignment has no result
return null;
}
private boolean prepareRegisters(Expression lhs, Expression rhs) {
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
if (lhsIsFloat && rhsIsFloat) {
lhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm2", "%xmm0");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else if (lhsIsFloat) {
lhs.welcome(this);
rhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm1");
return true;
} else if (rhsIsFloat) {
lhs.welcome(this);
asm.cvtsi2sd("%rax", "%xmm2");
rhs.welcome(this);
asm.mov("sd", "%xmm0", "%xmm1");
asm.mov("sd", "%xmm2", "%xmm0");
return true;
} else {
lhs.welcome(this);
// A single pushq brings the stack out of 16 byte alignment,
// so we need to increase the stack by another 8 bytes
asm.sub("q", "$8", "%rsp");
asm.push("q", "%rax");
rhs.welcome(this);
asm.mov("q", "%rax", "%rbx");
asm.pop("q", "%rax");
// Which we remove afterwards
asm.add("q", "$8", "%rsp");
return false;
}
}
}

View File

@@ -17,6 +17,6 @@ public class DestroyStatementTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:45 Variable with name \"x\" not defined.", e.getMessage());
assertEquals("Error in line 1:45 Variable or parameter with name \"x\" not defined.", e.getMessage());
}
}

View File

@@ -17,7 +17,7 @@ public class FieldAssignmentTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:46 Variable with name str not defined.", e.getMessage());
assertEquals("Error in line 1:46 Variable or parameter with name \"str\" not defined.", e.getMessage());
}
@Test
@@ -29,6 +29,6 @@ public class FieldAssignmentTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:62 Variable must reference a struct but references int.", e.getMessage());
assertEquals("Error in line 1:62 Variable or parameter must reference a struct but references int.", e.getMessage());
}
}

View File

@@ -92,4 +92,28 @@ public class FunctionDefinitionTest {
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:31 Parameter name Bar duplicates an enum of the same name.", e.getMessage());
}
@Test
void shouldResetVariablesAndParameterEnvironment() {
// given
var tree = Helper.prepareParser("""
function foo(x: int): int {
return 1;
}
function bar(): int {
let x: int = 0;
return x;
}
bar();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
// when / then
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
}

View File

@@ -0,0 +1,72 @@
import de.hsrm.compiler.Klang.ContextAnalysis;
import org.antlr.v4.runtime.tree.ParseTree;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class NaughtTest {
@Test
void shouldBeComparableToStruct() {
ParseTree tree = Helper.prepareParser("""
struct bar { a: int; }
function foo(): int {
let a: bar = create bar(1);
if (a == naught) {
return -1;
}
return 1;
}
foo();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldBeAssignableToStruct() {
ParseTree tree = Helper.prepareParser("""
struct bar { a: int; }
function foo(): int {
let a: bar = naught;
return 1;
}
foo();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
@Test
void shouldBeReturnable() {
ParseTree tree = Helper.prepareParser("""
struct bar { a: int; }
function foo(): bar {
return naught;
}
foo();
""");
var ctxAnal = new ContextAnalysis(
Helper.getFuncs(tree),
Helper.getStructs(tree),
Helper.getEnums(tree)
);
assertDoesNotThrow(() -> ctxAnal.visit(tree));
}
}

View File

@@ -16,7 +16,7 @@ public class StructFieldAccessTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:53 Variable with name str not defined.", e.getMessage());
assertEquals("Error in line 1:53 Variable or parameter with name \"str\" not defined.", e.getMessage());
}
@Test
@@ -28,6 +28,6 @@ public class StructFieldAccessTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:69 Variable must reference a struct but references int.", e.getMessage());
assertEquals("Error in line 1:69 Variable or parameter must reference a struct but references int.", e.getMessage());
}
}

View File

@@ -17,6 +17,6 @@ public class VariableAssignmentTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:22 Variable with name \"x\" not defined.", e.getMessage());
assertEquals("Error in line 1:22 Variable or parameter with name \"x\" not defined.", e.getMessage());
}
}

View File

@@ -89,7 +89,7 @@ public class VariableDeclarationTest {
// when / then
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:34 Redeclaration of variable with name \"x\".", e.getMessage());
assertEquals("Error in line 1:34 Redeclaration of variable or parameter with name \"x\".", e.getMessage());
}
@Test

View File

@@ -17,7 +17,7 @@ public class VariableTest {
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
assertEquals("Error in line 1:29 Variable with name \"x\" not defined.", e.getMessage());
assertEquals("Error in line 1:29 Variable or parameter with name \"x\" not defined.", e.getMessage());
}
@Test

View File

@@ -94,6 +94,11 @@ int testStructCreation() {
struct_testExpected_l("init inner field a", 20, resultRec->b->a);
struct_testExpected_s("init inner field b", NULL, resultRec->b->b);
// The result of this test is always 1 because if the tests fail
// a segmentation fault occurs.
long alignmentResult = isStackAlignedBeforeFunctionCall();
struct_testExpected_l("stack alignment before malloc", 1, alignmentResult);
free(resultRec);
free(innerStruct);
}

View File

@@ -28,4 +28,6 @@ long getStructFieldRecA(struct testStructRec *t);
struct testStructRec *getStructFieldRecB(struct testStructRec *t);
struct testStructRec *setStructFieldRecA(struct testStructRec *t, long a);
struct testStructRec *setStructFieldRecB(struct testStructRec *t, struct testStructRec *b);
struct testStructRec *setStructFieldRecB(struct testStructRec *t, struct testStructRec *b);
long isStackAlignedBeforeFunctionCall();

View File

@@ -555,6 +555,72 @@ function getTestStructRec(a: int, b: testStructRec): testStructRec {
return create testStructRec(a, b);
}
struct evenFieldsStruct {
a: int;
b: int;
}
struct oddFieldsStruct {
a: int;
b: int;
c: int;
}
function stackAlignmentTestInternal1(): oddFieldsStruct {
// an odd amount of constructor parameters could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
return create oddFieldsStruct(1, 1, 1);
}
function stackAlignmentTestInternal2(): evenFieldsStruct {
// an odd amount of local variables could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
let a: evenFieldsStruct = create evenFieldsStruct(1, 1);
return a;
}
function stackAlignmentTestInternal3(a: int): evenFieldsStruct {
// an odd amount of function parameters could lead to an unaligned stack
// which results in a segmentation fault when calling malloc
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal4(a: int): evenFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let b: int = 0;
let c: int = 0;
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal5(a: int, b: int): evenFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let c: int = 0;
return create evenFieldsStruct(1, 1);
}
function stackAlignmentTestInternal6(a: int, b: int): oddFieldsStruct {
// if (function parameters + local variables + constructor parameters) is odd
// then this could lead to an unaligned stack
// which results in a segmentation faul when calling malloc
let c: int = 0;
let d: int = 0;
return create oddFieldsStruct(1, 1, 1);
}
function isStackAlignedBeforeFunctionCall(): int {
let a: oddFieldsStruct = stackAlignmentTestInternal1();
let b: evenFieldsStruct = stackAlignmentTestInternal2();
let c: evenFieldsStruct = stackAlignmentTestInternal3(1);
let d: evenFieldsStruct = stackAlignmentTestInternal4(1);
let e: evenFieldsStruct = stackAlignmentTestInternal5(1, 1);
let f: oddFieldsStruct = stackAlignmentTestInternal6(1, 1);
return 1;
}
function getStructFieldA(t: testStruct): int {
return t.a;
}