Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/bytecode types #43

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions input/multipleClasses.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.multipleclasses;
package out;

class AClass {
private int value;
Expand Down Expand Up @@ -29,4 +29,4 @@ public AClass createNewInstance() {
// return 1;
// }

}
}
18 changes: 7 additions & 11 deletions input/primitiveTypes.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,27 @@
package de.primitivetypes;

public class A {

public class primitiveTypes {
public void testTypes() {
// Integer type
int a = 5;
int b = 10;
int c = a + b;
int negative = -4;

// Boolean type
boolean flag1 = true;
boolean flag2 = false;
boolean flag3 = flag1 && flag2;

/*

// Byte type
byte byteVar1 = 127b;
byte byteVar2 = 0B;
byte byteVar3 = 12b + 24B;

byte byteVar2 = 0b;
byte byteVar3 = 12b + 24b;

// Short type
short shortVar1 = 32767s;
short shortVar2 = 0S;
short shortVar3 = 15s % 4S;
short shortVar2 = 0s;
short shortVar3 = 15s % 4s;

// Long type
long longVar1 = 100000L;
Expand All @@ -45,7 +42,6 @@ public void testTypes() {
char charVar1 = 'A';
char charVar2 = 'B';
char charVar3 = 'C' + 'D';

*/
return;
}
}
103 changes: 39 additions & 64 deletions src/main/scala/de/students/ByteCodeGenerator/ASMHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,44 @@ private def visibilityModifier(methodDecl: MethodDecl): Int = {
+ ACC_PUBLIC
}

private def javaifyClass(fullName: String) = fullName.replace('.', '/')
private def typeStackSize(t: Type): Int = t match {
case IntType => 1
case BoolType => 1
case ShortType => 1
case LongType => 2
case ByteType => 1
case FloatType => 1
case DoubleType => 2
case CharType => 1
case ArrayType(_) => 1
case UserType(_) => 1
case _ => throw ByteCodeGeneratorException(f"type $t can't be pushed onto the stack")
}

private def javaifyClass(fullName: String) = makeObjectClassName(fullName.replace('.', '/'))
private def makeObjectClassName(name: String) =
if name.contains("Object") then "java/lang/Object" else name // TODO temporary, make issue for type check

private def asmType(t: Type): String = t match {
case NoneType => "" // TODO find out descriptor of NoneType
private def javaSignature(t: Type): String = t match {
case IntType => "I"
case BoolType => "Z"
case VoidType => "V"
case ArrayType(baseType) => f"[${asmType(baseType)}"
case ShortType => "S"
case LongType => "L"
case ByteType => "B"
case FloatType => "F"
case DoubleType => "D"
case CharType => "C"
case ArrayType(baseType) => f"[${javaSignature(baseType)}"
case UserType(name) => {
f"L${javaifyClass(name)};"
}
case FunctionType(returnType, parameterTypes) => {
val parameters = parameterTypes.map(asmType).fold("")((a, b) => a + b)
f"($parameters)${asmType(returnType)}"
val parameters = parameterTypes.map(javaSignature).fold("")((a, b) => a + b)
f"($parameters)${javaSignature(returnType)}"
}
case _ => throw ByteCodeGeneratorException(s"Unknown type $t cannot be converted to ASM-type")
case NoneType => "" // NOTE should not happen
case _ => throw ByteCodeGeneratorException(s"Unknown type $t cannot be converted to ASM-type")
}

private def asmUserType(t: Type): String = t match {
Expand All @@ -52,7 +74,7 @@ private def asmUserType(t: Type): String = t match {
}

private def asmConstructorType(parameters: List[Type]): String = {
asmType(FunctionType(VoidType, parameters))
javaSignature(FunctionType(VoidType, parameters))
}

private def functionType(methodDecl: MethodDecl): FunctionType =
Expand All @@ -61,69 +83,22 @@ private def functionType(methodDecl: MethodDecl): FunctionType =
private def constructorType(constructorDecl: ConstructorDecl): FunctionType =
FunctionType(VoidType, constructorDecl.params.map(param => param.varType))

private def binaryOpcode(op: String, t: Type): String = {
val prefix = t match {
case IntType => "I"
case BoolType => "I" // NOTE: this should be Z
case _ => throw ByteCodeGeneratorException(f"the type $t is not allowed")
}
val opName = op match {
case "+" => "ADD"
case "-" => "SUB"
case "*" => "MUL"
case "/" => "DIV"
case "%" => "REM"
case _ => throw ByteCodeGeneratorException(f"the operator \"$op\" is not allowed for binary operations")
}
prefix + opName
}
private def asmOpcode(opName: String): Int = opName match {
case "IADD" => IADD
case "ISUB" => ISUB
case "IMUL" => IMUL
case "IDIV" => IDIV
case "IREM" => IREM
case _ => throw NotImplementedError("asm opcode")
}

private def isBooleanOpcode(op: String): Boolean =
private def isBooleanOpcode(op: String): Boolean = {
op == "==" ||
op == "!=" ||
op == "<" ||
op == "<=" ||
op == ">" ||
op == ">=" ||
op == "&&" ||
op == "||"

private def asmLoadInsn(t: Type): Int = t match {
case IntType => ILOAD
case BoolType => ILOAD
case ArrayType(baseType) => ALOAD
case UserType(name) => ALOAD
case _ => throw ByteCodeGeneratorException(f"type ${asmType(t)} can not be fetched as variable")
}
private def asmStoreInsn(t: Type): Int = t match {
case IntType => ISTORE
case BoolType => ISTORE
case ArrayType(baseType) => ASTORE
case UserType(name) => ASTORE
case _ => throw ByteCodeGeneratorException(f"type ${asmType(t)} can not be saved as variable")
}

private def asmReturnCode(t: Type): Int = t match {
case IntType => IRETURN
case BoolType => IRETURN
case ArrayType(baseType) => ARETURN
case UserType(name) => ARETURN
case _ => throw ByteCodeGeneratorException(f"return type \"$t\" is not allowed")
op == "!=" ||
op == "<" ||
op == "<=" ||
op == ">" ||
op == ">=" ||
op == "&&" ||
op == "||"
}

private def makePrintStatement(toPrint: Expression, methodVisitor: MethodVisitor, state: MethodGeneratorState): Unit = {
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")
val t = generateTypedExpression(toPrint.asInstanceOf[TypedExpression], state)
state.stackDepth += 1
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", f"(${asmType(t)})V", false)
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", f"(${javaSignature(t)})V", false)
}

private def debugLogStack(state: MethodGeneratorState, where: String): Unit = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ private def generateClassBytecode(classDecl: ClassDecl): ClassBytecode = {
val classWriter = new ClassWriter(0)

val javaClassName = javaifyClass(classDecl.name)
val parent = "java/lang/Object" // TODO use real parent
val parent = javaifyClass(classDecl.parent) // "java/lang/Object" // TODO use real parent

// set class header
classWriter.visit(
Expand Down Expand Up @@ -61,7 +61,7 @@ private def generateClassBytecode(classDecl: ClassDecl): ClassBytecode = {
.visitField(
accessModifier(fieldDecl),
fieldDecl.name,
asmType(fieldDecl.varType),
javaSignature(fieldDecl.varType),
null, // signature
null // initial value, only used for static fields
)
Expand All @@ -88,7 +88,7 @@ private def generateConstructor(
val methodVisitor = classWriter.visitMethod(
ACC_PUBLIC,
"<init>",
asmType(constructorType(constructorDecl)),
javaSignature(constructorType(constructorDecl)),
null,
null
)
Expand All @@ -100,7 +100,13 @@ private def generateConstructor(
methodVisitor.visitCode()

methodVisitor.visitVarInsn(ALOAD, 0) // load this
methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false) // call Object constructor
methodVisitor.visitMethodInsn(
INVOKESPECIAL,
javaifyClass(classDecl.parent),
"<init>",
"()V",
false
) // call Object constructor

generateStatement(constructorDecl.body, state)

Expand All @@ -114,7 +120,7 @@ private def generateMethodBody(classDecl: ClassDecl, methodDecl: MethodDecl, cla
val methodVisitor = classWriter.visitMethod(
visibilityModifier(methodDecl),
methodDecl.name,
asmType(functionType(methodDecl)),
javaSignature(functionType(methodDecl)),
null, // signature
null // exceptions
)
Expand All @@ -129,12 +135,12 @@ private def generateMethodBody(classDecl: ClassDecl, methodDecl: MethodDecl, cla

methodVisitor.visitCode()

state.stackDepth = 0
state.stackDepth = state.localVariableCount
if (methodDecl.body.isDefined) {
generateStatement(methodDecl.body.get, state)
}

methodVisitor.visitMaxs(state.maxStackDepth + 1, state.localVariableCount)
methodVisitor.visitMaxs(state.maxStackDepth, state.localVariableCount)
methodVisitor.visitEnd()
}

Expand All @@ -149,7 +155,8 @@ private case class MethodGeneratorState(
val scopeEnds: ArrayBuffer[Label],
val loopStarts: ArrayBuffer[Label],
var currentScope: Int,
val variables: mutable.HashMap[String, VariableInfo]
val variables: mutable.HashMap[String, VariableInfo],
val stackTypes: ArrayBuffer[Type]
) {
def startSimpleScope(end: Label): Unit = {
scopeEnds += end
Expand All @@ -171,12 +178,27 @@ private case class MethodGeneratorState(
endSimpleScope()
}

def pushStack(): Unit = {
stackDepth += 1
def pushStack(t: Type): Unit = {
stackTypes.append(t)
pushStack(typeStackSize(t))
}
private def pushStack(count: Int): Unit = {
stackDepth += count
maxStackDepth = Math.max(stackDepth, maxStackDepth)
}
def popStack(): Unit = {
if (stackTypes.isEmpty) {
throw ByteCodeGeneratorException("empty stack cannot be popped, this is a bug in the bytecode generator")
}
val typeSize = typeStackSize(stackTypes.last)

stackDepth -= typeSize
stackTypes.remove(stackTypes.size - 1)
}
def popStack(count: Int): Unit = {
stackDepth -= count
for (i <- 0 until count) {
popStack()
}
}

def addVariable(name: String, t: Type): Int = {
Expand All @@ -189,8 +211,9 @@ private case class MethodGeneratorState(
if (res.isDefined) {
throw ByteCodeGeneratorException(f"variable $name already exists")
}
localVariableCount += 1
localVariableCount - 1
val typeSize = typeStackSize(t)
localVariableCount += typeSize
localVariableCount - typeSize
}

def getVariable(name: String): VariableInfo = {
Expand Down Expand Up @@ -226,7 +249,8 @@ private def defaultMethodGeneratorState(
ArrayBuffer.empty,
ArrayBuffer.empty,
0,
mutable.HashMap.empty
mutable.HashMap.empty,
ArrayBuffer(UserType("this")) // TODO use own type
)

private case class VariableInfo(
Expand Down
18 changes: 9 additions & 9 deletions src/main/scala/de/students/ByteCodeGenerator/Expressions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ private def generateExpression(expression: Expression, state: MethodGeneratorSta
expression match {
case TypedExpression(variableReference: VarRef, _) =>
generateVariableReference(variableReference, state)
case TypedExpression(literal: Literal, _) =>
generateLiteral(literal, state)
case TypedExpression(literal: Literal, t) =>
generateLiteral(literal, t, state)
case TypedExpression(binaryOperation: BinaryOp, _) =>
generateBinaryOperation(binaryOperation, state)
case TypedExpression(methodCall: MethodCall, returnType) =>
Expand Down Expand Up @@ -47,8 +47,8 @@ private def generateVariableReference(varRef: VarRef, state: MethodGeneratorStat
}

// LITERAL
private def generateLiteral(literal: Literal, state: MethodGeneratorState): Unit = {
Instructions.pushConstant(literal.value, state)
private def generateLiteral(literal: Literal, literalType: Type, state: MethodGeneratorState): Unit = {
Instructions.pushConstant(literal.value, literalType, state)

debugLogStack(state, f"pushed $literal")
}
Expand All @@ -65,7 +65,7 @@ private def generateBinaryOperation(operation: BinaryOp, state: MethodGeneratorS
val expressionType = generateTypedExpression(operation.left.asInstanceOf[TypedExpression], state)
generateExpression(operation.right, state) // should be same type, this is not our problem if not

val opcode = asmOpcode(binaryOpcode(operation.op, expressionType))
val opcode = binaryOpcode(operation.op, expressionType)
Instructions.binaryOperation(opcode, state)
}

Expand Down Expand Up @@ -93,7 +93,7 @@ private def generateBooleanOperation(operation: BinaryOp, state: MethodGenerator
Instructions.visitLabel(end, state)

// the stack counter has to be manually decreased to account for branching
state.popStack(1)
state.popStack()
} else if (operation.op == "||") {
val truePush = Label()
val end = Label()
Expand All @@ -109,7 +109,7 @@ private def generateBooleanOperation(operation: BinaryOp, state: MethodGenerator
Instructions.visitLabel(end, state)

// the stack counter has to be manually decreased to account for branching
state.popStack(1)
state.popStack()
} else {
val ifInsn = operation.op match {
case "==" => IFEQ
Expand Down Expand Up @@ -166,7 +166,7 @@ private def generateVariableAccess(varName: String, rvalue: Expression, state: M
} else {
generateExpression(rvalue, state)

Instructions.duplicateTop(state)
Instructions.duplicateTopType(state)

Instructions.storeVar(variableInfo.id, variableInfo.t, state)
}
Expand All @@ -182,7 +182,7 @@ private def generateTypedExpression(expression: TypedExpression, state: MethodGe
private def generateMethodCall(methodCall: MethodCall, returnType: Type, state: MethodGeneratorState): Unit = {
val classType = generateTypedExpression(methodCall.target.asInstanceOf[TypedExpression], state)
val parameterTypes = methodCall.args.map(expr => generateTypedExpression(expr.asInstanceOf[TypedExpression], state))
val methodDescriptor = asmType(FunctionType(returnType, parameterTypes))
val methodDescriptor = javaSignature(FunctionType(returnType, parameterTypes))
Instructions.callMethod(asmUserType(classType), methodCall.methodName, methodCall.args.size, methodDescriptor, state)
}

Expand Down
Loading
Loading