Compare commits
12 Commits
441d0122f8
...
c124587983
| Author | SHA1 | Date | |
|---|---|---|---|
| c124587983 | |||
| 07e5a338a4 | |||
| 76419d86bb | |||
| f55f2661de | |||
| 06609ae899 | |||
| c5c01041e4 | |||
| 9751a1da2f | |||
| 534b507f7a | |||
| cce58b6e38 | |||
| 8b17ced533 | |||
| bacc40d844 | |||
| f38bd3d69e |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -16,4 +16,5 @@ src/main/antlr4/de/hsrm/compiler/Klang/.antlr
|
|||||||
|
|
||||||
# build output
|
# build output
|
||||||
out
|
out
|
||||||
src/test/test
|
src/test/test
|
||||||
|
/.idea/uiDesigner.xml
|
||||||
|
|||||||
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -10,7 +10,7 @@
|
|||||||
</profile>
|
</profile>
|
||||||
</annotationProcessing>
|
</annotationProcessing>
|
||||||
<bytecodeTargetLevel>
|
<bytecodeTargetLevel>
|
||||||
<module name="klang" target="11" />
|
<module name="klang" target="17" />
|
||||||
</bytecodeTargetLevel>
|
</bytecodeTargetLevel>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
|
<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 url="file://$MODULE_DIR$/target/classes" />
|
||||||
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
<output-test url="file://$MODULE_DIR$/target/test-classes" />
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -40,7 +40,7 @@
|
|||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.8.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>11</release>
|
<release>17</release>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<!-- Plugin to compile the g4 files ahead of the java files
|
<!-- Plugin to compile the g4 files ahead of the java files
|
||||||
|
|||||||
@@ -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.loops.WhileLoop;
|
||||||
import de.hsrm.compiler.Klang.nodes.statements.*;
|
import de.hsrm.compiler.Klang.nodes.statements.*;
|
||||||
import de.hsrm.compiler.Klang.types.Type;
|
import de.hsrm.compiler.Klang.types.Type;
|
||||||
|
import org.antlr.v4.runtime.tree.TerminalNode;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
||||||
|
Map<String, VariableDeclaration> params = new HashMap<>();
|
||||||
Map<String, VariableDeclaration> vars = new HashMap<>();
|
Map<String, VariableDeclaration> vars = new HashMap<>();
|
||||||
Map<String, FunctionDefinition> functionDefs;
|
Map<String, FunctionDefinition> functionDefs;
|
||||||
Map<String, StructDefinition> structDefs;
|
Map<String, StructDefinition> structDefs;
|
||||||
@@ -199,8 +201,8 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vars.get(variableName) != null) {
|
if (vars.get(variableName) != null || params.get(variableName) != null) {
|
||||||
var error = "Redeclaration of variable with name \"" + variableName + "\".";
|
var error = "Redeclaration of variable or parameter with name \"" + variableName + "\".";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,31 +238,27 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitVariable_assignment(KlangParser.Variable_assignmentContext ctx) {
|
public Node visitVariable_assignment(KlangParser.Variable_assignmentContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var name = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
VariableDeclaration var = this.vars.get(name);
|
var variableOrParameter = getVariableOrParameter(name, line, col);
|
||||||
if (var == null) {
|
|
||||||
String error = "Variable with name \"" + name + "\" not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the expression
|
// 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
|
// Make sure expression can be assigned to the variable
|
||||||
try {
|
try {
|
||||||
expression.type.combine(var.type);
|
expression.type.combine(variableOrParameter.type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we assigned a value to this variable, we can consider it initialized
|
// 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
|
// 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.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
return result;
|
return result;
|
||||||
@@ -272,8 +270,7 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
ReturnStatement result = new ReturnStatement(expression);
|
ReturnStatement result = new ReturnStatement(expression);
|
||||||
|
|
||||||
// Check if this expression is a tail recursion
|
// Check if this expression is a tail recursion
|
||||||
if (expression instanceof FunctionCall) {
|
if (expression instanceof FunctionCall funCall) {
|
||||||
var funCall = (FunctionCall) expression;
|
|
||||||
if (funCall.name.equals(this.currentFunctionDefinitionName)) {
|
if (funCall.name.equals(this.currentFunctionDefinitionName)) {
|
||||||
// Flag this function call
|
// Flag this function call
|
||||||
funCall.isTailRecursive = true;
|
funCall.isTailRecursive = true;
|
||||||
@@ -288,46 +285,24 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitField_assignment(KlangParser.Field_assignmentContext ctx) {
|
public Node visitField_assignment(KlangParser.Field_assignmentContext ctx) {
|
||||||
String varName = ctx.IDENT(0).getText();
|
var varName = ctx.IDENT(0).getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
String[] path = new String[ctx.IDENT().size() - 1];
|
var path = createStructPath(ctx.IDENT());
|
||||||
|
|
||||||
for (int i = 1; i < ctx.IDENT().size(); i++) {
|
var variableOrParameter = getVariableOrParameter(varName, line, col);
|
||||||
path[i - 1] = ctx.IDENT(i).getText();
|
ensureReferencesStruct(variableOrParameter, line, col);
|
||||||
}
|
var fieldType = drillType(variableOrParameter, path, line, col);
|
||||||
|
|
||||||
// Get the referenced variable, make sure it is defined
|
// Visit the expression and make sure the type combines properly
|
||||||
var variableDef = this.vars.get(varName);
|
var expression = (Expression) visit(ctx.expression());
|
||||||
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());
|
|
||||||
try {
|
try {
|
||||||
fieldType.combine(expression.type);
|
fieldType.combine(expression.type);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + e.getMessage());
|
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.col = col;
|
||||||
result.line = line;
|
result.line = line;
|
||||||
return result;
|
return result;
|
||||||
@@ -338,23 +313,17 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
var baseName = ctx.IDENT(0).getText();
|
var baseName = ctx.IDENT(0).getText();
|
||||||
var line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
var col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
var path = createStructPath(ctx.IDENT());
|
||||||
// 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine if the base name points to an enum or a variable
|
// Determine if the base name points to an enum or a variable
|
||||||
var enumDef = enumDefs.get(baseName);
|
var enumDef = enumDefs.get(baseName);
|
||||||
if (enumDef != null) {
|
if (enumDef != null) {
|
||||||
if (path.size() != 1) {
|
if (path.length != 1) {
|
||||||
var error = "Illegal access to enum " + enumDef.name + ".";
|
var error = "Illegal access to enum " + enumDef.name + ".";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
var enumValueName = path.get(0);
|
var enumValueName = path[0];
|
||||||
var enumValue = Arrays.stream(enumDef.enums)
|
var enumValue = Arrays.stream(enumDef.enums)
|
||||||
.filter(e -> e.value.equals(enumValueName))
|
.filter(e -> e.value.equals(enumValueName))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
@@ -371,29 +340,11 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
return enumAccessExpression;
|
return enumAccessExpression;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the referenced variable, make sure it is defined
|
var variableOrParameter = getVariableOrParameter(baseName, line, col);
|
||||||
var variableDef = vars.get(baseName);
|
ensureReferencesStruct(variableOrParameter, line, col);
|
||||||
if (variableDef == null) {
|
var resultType = drillType(variableOrParameter, path, line, col);
|
||||||
var error = "Variable with name " + baseName + " not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure it references a struct
|
var memberAccessExpression = new MemberAccessExpression(baseName, variableOrParameter.type.getName(), path);
|
||||||
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]));
|
|
||||||
memberAccessExpression.type = resultType;
|
memberAccessExpression.type = resultType;
|
||||||
memberAccessExpression.line = line;
|
memberAccessExpression.line = line;
|
||||||
memberAccessExpression.col = col;
|
memberAccessExpression.col = col;
|
||||||
@@ -711,24 +662,20 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitVariable(KlangParser.VariableContext ctx) {
|
public Node visitVariable(KlangParser.VariableContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var variableName = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
var col = ctx.start.getCharPositionInLine();
|
||||||
|
|
||||||
VariableDeclaration var = this.vars.get(name);
|
var variableOrParameter = getVariableOrParameter(variableName, line, col);
|
||||||
if (var == null) {
|
|
||||||
String error = "Variable with name \"" + name + "\" not defined.";
|
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the variable has been initialized before it can be used
|
// Make sure the variable has been initialized before it can be used
|
||||||
if (!var.initialized) {
|
if (!variableOrParameter.initialized) {
|
||||||
String error = "Variable with name \"" + name + "\" has not been initialized.";
|
var error = "Variable with name \"" + variableName + "\" has not been initialized.";
|
||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
Variable result = new Variable(ctx.IDENT().getText());
|
var result = new Variable(ctx.IDENT().getText());
|
||||||
result.type = var.type;
|
result.type = variableOrParameter.type;
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
return result;
|
return result;
|
||||||
@@ -839,23 +786,23 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
throw new RuntimeException(Helper.getErrorPrefix(line, col) + error);
|
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
|
// this will be filled in the variable declaration visitor as well
|
||||||
vars = new HashMap<>();
|
vars = new HashMap<>();
|
||||||
|
params = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
// Process the parameter list by visiting every parameter in it
|
|
||||||
var paramCount = ctx.params.parameter().size();
|
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++) {
|
for (int i = 0; i < paramCount; i++) {
|
||||||
// Add the parameter to the list of parameters
|
functionParameters[i] = (Parameter) visit(ctx.params.parameter(i));
|
||||||
var param = (Parameter) visit(ctx.params.parameter(i));
|
|
||||||
params[i] = param;
|
|
||||||
|
|
||||||
// add the param as a variable
|
// add the param as a variable to the global parameter map so that
|
||||||
var var = new VariableDeclaration(param.name);
|
// child nodes can access them.
|
||||||
var.initialized = true; // parameters can always be considered initialized
|
var param = new VariableDeclaration(functionParameters[i].name);
|
||||||
var.type = param.type;
|
param.initialized = true; // parameters can always be considered initialized
|
||||||
vars.put(param.name, var);
|
param.type = functionParameters[i].type;
|
||||||
|
params.put(param.name, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Visit the block, make sure that a return value is guaranteed
|
// 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);
|
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.type = returnType;
|
||||||
functionDef.line = ctx.start.getLine();
|
functionDef.line = ctx.start.getLine();
|
||||||
functionDef.col = ctx.start.getCharPositionInLine();
|
functionDef.col = ctx.start.getCharPositionInLine();
|
||||||
@@ -988,19 +935,52 @@ public class ContextAnalysis extends KlangBaseVisitor<Node> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node visitDestroy_statement(KlangParser.Destroy_statementContext ctx) {
|
public Node visitDestroy_statement(KlangParser.Destroy_statementContext ctx) {
|
||||||
String name = ctx.IDENT().getText();
|
var varName = ctx.IDENT().getText();
|
||||||
int line = ctx.start.getLine();
|
var line = ctx.start.getLine();
|
||||||
int col = ctx.start.getCharPositionInLine();
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
Node result = new DestructorCall(name);
|
var variableOrParameter = getVariableOrParameter(varName, line, col);
|
||||||
|
ensureReferencesStruct(variableOrParameter, line, col);
|
||||||
|
|
||||||
|
var result = new DestructorCall(varName);
|
||||||
result.line = line;
|
result.line = line;
|
||||||
result.col = col;
|
result.col = col;
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +210,7 @@ public class GetDefinitions extends KlangBaseVisitor<Node> {
|
|||||||
parameters.put(paramName, parameter);
|
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.type = Type.getByName(ctx.returnType.type().getText());
|
||||||
functionDef.line = line;
|
functionDef.line = line;
|
||||||
functionDef.col = col;
|
functionDef.col = col;
|
||||||
|
|||||||
@@ -172,6 +172,10 @@ public class ASM {
|
|||||||
mnemonics.add(new Cqto());
|
mnemonics.add(new Cqto());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void leave() {
|
||||||
|
mnemonics.add(new Leave());
|
||||||
|
}
|
||||||
|
|
||||||
public void ret() {
|
public void ret() {
|
||||||
mnemonics.add(new Ret());
|
mnemonics.add(new Ret());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package de.hsrm.compiler.Klang.asm.mnemonics;
|
||||||
|
|
||||||
|
public class Leave extends NoOperandMnemonic {
|
||||||
|
@Override
|
||||||
|
public String toAsm() {
|
||||||
|
return "leave";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,20 @@
|
|||||||
package de.hsrm.compiler.Klang.nodes;
|
package de.hsrm.compiler.Klang.nodes;
|
||||||
|
|
||||||
|
import de.hsrm.compiler.Klang.nodes.statements.VariableDeclaration;
|
||||||
import de.hsrm.compiler.Klang.visitors.Visitor;
|
import de.hsrm.compiler.Klang.visitors.Visitor;
|
||||||
|
|
||||||
public class FunctionDefinition extends Node {
|
public class FunctionDefinition extends Node {
|
||||||
|
|
||||||
public String name;
|
public String name;
|
||||||
public Parameter[] parameters;
|
public Parameter[] parameters;
|
||||||
|
public VariableDeclaration[] localVariables;
|
||||||
public Block block;
|
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.name = name;
|
||||||
this.parameters = parameters;
|
this.parameters = parameters;
|
||||||
this.block = block;
|
this.block = block;
|
||||||
|
this.localVariables = localVariables;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
package de.hsrm.compiler.Klang.types;
|
package de.hsrm.compiler.Klang.types;
|
||||||
|
|
||||||
import de.hsrm.compiler.Klang.Value;
|
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 class NamedType extends Type {
|
||||||
public String name;
|
public String name;
|
||||||
@@ -21,7 +16,7 @@ public class NamedType extends Type {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type combine(Type that) {
|
public Type combine(Type that) {
|
||||||
if(this.equals(that)) {
|
if(this.equals(that) || (that instanceof NullType)) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,8 +44,7 @@ public class NamedType extends Type {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (that instanceof NamedType) {
|
if (that instanceof NamedType thatType) {
|
||||||
var thatType = (NamedType) that;
|
|
||||||
return getName().equals(thatType.getName());
|
return getName().equals(thatType.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.ForLoop;
|
||||||
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
|
import de.hsrm.compiler.Klang.nodes.loops.WhileLoop;
|
||||||
import de.hsrm.compiler.Klang.nodes.statements.*;
|
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;
|
import de.hsrm.compiler.Klang.types.Type;
|
||||||
|
|
||||||
public class EvalVisitor implements Visitor<Value> {
|
public class EvalVisitor implements Visitor<Value> {
|
||||||
@@ -400,7 +402,7 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
@Override
|
@Override
|
||||||
public Value visit(Block e) {
|
public Value visit(Block e) {
|
||||||
for (var stmt : e.statements) {
|
for (var stmt : e.statements) {
|
||||||
Value result = stmt.welcome(this);
|
var result = stmt.welcome(this);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -428,7 +430,7 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
this.env = newEnv;
|
this.env = newEnv;
|
||||||
|
|
||||||
// Execute
|
// Execute
|
||||||
Value result = func.block.welcome(this);
|
var result = func.block.welcome(this);
|
||||||
|
|
||||||
// Das alte env wiederherstellen
|
// Das alte env wiederherstellen
|
||||||
this.env = oldEnv;
|
this.env = oldEnv;
|
||||||
@@ -502,12 +504,17 @@ public class EvalVisitor implements Visitor<Value> {
|
|||||||
struct.put(structDef.fields[i].name, arg);
|
struct.put(structDef.fields[i].name, arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Value(struct);
|
var structValue = new Value(struct);
|
||||||
|
structValue.type = structDef.type;
|
||||||
|
|
||||||
|
return structValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Value visit(NullExpression e) {
|
public Value visit(NullExpression e) {
|
||||||
return null;
|
var nullValue = new Value(null);
|
||||||
|
nullValue.type = new NullType();
|
||||||
|
return nullValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -13,16 +13,16 @@ import de.hsrm.compiler.Klang.types.Type;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
public class GenASM implements Visitor<Void> {
|
public class GenASM implements Visitor<Void> {
|
||||||
private class FloatWriter {
|
private static class FloatWriter {
|
||||||
private StringBuilder sb = new StringBuilder();
|
private final StringBuilder sb = new StringBuilder();
|
||||||
private int id = -1;
|
private int id = -1;
|
||||||
|
|
||||||
public String getFloat(double d) {
|
public String getFloat(double d) {
|
||||||
Long longBits = Double.doubleToRawLongBits(d);
|
long longBits = Double.doubleToRawLongBits(d);
|
||||||
String binary = Long.toBinaryString(longBits);
|
StringBuilder binary = new StringBuilder(Long.toBinaryString(longBits));
|
||||||
int padCount = 64 - binary.length();
|
int padCount = 64 - binary.length();
|
||||||
while (padCount > 0) {
|
while (padCount > 0) {
|
||||||
binary = "0" + binary;
|
binary.insert(0, "0");
|
||||||
padCount--;
|
padCount--;
|
||||||
}
|
}
|
||||||
String upper = binary.substring(0, 32);
|
String upper = binary.substring(0, 32);
|
||||||
@@ -59,49 +59,19 @@ public class GenASM implements Visitor<Void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ASM asm;
|
private final ASM asm;
|
||||||
private FloatWriter fw = new FloatWriter();
|
private final FloatWriter fw = new FloatWriter();
|
||||||
private String mainName;
|
private final String mainName;
|
||||||
|
|
||||||
Map<String, Integer> env = new HashMap<>();
|
Map<String, Integer> env = new HashMap<>();
|
||||||
Map<String, StructDefinition> structs;
|
Map<String, StructDefinition> structs;
|
||||||
Set<String> vars;
|
|
||||||
String[] registers = { "%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9" };
|
String[] registers = { "%rdi", "%rsi", "%rdx", "%rcx", "%r8", "%r9" };
|
||||||
String[] floatRegisters = { "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7" };
|
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) {
|
private int lCount = 0;
|
||||||
boolean lhsIsFloat = lhs.type.equals(Type.getFloatType());
|
private int currentFunctionStartLabel = 0;
|
||||||
boolean rhsIsFloat = rhs.type.equals(Type.getFloatType());
|
private long bytesToClearFromTheStack = 0;
|
||||||
if (lhsIsFloat && rhsIsFloat) {
|
private Parameter[] currentFunctionParams;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public GenASM(String mainName, Map<String, StructDefinition> structs) {
|
public GenASM(String mainName, Map<String, StructDefinition> structs) {
|
||||||
this.mainName = mainName;
|
this.mainName = mainName;
|
||||||
@@ -109,10 +79,6 @@ public class GenASM implements Visitor<Void> {
|
|||||||
this.asm = new ASM();
|
this.asm = new ASM();
|
||||||
}
|
}
|
||||||
|
|
||||||
public GenASM(Map<String, StructDefinition> structs) {
|
|
||||||
this("main", structs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toAsm() {
|
public String toAsm() {
|
||||||
return asm.toAsm();
|
return asm.toAsm();
|
||||||
}
|
}
|
||||||
@@ -326,10 +292,19 @@ public class GenASM implements Visitor<Void> {
|
|||||||
@Override
|
@Override
|
||||||
public Void visit(ModuloExpression e) {
|
public Void visit(ModuloExpression e) {
|
||||||
e.lhs.welcome(this);
|
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");
|
asm.push("q", "%rax");
|
||||||
e.rhs.welcome(this);
|
e.rhs.welcome(this);
|
||||||
asm.mov("q", "%rax", "%rbx");
|
asm.mov("q", "%rax", "%rbx");
|
||||||
asm.pop("q", "%rax");
|
asm.pop("q", "%rax");
|
||||||
|
|
||||||
|
// Which we remove afterwards
|
||||||
|
asm.add("q", "$8", "%rsp");
|
||||||
|
|
||||||
asm.cqto();
|
asm.cqto();
|
||||||
asm.idiv("q", "%rbx");
|
asm.idiv("q", "%rbx");
|
||||||
asm.mov("q", "%rdx", "%rax");
|
asm.mov("q", "%rdx", "%rax");
|
||||||
@@ -357,7 +332,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
// Werte LHS aus
|
// Werte LHS aus
|
||||||
// Wenn LHS != 0 bedeutet das true
|
// 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);
|
e.lhs.welcome(this);
|
||||||
asm.cmp("q", 0, "%rax");
|
asm.cmp("q", 0, "%rax");
|
||||||
asm.jne(lblTrue);
|
asm.jne(lblTrue);
|
||||||
@@ -375,11 +350,11 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.mov("q", 1, "%rax");
|
asm.mov("q", 1, "%rax");
|
||||||
asm.jmp(lblEnd);
|
asm.jmp(lblEnd);
|
||||||
|
|
||||||
// Die Expressoin wertet zu false aus
|
// Die Expression wertet zu false aus
|
||||||
asm.label(lblFalse);
|
asm.label(lblFalse);
|
||||||
asm.mov("q", 0, "%rax");
|
asm.mov("q", 0, "%rax");
|
||||||
|
|
||||||
// Das hier ist das ende
|
// Das hier ist das Ende
|
||||||
asm.label(lblEnd);
|
asm.label(lblEnd);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -392,7 +367,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
// Werte LHS aus
|
// Werte LHS aus
|
||||||
// Wenn LHS == 0, bedeutet das false
|
// 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);
|
e.lhs.welcome(this);
|
||||||
asm.cmp("q", 0, "%rax");
|
asm.cmp("q", 0, "%rax");
|
||||||
asm.je(lblFalse);
|
asm.je(lblFalse);
|
||||||
@@ -410,11 +385,11 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.mov("q", 1, "%rax");
|
asm.mov("q", 1, "%rax");
|
||||||
asm.jmp(lblEnd);
|
asm.jmp(lblEnd);
|
||||||
|
|
||||||
// Die Expressoin wertet zu false aus
|
// Die Expression wertet zu false aus
|
||||||
asm.label(lblFalse);
|
asm.label(lblFalse);
|
||||||
asm.mov("q", 0, "%rax");
|
asm.mov("q", 0, "%rax");
|
||||||
|
|
||||||
// Das hier ist das ende
|
// Das hier ist das Ende
|
||||||
asm.label(lblEnd);
|
asm.label(lblEnd);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -424,9 +399,9 @@ public class GenASM implements Visitor<Void> {
|
|||||||
int lblFalse = ++lCount;
|
int lblFalse = ++lCount;
|
||||||
int lblEnd = ++lCount;
|
int lblEnd = ++lCount;
|
||||||
|
|
||||||
// Werte LHS aus
|
// Werte LHS aus.
|
||||||
// Wenn LHS != 0 bedeutet das true, also jumpe zum false Teil
|
// Wenn LHS != 0 bedeutet das true, also springe zum false Teil.
|
||||||
// Wenn nicht, falle durch zum true Teil
|
// Wenn nicht, falle durch zum true Teil.
|
||||||
e.lhs.welcome(this);
|
e.lhs.welcome(this);
|
||||||
asm.cmp("q", 0, "%rax");
|
asm.cmp("q", 0, "%rax");
|
||||||
asm.jne(lblFalse);
|
asm.jne(lblFalse);
|
||||||
@@ -551,8 +526,15 @@ public class GenASM implements Visitor<Void> {
|
|||||||
@Override
|
@Override
|
||||||
public Void visit(ReturnStatement e) {
|
public Void visit(ReturnStatement e) {
|
||||||
e.expression.welcome(this);
|
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();
|
asm.ret();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -574,32 +556,27 @@ public class GenASM implements Visitor<Void> {
|
|||||||
e.name = "main_by_user";
|
e.name = "main_by_user";
|
||||||
}
|
}
|
||||||
|
|
||||||
int lblStart = ++lCount;
|
var lblStart = ++lCount;
|
||||||
this.currentFunctionStartLabel = lblStart;
|
currentFunctionStartLabel = lblStart;
|
||||||
this.currentFunctionParams = e.parameters;
|
currentFunctionParams = e.parameters;
|
||||||
asm.functionHead(e.name);
|
asm.functionHead(e.name);
|
||||||
asm.push("q", "%rbp");
|
asm.push("q", "%rbp");
|
||||||
asm.mov("q", "%rsp", "%rbp");
|
asm.mov("q", "%rsp", "%rbp");
|
||||||
asm.label(lblStart);
|
asm.label(lblStart);
|
||||||
|
|
||||||
// hole die anzahl der lokalen variablen
|
// Create a new environment
|
||||||
this.vars = new TreeSet<String>();
|
env = new HashMap<>();
|
||||||
HashMap<String, Type> types = new HashMap<String, Type>();
|
|
||||||
GetVars getvars = new GetVars(vars, types);
|
|
||||||
getvars.visit(e);
|
|
||||||
|
|
||||||
// Erzeuge ein environment
|
// Remember the offsets of the arguments that were placed on the stack.
|
||||||
this.env = new HashMap<String, Integer>();
|
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
|
// Add arguments that were passed via the stack to the environment
|
||||||
int offset = 16; // Per Stack übergebene Parameter liegen über unserem BSP
|
|
||||||
int ri = 0;
|
|
||||||
int fi = 0;
|
|
||||||
// Per stack übergebene variablen in env registrieren
|
|
||||||
var registerParameters = new ArrayList<Parameter>();
|
var registerParameters = new ArrayList<Parameter>();
|
||||||
for (int i = 0; i < e.parameters.length; i++) {
|
for (int i = 0; i < e.parameters.length; i++) {
|
||||||
if (e.parameters[i].type.equals(Type.getFloatType())) {
|
if (e.parameters[i].type.equals(Type.getFloatType())) {
|
||||||
if (fi >= this.floatRegisters.length) {
|
if (fi >= floatRegisters.length) {
|
||||||
// parameter is on stack already
|
// parameter is on stack already
|
||||||
env.put(e.parameters[i].name, offset);
|
env.put(e.parameters[i].name, offset);
|
||||||
offset += 8;
|
offset += 8;
|
||||||
@@ -609,7 +586,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
fi++;
|
fi++;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ri >= this.registers.length) {
|
if (ri >= registers.length) {
|
||||||
// parameter is on stack already
|
// parameter is on stack already
|
||||||
env.put(e.parameters[i].name, offset);
|
env.put(e.parameters[i].name, offset);
|
||||||
offset += 8;
|
offset += 8;
|
||||||
@@ -624,29 +601,58 @@ public class GenASM implements Visitor<Void> {
|
|||||||
offset = 0;
|
offset = 0;
|
||||||
ri = 0;
|
ri = 0;
|
||||||
fi = 0;
|
fi = 0;
|
||||||
|
|
||||||
|
// Push arguments that were passed via registers onto the stack
|
||||||
|
// and add them to the environment.
|
||||||
for (var param: registerParameters) {
|
for (var param: registerParameters) {
|
||||||
if (param.type.equals(Type.getFloatType())) {
|
if (param.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", this.floatRegisters[fi], "%rax");
|
asm.mov("q", floatRegisters[fi], "%rax");
|
||||||
asm.push("q", "%rax");
|
asm.push("q", "%rax");
|
||||||
offset -= 8;
|
offset -= 8;
|
||||||
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
||||||
fi++;
|
fi++;
|
||||||
} else {
|
} else {
|
||||||
asm.push("q", this.registers[ri]);
|
asm.push("q", registers[ri]);
|
||||||
offset -= 8;
|
offset -= 8;
|
||||||
this.env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
env.put(param.name, offset); // negative, liegt unter aktuellem BP
|
||||||
ri++;
|
ri++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserviere Platz auf dem Stack für jede lokale variable
|
// Reserve memory on the stack for the local variables.
|
||||||
for (String lok_var : vars) {
|
if (e.localVariables.length > 0) {
|
||||||
offset -= 8;
|
// Each variable is at most 8 bytes in size.
|
||||||
asm.push("q", 0);
|
asm.sub("q", "$" + (8 * e.localVariables.length), "%rsp");
|
||||||
this.env.put(lok_var, offset);
|
|
||||||
|
// 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);
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -665,7 +671,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.push("q", "%rax");
|
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--) {
|
for (int i = e.arguments.length - 1; i >= 0; i--) {
|
||||||
asm.pop("q", this.env.get(this.currentFunctionParams[i].name) + "(%rbp)");
|
asm.pop("q", this.env.get(this.currentFunctionParams[i].name) + "(%rbp)");
|
||||||
}
|
}
|
||||||
@@ -674,6 +680,9 @@ public class GenASM implements Visitor<Void> {
|
|||||||
return null;
|
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) {
|
if (e.arguments.length > 0) {
|
||||||
// Mapping arguments index -> xmm registers index
|
// Mapping arguments index -> xmm registers index
|
||||||
@@ -684,9 +693,6 @@ public class GenASM implements Visitor<Void> {
|
|||||||
int[] rIdxs = new int[this.registers.length];
|
int[] rIdxs = new int[this.registers.length];
|
||||||
int ri = -1;
|
int ri = -1;
|
||||||
|
|
||||||
// Mapping arguments index -> stack
|
|
||||||
ArrayList<Integer> stackIdxs = new ArrayList<Integer>();
|
|
||||||
|
|
||||||
// Go through arguments
|
// Go through arguments
|
||||||
// sort them into the memory regions they go when being passed to functions
|
// sort them into the memory regions they go when being passed to functions
|
||||||
for (int i = 0; i < e.arguments.length; i++) {
|
for (int i = 0; i < e.arguments.length; i++) {
|
||||||
@@ -698,7 +704,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
xmmIdxs[fi] = i;
|
xmmIdxs[fi] = i;
|
||||||
} else {
|
} else {
|
||||||
// Float onto stack
|
// Float onto stack
|
||||||
stackIdxs.add(i);
|
stackIndices.add(i);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ri < this.registers.length - 1) {
|
if (ri < this.registers.length - 1) {
|
||||||
@@ -707,14 +713,39 @@ public class GenASM implements Visitor<Void> {
|
|||||||
rIdxs[ri] = i;
|
rIdxs[ri] = i;
|
||||||
} else {
|
} else {
|
||||||
// bool/int onto stack
|
// bool/int onto stack
|
||||||
stackIdxs.add(i);
|
stackIndices.add(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Welcome the arguments in order, push everything onto the stack
|
// Make sure that the stack is aligned after all arguments
|
||||||
for (var arg : e.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);
|
arg.welcome(this);
|
||||||
|
|
||||||
|
if (i % 2 == 0) {
|
||||||
|
asm.add("q", "$8", "%rsp");
|
||||||
|
}
|
||||||
|
|
||||||
if (arg.type.equals(Type.getFloatType())) {
|
if (arg.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", "%xmm0", "%rax");
|
asm.mov("q", "%xmm0", "%rax");
|
||||||
asm.push("q", "%rax");
|
asm.push("q", "%rax");
|
||||||
@@ -737,22 +768,38 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.mov("q", rspOffset, "%rsp", this.registers[i]);
|
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);
|
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;
|
stackStartOffset -= 8;
|
||||||
int indexInArguments = stackIdxs.get(i);
|
int indexInArguments = stackIndices.get(i);
|
||||||
int rspOffset = (((e.arguments.length - indexInArguments) - 1) * 8);
|
int rspOffset = (((e.arguments.length - indexInArguments) - 1) * 8);
|
||||||
asm.mov("q", rspOffset, "%rsp", "%rax");
|
asm.mov("q", rspOffset, "%rsp", "%rax");
|
||||||
asm.mov("q", "%rax", stackStartOffset, "%rsp");
|
asm.mov("q", "%rax", stackStartOffset, "%rsp");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rescue RSP
|
// The top n elements of the stack prepared in the step earlier will now become
|
||||||
asm.add("q", stackStartOffset, "%rsp");
|
// 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"
|
// 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,10 +887,29 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void visit(ConstructorCall e) {
|
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
|
// 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);
|
arg.welcome(this);
|
||||||
|
|
||||||
|
if (i % 2 != 0) {
|
||||||
|
asm.add("q", "$8", "%rsp");
|
||||||
|
}
|
||||||
|
|
||||||
// move float values from xmm0 to rax first
|
// move float values from xmm0 to rax first
|
||||||
if (arg.type.equals(Type.getFloatType())) {
|
if (arg.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", "%xmm0", "%rax");
|
asm.mov("q", "%xmm0", "%rax");
|
||||||
@@ -857,11 +923,16 @@ public class GenASM implements Visitor<Void> {
|
|||||||
asm.mov("l", Helper.getFieldSizeBytes(structDef), "%edi");
|
asm.mov("l", Helper.getFieldSizeBytes(structDef), "%edi");
|
||||||
asm.call("malloc@PLT");
|
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--) {
|
for (int i = e.args.length - 1; i >= 0; i--) {
|
||||||
asm.pop("q", Helper.getFieldOffset(structDef, i) + "(%rax)");
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,7 +957,7 @@ public class GenASM implements Visitor<Void> {
|
|||||||
|
|
||||||
e.expression.welcome(this);
|
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())) {
|
if (e.expression.type.equals(Type.getFloatType())) {
|
||||||
asm.mov("q", "%xmm0", "%rax");
|
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");
|
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.pop("q", Helper.getFieldOffset(structDef, fieldNameToUpdate) + "(%rax)");
|
||||||
asm.mov("q", 0 , "%rax"); // clear rax since an assignment has no result
|
asm.mov("q", 0 , "%rax"); // clear rax since an assignment has no result
|
||||||
|
|
||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,6 @@ public class DestroyStatementTest {
|
|||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ public class FieldAssignmentTest {
|
|||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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
|
@Test
|
||||||
@@ -29,6 +29,6 @@ public class FieldAssignmentTest {
|
|||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,4 +92,28 @@ public class FunctionDefinitionTest {
|
|||||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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());
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
72
src/test/java/NaughtTest.java
Normal file
72
src/test/java/NaughtTest.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ public class StructFieldAccessTest {
|
|||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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
|
@Test
|
||||||
@@ -28,6 +28,6 @@ public class StructFieldAccessTest {
|
|||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,6 @@ public class VariableAssignmentTest {
|
|||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ public class VariableDeclarationTest {
|
|||||||
|
|
||||||
// when / then
|
// when / then
|
||||||
var e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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
|
@Test
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class VariableTest {
|
|||||||
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
ContextAnalysis ctxAnal = new ContextAnalysis(funcs, structs, enums);
|
||||||
|
|
||||||
Exception e = assertThrows(RuntimeException.class, () -> ctxAnal.visit(tree));
|
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
|
@Test
|
||||||
|
|||||||
@@ -94,6 +94,11 @@ int testStructCreation() {
|
|||||||
struct_testExpected_l("init inner field a", 20, resultRec->b->a);
|
struct_testExpected_l("init inner field a", 20, resultRec->b->a);
|
||||||
struct_testExpected_s("init inner field b", NULL, resultRec->b->b);
|
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(resultRec);
|
||||||
free(innerStruct);
|
free(innerStruct);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,4 +28,6 @@ long getStructFieldRecA(struct testStructRec *t);
|
|||||||
struct testStructRec *getStructFieldRecB(struct testStructRec *t);
|
struct testStructRec *getStructFieldRecB(struct testStructRec *t);
|
||||||
|
|
||||||
struct testStructRec *setStructFieldRecA(struct testStructRec *t, long a);
|
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();
|
||||||
@@ -555,6 +555,72 @@ function getTestStructRec(a: int, b: testStructRec): testStructRec {
|
|||||||
return create testStructRec(a, b);
|
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 {
|
function getStructFieldA(t: testStruct): int {
|
||||||
return t.a;
|
return t.a;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user