From 949e468543660910da21b464bbd7b94d95d00e32 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 13 Sep 2018 00:46:52 +0200 Subject: [PATCH] more stackvm opcodes, and sort of finished the stackvm compiler --- docs/source/syntaxreference.rst | 5 +- il65/antlr/il65.g4 | 4 +- il65/examples/imported.ill | 1 + il65/examples/test.ill | 48 +-- il65/src/il65/Main.kt | 14 +- il65/src/il65/ast/AST.kt | 10 +- il65/src/il65/ast/AstChecker.kt | 57 ++-- il65/src/il65/ast/AstRecursionChecker.kt | 2 +- il65/src/il65/ast/StmtReorderer.kt | 2 +- il65/src/il65/compiler/Compiler.kt | 224 +++++++++++--- il65/src/il65/functions/BuiltinFunctions.kt | 2 +- .../il65/optimizing/ExpressionOptimizer.kt | 22 +- .../il65/optimizing/StatementsOptimizer.kt | 4 +- il65/src/il65/stackvm/StackVm.kt | 278 ++++++++++++++---- 14 files changed, 512 insertions(+), 161 deletions(-) diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 081849527..c1b42b61c 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -115,8 +115,9 @@ Directives Level: block. This directive can only be used inside a block. - The assembler will include the file as raw assembly source text at this point, il65 will not process this at all. - The scopelabel will be used as a prefix to access the labels from the included source code, + The assembler will include the file as raw assembly source text at this point, + il65 will not process this at all, with one exception: the labels. + The scopelabel argument will be used as a prefix to access the labels from the included source code, otherwise you would risk symbol redefinitions or duplications. .. data:: %breakpoint diff --git a/il65/antlr/il65.g4 b/il65/antlr/il65.g4 index 6a912eb43..fcedcb1ee 100644 --- a/il65/antlr/il65.g4 +++ b/il65/antlr/il65.g4 @@ -1,10 +1,10 @@ /* -IL65 combined lexer and parser grammar +IL65 combined lexer bitand parser grammar NOTES: - whitespace is ignored. (tabs/spaces) -- every position can be empty, be a comment, or contain ONE statement. +- every position can be empty, be a comment, bitor contain ONE statement. */ diff --git a/il65/examples/imported.ill b/il65/examples/imported.ill index b1edc0246..2251ef37f 100644 --- a/il65/examples/imported.ill +++ b/il65/examples/imported.ill @@ -18,6 +18,7 @@ label_in_extra2: X = 33 + return sub sub_in_extra2() -> () { return diff --git a/il65/examples/test.ill b/il65/examples/test.ill index 6c9f2936f..4685d6cc3 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -59,13 +59,13 @@ str ascending5 = "z" to "z" const byte cc = 4 + (2==9) byte cc2 = 4 - (2==10) - memory byte derp = max([$ffdd]) - memory byte derpA = abs(-20000) - memory byte derpB = max([1, 2.2, 4.4, 100]) - memory byte cderp = min([$ffdd])+ (1/1) - memory byte cderpA = min([$ffdd, 10, 20, 30]) - memory byte cderpB = min([1, 2.2, 4.4, 100]) - memory byte derp2 = 2+$ffdd+round(10*sin(3.1)) +; memory byte derp = max([$ffdd]) ; @todo implement memory vars in stackvm +; memory byte derpA = abs(-20000) +; memory byte derpB = max([1, 2.2, 4.4, 100]) +; memory byte cderp = min([$ffdd])+ (1/1) +; memory byte cderpA = min([$ffdd, 10, 20, 30]) +; memory byte cderpB = min([1, 2.2, 4.4, 100]) +; memory byte derp2 = 2+$ffdd+round(10*sin(3.1)) const byte hopla=55-33 const byte hopla3=100+(-hopla) const byte hopla4 = 100-hopla @@ -77,10 +77,10 @@ const byte equal = 4==4 const byte equal2 = (4+hopla)>0 - goto 64738 + ; goto 64738 - ; A++ - derp++ + A++ +; derp++ cc2-- goto mega @@ -92,12 +92,19 @@ A=100 } - %breakpoint byte equalQQ = 4==4 const byte equalQQ2 = (4+hopla)>0 - sin(1) ; @todo error statement has no effect (returnvalue of builtin function not used) + equalQQ++ + cos(2) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement) + cos(A) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement) + AX++ + A=X + round(sin(Y)) + equalQQ= X + equalQQ= len([X, Y, AX]) + len([X, Y, AX]) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement) + sin(1) ; @todo warning statement has no effect (returnvalue of builtin function not used) (remove statement) P_carry(1) P_irqd(0) @@ -118,7 +125,6 @@ if (5==5) { A=99 - %breakpoint } if(6==6) { @@ -144,12 +150,19 @@ } main.foo(1,2,3) + return sub start () -> () { word dinges = 0 word blerp1 =99 + byte blerp2 =99 dinges=blerp1 - A=blerp1 + A=blerp1 ; @todo error can't assign word to byte + A=blerp2 + A=9999 + XY=blerp1 + X=blerp1 ; @todo error can't assign word to byte + X=blerp2 return } @@ -158,6 +171,7 @@ mega: X += 1 cool: Y=2 + goto start sub foo () -> () { byte blerp = 3 @@ -166,8 +180,11 @@ cool: return ultrafoo() X =33 + mega: cool: + return + sub ultrafoo() -> () { X= extra233.thingy() ;return 33 @@ -180,9 +197,6 @@ cool: some_label_def: A=44 ;return 1+999 return - %breakpoint - %asminclude "derp", hopsa - %asmbinary "derp", 0, 200 } %import imported diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index 55bd20efa..186b7bbef 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -6,6 +6,7 @@ import il65.parser.* import il65.compiler.* import il65.optimizing.optimizeExpressions import il65.optimizing.optimizeStatements +import java.io.File import kotlin.system.exitProcess @@ -14,7 +15,7 @@ fun main(args: Array) { println("\nIL65 compiler by Irmen de Jong (irmen@razorvine.net)") println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n") - // import main module and process additional imports + // import main module bitand process additional imports if(args.size != 1) { System.err.println("module filename argument missing") @@ -45,7 +46,7 @@ fun main(args: Array) { ) - // perform syntax checks and optimizations + // perform syntax checks bitand optimizations moduleAst.checkIdentifiers() moduleAst.optimizeExpressions(globalNameSpaceBeforeOptimization) moduleAst.checkValid(globalNameSpaceBeforeOptimization, compilerOptions) // check if tree is valid @@ -58,11 +59,16 @@ fun main(args: Array) { // globalNamespaceAfterOptimize.debugPrint() - // compile the syntax tree into stackvmProg form, and optimize that + // compile the syntax tree into stackvmProg form, bitand optimize that val compiler = Compiler(compilerOptions) val intermediate = compiler.compile(moduleAst) intermediate.optimize() - intermediate.toTextLines().forEach { println(it) } + + val stackVmFilename = intermediate.name + "_stackvm.txt" + val stackvmFile = File(stackVmFilename).printWriter() + intermediate.toTextLines().forEach { stackvmFile.println(it) } + stackvmFile.close() + println("StackVM intermediary code written to $stackVmFilename") // val assembly = stackvmProg.compileToAssembly() // diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index 6bd9a7aa4..137661fb0 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -197,7 +197,7 @@ interface Node { } -// find the parent node of a specific type or interface +// find the parent node of a specific type bitor interface // (useful to figure out in what namespace/block something is defined, etc) inline fun findParentNode(node: Node): T? { var candidate = node.parent @@ -302,7 +302,7 @@ interface INameScope { /** * Inserted into the Ast in place of modified nodes (not inserted directly as a parser result) - * It can hold zero or more replacement statements that have to be inserted at that point. + * It can hold zero bitor more replacement statements that have to be inserted at that point. */ class AnonymousStatementList(override var parent: Node, var statements: List) : IStatement { override var position: Position? = null @@ -371,7 +371,7 @@ private class GlobalNamespace(override val name: String, override var statements: MutableList, override val position: Position?) : INameScope { - private val scopedNamesUsed: MutableSet = mutableSetOf("main", "main.start") // main and main.start are always used + private val scopedNamesUsed: MutableSet = mutableSetOf("main", "main.start") // main bitand main.start are always used override fun usedNames(): Set = scopedNamesUsed @@ -576,7 +576,7 @@ class VarDecl(val type: VarDeclType, else -> when (declaredDatatype) { DataType.BYTE -> DataType.ARRAY DataType.WORD -> DataType.ARRAY_W - else -> throw SyntaxError("array can only contain bytes or words", position) + else -> throw SyntaxError("array can only contain bytes bitor words", position) } } } @@ -816,7 +816,7 @@ class FunctionCall(override var target: IdentifierReference, override var arglis } override fun constValue(namespace: INameScope): LiteralValue? { - // if the function is a built-in function and the args are consts, should try to const-evaluate! + // if the function is a built-in function bitand the args are consts, should try to const-evaluate! if(target.nameInSource.size>1) return null try { return when (target.nameInSource[0]) { diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index d928a2723..7342d1d4f 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -68,6 +68,8 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: if(block.address!=null && (block.address<0 || block.address>65535)) { checkResult.add(SyntaxError("block memory address must be valid integer 0..\$ffff", block.position)) } + + checkSubroutinesPrecededByReturnOrJump(block.statements) return super.process(block) } @@ -98,9 +100,10 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: err("return registers should be unique") super.process(subroutine) + checkSubroutinesPrecededByReturnOrJump(subroutine.statements) - // subroutine must contain at least one 'return' or 'goto' - // (or if it has an asm block, that must contain a 'rts' or 'jmp') + // subroutine must contain at least one 'return' bitor 'goto' + // (bitor if it has an asm block, that must contain a 'rts' bitor 'jmp') if(subroutine.statements.count { it is Return || it is Jump } == 0) { if(subroutine.address==null) { val amount = subroutine.statements @@ -109,15 +112,31 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: .count { it.contains(" rts") || it.contains("\trts") || it.contains(" jmp") || it.contains("\tjmp")} if(amount==0 ) - err("subroutine must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)") + err("subroutine must have at least one 'return' bitor 'goto' in it (bitor 'rts' / 'jmp' in case of %asm)") } } return subroutine } + private fun checkSubroutinesPrecededByReturnOrJump(statements: MutableList) { + var preceding: IStatement = BuiltinFunctionStatementPlaceholder + for (stmt in statements) { + if(stmt is Subroutine) { + if(preceding !is Return + && preceding !is Jump + && preceding !is Subroutine + && preceding !is VarDecl + && preceding !is BuiltinFunctionStatementPlaceholder) { + checkResult.add(SyntaxError("subroutine definition should be preceded by a return, jump, vardecl, bitor another subroutine statement", stmt.position)) + } + } + preceding = stmt + } + } + /** - * Assignment target must be register, or a variable name + * Assignment target must be register, bitor a variable name * for constant-value assignments, check the datatype as well */ override fun process(assignment: Assignment): IStatement { @@ -130,7 +149,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: return super.process(assignment) } targetSymbol !is VarDecl -> { - checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position)) + checkResult.add(SyntaxError("assignment LHS must be register bitor variable", assignment.position)) return super.process(assignment) } targetSymbol.type == VarDeclType.CONST -> { @@ -219,12 +238,12 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: "%output" -> { if(directive.parent !is Module) err("this directive may only occur at module level") if(directive.args.size!=1 || directive.args[0].name != "raw" && directive.args[0].name != "prg") - err("invalid output directive type, expected raw or prg") + err("invalid output directive type, expected raw bitor prg") } "%launcher" -> { if(directive.parent !is Module) err("this directive may only occur at module level") if(directive.args.size!=1 || directive.args[0].name != "basic" && directive.args[0].name != "none") - err("invalid launcher directive type, expected basic or none") + err("invalid launcher directive type, expected basic bitor none") } "%zeropage" -> { if(directive.parent !is Module) err("this directive may only occur at module level") @@ -232,7 +251,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: directive.args[0].name != "basicsafe" && directive.args[0].name != "kernalsafe" && directive.args[0].name != "full") - err("invalid zp directive style, expected basicsafe, kernalsafe, or full") + err("invalid zp directive style, expected basicsafe, kernalsafe, bitor full") } "%address" -> { if(directive.parent !is Module) err("this directive may only occur at module level") @@ -297,11 +316,11 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } from.strvalue!=null && to.strvalue!=null -> { if(from.strvalue.length!=1 || to.strvalue.length!=1) - err("range from and to must be a single character") + err("range from bitand to must be a single character") if(from.strvalue[0] > to.strvalue[0]) err("range from is larger than to value") } - else -> err("range expression must be over integers or over characters") + else -> err("range expression must be over integers bitor over characters") } } return range @@ -333,9 +352,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position)) } else { if(target !is VarDecl || target.type==VarDeclType.CONST) { - checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position)) + checkResult.add(SyntaxError("can only increment bitor decrement a variable", postIncrDecr.position)) } else if(target.datatype!=DataType.FLOAT && target.datatype!=DataType.WORD && target.datatype!=DataType.BYTE) { - checkResult.add(SyntaxError("can only increment or decrement a byte/float/word variable", postIncrDecr.position)) + checkResult.add(SyntaxError("can only increment bitor decrement a byte/float/word variable", postIncrDecr.position)) } } } @@ -346,7 +365,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: val targetStatement = target.targetStatement(namespace) if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder) return targetStatement - checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)) + checkResult.add(SyntaxError("undefined function bitor subroutine: ${target.nameInSource.joinToString(".")}", statement.position)) return null } @@ -354,13 +373,13 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: if(call.target.nameInSource.size==1 && BuiltinFunctionNames.contains(call.target.nameInSource[0])) { val functionName = call.target.nameInSource[0] if(functionName=="P_carry" || functionName=="P_irqd") { - // these functions allow only 0 or 1 as argument + // these functions allow only 0 bitor 1 as argument if(call.arglist.size!=1 || call.arglist[0] !is LiteralValue) { - checkResult.add(SyntaxError("$functionName requires one argument, 0 or 1", position)) + checkResult.add(SyntaxError("$functionName requires one argument, 0 bitor 1", position)) } else { val value = call.arglist[0] as LiteralValue if(value.intvalue==null || value.intvalue < 0 || value.intvalue > 1) { - checkResult.add(SyntaxError("$functionName requires one argument, 0 or 1", position)) + checkResult.add(SyntaxError("$functionName requires one argument, 0 bitor 1", position)) } } } @@ -398,7 +417,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: return err("string length must be 1..65535") } DataType.ARRAY -> { - // value may be either a single byte, or a byte array + // value may be either a single byte, bitor a byte array if(value.isArray) { for (av in value.arrayvalue!!) { val number = (av as LiteralValue).intvalue @@ -419,7 +438,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } } DataType.ARRAY_W -> { - // value may be either a single word, or a word array + // value may be either a single word, bitor a word array if(value.isArray) { for (av in value.arrayvalue!!) { val number = (av as LiteralValue).intvalue @@ -440,7 +459,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } } DataType.MATRIX -> { - // value can only be a single byte, or a byte array (which represents the matrix) + // value can only be a single byte, bitor a byte array (which represents the matrix) if(value.isArray) { for (av in value.arrayvalue!!) { val number = (av as LiteralValue).intvalue diff --git a/il65/src/il65/ast/AstRecursionChecker.kt b/il65/src/il65/ast/AstRecursionChecker.kt index f9acbcb2f..a8effc6b3 100644 --- a/il65/src/il65/ast/AstRecursionChecker.kt +++ b/il65/src/il65/ast/AstRecursionChecker.kt @@ -66,7 +66,7 @@ class DirectedGraph { if(recStack[node]==true) return true if(visited[node]==true) return false - // mark current node as visited and add to recursion stack + // mark current node as visited bitand add to recursion stack visited[node] = true recStack[node] = true diff --git a/il65/src/il65/ast/StmtReorderer.kt b/il65/src/il65/ast/StmtReorderer.kt index 8def7d2da..5fd339dfb 100644 --- a/il65/src/il65/ast/StmtReorderer.kt +++ b/il65/src/il65/ast/StmtReorderer.kt @@ -4,7 +4,7 @@ class StatementReorderer: IAstProcessor { // Reorders the statements in a way the compiler needs. // - 'main' block must be the very first statement. // - in every scope: - // -- the directives '%output', '%launcher', '%zeropage', '%address' and '%option' will come first. + // -- the directives '%output', '%launcher', '%zeropage', '%address' bitand '%option' will come first. // -- all vardecls then follow. // -- the remaining statements then follow in their original order. // - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives. diff --git a/il65/src/il65/compiler/Compiler.kt b/il65/src/il65/compiler/Compiler.kt index 0a218bca3..1d08187c0 100644 --- a/il65/src/il65/compiler/Compiler.kt +++ b/il65/src/il65/compiler/Compiler.kt @@ -34,8 +34,8 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va val zero = Mflpt5(0, 0,0,0,0) fun fromNumber(num: Number): Mflpt5 { // see https://en.wikipedia.org/wiki/Microsoft_Binary_Format - // and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a - // and https://en.wikipedia.org/wiki/IEEE_754-1985 + // bitand https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a + // bitand https://en.wikipedia.org/wiki/IEEE_754-1985 val flt = num.toDouble() if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE) @@ -47,12 +47,12 @@ data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, va var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias var mantissa = flt.absoluteValue - // if mantissa is too large, shift right and adjust exponent + // if mantissa is too large, shift right bitand adjust exponent while(mantissa >= 0x100000000) { mantissa /= 2.0 exponent ++ } - // if mantissa is too small, shift left and adjust exponent + // if mantissa is too small, shift left bitand adjust exponent while(mantissa < 0x80000000) { mantissa *= 2.0 exponent -- @@ -95,10 +95,10 @@ class Compiler(private val options: CompilationOptions) { val intermediate = StackVmProgram(module.name) namespace.debugPrint() - // create the pool of all variables used in all blocks and scopes + // create the pool of all variables used in all blocks bitand scopes val varGather = VarGatherer(intermediate) varGather.process(module) - println("Number of allocated variables and constants: ${intermediate.variables.size} (${intermediate.variablesMemSize} bytes)") + println("Number of allocated variables bitand constants: ${intermediate.variables.size} (${intermediate.variablesMemSize} bytes)") val translator = StatementTranslator(intermediate, namespace) translator.process(module) @@ -112,8 +112,12 @@ class Compiler(private val options: CompilationOptions) { class VarGatherer(val stackvmProg: StackVmProgram): IAstProcessor { // collect all the VarDecls to make them into one global list override fun process(decl: VarDecl): IStatement { - assert(decl.type==VarDeclType.VAR) {"only VAR decls should remain: CONST and MEMORY should have been processed away"} - stackvmProg.blockvar(decl.scopedname, decl) + if(decl.type == VarDeclType.MEMORY) + TODO("stackVm doesn't support memory vars for now") + + if (decl.type == VarDeclType.VAR) { + stackvmProg.blockvar(decl.scopedname, decl) + } return super.process(decl) } } @@ -123,31 +127,41 @@ class Compiler(private val options: CompilationOptions) { private set override fun process(subroutine: Subroutine): IStatement { + stackvmProg.label(subroutine.scopedname) translate(subroutine.statements) return super.process(subroutine) } override fun process(block: Block): IStatement { + stackvmProg.label(block.scopedname) translate(block.statements) return super.process(block) } + override fun process(directive: Directive): IStatement { + when(directive.directive) { + "%asminclude" -> throw CompilerException("can't use %asminclude in stackvm") + "%asmbinary" -> throw CompilerException("can't use %asmbinary in stackvm") + "%breakpoint" -> stackvmProg.instruction("break") + } + return super.process(directive) + } + private fun translate(statements: List) { for (stmt: IStatement in statements) { stmtUniqueSequenceNr++ when (stmt) { is AnonymousStatementList -> translate(stmt.statements) - is BuiltinFunctionStatementPlaceholder -> translate(stmt) is Label -> translate(stmt) is Return -> translate(stmt) is Assignment -> translate(stmt) is PostIncrDecr -> translate(stmt) is Jump -> translate(stmt) is FunctionCallStatement -> translate(stmt) - is InlineAssembly -> translate(stmt) is IfStatement -> translate(stmt) is BranchStatement -> translate(stmt) is Directive, is VarDecl, is Subroutine -> {} // skip this, already processed these. + is InlineAssembly -> throw CompilerException("inline assembly is not supported by the StackVM") else -> TODO("translate statement $stmt") } } @@ -156,7 +170,7 @@ class Compiler(private val options: CompilationOptions) { private fun translate(branch: BranchStatement) { /* * A branch: IF_CC { stuff } else { other_stuff } - * Which is desugared into: + * Which is translated into: * BCS _stmt_999_else * stuff * JUMP _stmt_999_continue @@ -194,20 +208,116 @@ class Compiler(private val options: CompilationOptions) { private fun makeLabel(postfix: String): String = "_il65stmt_${stmtUniqueSequenceNr}_$postfix" private fun translate(stmt: IfStatement) { - println("@todo translate: #$stmtUniqueSequenceNr : $stmt") - stackvmProg.instruction("nop") // todo translate if statement + /* + * An IF statement: IF (condition-expression) { stuff } else { other_stuff } + * Which is translated into: + * + * BEQ _stmt_999_else + * stuff + * JUMP _stmt_999_continue + * _stmt_999_else: + * other_stuff ;; optional + * _stmt_999_continue: + * ... + */ + translate(stmt.condition) + val labelElse = makeLabel("else") + val labelContinue = makeLabel("continue") + if(stmt.elsepart.isEmpty()) { + stackvmProg.instruction("beq $labelContinue") + translate(stmt.statements) + stackvmProg.label(labelContinue) + } else { + stackvmProg.instruction("beq $labelElse") + translate(stmt.statements) + stackvmProg.instruction("jump $labelContinue") + stackvmProg.label(labelElse) + translate(stmt.elsepart) + stackvmProg.label(labelContinue) + } } - private fun translate(stmt: InlineAssembly) { - println("@todo translate: #$stmtUniqueSequenceNr : $stmt") - TODO("inline assembly not supported yet by stackvm") + private fun translate(expr: IExpression) { + when(expr) { + is RegisterExpr -> { + stackvmProg.instruction("push_var ${expr.register}") + } + is BinaryExpression -> { + translate(expr.left) + translate(expr.right) + translateBinaryOperator(expr.operator) + } + is FunctionCall -> { + expr.arglist.forEach { translate(it) } + val target = expr.target.targetStatement(namespace) + if(target is BuiltinFunctionStatementPlaceholder) { + // call to a builtin function + stackvmProg.instruction("syscall FUNC_${expr.target.nameInSource[0].toUpperCase()}") // call builtin function + } else { + when(target) { + is Subroutine -> stackvmProg.instruction("call ${target.scopedname}") + else -> TODO("non-builtin function call to $target") + } + } + } + is IdentifierReference -> { + val target = expr.targetStatement(namespace) + when(target) { + is VarDecl -> stackvmProg.instruction("push_var ${target.scopedname}") + else -> throw CompilerException("expression identifierref should be a vardef, not $target") + } + } + else -> { + val lv = expr.constValue(namespace) ?: throw CompilerException("constant expression required, not $expr") + when { + lv.isString -> stackvmProg.instruction("push \"${lv.strvalue}\"") + lv.isInteger -> { + val intval = lv.intvalue!! + if(intval<=255) + stackvmProg.instruction("push b:${intval.toString(16)}") + else if(intval <= 65535) + stackvmProg.instruction("push w:${intval.toString(16)}") + } + lv.isNumeric -> stackvmProg.instruction("push f:${lv.asNumericValue}") + lv.isArray -> { + lv.arrayvalue?.forEach { translate(it) } + stackvmProg.instruction("array w:${lv.arrayvalue!!.size.toString(16)}") + } + else -> throw CompilerException("expression constvalue invalid type $lv") + } + } + } + } + + private fun translateBinaryOperator(operator: String) { + val instruction = when(operator) { + "+" -> "add" + "-" -> "sub" + "*" -> "mul" + "/" -> "div" + "**" -> "pow" + "&" -> "bitand" + "|" -> "bitor" + "^" -> "bitxor" + "bitand" -> "bitand" + "bitor" -> "bitor" + "bitxor" -> "bitxor" + "<" -> "less" + ">" -> "greater" + "<=" -> "lesseq" + ">=" -> "greatereq" + "==" -> "equal" + "!=" -> "notequal" + else -> throw FatalAstException("const evaluation for invalid operator $operator") + } + stackvmProg.instruction(instruction) } private fun translate(stmt: FunctionCallStatement) { val targetStmt = stmt.target.targetStatement(namespace)!! if(targetStmt is BuiltinFunctionStatementPlaceholder) { - // call to a builtin function - TODO("BUILTIN_${stmt.target.nameInSource[0]}") // TODO + stmt.arglist.forEach { translate(it) } + stackvmProg.instruction("syscall FUNC_${stmt.target.nameInSource[0].toUpperCase()}") // call builtin function return } @@ -216,23 +326,10 @@ class Compiler(private val options: CompilationOptions) { is Subroutine -> targetStmt.scopedname else -> throw AstException("invalid call target node type: ${targetStmt::class}") } - - for (arg in stmt.arglist) { - val lv = arg.constValue(namespace) - if(lv==null) TODO("argument must be constant for now") // TODO non-const args - stackvmProg.instruction("push ${makeValue(lv)}") - } + stmt.arglist.forEach { translate(it) } stackvmProg.instruction("call $targetname") } - private fun makeValue(value: LiteralValue): String { - return when { - value.isString -> "\"${value.strvalue}\"" - value.isNumeric -> value.asNumericValue.toString() - else -> TODO("stackvm value for $value") - } - } - private fun translate(stmt: Jump) { val instr = if(stmt.address!=null) { @@ -250,19 +347,60 @@ class Compiler(private val options: CompilationOptions) { private fun translate(stmt: PostIncrDecr) { if(stmt.target.register!=null) { - TODO("register++/-- not yet implemented") + when(stmt.operator) { + "++" -> stackvmProg.instruction("inc_var ${stmt.target.register}") + "--" -> stackvmProg.instruction("dec_var ${stmt.target.register}") + } } else { val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl when(stmt.operator) { "++" -> stackvmProg.instruction("inc_var ${targetStatement.scopedname}") - "--" -> stackvmProg.instruction("decr_var ${targetStatement.scopedname}") + "--" -> stackvmProg.instruction("dec_var ${targetStatement.scopedname}") } } } private fun translate(stmt: Assignment) { - println("@todo translate: #$stmtUniqueSequenceNr : $stmt") - stackvmProg.instruction("nop") // todo translate assignment + translate(stmt.value) + if(stmt.aug_op!=null) { + // augmented assignment + if(stmt.target.identifier!=null) { + val target = stmt.target.identifier!!.targetStatement(namespace)!! + when(target) { + is VarDecl -> stackvmProg.instruction("push_var ${target.scopedname}") + else -> throw CompilerException("invalid assignment target type ${target::class}") + } + } else if(stmt.target.register!=null) { + stackvmProg.instruction("push_var ${stmt.target.register}") + } + translateAugAssignOperator(stmt.aug_op) + } + + // pop the result value back into the assignment target + if(stmt.target.identifier!=null) { + val target = stmt.target.identifier!!.targetStatement(namespace)!! + when(target) { + is VarDecl -> stackvmProg.instruction("pop_var ${target.scopedname}") + else -> throw CompilerException("invalid assignment target type ${target::class}") + } + } else if(stmt.target.register!=null) { + stackvmProg.instruction("pop_var ${stmt.target.register}") + } + } + + private fun translateAugAssignOperator(aug_op: String) { + val instruction = when(aug_op) { + "+=" -> "add" + "-=" -> "sub" + "/=" -> "div" + "*=" -> "mul" + "**=" -> "pow" + "&=" -> "bitand" + "|=" -> "bitor" + "^=" -> "bitxor" + else -> throw CompilerException("invalid aug assignment operator $aug_op") + } + stackvmProg.instruction(instruction) } private fun translate(stmt: Return) { @@ -275,11 +413,6 @@ class Compiler(private val options: CompilationOptions) { private fun translate(stmt: Label) { stackvmProg.label(stmt.scopedname) } - - private fun translate(stmt: BuiltinFunctionStatementPlaceholder) { - println("@todo translate: #$stmtUniqueSequenceNr : $stmt") - stackvmProg.instruction("nop") // todo translate builtinfunction placeholder - } } } @@ -316,12 +449,15 @@ class StackVmProgram(val name: String) { result.add("%end_memory") result.add("%variables") for(v in variables) { - assert(v.value.type==VarDeclType.VAR || v.value.type==VarDeclType.CONST) + if (!(v.value.type == VarDeclType.VAR || v.value.type == VarDeclType.CONST)) { + throw AssertionError("Should be only VAR bitor CONST variables") + } val litval = v.value.value as LiteralValue val litvalStr = when { - litval.isNumeric -> litval.asNumericValue.toString() + litval.isInteger -> litval.intvalue!!.toString(16) + litval.isFloat -> litval.floatvalue.toString() litval.isString -> "\"${litval.strvalue}\"" - else -> TODO() + else -> TODO("non-scalar value") } val line = "${v.key} ${v.value.datatype.toString().toLowerCase()} $litvalStr" result.add(line) diff --git a/il65/src/il65/functions/BuiltinFunctions.kt b/il65/src/il65/functions/BuiltinFunctions.kt index 84ad1d5ba..04bae9941 100644 --- a/il65/src/il65/functions/BuiltinFunctions.kt +++ b/il65/src/il65/functions/BuiltinFunctions.kt @@ -132,7 +132,7 @@ fun builtinDeg(args: List, position: Position?, namespace:INameScop = oneDoubleArg(args, position, namespace, Math::toDegrees) fun builtinAbs(args: List, position: Position?, namespace:INameScope): LiteralValue { - // 1 arg, type = float or int, result type= same as argument type + // 1 arg, type = float bitor int, result type= same as argument type if(args.size!=1) throw SyntaxError("abs requires one numeric argument", position) diff --git a/il65/src/il65/optimizing/ExpressionOptimizer.kt b/il65/src/il65/optimizing/ExpressionOptimizer.kt index fcff59285..f3a730c72 100644 --- a/il65/src/il65/optimizing/ExpressionOptimizer.kt +++ b/il65/src/il65/optimizing/ExpressionOptimizer.kt @@ -47,7 +47,7 @@ fun Module.optimizeExpressions(globalNamespace: INameScope) { X ^ 0 -> X todo expression optimization: reduce expression nesting / flattening of parenthesis - todo expression optimization: simplify logical expression when a term makes it always true or false (1 or 0) + todo expression optimization: simplify logical expression when a term makes it always true bitor false (1 bitor 0) todo expression optimization: optimize some simple multiplications into shifts (A*8 -> A<<3, A/4 -> A>>2) todo expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call) @@ -148,7 +148,7 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess optimizationsDone++ LiteralValue(floatvalue = -subexpr.floatvalue) } - else -> throw ExpressionError("can only take negative of int or float", subexpr.position) + else -> throw ExpressionError("can only take negative of int bitor float", subexpr.position) } expr.operator == "~" -> when { subexpr.intvalue != null -> { @@ -268,9 +268,9 @@ class ConstExprEvaluator { "&" -> bitwiseand(left, right) "|" -> bitwiseor(left, right) "^" -> bitwisexor(left, right) - "and" -> logicaland(left, right) - "or" -> logicalor(left, right) - "xor" -> logicalxor(left, right) + "bitand" -> logicaland(left, right) + "bitor" -> logicalor(left, right) + "bitxor" -> logicalxor(left, right) "<" -> compareless(left, right) ">" -> comparegreater(left, right) "<=" -> comparelessequal(left, right) @@ -369,7 +369,7 @@ class ConstExprEvaluator { } private fun logicalxor(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot compute $left locical-xor $right" + val error = "cannot compute $left locical-bitxor $right" val litval = when { left.intvalue!=null -> when { right.intvalue!=null -> LiteralValue( @@ -392,7 +392,7 @@ class ConstExprEvaluator { } private fun logicalor(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot compute $left locical-or $right" + val error = "cannot compute $left locical-bitor $right" val litval = when { left.intvalue!=null -> when { right.intvalue!=null -> LiteralValue( @@ -415,7 +415,7 @@ class ConstExprEvaluator { } private fun logicaland(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot compute $left locical-and $right" + val error = "cannot compute $left locical-bitand $right" val litval = when { left.intvalue!=null -> when { right.intvalue!=null -> LiteralValue( @@ -484,7 +484,7 @@ class ConstExprEvaluator { } private fun plus(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot add $left and $right" + val error = "cannot add $left bitand $right" val litval = when { left.intvalue!=null -> when { right.intvalue!=null -> LiteralValue(intvalue = left.intvalue + right.intvalue) @@ -503,7 +503,7 @@ class ConstExprEvaluator { } private fun minus(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot subtract $left and $right" + val error = "cannot subtract $left bitand $right" val litval = when { left.intvalue!=null -> when { right.intvalue!=null -> LiteralValue(intvalue = left.intvalue - right.intvalue) @@ -522,7 +522,7 @@ class ConstExprEvaluator { } private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot multiply $left and $right" + val error = "cannot multiply $left bitand $right" val litval = when { left.intvalue!=null -> when { right.intvalue!=null -> LiteralValue(intvalue = left.intvalue * right.intvalue) diff --git a/il65/src/il65/optimizing/StatementsOptimizer.kt b/il65/src/il65/optimizing/StatementsOptimizer.kt index 40cd24076..7d5440c08 100644 --- a/il65/src/il65/optimizing/StatementsOptimizer.kt +++ b/il65/src/il65/optimizing/StatementsOptimizer.kt @@ -24,8 +24,8 @@ fun Module.optimizeStatements(globalNamespace: INameScope, allScopedSymbolDefini todo remove statements that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc todo optimize addition with self into shift 1 (A+=A -> A<<=1) todo assignment optimization: optimize some simple multiplications into shifts (A*=8 -> A<<=3) - todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) - todo merge sequence of assignments into one (as long as the value is a constant and the target not a MEMORY type!) + todo analyse for unreachable code bitand remove that (f.i. code after goto bitor return that has no label so can never be jumped to) + todo merge sequence of assignments into one (as long as the value is a constant bitand the target not a MEMORY type!) todo report more always true/always false conditions */ diff --git a/il65/src/il65/stackvm/StackVm.kt b/il65/src/il65/stackvm/StackVm.kt index 30f918d7f..380a473ed 100644 --- a/il65/src/il65/stackvm/StackVm.kt +++ b/il65/src/il65/stackvm/StackVm.kt @@ -21,13 +21,14 @@ enum class Opcode { PUSH_MEM_F, // push float value from memory to stack PUSH_VAR, // push a variable DUP, // push topmost value once again + ARRAY, // create arrayvalue of number of integer values on the stack, given in argument // popping values off the (evaluation) stack, possibly storing them in another location DISCARD, // discard top value POP_MEM, // pop value into destination memory address POP_VAR, // pop value into variable - // numeric and bitwise arithmetic + // numeric bitand bitwise arithmetic ADD, SUB, MUL, @@ -58,14 +59,17 @@ enum class Opcode { ROR2_MEM, ROR2_MEM_W, ROR2_VAR, - AND, - OR, - XOR, + BITAND, + BITOR, + BITXOR, INV, LSB, MSB, - // logical operations (?) + // logical operations + AND, + OR, + XOR, // increment, decrement INC, @@ -77,7 +81,13 @@ enum class Opcode { DEC_MEM_W, DEC_VAR, - // comparisons (?) + // comparisons + LESS, + GREATER, + LESSEQ, + GREATEREQ, + EQUAL, + NOTEQUAL, // branching JUMP, @@ -100,25 +110,61 @@ enum class Opcode { SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations NOP, - TERMINATE + BREAK, // breakpoint + TERMINATE // end the program } enum class Syscall(val callNr: Short) { WRITE_MEMCHR(10), // print a single char from the memory WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory - WRITE_NUM(12), // pop from the evaluation stack and print it as a number - WRITE_CHAR(13), // pop from the evaluation stack and print it as a single petscii character - WRITE_VAR(14), // print the number or string from the given variable + WRITE_NUM(12), // pop from the evaluation stack bitand print it as a number + WRITE_CHAR(13), // pop from the evaluation stack bitand print it as a single petscii character + WRITE_VAR(14), // print the number bitor string from the given variable INPUT_VAR(15), // user input a string into a variable GFX_PIXEL(16), // plot a pixel at (x,y,color) pushed on stack in that order GFX_CLEARSCR(17), // clear the screen with color pushed on stack GFX_TEXT(18), // write text on screen at (x,y,text,color) pushed on stack in that order RANDOM(19), // push a random byte on the stack - RANDOM_W(20) // push a random word on the stack + RANDOM_W(20), // push a random word on the stack + + + FUNC_P_CARRY(100), + FUNC_P_IRQD(101), + FUNC_ROL(102), + FUNC_ROR(103), + FUNC_ROL2(104), + FUNC_ROR2(105), + FUNC_LSL(106), + FUNC_LSR(107), + FUNC_SIN(108), + FUNC_COS(109), + FUNC_ABS(110), + FUNC_ACOS(111), + FUNC_ASIN(112), + FUNC_TAN(113), + FUNC_ATAN(114), + FUNC_LOG(115), + FUNC_LOG10(116), + FUNC_SQRT(117), + FUNC_RAD(118), + FUNC_DEG(119), + FUNC_ROUND(120), + FUNC_FLOOR(121), + FUNC_CEIL(122), + FUNC_MAX(123), + FUNC_MIN(124), + FUNC_AVG(125), + FUNC_SUM(126), + FUNC_LEN(127), + FUNC_ANY(128), + FUNC_ALL(129), + FUNC_LSB(130), + FUNC_MSB(131) + } class Memory { - private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255 + private val mem = ShortArray(65536) // shorts because byte is signed bitand we store values 0..255 fun getByte(address: Int): Short { return mem[address] @@ -174,17 +220,33 @@ class Memory { } -data class Value(val type: DataType, private val numericvalue: Number?, val stringvalue: String?=null) { +class Value(val type: DataType, private val numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) { private var byteval: Short? = null private var wordval: Int? = null private var floatval: Double? = null + val asBooleanValue: Boolean init { when(type) { - DataType.BYTE -> byteval = (numericvalue!!.toInt() and 255).toShort() - DataType.WORD -> wordval = numericvalue!!.toInt() and 65535 - DataType.FLOAT -> floatval = numericvalue!!.toDouble() - DataType.STR -> if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type") + DataType.BYTE -> { + byteval = (numericvalue!!.toInt() and 255).toShort() + asBooleanValue = byteval != (0.toShort()) + } + DataType.WORD -> { + wordval = numericvalue!!.toInt() and 65535 + asBooleanValue = wordval != 0 + } + DataType.FLOAT -> { + floatval = numericvalue!!.toDouble() + asBooleanValue = floatval != 0.0 + } + DataType.ARRAY -> { + asBooleanValue = arrayvalue!!.isNotEmpty() + } + DataType.STR -> { + if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type") + asBooleanValue = stringvalue.isNotEmpty() + } else -> throw VmExecutionException("invalid datatype $type") } } @@ -257,7 +319,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri } fun rol(carry: Boolean): Pair { - // 9 or 17 bit rotate left (with carry)) + // 9 bitor 17 bit rotate left (with carry)) return when(type) { DataType.BYTE -> { val v = byteval!!.toInt() @@ -276,7 +338,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri } fun ror(carry: Boolean): Pair { - // 9 or 17 bit rotate right (with carry) + // 9 bitor 17 bit rotate right (with carry) return when(type) { DataType.BYTE -> { val v = byteval!!.toInt() @@ -295,7 +357,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri } fun rol2(): Value { - // 8 or 16 bit rotate left + // 8 bitor 16 bit rotate left return when(type) { DataType.BYTE -> { val v = byteval!!.toInt() @@ -314,7 +376,7 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri } fun ror2(): Value { - // 8 or 16 bit rotate right + // 8 bitor 16 bit rotate right return when(type) { DataType.BYTE -> { val v = byteval!!.toInt() @@ -341,27 +403,31 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri } } - fun and(other: Value): Value { + fun bitand(other: Value): Value { val v1 = integerValue() val v2 = other.integerValue() val result = v1.and(v2) return Value(type, result) } - fun or(other: Value): Value { + fun bitor(other: Value): Value { val v1 = integerValue() val v2 = other.integerValue() val result = v1.or(v2) return Value(type, result) } - fun xor(other: Value): Value { + fun bitxor(other: Value): Value { val v1 = integerValue() val v2 = other.integerValue() val result = v1.xor(v2) return Value(type, result) } + fun and(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue && other.asBooleanValue) 1 else 0) + fun or(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue || other.asBooleanValue) 1 else 0) + fun xor(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue xor other.asBooleanValue) 1 else 0) + fun inv(): Value { return when(type) { DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv()) @@ -403,6 +469,13 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri else -> throw VmExecutionException("not can only work on byte/word") } } + + fun compareLess(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() < other.numericValue().toDouble()) 1 else 0) + fun compareGreater(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() > other.numericValue().toDouble()) 1 else 0) + fun compareLessEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() <= other.numericValue().toDouble()) 1 else 0) + fun compareGreaterEq(other: Value) = Value(DataType.BYTE, if(this.numericValue().toDouble() >= other.numericValue().toDouble()) 1 else 0) + fun compareEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() == other.numericValue()) 1 else 0) + fun compareNotEqual(other: Value) = Value(DataType.BYTE, if(this.numericValue() != other.numericValue()) 1 else 0) } @@ -434,6 +507,8 @@ private class VmExecutionException(msg: String?) : Exception(msg) private class VmTerminationException(msg: String?) : Exception(msg) +private class VmBreakpointException : Exception("breakpoint") + private class MyStack : Stack() { fun peek(amount: Int) : List { return this.toList().subList(max(0, size-amount), size) @@ -492,8 +567,13 @@ class Program (prog: MutableList, val opcode=Opcode.valueOf(parts[0].toUpperCase()) val args = if(parts.size==2) parts[1] else null val instruction = when(opcode) { - Opcode.JUMP -> { - Instruction(opcode, callLabel = args) + Opcode.JUMP, Opcode.CALL, Opcode.BMI, Opcode.BPL, + Opcode.BEQ, Opcode.BNE, Opcode.BCS, Opcode.BCC -> { + if(args!!.startsWith('$')) { + Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16))) + } else { + Instruction(opcode, callLabel = args) + } } Opcode.INC_VAR, Opcode.DEC_VAR, Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR, @@ -510,7 +590,10 @@ class Program (prog: MutableList, val callValues = if(callValue==null) emptyList() else listOf(callValue) Instruction(opcode, Value(DataType.BYTE, call.callNr), callValues) } - else -> Instruction(opcode, getArgValue(args)) + else -> { + println("INSTR $opcode at $lineNr args=$args") // TODO weg + Instruction(opcode, getArgValue(args)) + } } instructions.add(instruction) if(nextInstructionLabelname.isNotEmpty()) { @@ -633,18 +716,33 @@ class Program (prog: MutableList, 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 -> { - val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = target + 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 + } } Opcode.BCC, Opcode.BCS, Opcode.BEQ, Opcode.BNE, Opcode.BMI, Opcode.BPL -> { - val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = jumpInstr - instr.nextAlt = nextInstr + 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 + } } Opcode.CALL -> { - val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = jumpInstr - instr.nextAlt = nextInstr // instruction to return to + 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 + instr.nextAlt = nextInstr // instruction to return to + } } else -> instr.next = nextInstr } @@ -669,6 +767,21 @@ class StackVm(val traceOutputFile: String?) { this.program = program.program this.canvas = canvas this.variables = program.variables.toMutableMap() + if(this.variables.contains("A") || + this.variables.contains("X") || + this.variables.contains("Y") || + this.variables.contains("XY") || + this.variables.contains("AX") || + this.variables.contains("AY")) + throw VmExecutionException("program contains variable(s) for the reserved registers A,X,...") + // define the 'registers' + this.variables["A"] = Value(DataType.BYTE, 0) + this.variables["X"] = Value(DataType.BYTE, 0) + this.variables["Y"] = Value(DataType.BYTE, 0) + this.variables["AX"] = Value(DataType.WORD, 0) + this.variables["AY"] = Value(DataType.WORD, 0) + this.variables["XY"] = Value(DataType.WORD, 0) + initMemory(program.memory) currentIns = this.program[0] } @@ -679,12 +792,17 @@ class StackVm(val traceOutputFile: String?) { val instructionsPerStep = 5000 val start = System.currentTimeMillis() for(i:Int in 0..instructionsPerStep) { - currentIns = dispatch(currentIns) + try { + currentIns = dispatch(currentIns) - if (evalstack.size > 128) - throw VmExecutionException("too many values on evaluation stack") - if (callstack.size > 128) - throw VmExecutionException("too many nested/recursive calls") + if (evalstack.size > 128) + throw VmExecutionException("too many values on evaluation stack") + if (callstack.size > 128) + throw VmExecutionException("too many nested/recursive calls") + } catch (bp: VmBreakpointException) { + currentIns = currentIns.next + throw bp + } } val time = System.currentTimeMillis()-start if(time > 100) { @@ -737,6 +855,17 @@ class StackVm(val traceOutputFile: String?) { evalstack.push(Value(DataType.FLOAT, mem.getFloat(address))) } Opcode.DUP -> evalstack.push(evalstack.peek()) + Opcode.ARRAY -> { + val amount = ins.arg!!.integerValue() + val array = mutableListOf() + for (i in 0..amount) { + val value = evalstack.pop() + if(value.type!=DataType.BYTE && value.type!=DataType.WORD) + throw VmExecutionException("array requires values to be all byte/word") + array.add(value.integerValue()) + } + evalstack.push(Value(DataType.ARRAY, null, arrayvalue = array.toIntArray())) + } Opcode.DISCARD -> evalstack.pop() Opcode.SWAP -> { val (top, second) = evalstack.pop2() @@ -805,17 +934,17 @@ class StackVm(val traceOutputFile: String?) { val v = evalstack.pop() evalstack.push(v.ror2()) } - Opcode.AND -> { + Opcode.BITAND -> { val (top, second) = evalstack.pop2() - evalstack.push(second.and(top)) + evalstack.push(second.bitand(top)) } - Opcode.OR -> { + Opcode.BITOR -> { val (top, second) = evalstack.pop2() - evalstack.push(second.or(top)) + evalstack.push(second.bitor(top)) } - Opcode.XOR -> { + Opcode.BITXOR -> { val (top, second) = evalstack.pop2() - evalstack.push(second.xor(top)) + evalstack.push(second.bitxor(top)) } Opcode.INV -> { val v = evalstack.pop() @@ -884,6 +1013,7 @@ class StackVm(val traceOutputFile: String?) { Opcode.SEC -> carry = true Opcode.CLC -> carry = false Opcode.TERMINATE -> throw VmTerminationException("execution terminated") + Opcode.BREAK -> throw VmBreakpointException() Opcode.INC_MEM -> { val addr = ins.arg!!.integerValue() @@ -985,10 +1115,10 @@ class StackVm(val traceOutputFile: String?) { Opcode.JUMP -> {} // do nothing; the next instruction is wired up already to the jump target Opcode.BCS -> return if(carry) ins.next else ins.nextAlt!! Opcode.BCC -> return if(carry) ins.nextAlt!! else ins.next - Opcode.BEQ -> return if(evalstack.peek().numericValue().toDouble()==0.0) ins.next else ins.nextAlt!! - Opcode.BNE -> return if(evalstack.peek().numericValue().toDouble()!=0.0) ins.next else ins.nextAlt!! - Opcode.BMI -> return if(evalstack.peek().numericValue().toDouble()<0.0) ins.next else ins.nextAlt!! - Opcode.BPL -> return if(evalstack.peek().numericValue().toDouble()>=0.0) ins.next else ins.nextAlt!! + Opcode.BEQ -> return if(evalstack.pop().numericValue().toDouble()==0.0) ins.next else ins.nextAlt!! + Opcode.BNE -> return if(evalstack.pop().numericValue().toDouble()!=0.0) ins.next else ins.nextAlt!! + Opcode.BMI -> return if(evalstack.pop().numericValue().toDouble()<0.0) ins.next else ins.nextAlt!! + Opcode.BPL -> return if(evalstack.pop().numericValue().toDouble()>=0.0) ins.next else ins.nextAlt!! Opcode.CALL -> callstack.push(ins.nextAlt) Opcode.RETURN -> return callstack.pop() Opcode.PUSH_VAR -> { @@ -1055,6 +1185,43 @@ class StackVm(val traceOutputFile: String?) { val v = evalstack.pop() evalstack.push(v.msb()) } + Opcode.AND -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.and(top)) + } + Opcode.OR -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.or(top)) + } + Opcode.XOR -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.xor(top)) + } + Opcode.LESS -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.compareLess(top)) + } + Opcode.GREATER -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.compareGreater(top)) + } + Opcode.LESSEQ -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.compareLessEq(top)) + } + Opcode.GREATEREQ -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.compareGreaterEq(top)) + } + Opcode.EQUAL -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.compareEqual(top)) + } + Opcode.NOTEQUAL -> { + val (top, second) = evalstack.pop2() + evalstack.push(second.compareNotEqual(top)) + } + else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}") } if(traceOutput!=null) { @@ -1068,7 +1235,7 @@ class StackVm(val traceOutputFile: String?) { fun main(args: Array) { - val program = Program.load("examples/stackvmtest.txt") + val program = Program.load(args.first()) val vm = StackVm(traceOutputFile = null) val dialog = ScreenDialog() vm.load(program, dialog.canvas) @@ -1077,7 +1244,14 @@ fun main(args: Array) { dialog.isVisible = true dialog.start() - val programTimer = Timer(10) { _ -> vm.step() } + val programTimer = Timer(10) { _ -> + try { + vm.step() + } catch(bp: VmBreakpointException) { + println("Breakpoint: execution halted. Press enter to resume.") + readLine() + } + } programTimer.start() } }