From 32622cdd7e42070f3db2c4fe477365cd5f366b78 Mon Sep 17 00:00:00 2001 From: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:42:50 +0000 Subject: [PATCH 1/8] feat: incomplete initial implementation of assemblyIR --- src/main/wacc/backend/assemblyIR.scala | 68 +++++++++++++++++++ .../wacc/{Frontend => frontend}/Error.scala | 0 .../wacc/{Frontend => frontend}/ast.scala | 0 .../wacc/{Frontend => frontend}/lexer.scala | 0 .../wacc/{Frontend => frontend}/parser.scala | 0 .../wacc/{Frontend => frontend}/renamer.scala | 0 .../{Frontend => frontend}/typeChecker.scala | 0 .../wacc/{Frontend => frontend}/types.scala | 0 8 files changed, 68 insertions(+) create mode 100644 src/main/wacc/backend/assemblyIR.scala rename src/main/wacc/{Frontend => frontend}/Error.scala (100%) rename src/main/wacc/{Frontend => frontend}/ast.scala (100%) rename src/main/wacc/{Frontend => frontend}/lexer.scala (100%) rename src/main/wacc/{Frontend => frontend}/parser.scala (100%) rename src/main/wacc/{Frontend => frontend}/renamer.scala (100%) rename src/main/wacc/{Frontend => frontend}/typeChecker.scala (100%) rename src/main/wacc/{Frontend => frontend}/types.scala (100%) diff --git a/src/main/wacc/backend/assemblyIR.scala b/src/main/wacc/backend/assemblyIR.scala new file mode 100644 index 0000000..0777dd1 --- /dev/null +++ b/src/main/wacc/backend/assemblyIR.scala @@ -0,0 +1,68 @@ +package wacc + +object assemblyIR { + enum RegSize { + case R64 + case E32 + + override def toString: String = this match { + case R64 => "r" + case E32 => "e" + } + } + + enum CLibFunc { + case Scanf, + Puts, + Fflush, + Exit, + PrintF + + private val plt = "@plt" + + override def toString: String = this match { + case Scanf => "scanf" + plt + case Puts => "puts" + plt + case Fflush => "fflush" + plt + case Exit => "exit" + plt + case PrintF => "printf" + plt + } + } + + enum Register { + case Named(name: String, size: RegSize) + case Scratch(num: Int, size: RegSize) + override def toString: String = this match { + case Named(name, size) => s"${size}${name}" + case Scratch(num, size) => s"${size}${num}" + } + } + case class MemLocation(pointer: Long) + case class ImmediateVal(value: Int) + + //TODO Check if dest and src are not both memory locations + sealed trait noDupeMemLoc + +// object noDupeMemLoc { +// def apply(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) : noDupeMemLoc { +// require(!(dest.isInstanceOf[MemLocation] && src.isInstanceOf[MemLocation])) +// } +// } + case class Add(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc + case class Subtract(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc + case class Multiply(src: Register) + case class Divide(src: Register) + + case class And(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc + case class Or(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc + case class Compare(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc + + case class Call(func: CLibFunc) + + case class Push(src: Register | MemLocation | ImmediateVal) + case class Pop(src: Register | MemLocation | ImmediateVal) + + case class Jump() + +} + diff --git a/src/main/wacc/Frontend/Error.scala b/src/main/wacc/frontend/Error.scala similarity index 100% rename from src/main/wacc/Frontend/Error.scala rename to src/main/wacc/frontend/Error.scala diff --git a/src/main/wacc/Frontend/ast.scala b/src/main/wacc/frontend/ast.scala similarity index 100% rename from src/main/wacc/Frontend/ast.scala rename to src/main/wacc/frontend/ast.scala diff --git a/src/main/wacc/Frontend/lexer.scala b/src/main/wacc/frontend/lexer.scala similarity index 100% rename from src/main/wacc/Frontend/lexer.scala rename to src/main/wacc/frontend/lexer.scala diff --git a/src/main/wacc/Frontend/parser.scala b/src/main/wacc/frontend/parser.scala similarity index 100% rename from src/main/wacc/Frontend/parser.scala rename to src/main/wacc/frontend/parser.scala diff --git a/src/main/wacc/Frontend/renamer.scala b/src/main/wacc/frontend/renamer.scala similarity index 100% rename from src/main/wacc/Frontend/renamer.scala rename to src/main/wacc/frontend/renamer.scala diff --git a/src/main/wacc/Frontend/typeChecker.scala b/src/main/wacc/frontend/typeChecker.scala similarity index 100% rename from src/main/wacc/Frontend/typeChecker.scala rename to src/main/wacc/frontend/typeChecker.scala diff --git a/src/main/wacc/Frontend/types.scala b/src/main/wacc/frontend/types.scala similarity index 100% rename from src/main/wacc/Frontend/types.scala rename to src/main/wacc/frontend/types.scala From 4e58e41a2a120bcb5c4293f73996ef3c1521f3e9 Mon Sep 17 00:00:00 2001 From: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com> Date: Sun, 16 Feb 2025 17:58:35 +0000 Subject: [PATCH 2/8] refactor: abstracted operations, used trait Operand --- src/main/wacc/backend/assemblyIR.scala | 79 ++++++++++++++++++-------- 1 file changed, 54 insertions(+), 25 deletions(-) diff --git a/src/main/wacc/backend/assemblyIR.scala b/src/main/wacc/backend/assemblyIR.scala index 0777dd1..d653bfb 100644 --- a/src/main/wacc/backend/assemblyIR.scala +++ b/src/main/wacc/backend/assemblyIR.scala @@ -1,17 +1,27 @@ package wacc object assemblyIR { + + sealed trait Operand + + // did not use option because we'd have to wrap each op around Some(). can refactor + case object NoOperand extends Operand { + override def toString = "" + } + sealed trait Src extends Operand // mem location, register and imm value + sealed trait Dest extends Operand // mem location and register enum RegSize { case R64 case E32 - override def toString: String = this match { + override def toString = this match { case R64 => "r" case E32 => "e" } } - enum CLibFunc { + //arguments + enum CLibFunc extends Operand { case Scanf, Puts, Fflush, @@ -20,7 +30,7 @@ object assemblyIR { private val plt = "@plt" - override def toString: String = this match { + override def toString = this match { case Scanf => "scanf" + plt case Puts => "puts" + plt case Fflush => "fflush" + plt @@ -29,40 +39,59 @@ object assemblyIR { } } - enum Register { + enum Register extends Dest with Src { case Named(name: String, size: RegSize) case Scratch(num: Int, size: RegSize) - override def toString: String = this match { - case Named(name, size) => s"${size}${name}" + override def toString = this match { + case Named(name, size) => s"${size}${name.toLowerCase()}" case Scratch(num, size) => s"${size}${num}" } } - case class MemLocation(pointer: Long) - case class ImmediateVal(value: Int) + case class MemLocation(pointer: Long | Register) extends Dest with Src { + override def toString = pointer match { + case hex: Long => f"[0x$hex%X]" + case reg: Register => s"[$reg]" + } + } + + case class ImmediateVal(value: Int) extends Src { + override def toString = s"#${value.toString}" + } + + case class LabelArg(name: String) extends Operand { + override def toString = name + } //TODO Check if dest and src are not both memory locations - sealed trait noDupeMemLoc + abstract class Operation(ins: String, op1: Operand = NoOperand, op2: Operand = NoOperand) { + override def toString: String = if (op2 == NoOperand) { + s"$ins ${op1.toString}" + } else { + s"$ins ${op1.toString}, ${op2.toString}" + } + } + case class Add(op1: Dest, op2: Src) extends Operation("add", op1, op2) + case class Subtract(op1: Dest, op2: Src) extends Operation("sub", op1, op2) + case class Multiply(op1: Src) extends Operation("mul", op1) + case class Divide(op1: Src) extends Operation("div", op1) -// object noDupeMemLoc { -// def apply(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) : noDupeMemLoc { -// require(!(dest.isInstanceOf[MemLocation] && src.isInstanceOf[MemLocation])) -// } -// } - case class Add(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc - case class Subtract(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc - case class Multiply(src: Register) - case class Divide(src: Register) + case class And(op1: Dest, op2: Src) extends Operation("and", op1, op2) + case class Or(op1: Dest, op2: Src) extends Operation("or", op1, op2) + case class Compare(op1: Dest, op2: Src) extends Operation("cmp", op1, op2) - case class And(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc - case class Or(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc - case class Compare(dest: Register | MemLocation, src: Register | MemLocation | ImmediateVal) extends noDupeMemLoc + //stack operations + case class Push(op1: Src) extends Operation("push", op1) + case class Pop(op1: Src) extends Operation("pop", op1) + case class Call(op1: CLibFunc) extends Operation("call", op1) - case class Call(func: CLibFunc) + case class Return() extends Operation("ret") - case class Push(src: Register | MemLocation | ImmediateVal) - case class Pop(src: Register | MemLocation | ImmediateVal) + case class Jump(op1: LabelArg) extends Operation("jmp", op1) + case class JumpIfEqual(op1: LabelArg) extends Operation("je", op1) - case class Jump() + case class LabelDef(name: String) { + override def toString = s"$name:" + } } From 7525e523bb48e3a5be46fe707a1826d087c22152 Mon Sep 17 00:00:00 2001 From: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com> Date: Sun, 16 Feb 2025 18:02:02 +0000 Subject: [PATCH 3/8] style: reformatted to pass stylecheck --- src/main/wacc/backend/assemblyIR.scala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/main/wacc/backend/assemblyIR.scala b/src/main/wacc/backend/assemblyIR.scala index d653bfb..d617202 100644 --- a/src/main/wacc/backend/assemblyIR.scala +++ b/src/main/wacc/backend/assemblyIR.scala @@ -20,13 +20,13 @@ object assemblyIR { } } - //arguments + // arguments enum CLibFunc extends Operand { case Scanf, - Puts, - Fflush, - Exit, - PrintF + Puts, + Fflush, + Exit, + PrintF private val plt = "@plt" @@ -43,13 +43,13 @@ object assemblyIR { case Named(name: String, size: RegSize) case Scratch(num: Int, size: RegSize) override def toString = this match { - case Named(name, size) => s"${size}${name.toLowerCase()}" + case Named(name, size) => s"${size}${name.toLowerCase()}" case Scratch(num, size) => s"${size}${num}" } } case class MemLocation(pointer: Long | Register) extends Dest with Src { override def toString = pointer match { - case hex: Long => f"[0x$hex%X]" + case hex: Long => f"[0x$hex%X]" case reg: Register => s"[$reg]" } } @@ -62,7 +62,7 @@ object assemblyIR { override def toString = name } - //TODO Check if dest and src are not both memory locations + // TODO Check if dest and src are not both memory locations abstract class Operation(ins: String, op1: Operand = NoOperand, op2: Operand = NoOperand) { override def toString: String = if (op2 == NoOperand) { s"$ins ${op1.toString}" @@ -79,7 +79,7 @@ object assemblyIR { case class Or(op1: Dest, op2: Src) extends Operation("or", op1, op2) case class Compare(op1: Dest, op2: Src) extends Operation("cmp", op1, op2) - //stack operations + // stack operations case class Push(op1: Src) extends Operation("push", op1) case class Pop(op1: Src) extends Operation("pop", op1) case class Call(op1: CLibFunc) extends Operation("call", op1) @@ -94,4 +94,3 @@ object assemblyIR { } } - From 2c281066a89a0061eafe96353fef08cbc6e74eef Mon Sep 17 00:00:00 2001 From: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com> Date: Mon, 17 Feb 2025 18:07:36 +0000 Subject: [PATCH 4/8] refactor: used varargs instead of noOperand --- src/main/wacc/backend/assemblyIR.scala | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/main/wacc/backend/assemblyIR.scala b/src/main/wacc/backend/assemblyIR.scala index d617202..f1ce3a0 100644 --- a/src/main/wacc/backend/assemblyIR.scala +++ b/src/main/wacc/backend/assemblyIR.scala @@ -3,11 +3,6 @@ package wacc object assemblyIR { sealed trait Operand - - // did not use option because we'd have to wrap each op around Some(). can refactor - case object NoOperand extends Operand { - override def toString = "" - } sealed trait Src extends Operand // mem location, register and imm value sealed trait Dest extends Operand // mem location and register enum RegSize { @@ -63,12 +58,8 @@ object assemblyIR { } // TODO Check if dest and src are not both memory locations - abstract class Operation(ins: String, op1: Operand = NoOperand, op2: Operand = NoOperand) { - override def toString: String = if (op2 == NoOperand) { - s"$ins ${op1.toString}" - } else { - s"$ins ${op1.toString}, ${op2.toString}" - } + abstract class Operation(ins: String, ops: Operand*) { + override def toString: String = s"$ins ${ops.mkString(", ")}" } case class Add(op1: Dest, op2: Src) extends Operation("add", op1, op2) case class Subtract(op1: Dest, op2: Src) extends Operation("sub", op1, op2) From d49c267d50922ce8c37aa4087bc3cdc6ae78776f Mon Sep 17 00:00:00 2001 From: Guy C Date: Tue, 18 Feb 2025 18:26:50 +0000 Subject: [PATCH 5/8] feat: extends IR for load/move instructions and generalised conditional jumps --- src/main/wacc/backend/assemblyIR.scala | 34 +++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/main/wacc/backend/assemblyIR.scala b/src/main/wacc/backend/assemblyIR.scala index f1ce3a0..d9955c2 100644 --- a/src/main/wacc/backend/assemblyIR.scala +++ b/src/main/wacc/backend/assemblyIR.scala @@ -39,7 +39,7 @@ object assemblyIR { case Scratch(num: Int, size: RegSize) override def toString = this match { case Named(name, size) => s"${size}${name.toLowerCase()}" - case Scratch(num, size) => s"${size}${num}" + case Scratch(num, size) => s"r${num}${if (size == RegSize.E32) "d" else ""}" } } case class MemLocation(pointer: Long | Register) extends Dest with Src { @@ -50,7 +50,7 @@ object assemblyIR { } case class ImmediateVal(value: Int) extends Src { - override def toString = s"#${value.toString}" + override def toString = value.toString } case class LabelArg(name: String) extends Operand { @@ -59,11 +59,11 @@ object assemblyIR { // TODO Check if dest and src are not both memory locations abstract class Operation(ins: String, ops: Operand*) { - override def toString: String = s"$ins ${ops.mkString(", ")}" + override def toString: String = s"\t$ins ${ops.mkString(", ")}" } case class Add(op1: Dest, op2: Src) extends Operation("add", op1, op2) case class Subtract(op1: Dest, op2: Src) extends Operation("sub", op1, op2) - case class Multiply(op1: Src) extends Operation("mul", op1) + case class Multiply(ops: Operand*) extends Operation("mul", ops*) case class Divide(op1: Src) extends Operation("div", op1) case class And(op1: Dest, op2: Src) extends Operation("and", op1, op2) @@ -75,13 +75,35 @@ object assemblyIR { case class Pop(op1: Src) extends Operation("pop", op1) case class Call(op1: CLibFunc) extends Operation("call", op1) + case class Move(op1: Dest, op2: Src) extends Operation("mov", op1, op2) + case class Load(op1: Register, op2: MemLocation) extends Operation("lea ", op1, op2) + case class Return() extends Operation("ret") - case class Jump(op1: LabelArg) extends Operation("jmp", op1) - case class JumpIfEqual(op1: LabelArg) extends Operation("je", op1) + case class Jump(op1: LabelArg, condition: Cond = Cond.Always) extends Operation(s"j${condition.toString}", op1) case class LabelDef(name: String) { override def toString = s"$name:" } + enum Cond { + case Equal, + NotEqual, + Greater, + GreaterEqual, + Less, + LessEqual, + Overflow, + Always + override def toString(): String = this match { + case Equal => "e" + case NotEqual => "ne" + case Greater => "g" + case GreaterEqual => "ge" + case Less => "l" + case LessEqual => "le" + case Overflow => "o" + case Always => "mp" + } + } } From 43682b902ba0b5044cb7aa13b081344351d47e9e Mon Sep 17 00:00:00 2001 From: Guy C Date: Tue, 18 Feb 2025 18:27:42 +0000 Subject: [PATCH 6/8] test: add unit tests for register and instruction toString methods --- src/test/wacc/instructionSpec.scala | 65 +++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/test/wacc/instructionSpec.scala diff --git a/src/test/wacc/instructionSpec.scala b/src/test/wacc/instructionSpec.scala new file mode 100644 index 0000000..6d427a5 --- /dev/null +++ b/src/test/wacc/instructionSpec.scala @@ -0,0 +1,65 @@ +import org.scalatest.funsuite.AnyFunSuite +import wacc.assemblyIR._ + +class instructionSpec extends AnyFunSuite { + + val named64BitRegister = Register.Named("ax", RegSize.R64) + + test("named 64-bit register toString") { + assert(named64BitRegister.toString == "rax") + } + + val named32BitRegister = Register.Named("ax", RegSize.E32) + + test("named 32-bit register toString") { + assert(named32BitRegister.toString == "eax") + } + + val scratch64BitRegister = Register.Scratch(1, RegSize.R64) + + test("scratch 64-bit register toString") { + assert(scratch64BitRegister.toString == "r1") + } + + val scratch32BitRegister = Register.Scratch(1, RegSize.E32) + + test("scratch 32-bit register toString") { + assert(scratch32BitRegister.toString == "r1d") + } + + val memLocationWithHex = MemLocation(0x12345678) + + test("mem location with hex toString") { + assert(memLocationWithHex.toString == "[0x12345678]") + } + + val memLocationWithRegister = MemLocation(named64BitRegister) + + test("mem location with register toString") { + assert(memLocationWithRegister.toString == "[rax]") + } + + val immediateVal = ImmediateVal(123) + + test("immediate value toString") { + assert(immediateVal.toString == "123") + } + + val addInstruction = Add(named64BitRegister, immediateVal) + + test("x86: add instruction toString") { + assert(addInstruction.toString == "\tadd rax, 123") + } + + val subInstruction = Subtract(scratch64BitRegister, named64BitRegister) + + test("x86: sub instruction toString") { + assert(subInstruction.toString == "\tsub r1, rax") + } + + val callInstruction = Call(CLibFunc.Scanf) + + test("x86: call instruction toString") { + assert(callInstruction.toString == "\tcall scanf@plt") + } +} From 006e85d0f8c82cb59710244581b21ecab4702850 Mon Sep 17 00:00:00 2001 From: Guy C Date: Tue, 18 Feb 2025 18:29:16 +0000 Subject: [PATCH 7/8] style: improve formatting for Jump case class and condition toString method --- src/main/wacc/backend/assemblyIR.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/wacc/backend/assemblyIR.scala b/src/main/wacc/backend/assemblyIR.scala index d9955c2..0d167c2 100644 --- a/src/main/wacc/backend/assemblyIR.scala +++ b/src/main/wacc/backend/assemblyIR.scala @@ -80,7 +80,8 @@ object assemblyIR { case class Return() extends Operation("ret") - case class Jump(op1: LabelArg, condition: Cond = Cond.Always) extends Operation(s"j${condition.toString}", op1) + case class Jump(op1: LabelArg, condition: Cond = Cond.Always) + extends Operation(s"j${condition.toString}", op1) case class LabelDef(name: String) { override def toString = s"$name:" @@ -96,14 +97,14 @@ object assemblyIR { Overflow, Always override def toString(): String = this match { - case Equal => "e" - case NotEqual => "ne" - case Greater => "g" + case Equal => "e" + case NotEqual => "ne" + case Greater => "g" case GreaterEqual => "ge" - case Less => "l" - case LessEqual => "le" - case Overflow => "o" - case Always => "mp" + case Less => "l" + case LessEqual => "le" + case Overflow => "o" + case Always => "mp" } } } From b692982d739ed298ffe2d78c358b3f4de06b6f7d Mon Sep 17 00:00:00 2001 From: Barf-Vader <47476490+Barf-Vader@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:57:41 +0000 Subject: [PATCH 8/8] refactor: gitignore .idea/ --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 64ecf6a..03801cc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ .scala-build/ .vscode/ wacc-examples/ +.idea/ +