Skip to content
Merged
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
3 changes: 3 additions & 0 deletions src/jsMain/kotlin/assembly/AsmConstruct.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package assembly

import kotlinx.serialization.Serializable

@Serializable
sealed class AsmConstruct {
protected fun indent(level: Int): String = " ".repeat(level)
}
57 changes: 53 additions & 4 deletions src/jsMain/kotlin/assembly/CodeEmitter.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
package assembly
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

class CodeEmitter {
// TODO make it based on the device instead hard coded
private val useLinuxPrefix = true

@kotlinx.serialization.Serializable
data class RawInstruction(val code: String, val sourceId: String?)

@kotlinx.serialization.Serializable
data class RawFunction(
val name: String,
val body: List<RawInstruction>,
val stackSize: Int
)
fun emit(program: AsmProgram): String = program.functions.joinToString("\n\n") { emitFunction(it) }

fun emitRaw(program: AsmProgram): String = program.functions.joinToString("\n\n") { emitFunctionRaw(it) }
private fun emitFunction(function: AsmFunction): String {
val functionName = formatLabel(function.name)
val bodyAsm = function.body.joinToString("\n") { emitInstruction(it) }
Expand All @@ -24,6 +36,39 @@ class CodeEmitter {
}
}

private fun emitFunctionRaw(function: AsmFunction): String {
val bodyRaw = function.body.map { emitInstructionRaw(it) }
val rawFunc = RawFunction(function.name, bodyRaw, function.stackSize)
return Json.encodeToString(rawFunc) // returns valid JSON
}

private fun emitInstructionRaw(instruction: Instruction): RawInstruction {
val indent = " "
return when (instruction) {
is Call -> RawInstruction("call ${formatLabel(instruction.identifier)}", instruction.sourceId.toString())
is Push -> {
val operand = emitOperand(instruction.operand, size = OperandSize.QUAD)
RawInstruction("push $operand", instruction.sourceId.toString())
}
is DeAllocateStack -> RawInstruction("addq rsp, ${instruction.size}", instruction.sourceId.toString())
is Mov -> RawInstruction("mov ${emitOperand(instruction.dest)}, ${emitOperand(instruction.src)}", instruction.sourceId.toString())
is AsmUnary -> RawInstruction("${instruction.op.text} ${emitOperand(instruction.dest)}", instruction.sourceId.toString())
is AsmBinary -> RawInstruction("${instruction.op.text} ${emitOperand(instruction.dest)}, ${emitOperand(instruction.src)}", instruction.sourceId.toString())
is Cmp -> RawInstruction("cmp ${emitOperand(instruction.dest)}, ${emitOperand(instruction.src)}", instruction.sourceId.toString())
is Idiv -> RawInstruction("idiv ${emitOperand(instruction.divisor)}", instruction.sourceId.toString())
is AllocateStack -> RawInstruction("subq rsp, ${instruction.size}", instruction.sourceId.toString())
is Cdq -> RawInstruction("cdq", instruction.sourceId.toString())
is Label -> RawInstruction("${formatLabel(instruction.name)}:", instruction.sourceId.toString())
is Jmp -> RawInstruction("jmp ${formatLabel(instruction.label.name)}", instruction.sourceId.toString())
is JmpCC -> RawInstruction("j${instruction.condition.text} ${formatLabel(instruction.label.name)}", instruction.sourceId.toString())
is SetCC -> {
val destOperand = emitOperand(instruction.dest, size = OperandSize.BYTE)
RawInstruction("set${instruction.condition.text} $destOperand", instruction.sourceId.toString())
}
is Ret -> RawInstruction("ret", instruction.sourceId.toString())
}
}

private fun emitInstruction(instruction: Instruction): String {
val indent = " "
return when (instruction) {
Expand All @@ -33,7 +78,6 @@ class CodeEmitter {
"${indent}push $operand"
}
is DeAllocateStack -> "${indent}addq rsp, ${instruction.size}"

is Mov -> "${indent}mov ${emitOperand(instruction.dest)}, ${emitOperand(instruction.src)}"
is AsmUnary -> "${indent}${instruction.op.text} ${emitOperand(instruction.dest)}"
is AsmBinary -> "${indent}${instruction.op.text} ${emitOperand(instruction.dest)}, ${emitOperand(instruction.src)}"
Expand All @@ -44,15 +88,20 @@ class CodeEmitter {
is Label -> formatLabel(instruction.name) + ":"
is Jmp -> "${indent}jmp ${formatLabel(instruction.label.name)}"
is JmpCC -> "${indent}j${instruction.condition.text} ${formatLabel(instruction.label.name)}"

is SetCC -> {
val destOperand = emitOperand(instruction.dest, size = OperandSize.BYTE)
"${indent}set${instruction.condition.text} $destOperand"
}

is Ret -> ""
}
}

else -> throw NotImplementedError("Emission for ${instruction::class.simpleName} not implemented.")
private fun emitOperandRaw(operand: Operand): String {
return when (operand) {
is Imm -> "Imm(value=${operand.value})"
is Stack -> "Stack(offset=${operand.offset})"
is Register -> "Register(name=${operand.name})"
is Pseudo -> "Pseudo(name=${operand.name})"
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/jsMain/kotlin/assembly/Functions.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package assembly

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
sealed class Function : AsmConstruct()

@Serializable
@SerialName("AsmFunction")
data class AsmFunction(
val name: String,
var body: List<Instruction>,
Expand Down
33 changes: 17 additions & 16 deletions src/jsMain/kotlin/assembly/InstructionFixer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,22 @@ class InstructionFixer {
// Idiv cannot take an immediate value directly.
instruction is Idiv && instruction.divisor is Imm -> {
listOf(
Mov(instruction.divisor, Register(HardwareRegister.R10D)),
Idiv(Register(HardwareRegister.R10D))
Mov(instruction.divisor, Register(HardwareRegister.R10D), instruction.sourceId),
Idiv(Register(HardwareRegister.R10D), instruction.sourceId)
)
}

instruction is Push && instruction.operand is Stack -> {
listOf(
Mov(instruction.operand, Register(HardwareRegister.EAX)), // Use a caller-saved register
Push(Register(HardwareRegister.EAX))
Mov(instruction.operand, Register(HardwareRegister.EAX), instruction.sourceId), // Use a caller-saved register
Push(Register(HardwareRegister.EAX), instruction.sourceId)
)
}

instruction is Mov && instruction.src is Stack && instruction.dest is Stack -> {
listOf(
Mov(instruction.src, Register(HardwareRegister.R10D)),
Mov(Register(HardwareRegister.R10D), instruction.dest)
Mov(instruction.src, Register(HardwareRegister.R10D), instruction.sourceId),
Mov(Register(HardwareRegister.R10D), instruction.dest, instruction.sourceId)
)
}

Expand All @@ -40,34 +40,34 @@ class InstructionFixer {
instruction.src is Stack &&
instruction.dest is Stack -> {
listOf(
Mov(instruction.src, Register(HardwareRegister.R10D)),
AsmBinary(instruction.op, Register(HardwareRegister.R10D), instruction.dest)
Mov(instruction.src, Register(HardwareRegister.R10D), instruction.sourceId),
AsmBinary(instruction.op, Register(HardwareRegister.R10D), instruction.dest, instruction.sourceId)
)
}

instruction is AsmBinary &&
instruction.op == AsmBinaryOp.MUL &&
instruction.dest is Stack -> {
listOf(
Mov(instruction.dest, Register(HardwareRegister.R11D)),
AsmBinary(instruction.op, instruction.src, Register(HardwareRegister.R11D)),
Mov(Register(HardwareRegister.R11D), instruction.dest)
Mov(instruction.dest, Register(HardwareRegister.R11D), instruction.sourceId),
AsmBinary(instruction.op, instruction.src, Register(HardwareRegister.R11D), instruction.sourceId),
Mov(Register(HardwareRegister.R11D), instruction.dest, instruction.sourceId)
)
}

// `cmp` cannot be memory-to-memory.
instruction is Cmp && instruction.src is Stack && instruction.dest is Stack -> {
listOf(
Mov(instruction.src, Register(HardwareRegister.R10D)),
Cmp(Register(HardwareRegister.R10D), instruction.dest)
Mov(instruction.src, Register(HardwareRegister.R10D), instruction.sourceId),
Cmp(Register(HardwareRegister.R10D), instruction.dest, instruction.sourceId)
)
}

// The destination of `cmp` cannot be an immediate.
instruction is Cmp && instruction.dest is Imm -> {
listOf(
Mov(instruction.dest, Register(HardwareRegister.R11D)),
Cmp(instruction.src, Register(HardwareRegister.R11D))
Mov(instruction.dest, Register(HardwareRegister.R11D), instruction.sourceId),
Cmp(instruction.src, Register(HardwareRegister.R11D), instruction.sourceId)
)
}

Expand All @@ -83,7 +83,8 @@ class InstructionFixer {

val finalInstructions =
if (stackSpace > 0) {
listOf(AllocateStack(stackSpace)) + fixedInstructions
// Stack allocation is a function-level operation, not tied to a specific source instruction
listOf(AllocateStack(stackSpace, "")) + fixedInstructions
} else {
fixedInstructions
}
Expand Down
85 changes: 69 additions & 16 deletions src/jsMain/kotlin/assembly/Instructions.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
package assembly

sealed class Instruction : AsmConstruct()
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

object Ret : Instruction()
@Serializable
sealed class Instruction() : AsmConstruct() {
abstract val sourceId: String
}

@Serializable
@SerialName("Ret")
data class Ret(
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("Mov")
data class Mov(
val src: Operand,
val dest: Operand
val dest: Operand,
override val sourceId: String = ""
) : Instruction()

enum class AsmUnaryOp(
Expand All @@ -24,56 +37,93 @@ enum class AsmBinaryOp(
MUL("imul")
}

@Serializable
@SerialName("AsmUnary")
data class AsmUnary(
val op: AsmUnaryOp,
val dest: Operand
val dest: Operand,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("AsmBinary")
data class AsmBinary(
val op: AsmBinaryOp,
val src: Operand,
val dest: Operand
val dest: Operand,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("Idiv")
data class Idiv(
val divisor: Operand
val divisor: Operand,
override val sourceId: String = ""
) : Instruction()

// Convert Doubleword 32 to Quadword 64
object Cdq : Instruction()
@Serializable
@SerialName("Cdq")
data class Cdq(
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("AllocateStack")
data class AllocateStack(
val size: Int
val size: Int,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("DeAllocateStack")
data class DeAllocateStack(
val size: Int
val size: Int,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("Push")
data class Push(
val operand: Operand
val operand: Operand,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("Call")
data class Call(
val identifier: String
val identifier: String,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("Label")
data class Label(
val name: String
val name: String,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("Jmp")
data class Jmp(
val label: Label
val label: Label,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("JmpCC")
data class JmpCC(
val condition: ConditionCode,
val label: Label
val label: Label,
override val sourceId: String = ""
) : Instruction()

@Serializable
@SerialName("Cmp")
data class Cmp(
val src: Operand,
val dest: Operand
val dest: Operand,
override val sourceId: String = ""
) : Instruction()

enum class ConditionCode(
Expand All @@ -87,7 +137,10 @@ enum class ConditionCode(
GE("ge") // Greater or Equal
}

@Serializable
@SerialName("SetCC")
data class SetCC(
val condition: ConditionCode,
val dest: Operand
val dest: Operand,
override val sourceId: String = ""
) : Instruction()
12 changes: 12 additions & 0 deletions src/jsMain/kotlin/assembly/Operands.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
package assembly

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
sealed class Operand : AsmConstruct()

@Serializable
@SerialName("Imm")
data class Imm(
val value: Int
) : Operand()

@Serializable
@SerialName("Register")
data class Register(
val name: HardwareRegister
) : Operand()

@Serializable
@SerialName("Pseudo")
data class Pseudo(
val name: String
) : Operand()

@Serializable
@SerialName("Stack")
data class Stack(
val offset: Int
) : Operand()
Expand Down
6 changes: 6 additions & 0 deletions src/jsMain/kotlin/assembly/Programs.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package assembly

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
sealed class Program : AsmConstruct()

@Serializable
@SerialName("AsmProgram")
data class AsmProgram(
val functions: List<AsmFunction>
) : Program()
Loading