diff --git a/compiler/res/version.txt b/compiler/res/version.txt index 67d66eefd..d798f5093 100644 --- a/compiler/res/version.txt +++ b/compiler/res/version.txt @@ -1 +1 @@ -1.4 (beta) +1.5 (beta) diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 067776269..08abce6e0 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -377,7 +377,16 @@ internal class Compiler(private val rootModule: Module, } private fun translate(stmt: InlineAssembly) { - prog.instr(Opcode.INLINE_ASSEMBLY, callLabel = stmt.assembly) + // If the inline assembly is the only statement inside a subroutine (except vardecls), + // we can use the name of that subroutine to identify it. + // The compiler could then convert it to a special system call + val sub = stmt.parent as? Subroutine + val scopename = + if(sub!=null && sub.statements.filter{it !is VarDecl}.size==1) + sub.scopedname + else + null + prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=scopename, callLabel2 = stmt.assembly) } private fun translate(stmt: Continue) { @@ -2233,7 +2242,7 @@ internal class Compiler(private val rootModule: Module, File(filename).readText() } - prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=scopeprefix+sourcecode+scopeprefixEnd) + prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=null, callLabel2=scopeprefix+sourcecode+scopeprefixEnd) } private fun translateAsmBinary(args: List) { diff --git a/compiler/src/prog8/compiler/intermediate/Instruction.kt b/compiler/src/prog8/compiler/intermediate/Instruction.kt index 79c9487ee..b1d6dfaea 100644 --- a/compiler/src/prog8/compiler/intermediate/Instruction.kt +++ b/compiler/src/prog8/compiler/intermediate/Instruction.kt @@ -10,13 +10,21 @@ open class Instruction(val opcode: Opcode, { lateinit var next: Instruction var nextAlt: Instruction? = null + var branchAddress: Int? = null override fun toString(): String { val argStr = arg?.toString() ?: "" val result = when { opcode==Opcode.LINE -> "_line $callLabel" - opcode==Opcode.INLINE_ASSEMBLY -> "inline_assembly" + opcode==Opcode.INLINE_ASSEMBLY -> { + // inline assembly is not written out (it can't be processed as intermediate language) + // instead, it is converted into a system call that can be intercepted by the vm + if(callLabel!=null) + "syscall SYSASM.$callLabel\n return" + else + "inline_assembly" + } opcode==Opcode.SYSCALL -> { val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() } "syscall $syscall" diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index ac1d0b993..b56366992 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -476,7 +476,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, Opcode.DISCARD_BYTE -> " inx" Opcode.DISCARD_WORD -> " inx" Opcode.DISCARD_FLOAT -> " inx | inx | inx" - Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel ?: "") // All of the inline assembly is stored in the calllabel property. the '@inline@' is a special marker to process it. + Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to process it. Opcode.SYSCALL -> { if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr }) throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}") diff --git a/compiler/src/prog8/stackvm/Program.kt b/compiler/src/prog8/stackvm/Program.kt index 6dbe37d1a..b664caf66 100644 --- a/compiler/src/prog8/stackvm/Program.kt +++ b/compiler/src/prog8/stackvm/Program.kt @@ -142,8 +142,19 @@ class Program (val name: String, Instruction(opcode, callLabel = withoutQuotes) } Opcode.SYSCALL -> { - val call = Syscall.valueOf(args!!) - Instruction(opcode, Value(DataType.UBYTE, call.callNr)) + if(args!! in syscallNames) { + val call = Syscall.valueOf(args) + Instruction(opcode, Value(DataType.UBYTE, call.callNr)) + } else { + val args2 = args.replace('.', '_') + if(args2 in syscallNames) { + val call = Syscall.valueOf(args2) + Instruction(opcode, Value(DataType.UBYTE, call.callNr)) + } else { + // the syscall is not yet implemented. emit a stub. + Instruction(Opcode.SYSCALL, Value(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2) + } + } } else -> { Instruction(opcode, getArgValue(args, heap)) @@ -275,31 +286,24 @@ class Program (val name: String, Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic Opcode.JUMP -> { - if(instr.callLabel==null) { + if(instr.callLabel==null) throw VmExecutionException("stackVm doesn't support JUMP to memory address") - } else { - // jump to label - val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = target - } + else + linkJumpTarget(instr) } Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS, Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> { if(instr.callLabel==null) { throw VmExecutionException("stackVm doesn't support branch to memory address") } else { - // branch to label - val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = jumpInstr - instr.nextAlt = nextInstr + linkJumpTarget(instr) // branch label + instr.nextAlt = nextInstr // alternate branch } } Opcode.CALL -> { if(instr.callLabel==null) { throw VmExecutionException("stackVm doesn't support CALL to memory address") } else { - // call label - val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = jumpInstr + linkJumpTarget(instr) // call label instr.nextAlt = nextInstr // instruction to return to } } @@ -307,4 +311,17 @@ class Program (val name: String, } } } + + private fun linkJumpTarget(instr: Instruction) { + val target = labels[instr.callLabel] + if(target==null) { + val memtarget = memoryPointers[instr.callLabel]?.first + if(memtarget==null) + throw VmExecutionException("undefined label: ${instr.callLabel}") + else { + instr.branchAddress=memtarget + } + } else + instr.next = target + } } diff --git a/compiler/src/prog8/stackvm/ScreenDialog.kt b/compiler/src/prog8/stackvm/ScreenDialog.kt index 9c4bac2a2..ce6725217 100644 --- a/compiler/src/prog8/stackvm/ScreenDialog.kt +++ b/compiler/src/prog8/stackvm/ScreenDialog.kt @@ -15,6 +15,8 @@ class BitmapScreenPanel : KeyListener, JPanel() { private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB) private val g2d = image.graphics as Graphics2D + private var cursorX: Int=0 + private var cursorY: Int=0 init { val size = Dimension(image.width * SCALING, image.height * SCALING) @@ -56,13 +58,13 @@ class BitmapScreenPanel : KeyListener, JPanel() { g2d.color = palette[color and 15] g2d.drawLine(x1, y1, x2, y2) } - fun writeText(x: Int, y: Int, text: String, color: Int) { + fun writeText(x: Int, y: Int, text: String, color: Int, lowercase: Boolean) { if(color!=1) { TODO("text can only be white for now") } var xx=x var yy=y - for(sc in Petscii.encodeScreencode(text, true)) { + for(sc in Petscii.encodeScreencode(text, lowercase)) { setChar(xx, yy, sc) xx++ if(xx>=(SCREENWIDTH/8)) { @@ -75,6 +77,15 @@ class BitmapScreenPanel : KeyListener, JPanel() { g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null) } + fun setCursorPos(x: Int, y: Int) { + cursorX = x + cursorY = y + } + + fun getCursorPos(): Pair { + return Pair(cursorX, cursorY) + } + companion object { const val SCREENWIDTH = 320 diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index f63b79461..ac50a69e9 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -79,12 +79,21 @@ enum class Syscall(val callNr: Short) { FUNC_MEMCOPY(138), FUNC_MEMSET(139), FUNC_MEMSETW(140), - FUNC_READ_FLAGS(141) + FUNC_READ_FLAGS(141), // note: not all builtin functions of the Prog8 language are present as functions: // some of them are straight opcodes (such as MSB, LSB, LSL, LSR, ROL_BYTE, ROR, ROL2, ROR2, and FLT)! + + // vm intercepts of system routines: + SYSCALLSTUB(200), + SYSASM_c64scr_PLOT(201), + SYSASM_c64scr_print(202), + SYSASM_c64scr_setcc(203), } + +val syscallNames = enumValues().map { it.name }.toSet() + val syscallsForStackVm = setOf( Syscall.VM_WRITE_MEMCHR, Syscall.VM_WRITE_MEMSTR, @@ -1502,7 +1511,7 @@ class StackVm(private var traceOutputFile: String?) { } Opcode.RSAVEX -> evalstack.push(variables["X"]) Opcode.RRESTOREX -> variables["X"] = evalstack.pop() - Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code") + Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code $ins") Opcode.PUSH_ADDR_HEAPVAR -> { val heapId = variables[ins.callLabel]!!.heapId if(heapId<0) @@ -1538,7 +1547,16 @@ class StackVm(private var traceOutputFile: String?) { evalstack.printTop(4, traceOutput!!) } - return ins.next + if(ins.branchAddress!=null) { + // this is an instruction that jumps to a system routine (memory address) + if(ins.nextAlt==null) + throw VmExecutionException("call to system routine requires nextAlt return instruction set: $ins") + else { + println("CALLING SYSTEM ROUTINE $ins") + return ins.nextAlt!! + } + } else + return ins.next } private fun typecast(from: DataType, to: DataType) { @@ -1599,7 +1617,7 @@ class StackVm(private var traceOutputFile: String?) { val color = evalstack.pop() val (cy, cx) = evalstack.pop2() val text = heap.get(textPtr) - canvas?.writeText(cx.integerValue(), cy.integerValue(), text.str!!, color.integerValue()) + canvas?.writeText(cx.integerValue(), cy.integerValue(), text.str!!, color.integerValue(), true) } Syscall.FUNC_RND -> evalstack.push(Value(DataType.UBYTE, rnd.nextInt() and 255)) Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.UWORD, rnd.nextInt() and 65535)) @@ -1803,7 +1821,29 @@ class StackVm(private var traceOutputFile: String?) { mem.setSWord(addr, wordvalue) else -> throw VmExecutionException("(u)word value expected") } - } } + } + Syscall.SYSCALLSTUB -> throw VmExecutionException("unimplemented sysasm called: ${ins.callLabel} Create a Syscall enum for this and implement the vm intercept for it.") + Syscall.SYSASM_c64scr_PLOT -> { + val x = variables["Y"]!!.integerValue() + val y = variables["A"]!!.integerValue() + canvas?.setCursorPos(x, y) + } + Syscall.SYSASM_c64scr_print -> { + val (x, y) = canvas!!.getCursorPos() + val straddr = variables["A"]!!.integerValue() + 256*variables["Y"]!!.integerValue() + val str = heap.get(straddr).str!! + canvas?.writeText(x, y, str, 1, true) + } + Syscall.SYSASM_c64scr_setcc -> { + val x = variables["c64scr.setcc.column"]!!.integerValue() + val y = variables["c64scr.setcc.row"]!!.integerValue() + val char = variables["c64scr.setcc.char"]!!.integerValue() + // val color = variables["c64scr.setcc.color"]!!.integerValue() // text color other than 1 (white) can't be used right now + canvas?.setChar(x, y, char.toShort()) + println("setcc $x $y") // TODO the vm is in an endless loop here when running tehtriz with this! + } + else -> throw VmExecutionException("unimplemented syscall $syscall") + } } fun irq(timestamp: Long) { diff --git a/examples/tehtriz.p8 b/examples/tehtriz.p8 index 4719867e6..6188563b2 100644 --- a/examples/tehtriz.p8 +++ b/examples/tehtriz.p8 @@ -323,7 +323,7 @@ waitkey: ubyte[5] colors = [6,8,7,5,4] for i in len(colors)-1 to 0 step -1 { for ubyte x in 5 to 0 step -1 { - c64scr.setcc(6+x-i, 11+2*i, 102, colors[i]) + c64scr.setcc(6+x-i, 11+2*i, 102, colors[i]) ; @todo when run in the stackVM, this loop never ends and corrupts the screen } } drawScore()