diff --git a/codeAst/src/prog8/code/ast/AstExpressions.kt b/codeAst/src/prog8/code/ast/AstExpressions.kt index 9685d45ae..bddcf5991 100644 --- a/codeAst/src/prog8/code/ast/AstExpressions.kt +++ b/codeAst/src/prog8/code/ast/AstExpressions.kt @@ -11,6 +11,25 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit override fun printProperties() { print(type) } + + infix fun isSameTargetAs(other: PtExpression): Boolean = when(this) { + is PtArrayIndexer -> { + if(other is PtArrayIndexer && other.type==type) { + if(other.variable isSameTargetAs variable) { + other.index isSameTargetAs index + } + } + false + } + is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName + is PtMachineRegister -> other is PtMachineRegister && other.register==register + is PtMemoryByte -> other is PtMemoryByte && address isSameTargetAs other.address + is PtNumber -> other is PtNumber && other.type == type && other.number==number + is PtAddressOf -> other is PtAddressOf && other.identifier isSameTargetAs identifier + is PtPrefix -> other is PtPrefix && other.operator==operator && other.value isSameTargetAs value + is PtTypeCast -> other is PtTypeCast && other.type==type && other.value isSameTargetAs value + else -> false + } } diff --git a/codeAst/src/prog8/code/ast/AstStatements.kt b/codeAst/src/prog8/code/ast/AstStatements.kt index bf851bc19..059d2da35 100644 --- a/codeAst/src/prog8/code/ast/AstStatements.kt +++ b/codeAst/src/prog8/code/ast/AstStatements.kt @@ -45,6 +45,14 @@ class PtAssignment(position: Position) : PtNode(position) { get() = children[1] as PtExpression override fun printProperties() { } + + val isInplaceAssign: Boolean by lazy { + val target = target.children.single() as PtExpression + if(value is PtBinaryExpression) { + target isSameTargetAs (value as PtBinaryExpression).left + } else + target isSameTargetAs value + } } diff --git a/codeGenVirtual/src/prog8/codegen/virtual/AssignmentGen.kt b/codeGenVirtual/src/prog8/codegen/virtual/AssignmentGen.kt new file mode 100644 index 000000000..f406092bd --- /dev/null +++ b/codeGenVirtual/src/prog8/codegen/virtual/AssignmentGen.kt @@ -0,0 +1,136 @@ +package prog8.codegen.virtual + +import prog8.code.ast.* +import prog8.code.core.AssemblyError +import prog8.code.core.DataType +import prog8.vm.Opcode +import prog8.vm.VmDataType + +internal class AssignmentGen(private val codeGen: CodeGen, private val expressionEval: ExpressionGen) { + + internal fun translate(assignment: PtAssignment): VmCodeChunk { + return if (assignment.isInplaceAssign) + translateInplaceAssign(assignment) + else + translateRegularAssign(assignment) + } + + private fun translateInplaceAssign(assignment: PtAssignment): VmCodeChunk { + // TODO can in-place assignments be optimized more? use special memory versions of instructions instead of register ones? + return translateRegularAssign(assignment) + } + + private fun translateRegularAssign(assignment: PtAssignment): VmCodeChunk { + // note: assigning array and string values is done via an explicit memcopy/stringcopy function call. + if(assignment.target.children.single() is PtMachineRegister) + throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister") + 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) + val fixedIndex = constIntValue(array.index) + if(zero) { + if(fixedIndex!=null) { + variableAddr += fixedIndex*itemsize + code += VmCodeInstruction(Opcode.STOREZM, VmDataType.FLOAT, value=variableAddr) + } else { + val indexReg = codeGen.vmRegisters.nextFree() + code += loadIndexReg(array, itemsize, indexReg) + code += VmCodeInstruction(Opcode.STOREZX, VmDataType.FLOAT, 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 + } +} \ No newline at end of file diff --git a/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt b/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt index fbc186c92..328b5581d 100644 --- a/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt +++ b/codeGenVirtual/src/prog8/codegen/virtual/CodeGen.kt @@ -43,6 +43,7 @@ class CodeGen(internal val program: PtProgram, internal val allocations = VariableAllocator(symbolTable, program, errors) 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? { @@ -52,7 +53,7 @@ class CodeGen(internal val program: PtProgram, // collect global variables initializers program.allBlocks().forEach { val code = VmCodeChunk() - it.children.filterIsInstance().forEach { assign -> code += translate(assign) } + it.children.filterIsInstance().forEach { assign -> code += assignmentGen.translate(assign) } vmprog.addGlobalInits(code) } } @@ -75,7 +76,7 @@ class CodeGen(internal val program: PtProgram, 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 -> translate(node) + is PtAssignment -> assignmentGen.translate(node) is PtNodeGroup -> translateGroup(node.children) is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0) is PtFunctionCall -> expressionEval.translate(node, 0, 0) @@ -651,120 +652,6 @@ class CodeGen(internal val program: PtProgram, return code } - private fun translate(assignment: PtAssignment): VmCodeChunk { - // TODO can in-place assignments be optimized more? use special memory versions of instructions instead of register ones? - // note: assigning array and string values is done via an explicit memcopy/stringcopy function call. - if(assignment.target.children.single() is PtMachineRegister) - throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister") - val code = VmCodeChunk() - val ident = assignment.target.identifier - val memory = assignment.target.memory - val array = assignment.target.array - val vmDt = vmType(assignment.value.type) - var resultRegister = -1 - var resultFpRegister = -1 - val zero = isZero(assignment.value) - if(!zero) { - // calculate the assignment value - if (vmDt == VmDataType.FLOAT) { - resultFpRegister = vmRegisters.nextFreeFloat() - code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister) - } else { - resultRegister = if (assignment.value is PtMachineRegister) { - (assignment.value as PtMachineRegister).register - } else { - val reg = vmRegisters.nextFree() - code += expressionEval.translateExpression(assignment.value, reg, -1) - reg - } - } - } - if(ident!=null) { - val address = 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 = allocations.get(variable) - val itemsize = program.memsizer.memorySize(array.type) - val fixedIndex = constIntValue(array.index) - if(zero) { - if(fixedIndex!=null) { - variableAddr += fixedIndex*itemsize - code += VmCodeInstruction(Opcode.STOREZM, VmDataType.FLOAT, value=variableAddr) - } else { - val indexReg = vmRegisters.nextFree() - code += loadIndexReg(array, itemsize, indexReg) - code += VmCodeInstruction(Opcode.STOREZX, VmDataType.FLOAT, 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 = 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 = 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 = 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 = 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 - } - private fun translate(ret: PtReturn): VmCodeChunk { val code = VmCodeChunk() val value = ret.value @@ -828,3 +715,4 @@ class CodeGen(internal val program: PtProgram, internal fun isOne(expression: PtExpression): Boolean = expression is PtNumber && expression.number==1.0 } + diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 83cf09df7..50a0e6d95 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,8 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- vm: add more instructions operating directly on memory instead of only registers? (translate assignment self-assigns) +- vm: implement the sin/cos functions in math.p8 and make an example 'shader' that uses them +- vm: add more instructions operating directly on memory instead of only registers? (translate assignment self-assigns in AssignmentGen) - 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? @@ -23,8 +24,6 @@ Compiler: - vm: codeGen: various TODOs to tweak code - vm: somehow deal with asmsubs otherwise the vm IR can't fully encode all of prog8 -- vm: make registers typed? so that it's immediately obvious what type they represent. Much like regular variables in memory. - so we have a set of byte registers, a set of word registers, and other sets if we introduce other types. - vm: don't store symbol names in instructions to make optimizing the IR easier? but what about jumps to labels. And it's no longer readable by humans. - vm: how to remove all unused subroutines? (in the assembly codegen, we let 64tass solve this for us) - vm: rather than being able to jump to any 'address' (IPTR), use 'blocks' that have entry and exit points -> even better dead code elimination possible too @@ -53,7 +52,7 @@ Libraries: - optimize several inner loops in gfx2 even further? - add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)? - add a flood fill routine to gfx2? -- diskio: use cx16 MACPTR() to load stuff faster? (see its use in X16edit to fast load blocks) +- diskio: use cx16 MACPTR() in f_read() to load stuff faster? (see its use in X16edit to fast load blocks) note that it might fail on non sdcard files so have to make graceful degradation Expressions: diff --git a/examples/test.p8 b/examples/test.p8 index 3e2bef086..fb54a0429 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,114 +1,22 @@ -%import textio %import math -%import string -%import floats -%zeropage dontuse - ; NOTE: meant to test to virtual machine output target (use -target vitual) main { - ubyte value = 42 - - sub inline_candidate() -> ubyte { - return math.sin8u(value) - } - - sub add(ubyte first, ubyte second) -> ubyte { - return first + second - } - - sub mul(ubyte first, ubyte second) -> ubyte { - return first * second - } - - ubyte ix - sub start() { - uword[] array = [1111,2222,3333,4444] + ; a "pixelshader": + sys.gfx_enable(0) ; enable lo res screen - sub pa() { - uword ww - for ww in array { - txt.print_uw(ww) - txt.spc() - } - txt.nl() + ubyte angle + + for angle in 0 to 255 { + ubyte xx = math.sin8u(angle) + ubyte yy = math.cos8u(angle) + sys.gfx_plot(xx, yy, 255) } -; pa() -; array[2] = 9999 -; pa() - ix=2 - array[ix]= 8888 ; TODO fix indexing offset in vm - pa() - txt.print_uw(array[ix]) - txt.spc() - txt.print_uw(array[ix+1]) - txt.nl() - -; ubyte @shared value = inline_candidate() - -; txt.print_ub(inline_candidate()) -; txt.nl() - -; ubyte value = add(3,4) |> add(10) |> mul(2) |> math.sin8u() -; txt.print_ub(value) -; txt.nl() -; uword wvalue = add(3,4) |> add($30) |> mkword($ea) -; txt.print_uwhex(wvalue, true) -; txt.nl() - - ; expected output: aaabbb aaa bbb - -; float f1 = 1.555 -; floats.print_f(floats.sin(f1)) -; txt.nl() -; floats.print_f(floats.cos(f1)) -; txt.nl() -; floats.print_f(floats.tan(f1)) -; txt.nl() -; floats.print_f(floats.atan(f1)) -; txt.nl() -; floats.print_f(floats.ln(f1)) -; txt.nl() -; floats.print_f(floats.log2(f1)) -; txt.nl() -; floats.print_f(floats.sqrt(f1)) -; txt.nl() -; floats.print_f(floats.rad(f1)) -; txt.nl() -; floats.print_f(floats.deg(f1)) -; txt.nl() -; floats.print_f(floats.round(f1)) -; txt.nl() -; floats.print_f(floats.floor(f1)) -; txt.nl() -; floats.print_f(floats.ceil(f1)) -; txt.nl() -; floats.print_f(floats.rndf()) -; txt.nl() -; "sin", "cos", "tan", "atan", -; "ln", "log2", "sqrt", "rad", -; "deg", "round", "floor", "ceil", "rndf" - - ; a "pixelshader": -; sys.gfx_enable(0) ; enable lo res screen -; ubyte shifter -; -; repeat { -; uword xx -; uword yy = 0 -; repeat 240 { -; xx = 0 -; repeat 320 { -; sys.gfx_plot(xx, yy, xx*yy + shifter as ubyte) -; xx++ -; } -; yy++ -; } -; shifter+=4 -; } + repeat { + } } }