vm: fixed string comparisons, added missing vm string module

This commit is contained in:
Irmen de Jong 2022-05-13 23:10:13 +02:00
parent 6e31eebfb5
commit 4dc9b45297
10 changed files with 270 additions and 70 deletions

View File

@ -3,10 +3,7 @@ package prog8.codegen.virtual
import prog8.code.StStaticVariable import prog8.code.StStaticVariable
import prog8.code.StSub import prog8.code.StSub
import prog8.code.ast.* import prog8.code.ast.*
import prog8.code.core.AssemblyError import prog8.code.core.*
import prog8.code.core.DataType
import prog8.code.core.PassByValueDatatypes
import prog8.code.core.SignedDatatypes
import prog8.vm.Opcode import prog8.vm.Opcode
import prog8.vm.VmDataType import prog8.vm.VmDataType
@ -187,13 +184,7 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask) code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask)
} }
"not" -> { "not" -> {
val label = codeGen.createLabelName() code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
code += VmCodeInstruction(Opcode.BZ, vmDt, reg1=resultRegister, symbol = label)
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=1)
code += VmCodeLabel(label)
val regMask = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value=1)
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask)
} }
else -> throw AssemblyError("weird prefix operator") else -> throw AssemblyError("weird prefix operator")
} }
@ -330,15 +321,28 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
} }
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister) code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
} else { } else {
val rightResultReg = codeGen.vmRegisters.nextFree() if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
code += translateExpression(binExpr.left, resultRegister, -1) val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
code += translateExpression(binExpr.right, rightResultReg, -1) comparisonCall.children.add(binExpr.left)
val ins = if (signed) { comparisonCall.children.add(binExpr.right)
if (greaterEquals) Opcode.SGES else Opcode.SGTS code += translate(comparisonCall, resultRegister, -1)
val zeroRegister = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
code += if(greaterEquals)
VmCodeInstruction(Opcode.SGES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
else
VmCodeInstruction(Opcode.SGTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
} else { } else {
if (greaterEquals) Opcode.SGE else Opcode.SGT val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
val ins = if (signed) {
if (greaterEquals) Opcode.SGES else Opcode.SGTS
} else {
if (greaterEquals) Opcode.SGE else Opcode.SGT
}
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
} }
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
} }
return code return code
} }
@ -366,15 +370,28 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
} }
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister) code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
} else { } else {
val rightResultReg = codeGen.vmRegisters.nextFree() if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
code += translateExpression(binExpr.left, resultRegister, -1) val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
code += translateExpression(binExpr.right, rightResultReg, -1) comparisonCall.children.add(binExpr.left)
val ins = if (signed) { comparisonCall.children.add(binExpr.right)
if (lessEquals) Opcode.SLES else Opcode.SLTS code += translate(comparisonCall, resultRegister, -1)
val zeroRegister = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
code += if(lessEquals)
VmCodeInstruction(Opcode.SLES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
else
VmCodeInstruction(Opcode.SLTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
} else { } else {
if (lessEquals) Opcode.SLE else Opcode.SLT val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
val ins = if (signed) {
if (lessEquals) Opcode.SLES else Opcode.SLTS
} else {
if (lessEquals) Opcode.SLE else Opcode.SLT
}
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
} }
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
} }
return code return code
} }
@ -386,22 +403,37 @@ internal class ExpressionGen(private val codeGen: CodeGen) {
val rightFpReg = codeGen.vmRegisters.nextFreeFloat() val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(binExpr.left, -1, leftFpReg) code += translateExpression(binExpr.left, -1, leftFpReg)
code += translateExpression(binExpr.right, -1, rightFpReg) code += translateExpression(binExpr.right, -1, rightFpReg)
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg) if (notEquals) {
if(!notEquals) { code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
} else {
val label = codeGen.createLabelName() val label = codeGen.createLabelName()
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=resultRegister, symbol = label) val valueReg = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=1) code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=1)
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=valueReg, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=valueReg, symbol = label)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=0)
code += VmCodeLabel(label) code += VmCodeLabel(label)
val regMask = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=regMask, value=1)
code += VmCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=resultRegister, reg2=regMask)
} }
} else { } else {
val rightResultReg = codeGen.vmRegisters.nextFree() if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
code += translateExpression(binExpr.left, resultRegister, -1) val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
code += translateExpression(binExpr.right, rightResultReg, -1) comparisonCall.children.add(binExpr.left)
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ comparisonCall.children.add(binExpr.right)
code += VmCodeInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg) code += translate(comparisonCall, resultRegister, -1)
if(notEquals) {
val maskReg = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=maskReg, value=1)
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=maskReg)
} else {
code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
}
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
code += VmCodeInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
}
} }
return code return code
} }

View File

@ -37,4 +37,19 @@ prog8_lib {
} }
return false return false
} }
sub string_compare(str st1, str st2) -> byte {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%asm {{
loadm.w r0, {prog8_lib.string_compare.st1}
loadm.w r1, {prog8_lib.string_compare.st2}
syscall 29
return
}}
}
} }

View File

@ -0,0 +1,119 @@
; 0-terminated string manipulation routines. For the Virtual Machine target.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
string {
sub length(str st) -> ubyte {
; Returns the number of bytes in the string.
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
; regardless of the size of the string during compilation time. Dont confuse this with len and sizeof!
ubyte count = 0
while st[count]
count++
return count
}
sub left(str source, ubyte slen, str target) {
; Copies the left side of the source string of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
target[slen] = 0
ubyte ix
for ix in 0 to slen-1 {
target[ix] = source[ix]
}
}
sub right(str source, ubyte slen, str target) {
; Copies the right side of the source string of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
ubyte offset = length(source)-slen
ubyte ix
for ix in 0 to slen-1 {
target[ix] = source[ix+offset]
}
target[ix]=0
}
sub slice(str source, ubyte start, ubyte slen, str target) {
; Copies a segment from the source string, starting at the given index,
; and of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that start and length are within bounds of the strings.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
ubyte ix
for ix in 0 to slen-1 {
target[ix] = source[ix+start]
}
target[ix]=0
}
sub find(str st, ubyte character) -> ubyte {
; Locates the first position of the given character in the string,
; returns Carry set if found + index in A, or Carry clear if not found.
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix]==character {
sys.set_carry()
return ix
}
}
sys.clear_carry()
return 0
}
sub copy(str source, str target) -> ubyte {
; Copy a string to another, overwriting that one.
; Returns the length of the string that was copied.
; Often you dont have to call this explicitly and can just write string1 = string2
; but this function is useful if youre dealing with addresses for instance.
ubyte ix
repeat {
ubyte char=source[ix]
target[ix]=char
if not char
return ix
ix++
}
}
sub compare(str st1, str st2) -> byte {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
return prog8_lib.string_compare(st1, st2)
}
sub lower(str st) -> ubyte {
; Lowercases the petscii string in-place. Returns length of the string.
; (for efficiency, non-letter characters > 128 will also not be left intact,
; but regular text doesn't usually contain those characters anyway.)
ubyte ix
repeat {
ubyte char=st[ix]
if not char
return ix
if char >= 'A' and char <= 'Z'
st[ix] = char | %00100000
ix++
}
}
sub upper(str st) -> ubyte {
; Uppercases the petscii string in-place. Returns length of the string.
ubyte ix
repeat {
ubyte char=st[ix]
if not char
return ix
if char >= 97 and char <= 122
st[ix] = char & %11011111
ix++
}
}
}

View File

@ -388,8 +388,8 @@ private fun createAssemblyAndAssemble(program: Program,
// to help clean up the code that still depends on them. // to help clean up the code that still depends on them.
// removeAllVardeclsFromAst(program) // removeAllVardeclsFromAst(program)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************") println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program) printProgram(program)
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly() val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
errors.report() errors.report()

View File

@ -889,7 +889,7 @@ internal class AstChecker(private val program: Program,
if(rightDt!in NumericDatatypes && rightDt != DataType.STR) if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
errors.err("right operand is not numeric or str", expr.right.position) errors.err("right operand is not numeric or str", expr.right.position)
if(leftDt!=rightDt) { if(leftDt!=rightDt) {
if(leftDt==DataType.STR && rightDt in IntegerDatatypes) { if(leftDt==DataType.STR && rightDt in IntegerDatatypes && expr.operator=="*") {
// only exception allowed: str * constvalue // only exception allowed: str * constvalue
if(expr.right.constValue(program)==null) if(expr.right.constValue(program)==null)
errors.err("can only use string repeat with a constant number value", expr.left.position) errors.err("can only use string repeat with a constant number value", expr.left.position)

View File

@ -3,9 +3,10 @@ TODO
For next release For next release
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
- vm: get rid of all the conditional set instructions - c64 target: after exit, switching charset case is still disabled. Don't disable this by default in startup?
- vm: add more instructions operating directly on memory instead of only registers?
- vm: check array type in PtAssignTarget - vm: check array type in PtAssignTarget
- vm: animals example game breaks after adding first new animal...
- vm: add more instructions operating directly on memory instead of only registers? (translate assignment self-assigns)
- in-place modifiying functions (rol, ror, ..) don't accept a memory address but require a memory-read expression. that is weird. - in-place modifiying functions (rol, ror, ..) don't accept a memory address but require a memory-read expression. that is weird.
- complete the Inliner - complete the Inliner
- add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value? - add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value?

View File

@ -1,5 +1,6 @@
%import textio %import textio
%import math %import math
%import string
%import floats %import floats
%zeropage dontuse %zeropage dontuse
@ -25,37 +26,44 @@ main {
ubyte @shared value = inline_candidate() ubyte @shared value = inline_candidate()
ubyte lowb = 4 str name = "irmen123ABC"
ubyte highb = $ea str other = "zrmen123ABC"
if lowb+4 txt.print_ub(string.upper(name))
lowb++ txt.print(name)
if math.sin8u(lowb) txt.nl()
lowb++ txt.print_ub(string.lower(name))
if lowb txt.print(name)
lowb++ txt.nl()
if lowb==0 uword otheraddr = &other
lowb++ txt.print_ub(name!=other)
txt.spc()
txt.print_ub(name==other)
txt.nl()
txt.print_ub(name>other)
txt.spc()
txt.print_ub(name>=other)
txt.nl()
txt.print_ub(name<other)
txt.spc()
txt.print_ub(name<=other)
txt.nl()
txt.nl()
other[0]='i'
txt.print_ub(name!=other)
txt.spc()
txt.print_ub(name==other)
txt.nl()
txt.print_ub(name>other)
txt.spc()
txt.print_ub(name>=other)
txt.nl()
txt.print_ub(name<other)
txt.spc()
txt.print_ub(name<=other)
txt.nl()
if lowb!=0
lowb++
if lowb==3
lowb++
if lowb!=3
lowb++
if lowb==3
lowb++
else
lowb--
if lowb!=3
lowb++
else
lowb--
; txt.print_ub(inline_candidate()) ; txt.print_ub(inline_candidate())
; txt.nl() ; txt.nl()

View File

@ -116,6 +116,7 @@ All have type b or w.
and reg1, reg2 - reg1 = reg1 bitwise and reg2 and reg1, reg2 - reg1 = reg1 bitwise and reg2
or reg1, reg2 - reg1 = reg1 bitwise or reg2 or reg1, reg2 - reg1 = reg1 bitwise or reg2
xor reg1, reg2 - reg1 = reg1 bitwise xor reg2 xor reg1, reg2 - reg1 = reg1 bitwise xor reg2
not reg1 - reg1 = boolean not of reg1 (0->1 , ~0 -> 0)
lsrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits + set Carry to shifted bit lsrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits + set Carry to shifted bit
asrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits (signed) + set Carry to shifted bit asrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits (signed) + set Carry to shifted bit
lsln reg1, reg2 - reg1 = multi-shift reg1 left by reg2 bits + set Carry to shifted bit lsln reg1, reg2 - reg1 = multi-shift reg1 left by reg2 bits + set Carry to shifted bit
@ -225,6 +226,7 @@ enum class Opcode {
AND, AND,
OR, OR,
XOR, XOR,
NOT,
ASRN, ASRN,
LSRN, LSRN,
LSLN, LSLN,
@ -273,7 +275,9 @@ val OpcodesWithAddress = setOf(
Opcode.STOREM, Opcode.STOREM,
Opcode.STOREX, Opcode.STOREX,
Opcode.STOREZM, Opcode.STOREZM,
Opcode.STOREZX Opcode.STOREZX,
Opcode.INCM,
Opcode.DECM
) )
@ -474,6 +478,7 @@ val instructionFormats = mutableMapOf(
Opcode.AND to InstructionFormat.from("BW,r1,r2"), Opcode.AND to InstructionFormat.from("BW,r1,r2"),
Opcode.OR to InstructionFormat.from("BW,r1,r2"), Opcode.OR to InstructionFormat.from("BW,r1,r2"),
Opcode.XOR to InstructionFormat.from("BW,r1,r2"), Opcode.XOR to InstructionFormat.from("BW,r1,r2"),
Opcode.NOT to InstructionFormat.from("BW,r1"),
Opcode.ASRN to InstructionFormat.from("BW,r1,r2"), Opcode.ASRN to InstructionFormat.from("BW,r1,r2"),
Opcode.LSRN to InstructionFormat.from("BW,r1,r2"), Opcode.LSRN to InstructionFormat.from("BW,r1,r2"),
Opcode.LSLN to InstructionFormat.from("BW,r1,r2"), Opcode.LSLN to InstructionFormat.from("BW,r1,r2"),

View File

@ -34,6 +34,7 @@ SYSCALLS:
26 = reverse_bytes array 26 = reverse_bytes array
27 = reverse_words array 27 = reverse_words array
28 = reverse_floats array 28 = reverse_floats array
29 = compare strings
*/ */
enum class Syscall { enum class Syscall {
@ -66,6 +67,7 @@ enum class Syscall {
REVERSE_BYTES, REVERSE_BYTES,
REVERSE_WORDS, REVERSE_WORDS,
REVERSE_FLOATS, REVERSE_FLOATS,
COMPARE_STRINGS
} }
object SysCalls { object SysCalls {
@ -255,6 +257,14 @@ object SysCalls {
val string = vm.memory.getString(stringAddr.toInt()) val string = vm.memory.getString(stringAddr.toInt())
vm.registers.setSW(0, string.toShort()) vm.registers.setSW(0, string.toShort())
} }
Syscall.COMPARE_STRINGS -> {
val firstAddr = vm.registers.getUW(0)
val secondAddr = vm.registers.getUW(1)
val first = vm.memory.getString(firstAddr.toInt())
val second = vm.memory.getString(secondAddr.toInt())
val comparison = first.compareTo(second)
vm.registers.setSB(0, comparison.toByte())
}
else -> TODO("syscall ${call.name}") else -> TODO("syscall ${call.name}")
} }
} }

View File

@ -150,6 +150,7 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
Opcode.AND -> InsAND(ins) Opcode.AND -> InsAND(ins)
Opcode.OR -> InsOR(ins) Opcode.OR -> InsOR(ins)
Opcode.XOR -> InsXOR(ins) Opcode.XOR -> InsXOR(ins)
Opcode.NOT -> InsNOT(ins)
Opcode.ASRN -> InsASRM(ins) Opcode.ASRN -> InsASRM(ins)
Opcode.LSRN -> InsLSRM(ins) Opcode.LSRN -> InsLSRM(ins)
Opcode.LSLN -> InsLSLM(ins) Opcode.LSLN -> InsLSLM(ins)
@ -852,6 +853,15 @@ class VirtualMachine(val memory: Memory, program: List<Instruction>) {
pc++ pc++
} }
private fun InsNOT(i: Instruction) {
when(i.type!!) {
VmDataType.BYTE -> registers.setUB(i.reg1!!, if(registers.getUB(i.reg1)==0.toUByte()) 1u else 0u)
VmDataType.WORD -> registers.setUW(i.reg1!!, if(registers.getUW(i.reg1)==0.toUShort()) 1u else 0u)
VmDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
}
pc++
}
private fun InsASRM(i: Instruction) { private fun InsASRM(i: Instruction) {
val (left: Int, right: Int) = getLogicalOperandsS(i) val (left: Int, right: Int) = getLogicalOperandsS(i)
statusCarry = (left and 1)!=0 statusCarry = (left and 1)!=0