mirror of
https://github.com/irmen/prog8.git
synced 2025-02-18 05:30:34 +00:00
refactor IR codegen into separate module
This commit is contained in:
parent
d07d00fa41
commit
b4352ad38b
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@ -5,6 +5,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
|
@ -26,7 +26,7 @@ compileTestKotlin {
|
||||
dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation project(':intermediate')
|
||||
implementation project(':virtualmachine')
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
@ -10,8 +10,8 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
</component>
|
||||
</module>
|
File diff suppressed because it is too large
Load Diff
64
codeGenIntermediate/build.gradle
Normal file
64
codeGenIntermediate/build.gradle
Normal file
@ -0,0 +1,64 @@
|
||||
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(javaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation project(':intermediate')
|
||||
implementation project(':virtualmachine')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDir "${project.projectDir}/test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
19
codeGenIntermediate/codeGenIntermediate.iml
Normal file
19
codeGenIntermediate/codeGenIntermediate.iml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -1,4 +1,4 @@
|
||||
package prog8.codegen.experimental
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
@ -10,7 +10,7 @@ import prog8.intermediate.IRCodeInstruction
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.VmDataType
|
||||
|
||||
internal class AssignmentGen(private val codeGen: CodeGen, private val expressionEval: ExpressionGen) {
|
||||
internal class AssignmentGen(private val codeGen: IntermediateCodeGen, private val expressionEval: ExpressionGen) {
|
||||
|
||||
internal fun translate(assignment: PtAssignment): IRCodeChunk {
|
||||
if(assignment.target.children.single() is PtMachineRegister)
|
@ -1,4 +1,4 @@
|
||||
package prog8.codegen.experimental
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
@ -9,7 +9,7 @@ import prog8.intermediate.*
|
||||
import prog8.vm.Syscall
|
||||
|
||||
|
||||
internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: ExpressionGen) {
|
||||
internal class BuiltinFuncGen(private val codeGen: IntermediateCodeGen, private val exprGen: ExpressionGen) {
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunk {
|
||||
return when(call.name) {
|
@ -1,4 +1,4 @@
|
||||
package prog8.codegen.experimental
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StRomSub
|
||||
import prog8.code.StStaticVariable
|
||||
@ -8,7 +8,7 @@ import prog8.code.core.*
|
||||
import prog8.intermediate.*
|
||||
|
||||
|
||||
internal class ExpressionGen(private val codeGen: CodeGen) {
|
||||
internal class ExpressionGen(private val codeGen: IntermediateCodeGen) {
|
||||
fun translateExpression(expr: PtExpression, resultRegister: Int, resultFpRegister: Int): IRCodeChunk {
|
||||
require(codeGen.vmRegisters.peekNext() > resultRegister)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.core.AssemblyError
|
||||
|
||||
internal class RegisterPool {
|
||||
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
|
||||
private var firstFreeFloat: Int=0
|
||||
|
||||
fun peekNext() = firstFree
|
||||
fun peekNextFloat() = firstFreeFloat
|
||||
|
||||
fun nextFree(): Int {
|
||||
val result = firstFree
|
||||
firstFree++
|
||||
if(firstFree>65535)
|
||||
throw AssemblyError("out of virtual registers (int)")
|
||||
return result
|
||||
}
|
||||
|
||||
fun nextFreeFloat(): Int {
|
||||
val result = firstFreeFloat
|
||||
firstFreeFloat++
|
||||
if(firstFreeFloat>65535)
|
||||
throw AssemblyError("out of virtual registers (fp)")
|
||||
return result
|
||||
}
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
package prog8tests.vm.helpers
|
||||
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.IMemSizer
|
178
codeGenIntermediate/test/TestIRPeepholeOpt.kt
Normal file
178
codeGenIntermediate/test/TestIRPeepholeOpt.kt
Normal file
@ -0,0 +1,178 @@
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.intermediate.*
|
||||
|
||||
class TestIRPeepholeOpt: FunSpec({
|
||||
fun makeIRProgram(lines: List<IRCodeLine>): IRProgram {
|
||||
val block = IRBlock("main", null, IRBlock.BlockAlignment.NONE, Position.DUMMY)
|
||||
val sub = IRSubroutine("main.start", emptyList(), null, Position.DUMMY)
|
||||
val chunk = IRCodeChunk(Position.DUMMY)
|
||||
for(line in lines)
|
||||
chunk += line
|
||||
sub += chunk
|
||||
block += sub
|
||||
val st = SymbolTable()
|
||||
val target = VMTarget()
|
||||
val options = CompilationOptions(
|
||||
OutputType.RAW,
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
emptyList(),
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
compTarget = target,
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||
)
|
||||
val prog = IRProgram("test", st, options, target)
|
||||
prog.addBlock(block)
|
||||
return prog
|
||||
}
|
||||
|
||||
fun IRProgram.lines(): List<IRCodeLine> = this.blocks.flatMap { it.subroutines }.flatMap { it.chunks }.flatMap { it.lines }
|
||||
|
||||
test("remove nops") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.JUMP, labelSymbol = "dummy"),
|
||||
IRCodeInstruction(Opcode.NOP),
|
||||
IRCodeInstruction(Opcode.NOP)
|
||||
))
|
||||
irProg.lines().size shouldBe 3
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
irProg.lines().size shouldBe 1
|
||||
}
|
||||
|
||||
test("remove jmp to label below") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.JUMP, labelSymbol = "label"), // removed
|
||||
IRCodeLabel("label"),
|
||||
IRCodeInstruction(Opcode.JUMP, labelSymbol = "label2"), // removed
|
||||
IRCodeInstruction(Opcode.NOP), // removed
|
||||
IRCodeLabel("label2"),
|
||||
IRCodeInstruction(Opcode.JUMP, labelSymbol = "label3"),
|
||||
IRCodeInstruction(Opcode.INC, VmDataType.BYTE, reg1=1),
|
||||
IRCodeLabel("label3")
|
||||
))
|
||||
irProg.lines().size shouldBe 8
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 5
|
||||
(lines[0] as IRCodeLabel).name shouldBe "label"
|
||||
(lines[1] as IRCodeLabel).name shouldBe "label2"
|
||||
(lines[2] as IRCodeInstruction).ins.opcode shouldBe Opcode.JUMP
|
||||
(lines[3] as IRCodeInstruction).ins.opcode shouldBe Opcode.INC
|
||||
(lines[4] as IRCodeLabel).name shouldBe "label3"
|
||||
}
|
||||
|
||||
test("remove double sec/clc") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.SEC),
|
||||
IRCodeInstruction(Opcode.SEC),
|
||||
IRCodeInstruction(Opcode.SEC),
|
||||
IRCodeInstruction(Opcode.CLC),
|
||||
IRCodeInstruction(Opcode.CLC),
|
||||
IRCodeInstruction(Opcode.CLC)
|
||||
))
|
||||
irProg.lines().size shouldBe 6
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 1
|
||||
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.CLC
|
||||
}
|
||||
|
||||
test("push followed by pop") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=42),
|
||||
IRCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=42),
|
||||
IRCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=99),
|
||||
IRCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=222)
|
||||
))
|
||||
irProg.lines().size shouldBe 4
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 1
|
||||
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOADR
|
||||
(lines[0] as IRCodeInstruction).ins.reg1 shouldBe 222
|
||||
(lines[0] as IRCodeInstruction).ins.reg2 shouldBe 99
|
||||
}
|
||||
|
||||
test("remove useless div/mul, add/sub") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRCodeInstruction(Opcode.DIV, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRCodeInstruction(Opcode.DIVS, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRCodeInstruction(Opcode.MUL, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRCodeInstruction(Opcode.MOD, VmDataType.BYTE, reg1=42, value = 2),
|
||||
IRCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 0)
|
||||
))
|
||||
irProg.lines().size shouldBe 10
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace add/sub 1 by inc/dec") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.ADD, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRCodeInstruction(Opcode.SUB, VmDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
irProg.lines().size shouldBe 2
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 2
|
||||
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.INC
|
||||
(lines[1] as IRCodeInstruction).ins.opcode shouldBe Opcode.DEC
|
||||
}
|
||||
|
||||
test("remove useless and/or/xor") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 255),
|
||||
IRCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 65535),
|
||||
IRCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 200),
|
||||
IRCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 60000),
|
||||
IRCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 1),
|
||||
IRCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=42, value = 1)
|
||||
))
|
||||
irProg.lines().size shouldBe 8
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 4
|
||||
}
|
||||
|
||||
test("replace and/or/xor by constant number") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=42, value = 0),
|
||||
IRCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=42, value = 0),
|
||||
IRCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=42, value = 255),
|
||||
IRCodeInstruction(Opcode.OR, VmDataType.WORD, reg1=42, value = 65535)
|
||||
))
|
||||
irProg.lines().size shouldBe 4
|
||||
val opt = IRPeepholeOptimizer(irProg)
|
||||
opt.optimize()
|
||||
val lines = irProg.lines()
|
||||
lines.size shouldBe 4
|
||||
(lines[0] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[1] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[2] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[3] as IRCodeInstruction).ins.opcode shouldBe Opcode.LOAD
|
||||
(lines[0] as IRCodeInstruction).ins.value shouldBe 0
|
||||
(lines[1] as IRCodeInstruction).ins.value shouldBe 0
|
||||
(lines[2] as IRCodeInstruction).ins.value shouldBe 255
|
||||
(lines[3] as IRCodeInstruction).ins.value shouldBe 65535
|
||||
}
|
||||
})
|
@ -27,7 +27,7 @@ compileTestKotlin {
|
||||
dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation project(':intermediate')
|
||||
implementation project(':virtualmachine')
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
@ -5,6 +5,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
@ -13,7 +14,7 @@
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
</component>
|
||||
</module>
|
@ -1,140 +0,0 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IAssemblyProgram
|
||||
import prog8.intermediate.Instruction
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.OpcodesWithAddress
|
||||
import prog8.intermediate.VmDataType
|
||||
import java.io.BufferedWriter
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.bufferedWriter
|
||||
import kotlin.io.path.div
|
||||
|
||||
|
||||
class AssemblyProgram(override val name: String, private val allocations: VariableAllocator
|
||||
) : IAssemblyProgram {
|
||||
|
||||
private val globalInits = mutableListOf<VmCodeLine>()
|
||||
private val blocks = mutableListOf<VmCodeChunk>()
|
||||
|
||||
override fun assemble(options: CompilationOptions): Boolean {
|
||||
val outfile = options.outputDir / ("$name.p8virt")
|
||||
println("write code to $outfile")
|
||||
outfile.bufferedWriter().use { out ->
|
||||
allocations.asVmMemory().forEach { (name, alloc) ->
|
||||
out.write("var ${name.joinToString(".")} $alloc\n")
|
||||
}
|
||||
out.write("------PROGRAM------\n")
|
||||
|
||||
if(!options.dontReinitGlobals) {
|
||||
out.write("; global var inits\n")
|
||||
globalInits.forEach { out.writeLine(it) }
|
||||
}
|
||||
|
||||
out.write("; actual program code\n")
|
||||
blocks.asSequence().flatMap { it.lines }.forEach { line->out.writeLine(line) }
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun BufferedWriter.writeLine(line: VmCodeLine) {
|
||||
when(line) {
|
||||
is VmCodeComment -> write("; ${line.comment}\n")
|
||||
is VmCodeInstruction -> {
|
||||
write(line.ins.toString() + "\n")
|
||||
}
|
||||
is VmCodeLabel -> write("_" + line.name.joinToString(".") + ":\n")
|
||||
is VmCodeInlineAsm -> {
|
||||
// TODO do we have to replace variable names by their allocated address???
|
||||
write(line.assembly+"\n")
|
||||
}
|
||||
is VmCodeInlineBinary -> {
|
||||
write("incbin \"${line.file}\"")
|
||||
if(line.offset!=null)
|
||||
write(",${line.offset}")
|
||||
if(line.length!=null)
|
||||
write(",${line.length}")
|
||||
write("\n")
|
||||
}
|
||||
else -> throw AssemblyError("invalid vm code line")
|
||||
}
|
||||
}
|
||||
|
||||
fun addGlobalInits(chunk: VmCodeChunk) = globalInits.addAll(chunk.lines)
|
||||
fun addBlock(block: VmCodeChunk) = blocks.add(block)
|
||||
fun getBlocks(): List<VmCodeChunk> = blocks
|
||||
}
|
||||
|
||||
sealed class VmCodeLine
|
||||
|
||||
class VmCodeInstruction(
|
||||
opcode: Opcode,
|
||||
type: VmDataType?=null,
|
||||
reg1: Int?=null, // 0-$ffff
|
||||
reg2: Int?=null, // 0-$ffff
|
||||
fpReg1: Int?=null, // 0-$ffff
|
||||
fpReg2: Int?=null, // 0-$ffff
|
||||
value: Int?=null, // 0-$ffff
|
||||
fpValue: Float?=null,
|
||||
labelSymbol: List<String>?=null // alternative to value for branch/call/jump labels
|
||||
): VmCodeLine() {
|
||||
val ins = Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, value, fpValue, labelSymbol)
|
||||
|
||||
init {
|
||||
if(reg1!=null && (reg1<0 || reg1>65536))
|
||||
throw IllegalArgumentException("reg1 out of bounds")
|
||||
if(reg2!=null && (reg2<0 || reg2>65536))
|
||||
throw IllegalArgumentException("reg2 out of bounds")
|
||||
if(fpReg1!=null && (fpReg1<0 || fpReg1>65536))
|
||||
throw IllegalArgumentException("fpReg1 out of bounds")
|
||||
if(fpReg2!=null && (fpReg2<0 || fpReg2>65536))
|
||||
throw IllegalArgumentException("fpReg2 out of bounds")
|
||||
|
||||
if(value!=null && opcode !in OpcodesWithAddress) {
|
||||
when (type) {
|
||||
VmDataType.BYTE -> {
|
||||
if (value < -128 || value > 255)
|
||||
throw IllegalArgumentException("value out of range for byte: $value")
|
||||
}
|
||||
VmDataType.WORD -> {
|
||||
if (value < -32768 || value > 65535)
|
||||
throw IllegalArgumentException("value out of range for word: $value")
|
||||
}
|
||||
VmDataType.FLOAT, null -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class VmCodeLabel(val name: List<String>): VmCodeLine()
|
||||
internal class VmCodeComment(val comment: String): VmCodeLine()
|
||||
|
||||
class VmCodeChunk(initial: VmCodeLine? = null) {
|
||||
val lines = mutableListOf<VmCodeLine>()
|
||||
|
||||
init {
|
||||
if(initial!=null)
|
||||
lines.add(initial)
|
||||
}
|
||||
|
||||
operator fun plusAssign(line: VmCodeLine) {
|
||||
lines.add(line)
|
||||
}
|
||||
|
||||
operator fun plusAssign(chunk: VmCodeChunk) {
|
||||
lines.addAll(chunk.lines)
|
||||
}
|
||||
}
|
||||
|
||||
internal class VmCodeInlineAsm(asm: String): VmCodeLine() {
|
||||
val assembly: String = asm.trimIndent()
|
||||
}
|
||||
|
||||
internal class VmCodeInlineBinary(val file: Path, val offset: UInt?, val length: UInt?): VmCodeLine() {
|
||||
init {
|
||||
require(file.isAbsolute)
|
||||
require(file.toFile().isFile)
|
||||
}
|
||||
}
|
@ -1,249 +0,0 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.VmDataType
|
||||
|
||||
internal class AssignmentGen(private val codeGen: CodeGen, private val expressionEval: ExpressionGen) {
|
||||
|
||||
internal fun translate(assignment: PtAssignment): VmCodeChunk {
|
||||
if(assignment.target.children.single() is PtMachineRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
return if (assignment.isInplaceAssign)
|
||||
translateInplaceAssign(assignment)
|
||||
else
|
||||
translateRegularAssign(assignment)
|
||||
}
|
||||
|
||||
private fun translateInplaceAssign(assignment: PtAssignment): VmCodeChunk {
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
|
||||
return if(ident!=null) {
|
||||
val address = codeGen.allocations.get(ident.targetName)
|
||||
assignSelfInMemory(address, assignment.value, assignment)
|
||||
} else if(memory != null) {
|
||||
if(memory.address is PtNumber)
|
||||
assignSelfInMemory((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
||||
else
|
||||
fallbackAssign(assignment)
|
||||
} else if(array!=null) {
|
||||
// NOTE: naive fallback assignment here will sometimes generate code that loads the index value multiple times
|
||||
// in a register. It's way too much work to optimize that here - instead, we trust that the generated IL assembly
|
||||
// will be optimized later and have the double assignments removed.
|
||||
fallbackAssign(assignment)
|
||||
} else {
|
||||
fallbackAssign(assignment)
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignSelfInMemory(
|
||||
address: Int,
|
||||
value: PtExpression,
|
||||
origAssign: PtAssignment
|
||||
): VmCodeChunk {
|
||||
val vmDt = codeGen.vmType(value.type)
|
||||
val code = VmCodeChunk()
|
||||
when(value) {
|
||||
is PtIdentifier -> return code // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address)
|
||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, origAssign)
|
||||
is PtMemoryByte -> {
|
||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
||||
code // do nothing, mem=mem null assignment.
|
||||
else {
|
||||
// read and write a (i/o) memory location to itself.
|
||||
val tempReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
||||
code
|
||||
}
|
||||
}
|
||||
else -> return fallbackAssign(origAssign)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun fallbackAssign(origAssign: PtAssignment): VmCodeChunk {
|
||||
if (codeGen.options.slowCodegenWarnings)
|
||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
||||
return translateRegularAssign(origAssign)
|
||||
}
|
||||
|
||||
private fun inplaceBinexpr(
|
||||
operator: String,
|
||||
operand: PtExpression,
|
||||
vmDt: VmDataType,
|
||||
signed: Boolean,
|
||||
address: Int,
|
||||
origAssign: PtAssignment
|
||||
): VmCodeChunk {
|
||||
when(operator) {
|
||||
"+" -> return expressionEval.operatorPlusInplace(address, vmDt, operand)
|
||||
"-" -> return expressionEval.operatorMinusInplace(address, vmDt, operand)
|
||||
"*" -> return expressionEval.operatorMultiplyInplace(address, vmDt, operand)
|
||||
"/" -> return expressionEval.operatorDivideInplace(address, vmDt, signed, operand)
|
||||
"|" -> return expressionEval.operatorOrInplace(address, vmDt, operand)
|
||||
"&" -> return expressionEval.operatorAndInplace(address, vmDt, operand)
|
||||
"^" -> return expressionEval.operatorXorInplace(address, vmDt, operand)
|
||||
"<<" -> return expressionEval.operatorShiftLeftInplace(address, vmDt, operand)
|
||||
">>" -> return expressionEval.operatorShiftRightInplace(address, vmDt, signed, operand)
|
||||
else -> {}
|
||||
}
|
||||
return fallbackAssign(origAssign)
|
||||
}
|
||||
|
||||
private fun inplacePrefix(operator: String, vmDt: VmDataType, address: Int): VmCodeChunk {
|
||||
val code= VmCodeChunk()
|
||||
when(operator) {
|
||||
"+" -> { }
|
||||
"-" -> {
|
||||
code += VmCodeInstruction(Opcode.NEGM, vmDt, value = address)
|
||||
}
|
||||
"~" -> {
|
||||
val regMask = codeGen.vmRegisters.nextFree()
|
||||
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
||||
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translateRegularAssign(assignment: PtAssignment): VmCodeChunk {
|
||||
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
val vmDt = codeGen.vmType(assignment.value.type)
|
||||
|
||||
val code = VmCodeChunk()
|
||||
var resultRegister = -1
|
||||
var resultFpRegister = -1
|
||||
val zero = codeGen.isZero(assignment.value)
|
||||
if(!zero) {
|
||||
// calculate the assignment value
|
||||
if (vmDt == VmDataType.FLOAT) {
|
||||
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
|
||||
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
|
||||
} else {
|
||||
resultRegister = if (assignment.value is PtMachineRegister) {
|
||||
(assignment.value as PtMachineRegister).register
|
||||
} else {
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(assignment.value, reg, -1)
|
||||
reg
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ident!=null) {
|
||||
val address = codeGen.allocations.get(ident.targetName)
|
||||
code += if(zero) {
|
||||
VmCodeInstruction(Opcode.STOREZM, vmDt, value = address)
|
||||
} else {
|
||||
if (vmDt == VmDataType.FLOAT)
|
||||
VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value = address)
|
||||
else
|
||||
VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value = address)
|
||||
}
|
||||
}
|
||||
else if(array!=null) {
|
||||
val variable = array.variable.targetName
|
||||
var variableAddr = codeGen.allocations.get(variable)
|
||||
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
||||
|
||||
if(array.variable.type==DataType.UWORD) {
|
||||
// indexing a pointer var instead of a real array or string
|
||||
if(itemsize!=1)
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
if(array.index.type!=DataType.UBYTE)
|
||||
throw AssemblyError("non-array var indexing requires bytes index")
|
||||
val idxReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(array.index, idxReg, -1)
|
||||
if(zero) {
|
||||
// there's no STOREZIX instruction
|
||||
resultRegister = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=0)
|
||||
}
|
||||
code += VmCodeInstruction(Opcode.STOREIX, vmDt, reg1=resultRegister, reg2=idxReg, value = variableAddr)
|
||||
return code
|
||||
}
|
||||
|
||||
val fixedIndex = constIntValue(array.index)
|
||||
if(zero) {
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=variableAddr)
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg)
|
||||
code += VmCodeInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, value=variableAddr)
|
||||
}
|
||||
} else {
|
||||
if(vmDt== VmDataType.FLOAT) {
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value=variableAddr)
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg)
|
||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
|
||||
}
|
||||
} else {
|
||||
if(fixedIndex!=null) {
|
||||
variableAddr += fixedIndex*itemsize
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value=variableAddr)
|
||||
} else {
|
||||
val indexReg = codeGen.vmRegisters.nextFree()
|
||||
code += loadIndexReg(array, itemsize, indexReg)
|
||||
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(memory!=null) {
|
||||
require(vmDt== VmDataType.BYTE)
|
||||
if(zero) {
|
||||
if(memory.address is PtNumber) {
|
||||
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt())
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
|
||||
}
|
||||
} else {
|
||||
if(memory.address is PtNumber) {
|
||||
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += expressionEval.translateExpression(memory.address, addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird assigntarget")
|
||||
return code
|
||||
}
|
||||
|
||||
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(itemsize==1) {
|
||||
code += expressionEval.translateExpression(array.index, indexReg, -1)
|
||||
}
|
||||
else {
|
||||
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
|
||||
mult.children += array.index
|
||||
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
|
||||
code += expressionEval.translateExpression(mult, indexReg, -1)
|
||||
}
|
||||
return code
|
||||
}
|
||||
}
|
@ -1,369 +0,0 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.VmDataType
|
||||
import prog8.vm.Syscall
|
||||
|
||||
internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: ExpressionGen) {
|
||||
|
||||
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
return when(call.name) {
|
||||
"any" -> funcAny(call, resultRegister)
|
||||
"all" -> funcAll(call, resultRegister)
|
||||
"abs" -> funcAbs(call, resultRegister)
|
||||
"cmp" -> funcCmp(call)
|
||||
"sgn" -> funcSgn(call, resultRegister)
|
||||
"sqrt16" -> funcSqrt16(call, resultRegister)
|
||||
"pop" -> funcPop(call)
|
||||
"popw" -> funcPopw(call)
|
||||
"push" -> funcPush(call)
|
||||
"pushw" -> funcPushw(call)
|
||||
"rsave",
|
||||
"rsavex",
|
||||
"rrestore",
|
||||
"rrestorex" -> VmCodeChunk() // vm doesn't have registers to save/restore
|
||||
"rnd" -> funcRnd(resultRegister)
|
||||
"rndw" -> funcRndw(resultRegister)
|
||||
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
||||
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
||||
"msb" -> funcMsb(call, resultRegister)
|
||||
"lsb" -> funcLsb(call, resultRegister)
|
||||
"memory" -> funcMemory(call, resultRegister)
|
||||
"peek" -> funcPeek(call, resultRegister)
|
||||
"peekw" -> funcPeekW(call, resultRegister)
|
||||
"poke" -> funcPoke(call)
|
||||
"pokew" -> funcPokeW(call)
|
||||
"pokemon" -> VmCodeChunk()
|
||||
"mkword" -> funcMkword(call, resultRegister)
|
||||
"sort" -> funcSort(call)
|
||||
"reverse" -> funcReverse(call)
|
||||
"rol" -> funcRolRor(Opcode.ROXL, call, resultRegister)
|
||||
"ror" -> funcRolRor(Opcode.ROXR, call, resultRegister)
|
||||
"rol2" -> funcRolRor(Opcode.ROL, call, resultRegister)
|
||||
"ror2" -> funcRolRor(Opcode.ROR, call, resultRegister)
|
||||
else -> throw AssemblyError("missing builtinfunc for ${call.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCmp(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val leftRegister = codeGen.vmRegisters.nextFree()
|
||||
val rightRegister = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
|
||||
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val code = VmCodeChunk()
|
||||
val syscall =
|
||||
when (array.dt) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> Syscall.ANY_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> Syscall.ANY_WORD
|
||||
DataType.ARRAY_F -> Syscall.ANY_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value = syscall.ordinal)
|
||||
if (resultRegister != 0)
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B -> Syscall.ALL_BYTE
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W -> Syscall.ALL_WORD
|
||||
DataType.ARRAY_F -> Syscall.ALL_FLOAT
|
||||
else -> throw IllegalArgumentException("weird type")
|
||||
}
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=syscall.ordinal)
|
||||
if(resultRegister!=0)
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val sourceDt = call.args.single().type
|
||||
if(sourceDt!=DataType.UWORD) {
|
||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
when (sourceDt) {
|
||||
DataType.UBYTE -> {
|
||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=compareReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=compareReg, value=0x80)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
|
||||
code += VmCodeLabel(notNegativeLabel)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.LOADR, VmDataType.WORD, reg1=compareReg, reg2=resultRegister)
|
||||
code += VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=compareReg, value=0x8000)
|
||||
code += VmCodeInstruction(Opcode.BZ, VmDataType.WORD, reg1=compareReg, labelSymbol = notNegativeLabel)
|
||||
code += VmCodeInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
|
||||
code += VmCodeLabel(notNegativeLabel)
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPop(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
|
||||
code += assignRegisterTo(call.args.single(), reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPopw(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
|
||||
code += assignRegisterTo(call.args.single(), reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPush(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPushw(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val reg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), reg, -1)
|
||||
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val sortSyscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> Syscall.REVERSE_BYTES
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> Syscall.REVERSE_WORDS
|
||||
DataType.ARRAY_F -> Syscall.REVERSE_FLOATS
|
||||
else -> throw IllegalArgumentException("weird type to reverse")
|
||||
}
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val sortSyscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB -> Syscall.SORT_UBYTE
|
||||
DataType.ARRAY_B -> Syscall.SORT_BYTE
|
||||
DataType.ARRAY_UW -> Syscall.SORT_UWORD
|
||||
DataType.ARRAY_W -> Syscall.SORT_WORD
|
||||
DataType.STR -> Syscall.SORT_UBYTE
|
||||
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
|
||||
else -> throw IllegalArgumentException("weird type to sort")
|
||||
}
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], 0, -1)
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
|
||||
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val msbReg = codeGen.vmRegisters.nextFree()
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], msbReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPokeW(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
|
||||
}
|
||||
} else {
|
||||
val valueReg = codeGen.vmRegisters.nextFree()
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPoke(call: PtBuiltinFunctionCall): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(codeGen.isZero(call.args[1])) {
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
|
||||
}
|
||||
} else {
|
||||
val valueReg = codeGen.vmRegisters.nextFree()
|
||||
if (call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args[0], addressReg, -1)
|
||||
code += exprGen.translateExpression(call.args[1], valueReg, -1)
|
||||
code += VmCodeInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
if(call.args[0] is PtNumber) {
|
||||
val address = (call.args[0] as PtNumber).number.toInt()
|
||||
code += VmCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address)
|
||||
} else {
|
||||
val addressReg = codeGen.vmRegisters.nextFree()
|
||||
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
|
||||
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRnd(resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRndw(resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val name = (call.args[0] as PtString).value
|
||||
val size = (call.args[1] as PtNumber).number.toUInt()
|
||||
val align = (call.args[2] as PtNumber).number.toUInt()
|
||||
val prefixedName = "prog8_memoryslab_$name"
|
||||
val memorySlab = codeGen.allocations.getMemorySlab(prefixedName) ?: codeGen.allocations.allocateMemorySlab(prefixedName, size, align, call.position)
|
||||
val code = VmCodeChunk()
|
||||
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, value=memorySlab.allocatedAddress!!.toInt())
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
|
||||
code += VmCodeInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
|
||||
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
|
||||
return code
|
||||
}
|
||||
|
||||
private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
|
||||
val vmDt = codeGen.vmType(call.args[0].type)
|
||||
val code = VmCodeChunk()
|
||||
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
|
||||
code += VmCodeInstruction(opcode, vmDt, reg1=resultRegister)
|
||||
code += assignRegisterTo(call.args[0], resultRegister)
|
||||
return code
|
||||
}
|
||||
|
||||
private fun assignRegisterTo(target: PtExpression, register: Int): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val assignment = PtAssignment(target.position)
|
||||
val assignTarget = PtAssignTarget(target.position)
|
||||
assignTarget.children.add(target)
|
||||
assignment.children.add(assignTarget)
|
||||
assignment.children.add(PtMachineRegister(register, target.type, target.position))
|
||||
code += codeGen.translateNode(assignment)
|
||||
return code
|
||||
}
|
||||
}
|
@ -1,814 +0,0 @@
|
||||
package prog8.codegen.virtual
|
||||
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.intermediate.Opcode
|
||||
import prog8.intermediate.VmDataType
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
internal class VmRegisterPool {
|
||||
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
|
||||
private var firstFreeFloat: Int=0
|
||||
|
||||
fun peekNext() = firstFree
|
||||
fun peekNextFloat() = firstFreeFloat
|
||||
|
||||
fun nextFree(): Int {
|
||||
val result = firstFree
|
||||
firstFree++
|
||||
if(firstFree>65535)
|
||||
throw AssemblyError("out of virtual registers (int)")
|
||||
return result
|
||||
}
|
||||
|
||||
fun nextFreeFloat(): Int {
|
||||
val result = firstFreeFloat
|
||||
firstFreeFloat++
|
||||
if(firstFreeFloat>65535)
|
||||
throw AssemblyError("out of virtual registers (fp)")
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CodeGen(internal val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter
|
||||
): IAssemblyGenerator {
|
||||
|
||||
internal val allocations = VariableAllocator(symbolTable, program)
|
||||
private val expressionEval = ExpressionGen(this)
|
||||
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
|
||||
private val assignmentGen = AssignmentGen(this, expressionEval)
|
||||
internal val vmRegisters = VmRegisterPool()
|
||||
|
||||
override fun compileToAssembly(): IAssemblyProgram? {
|
||||
val vmprog = AssemblyProgram(program.name, allocations)
|
||||
|
||||
if(!options.dontReinitGlobals) {
|
||||
// collect global variables initializers
|
||||
program.allBlocks().forEach {
|
||||
val code = VmCodeChunk()
|
||||
it.children.filterIsInstance<PtAssignment>().forEach { assign -> code += assignmentGen.translate(assign) }
|
||||
vmprog.addGlobalInits(code)
|
||||
}
|
||||
}
|
||||
|
||||
if(options.symbolDefs.isNotEmpty())
|
||||
throw AssemblyError("virtual target doesn't support symbols defined on the commandline")
|
||||
if(options.evalStackBaseAddress!=null)
|
||||
throw AssemblyError("virtual target doesn't use eval-stack")
|
||||
|
||||
for (block in program.allBlocks()) {
|
||||
vmprog.addBlock(translate(block))
|
||||
}
|
||||
|
||||
if(options.optimize) {
|
||||
val optimizer = VmPeepholeOptimizer(vmprog)
|
||||
optimizer.optimize()
|
||||
}
|
||||
|
||||
println("Vm codegen: memory usage=${allocations.freeMem}")
|
||||
|
||||
return vmprog
|
||||
}
|
||||
|
||||
|
||||
internal fun translateNode(node: PtNode): VmCodeChunk {
|
||||
val code = when(node) {
|
||||
is PtBlock -> translate(node)
|
||||
is PtSub -> translate(node)
|
||||
is PtAsmSub -> translate(node)
|
||||
is PtScopeVarsDecls -> VmCodeChunk() // vars should be looked up via symbol table
|
||||
is PtVariable -> VmCodeChunk() // var should be looked up via symbol table
|
||||
is PtMemMapped -> VmCodeChunk() // memmapped var should be looked up via symbol table
|
||||
is PtConstant -> VmCodeChunk() // constants have all been folded into the code
|
||||
is PtAssignment -> assignmentGen.translate(node)
|
||||
is PtNodeGroup -> translateGroup(node.children)
|
||||
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
|
||||
is PtFunctionCall -> expressionEval.translate(node, 0, 0)
|
||||
is PtNop -> VmCodeChunk()
|
||||
is PtReturn -> translate(node)
|
||||
is PtJump -> translate(node)
|
||||
is PtWhen -> translate(node)
|
||||
is PtForLoop -> translate(node)
|
||||
is PtIfElse -> translate(node)
|
||||
is PtPostIncrDecr -> translate(node)
|
||||
is PtRepeatLoop -> translate(node)
|
||||
is PtLabel -> VmCodeChunk(VmCodeLabel(node.scopedName))
|
||||
is PtBreakpoint -> VmCodeChunk(VmCodeInstruction(Opcode.BREAKPOINT))
|
||||
is PtConditionalBranch -> translate(node)
|
||||
is PtInlineAssembly -> VmCodeChunk(VmCodeInlineAsm(node.assembly))
|
||||
is PtIncludeBinary -> VmCodeChunk(VmCodeInlineBinary(node.file, node.offset, node.length))
|
||||
is PtAddressOf,
|
||||
is PtContainmentCheck,
|
||||
is PtMemoryByte,
|
||||
is PtProgram,
|
||||
is PtArrayIndexer,
|
||||
is PtBinaryExpression,
|
||||
is PtIdentifier,
|
||||
is PtWhenChoice,
|
||||
is PtPrefix,
|
||||
is PtRange,
|
||||
is PtAssignTarget,
|
||||
is PtTypeCast,
|
||||
is PtSubroutineParameter,
|
||||
is PtNumber,
|
||||
is PtArray,
|
||||
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
|
||||
else -> TODO("missing codegen for $node")
|
||||
}
|
||||
if(code.lines.isNotEmpty() && node.position.line!=0)
|
||||
code.lines.add(0, VmCodeComment(node.position.toString()))
|
||||
return code
|
||||
}
|
||||
|
||||
private fun translate(branch: PtConditionalBranch): VmCodeChunk {
|
||||
val code = VmCodeChunk()
|
||||
val elseLabel = createLabelName()
|
||||
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
|
||||
code += when(branch.condition) {
|
||||
BranchCondition.CS -> VmCodeInstruction(Opcode.BSTCC, labelSymbol = elseLabel)
|
||||
BranchCondition.CC -> VmCodeInstruction(Opcode.BSTCS, labelSymbol = elseLabel)
|
||||