IR: adding LOADFIELD and STOREFIELD instructions

This commit is contained in:
Irmen de Jong
2025-06-07 22:06:45 +02:00
parent 59387b2ae8
commit e6bab3ceeb
9 changed files with 271 additions and 126 deletions
@@ -135,7 +135,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
if(pointerDeref!=null) {
val inplaceInstrs = mutableListOf<IRCodeChunkBase>()
val addressReg = codeGen.evaluatePointerAddressIntoReg(inplaceInstrs, pointerDeref)
val (addressReg, fieldOffset) = codeGen.evaluatePointerAddressIntoReg(inplaceInstrs, pointerDeref)
val oldvalueReg = codeGen.registers.next(targetDt)
var operandTr = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
if(augAssign.operator!="or=" && augAssign.operator!="and=") {
@@ -144,7 +144,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
inplaceInstrs += operandTr.chunks
}
if(targetDt== IRDataType.FLOAT) {
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg), null)
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, fpReg1 = oldvalueReg, reg1 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, fpReg1 = oldvalueReg, fpReg2 = operandTr.resultFpReg), null)
@@ -155,7 +158,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
else -> throw AssemblyError("invalid augmented assign operator for floats ${augAssign.operator}")
}
} else {
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg), null)
if(fieldOffset>0u)
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADFIELD, targetDt, reg1 = oldvalueReg, reg2 = addressReg, immediate = fieldOffset.toInt()), null)
else
addInstr(inplaceInstrs, IRInstruction(Opcode.LOADI, targetDt, reg1 = oldvalueReg, reg2 = addressReg), null)
when(augAssign.operator) {
"+=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.ADDR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
"-=" -> addInstr(inplaceInstrs, IRInstruction(Opcode.SUBR, targetDt, reg1 = oldvalueReg, reg2 = operandTr.resultReg), null)
@@ -191,8 +197,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
}
}
// TODO this could probably be omitted if we had indirect addressing modes for all above inplace instructions:
codeGen.storeValueAtPointersLocation(inplaceInstrs, addressReg, pointerDeref.type, false, oldvalueReg)
codeGen.storeValueAtPointersLocation(inplaceInstrs, addressReg, fieldOffset, pointerDeref.type, false, oldvalueReg)
chunks = inplaceInstrs
} else {
chunks = when (augAssign.operator) {
@@ -516,31 +521,33 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
val ptrWithOffset = targetMemory.address as? PtBinaryExpression
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
if((ptrWithOffset.right as? PtNumber)?.number?.toInt() in 0..255) {
// STOREIX only works with byte index.
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = ptrWithOffset.right.asConstInteger())
it += IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=offsetReg, labelSymbol = ptrName)
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.STOREFIELD, IRDataType.BYTE, reg1=valueRegister, reg2=pointerReg, immediate = constOffset)
}
return result
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// STOREIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
expressionEval.translateExpression(offsetTypecast.value)
else
expressionEval.translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return result
}
}
val offsetTypecast = ptrWithOffset?.right as? PtTypeCast
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier
&& (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// STOREIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
expressionEval.translateExpression(offsetTypecast.value)
else
expressionEval.translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.STOREIX, IRDataType.BYTE, reg1=valueRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return result
}
val tr = expressionEval.translateExpression(targetMemory.address)
val addressReg = tr.resultReg
addToResult(result, tr, tr.resultReg, -1)
@@ -551,9 +558,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return result
}
else if(targetPointerDeref!=null) {
val addressReg = codeGen.evaluatePointerAddressIntoReg(result, targetPointerDeref)
val (addressReg, offset) = codeGen.evaluatePointerAddressIntoReg(result, targetPointerDeref)
val actualValueReg = if(targetPointerDeref.type.isFloat) valueFpRegister else valueRegister
codeGen.storeValueAtPointersLocation(result, addressReg, targetPointerDeref.type, zero, actualValueReg)
codeGen.storeValueAtPointersLocation(result, addressReg, offset, targetPointerDeref.type, zero, actualValueReg)
return result
}
else
@@ -584,7 +591,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
result += code
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=pointerReg, reg2=indexReg), null)
}
codeGen.storeValueAtPointersLocation(result, pointerReg, targetIdent.type.dereference(), true, -1)
codeGen.storeValueAtPointersLocation(result, pointerReg, 0u, targetIdent.type.dereference(), true, -1)
} else {
if(constIndex!=null) {
val offset = eltSize * constIndex
@@ -595,7 +602,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1=pointerReg, reg2=indexReg), null)
}
val realValueReg = if(targetDt == IRDataType.FLOAT) valueFpRegister else valueRegister
codeGen.storeValueAtPointersLocation(result, pointerReg, targetIdent.type.dereference(), false, realValueReg)
codeGen.storeValueAtPointersLocation(result, pointerReg, 0u, targetIdent.type.dereference(), false, realValueReg)
}
}
@@ -159,24 +159,31 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
pointerReg = tr.resultReg
}
result += traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
when {
deref.type.isByteOrBool -> {
val resultReg = codeGen.registers.next(IRDataType.BYTE)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.BYTE, reg1 = resultReg, reg2 = pointerReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
deref.type.isWord || deref.type.isPointer -> {
val resultReg = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = resultReg, reg2 = pointerReg), null)
return ExpressionCodeResult(result, IRDataType.WORD, resultReg, -1)
}
deref.type.isFloat -> {
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
result += instructions
if(offset==0u) {
val irdt = irType(deref.type)
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg), null)
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADI, irdt, reg1 = resultReg, reg2 = pointerReg), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
else -> throw AssemblyError("unsupported dereference type ${deref.type} at ${deref.position}")
}
// load field with offset
return if(deref.type.isFloat) {
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOADFIELD, IRDataType.FLOAT, fpReg1 = resultReg, reg1 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultReg)
} else {
val irdt = irType(deref.type)
val resultReg = codeGen.registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOADFIELD, irdt, reg1 = resultReg, reg2 = pointerReg, immediate = offset.toInt()), null)
ExpressionCodeResult(result, irdt, resultReg, -1)
}
}
@@ -300,7 +307,9 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
require(vmDt==IRDataType.WORD)
val pointerTr = translateExpression(expr.dereference!!.startpointer)
result += pointerTr.chunks
result += traverseRestOfDerefChainToCalculateFinalAddress(expr.dereference!!, pointerTr.resultReg)
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(expr.dereference!!, pointerTr.resultReg)
result += instructions
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = offset.toInt()), null)
return ExpressionCodeResult(result, vmDt, pointerTr.resultReg, -1)
}
}
@@ -316,31 +325,32 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
val ptrWithOffset = mem.address as? PtBinaryExpression
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
if((ptrWithOffset.right as? PtNumber)?.number?.toInt() in 0..255) {
// LOADIX only works with byte index.
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val offsetReg = codeGen.registers.next(IRDataType.BYTE)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=offsetReg, immediate = ptrWithOffset.right.asConstInteger())
it += IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=offsetReg, labelSymbol = ptrName)
if(ptrWithOffset!=null) {
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier) {
val constOffset = (ptrWithOffset.right as? PtNumber)?.number?.toInt()
if(constOffset in 0..255) {
val ptrName = (ptrWithOffset.left as PtIdentifier).name
val pointerReg = codeGen.registers.next(IRDataType.WORD)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1 = pointerReg, labelSymbol = ptrName)
it += IRInstruction(Opcode.LOADFIELD, IRDataType.BYTE, reg1=resultRegister, reg2=pointerReg, immediate = constOffset)
}
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val offsetTypecast = ptrWithOffset.right as? PtTypeCast
if(ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier && (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
translateExpression(offsetTypecast.value)
else
translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
}
val offsetTypecast = ptrWithOffset?.right as? PtTypeCast
if(ptrWithOffset!=null && ptrWithOffset.operator=="+" && ptrWithOffset.left is PtIdentifier
&& (ptrWithOffset.right.type.isByte || offsetTypecast?.value?.type?.isByte==true)) {
// LOADIX only works with byte index.
val tr = if(offsetTypecast?.value?.type?.isByte==true)
translateExpression(offsetTypecast.value)
else
translateExpression(ptrWithOffset.right)
addToResult(result, tr, tr.resultReg, -1)
val ptrName = (ptrWithOffset.left as PtIdentifier).name
addInstr(result, IRInstruction(Opcode.LOADIX, IRDataType.BYTE, reg1=resultRegister, reg2=tr.resultReg, labelSymbol = ptrName), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
}
val tr = translateExpression(mem.address)
addToResult(result, tr, tr.resultReg, -1)
@@ -1635,11 +1645,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
return ExpressionCodeResult(result, vmDt, resultReg, resultFpReg)
}
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): IRCodeChunks {
internal fun traverseRestOfDerefChainToCalculateFinalAddress(targetPointerDeref: PtPointerDeref, pointerReg: Int): Pair<IRCodeChunks, UInt> {
// returns instructions to calculate the pointer address, and the offset into the struct it points to
// so that LOADFIELD and STOREFIELD opcodes can be used instead of having an explicit extra ADD
val result = mutableListOf<IRCodeChunkBase>()
if(targetPointerDeref.chain.isEmpty())
return result // nothing to do; there's no deref chain
return result to 0u // nothing to do; there's no deref chain
var struct: StStruct? = null
if(targetPointerDeref.startpointer.type.subType!=null)
@@ -1661,15 +1674,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val field = targetPointerDeref.chain.last()
val fieldinfo = struct!!.getField(field, codeGen.program.memsizer)
if(fieldinfo.second>0u) {
// add the field offset
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// add the field offset
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = fieldinfo.second.toInt()), null)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
return result to 0u
} else {
return result to fieldinfo.second
}
}
if(targetPointerDeref.derefLast) {
require(fieldinfo.first.isPointer)
// LOADI has an exception to allow reg1 and reg2 to be the same, so we can avoid using extra temporary registers and LOADS
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg), null)
}
return result
return result to 0u
}
}
@@ -1986,42 +1986,45 @@ class IRCodeGen(
return chunk
}
internal fun evaluatePointerAddressIntoReg(result: MutableList<IRCodeChunkBase>, deref: PtPointerDeref): Int {
internal fun evaluatePointerAddressIntoReg(result: MutableList<IRCodeChunkBase>, deref: PtPointerDeref): Pair<Int, UInt> {
// calculates the pointer address and returns the register it's in + remaining offset into the struct (so that LOADFIELD/STOREFIELD instructions can be used)
val pointerTr = expressionEval.translateExpression(deref.startpointer)
result += pointerTr.chunks
result += expressionEval.traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerTr.resultReg)
return pointerTr.resultReg
val (instructions, offset) = expressionEval.traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerTr.resultReg)
result += instructions
return pointerTr.resultReg to offset
}
internal fun storeValueAtPointersLocation(result: MutableList<IRCodeChunkBase>, addressReg: Int, type: DataType, valueIsZero: Boolean, valueRegister: Int) {
val instr = when {
type.isByteOrBool -> {
if(valueIsZero)
IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = addressReg)
else
IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueRegister, reg2 = addressReg)
internal fun storeValueAtPointersLocation(result: MutableList<IRCodeChunkBase>, addressReg: Int, offset: UInt, type: DataType, valueIsZero: Boolean, existingValueRegister: Int) {
if(offset==0u) {
val irdt = irType(type)
val instr = if(type.isFloat) {
if (valueIsZero) IRInstruction(Opcode.STOREZI, IRDataType.FLOAT, reg1 = addressReg)
else IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = existingValueRegister, reg1 = addressReg)
} else {
if (valueIsZero) IRInstruction(Opcode.STOREZI, irdt, reg1 = addressReg)
else IRInstruction(Opcode.STOREI, irdt, reg1 = existingValueRegister, reg2 = addressReg)
}
type.isWord -> {
if(valueIsZero)
IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = addressReg)
else
IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueRegister, reg2 = addressReg)
}
type.isFloat -> {
if(valueIsZero)
IRInstruction(Opcode.STOREZI, IRDataType.FLOAT, reg1 = addressReg)
else
IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1 = valueRegister, reg1 = addressReg)
}
type.isPointer -> {
// stores value into the pointer itself
if(valueIsZero)
IRInstruction(Opcode.STOREZI, IRDataType.WORD, reg1 = addressReg)
else
IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1 = valueRegister, reg2 = addressReg)
}
else -> throw AssemblyError("weird pointer dereference type $type")
addInstr(result, instr, null)
return
}
// store with field offset
var valueRegister = existingValueRegister
val irdt = irType(type)
if(valueIsZero && valueRegister<0) {
if(type.isFloat) {
valueRegister = registers.next(IRDataType.FLOAT)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.FLOAT, fpReg1 = valueRegister, immediateFp = 0.0), null)
} else {
valueRegister = registers.next(irdt)
addInstr(result, IRInstruction(Opcode.LOAD, irdt, reg1 = valueRegister, immediate = 0), null)
}
}
val instr = if (type.isFloat)
IRInstruction(Opcode.STOREFIELD, IRDataType.FLOAT, fpReg1 = valueRegister, reg1 = addressReg, immediate = offset.toInt())
else
IRInstruction(Opcode.STOREFIELD, irdt, reg1 = valueRegister, reg2 = addressReg, immediate = offset.toInt())
addInstr(result, instr, null)
}
+45
View File
@@ -181,6 +181,51 @@ other {
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null
}
test("struct pointers") {
val src="""
%option enable_floats
main {
struct Node {
bool bb
float f
word w
^^Node next
}
sub start() {
^^Node n1 = Node(false, 1.1, 1111, 0)
^^Node n2 = Node(false, 2.2, 2222, 0)
^^Node n3 = Node(true, 3.3, 3333, 0)
n1.next = n2
n2.next = n3
n3.next = 0
bool bb = n1.bb
float f = n1.f
uword next = n1.next
word w = n1.w
bb = n2.bb
f = n2.f
next = n2.next
w = n2.w
n1.next.next.bb = false
n1.next.next.f = 42.999
n1.next.next.w = 5555
n1.next.next.w++
bb = n1.next.next.bb
f = n1.next.next.f
}
}
"""
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
// TODO compileText(C64Target(), false, src, outputDir) shouldNotBe null
}
test("pointer walking using simple dot notation should be equivalent to explicit dereference chain") {
val src="""
main {
+2 -2
View File
@@ -100,8 +100,8 @@ Future Things and Ideas
IR/VM
-----
- add LOADFIELD/STOREFIELD instructions that encode the field offset as immediate value so we avoid a separate ADD instruction to calculate the address
- change the instruction format so an indirect register (a pointer) can be used, at least for the inplace assignment operators that operate on pointer
- possible to use LOADFIELD/STOREFIELD instructions more?
- change the instruction format so an indirect register (a pointer) can be used more often, at least for the inplace assignment operators that operate on pointer
- getting it in shape for code generation...: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
- fix call() return value handling (... what's wrong with it again?)
- encode asmsub/extsub clobber info in the call , or maybe include these definitions in the p8ir file itself too. (return registers are already encoded in the CALL instruction)
+34 -16
View File
@@ -2,31 +2,49 @@
%import textio
main {
ubyte[] array = [11,22,33,44,55]
float[] flarray = [1.1, 2.2, 3.3, 4.4, 5.5]
struct Node {
bool bb
float f
word w
^^Node next
}
sub start() {
txt.print_ub(array[2])
txt.nl()
cx16.r0L = 2
txt.print_ub(array[cx16.r0L])
^^Node n1 = Node(false, 1.1, 1111, 0)
^^Node n2 = Node(false, 2.2, 2222, 0)
^^Node n3 = Node(true, 3.3, 3333, 0)
n1.next = n2
n2.next = n3
n3.next = 0
txt.print_bool(n1.bb)
txt.spc()
txt.print_f(n1.f)
txt.spc()
txt.print_uw(n1.next)
txt.nl()
txt.print_f(flarray[2])
txt.nl()
cx16.r0L = 2
txt.print_f(flarray[cx16.r0L])
txt.print_bool(n2.bb)
txt.spc()
txt.print_f(n2.f)
txt.spc()
txt.print_uw(n2.next)
txt.nl()
^^ubyte bptr = &array[2]
^^float fptr = &flarray[2]
txt.print_ub(bptr[2])
txt.print_bool(n3.bb)
txt.spc()
txt.print_ub(bptr[cx16.r0L])
txt.print_f(n3.f)
txt.spc()
txt.print_uw(n3.next)
txt.nl()
txt.print_f(fptr[2])
n1.next.next.bb = false
n1.next.next.f = 42.999999
txt.print_bool(n1.next.next.bb)
txt.spc()
txt.print_f(fptr[cx16.r0L])
txt.print_f(n1.next.next.f)
txt.nl()
}
}
@@ -42,14 +42,15 @@ loadi reg1, reg2 - load reg1 with value at memory indirect,
loadx reg1, reg2, address - load reg1 with value at memory address indexed by value in reg2 (0-255, a byte)
loadix reg1, reg2, pointeraddr - load reg1 with value at memory indirect, pointed to by pointeraddr indexed by value in reg2 (0-255, a byte)
loadr reg1, reg2 - load reg1 with value in register reg2, "reg1 = reg2"
loadfield reg1, reg2, value - load reg1 with value in memory pointed to by reg2 + value 0-255 (gets a field from a pointer to a struct, like LOADI, but with additional field offset 0-255)
loadha reg1 - load cpu hardware register A into reg1.b
loadhx reg1 - load cpu hardware register X into reg1.b
loadhy reg1 - load cpu hardware register Y into reg1.b
loadhax reg1 - load cpu hardware register pair AX into reg1.w
loadhay reg1 - load cpu hardware register pair AY into reg1.w
loadhxy reg1 - load cpu hardware register pair XY into reg1.w
loadfaczero fpreg1 - load "cpu hardware register" fac0 into freg1.f
loadfacone fpreg1 - load "cpu hardware register" fac1 into freg1.f
loadhfaczero fpreg1 - load "cpu hardware register" fac0 into freg1.f
loadhfacone fpreg1 - load "cpu hardware register" fac1 into freg1.f
storem reg1, address - store reg1 at memory address
storei reg1, reg2 - store reg1 at memory indirect, memory pointed to by reg2
storex reg1, reg2, address - store reg1 at memory address, indexed by value in reg2 (0-255, a byte)
@@ -57,6 +58,7 @@ storeix reg1, reg2, pointeraddr - store reg1 at memory indirect, pointed t
storezm address - store zero at memory address
storezi reg1 - store zero at memory pointed to by reg1
storezx reg1, address - store zero at memory address, indexed by value in reg1 (0-255, a byte)
storefield reg1, reg2, value - store reg1 in memory pointed to by reg2 + value 0-255 (set a field from a pointer to a struct, like STOREI, but with additional field offset 0-255)
storeha reg1 - store reg1.b into cpu hardware register A
storehx reg1 - store reg1.b into cpu hardware register X
storehy reg1 - store reg1.b into cpu hardware register Y
@@ -265,6 +267,7 @@ enum class Opcode {
LOADHAX,
LOADHAY,
LOADHXY,
LOADFIELD,
LOADHFACZERO,
LOADHFACONE,
STOREM,
@@ -280,6 +283,7 @@ enum class Opcode {
STOREHAX,
STOREHAY,
STOREHXY,
STOREFIELD,
STOREHFACZERO,
STOREHFACONE,
@@ -479,6 +483,7 @@ val OpcodesThatSetStatusbitsButNotCarry = arrayOf(
Opcode.LOADHAX,
Opcode.LOADHAY,
Opcode.LOADHXY,
Opcode.LOADFIELD,
Opcode.NEG,
Opcode.NEGM,
Opcode.INC,
@@ -610,6 +615,7 @@ val instructionFormats = mutableMapOf(
Opcode.LOADHAX to InstructionFormat.from("W,>r1"),
Opcode.LOADHAY to InstructionFormat.from("W,>r1"),
Opcode.LOADHXY to InstructionFormat.from("W,>r1"),
Opcode.LOADFIELD to InstructionFormat.from("BW,>r1,<r2,<i | F,>fr1,<r1,<i"),
Opcode.LOADHFACZERO to InstructionFormat.from("F,>fr1"),
Opcode.LOADHFACONE to InstructionFormat.from("F,>fr1"),
Opcode.STOREM to InstructionFormat.from("BW,<r1,>a | F,<fr1,>a"),
@@ -626,6 +632,7 @@ val instructionFormats = mutableMapOf(
Opcode.STOREHAX to InstructionFormat.from("W,<r1"),
Opcode.STOREHAY to InstructionFormat.from("W,<r1"),
Opcode.STOREHXY to InstructionFormat.from("W,<r1"),
Opcode.STOREFIELD to InstructionFormat.from("BW,<r1,<r2,<i | F,<fr1,<r1,<i"),
Opcode.STOREHFACZERO to InstructionFormat.from("F,<fr1"),
Opcode.STOREHFACONE to InstructionFormat.from("F,<fr1"),
Opcode.JUMP to InstructionFormat.from("N,<a"),
@@ -834,11 +841,17 @@ data class IRInstruction(
if(format.fpReg1==OperandDirection.UNUSED) require(fpReg1==null) { "invalid fpReg1" }
if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" }
if(format.immediate) {
if(type==IRDataType.FLOAT)
requireNotNull(immediateFp) {"missing immediate fp value"}
if(type==IRDataType.FLOAT) {
if(opcode!=Opcode.LOADFIELD && opcode!=Opcode.STOREFIELD)
requireNotNull(immediateFp) { "missing immediate fp value" }
}
else
require(immediate!=null || labelSymbol!=null) {"missing immediate value or labelsymbol"}
}
if(opcode==Opcode.LOADFIELD || opcode==Opcode.STOREFIELD) {
require(immediate != null) {
"missing immediate value for $opcode" }
}
if(type!=IRDataType.FLOAT)
require(fpReg1==null && fpReg2==null) {"int instruction can't use fp reg"}
if(format.address!=OperandDirection.UNUSED)
@@ -1054,7 +1067,7 @@ data class IRInstruction(
private fun determineReg2Type(): IRDataType? {
if(opcode==Opcode.LOADX || opcode==Opcode.LOADIX || opcode==Opcode.STOREX || opcode==Opcode.STOREIX)
return IRDataType.BYTE
if(opcode==Opcode.LOADI || opcode==Opcode.STOREI)
if(opcode==Opcode.LOADI || opcode==Opcode.STOREI || opcode==Opcode.LOADFIELD || opcode==Opcode.STOREFIELD)
return IRDataType.WORD
if(opcode==Opcode.MSIG || opcode==Opcode.LSIG)
return when(type) {
+1 -1
View File
@@ -159,7 +159,7 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
val value = if(oper[0]=='#') parseIRValue(oper.drop(1)) else parseIRValue(oper)
if (format.immediate) {
if (immediateInt == null && immediateFp == null) {
if (type == IRDataType.FLOAT)
if (type == IRDataType.FLOAT && opcode != Opcode.LOADFIELD && opcode != Opcode.STOREFIELD)
immediateFp = value
else
immediateInt = value.toInt()
@@ -199,6 +199,7 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.LOADHXY -> InsLOADHXY(ins)
Opcode.LOADHFACZERO -> InsLOADHFACZERO(ins)
Opcode.LOADHFACONE -> InsLOADHFACONE(ins)
Opcode.LOADFIELD -> InsLOADFIELD(ins)
Opcode.STOREM -> InsSTOREM(ins)
Opcode.STOREX -> InsSTOREX(ins)
Opcode.STOREIX -> InsSTOREIX(ins)
@@ -214,6 +215,7 @@ class VirtualMachine(irProgram: IRProgram) {
Opcode.STOREHXY -> InsSTOREHXY(ins)
Opcode.STOREHFACZERO -> InsSTOREHFACZERO(ins)
Opcode.STOREHFACONE-> InsSTOREHFACONE(ins)
Opcode.STOREFIELD-> InsSTOREFIELD(ins)
Opcode.JUMP -> InsJUMP(ins)
Opcode.JUMPI -> InsJUMPI(ins)
Opcode.CALLI -> throw IllegalArgumentException("VM cannot run code from memory bytes")
@@ -492,6 +494,27 @@ class VirtualMachine(irProgram: IRProgram) {
nextPc()
}
private fun InsLOADFIELD(i: IRInstruction) {
val offset = i.immediate!!
require(offset in 0..255)
when(i.type!!) {
IRDataType.BYTE -> {
val value = memory.getUB(registers.getUW(i.reg2!!).toInt() + offset)
registers.setUB(i.reg1!!, value)
statusbitsNZ(value.toInt(), i.type!!)
}
IRDataType.WORD -> {
val value = memory.getUW(registers.getUW(i.reg2!!).toInt() + offset)
registers.setUW(i.reg1!!, value)
statusbitsNZ(value.toInt(), i.type!!)
}
IRDataType.FLOAT -> {
registers.setFloat(i.fpReg1!!, memory.getFloat(registers.getUW(i.reg1!!).toInt() + offset))
}
}
nextPc()
}
private fun InsLOADX(i: IRInstruction) {
when (i.type!!) {
IRDataType.BYTE -> {
@@ -566,6 +589,21 @@ class VirtualMachine(irProgram: IRProgram) {
nextPc()
}
private fun InsSTOREFIELD(i: IRInstruction) {
val offset = i.immediate!!
require(offset in 0..255)
when (i.type!!) {
IRDataType.BYTE -> memory.setUB(registers.getUW(i.reg2!!).toInt() + offset, registers.getUB(i.reg1!!))
IRDataType.WORD -> memory.setUW(registers.getUW(i.reg2!!).toInt() + offset, registers.getUW(i.reg1!!))
IRDataType.FLOAT -> {
val a = registers.getUW(i.reg1!!).toInt()
val f = registers.getFloat(i.fpReg1!!)
memory.setFloat(registers.getUW(i.reg1!!).toInt() + offset, registers.getFloat(i.fpReg1!!))
}
}
nextPc()
}
private fun InsSTOREX(i: IRInstruction) {
when (i.type!!) {
IRDataType.BYTE -> memory.setUB(i.address!! + registers.getUB(i.reg2!!).toInt(), registers.getUB(i.reg1!!))