diff --git a/examples/c64/panic_test.mfk b/examples/c64/panic_test.mfk new file mode 100644 index 00000000..a91ad5b2 --- /dev/null +++ b/examples/c64/panic_test.mfk @@ -0,0 +1,3 @@ +void main() { + panic() +} \ No newline at end of file diff --git a/include/a8.ini b/include/a8.ini index 0723c075..c6d14bd1 100644 --- a/include/a8.ini +++ b/include/a8.ini @@ -1,6 +1,6 @@ [compilation] arch=strict -modules=a8_kernel +modules=a8_kernel,default_panic [allocation] diff --git a/include/c128.ini b/include/c128.ini index 6d40a6cd..f74766fa 100644 --- a/include/c128.ini +++ b/include/c128.ini @@ -1,6 +1,6 @@ [compilation] arch=nmos -modules=c128_hardware,loader_1c01,c128_kernal +modules=c128_hardware,loader_1c01,c128_kernal,default_panic [allocation] diff --git a/include/c16.ini b/include/c16.ini index bba71a26..a5dd1173 100644 --- a/include/c16.ini +++ b/include/c16.ini @@ -1,6 +1,6 @@ [compilation] arch=nmos -modules=loader_1001,c264_kernal,c264_hardware +modules=loader_1001,c264_kernal,c264_hardware,default_panic [allocation] diff --git a/include/c64.ini b/include/c64.ini index dcaf4d21..e67561d1 100644 --- a/include/c64.ini +++ b/include/c64.ini @@ -5,7 +5,7 @@ ; CPU architecture: nmos, strictnmos, ricoh, strictricoh, cmos arch=nmos ; modules to load -modules=c64_hardware,loader_0801,c64_kernal,stdlib +modules=c64_hardware,loader_0801,c64_kernal,c64_panic,stdlib ; optionally: default flags emit_illegals=true diff --git a/include/c64_panic.mfk b/include/c64_panic.mfk new file mode 100644 index 00000000..119731f2 --- /dev/null +++ b/include/c64_panic.mfk @@ -0,0 +1,27 @@ +void _panic() { + asm { + SEI + PLA // status register + PLA + TAY + PLA + + TAX + JSR hi_nibble_to_hex + JSR putchar + TXA + JSR lo_nibble_to_hex + JSR putchar + + TYA + JSR hi_nibble_to_hex + JSR putchar + TYA + JSR lo_nibble_to_hex + JSR putchar + + LDA #2 + STA $D020 + } + while(true){} +} \ No newline at end of file diff --git a/include/default_panic.mfk b/include/default_panic.mfk new file mode 100644 index 00000000..ca25d37e --- /dev/null +++ b/include/default_panic.mfk @@ -0,0 +1,3 @@ +void _panic() { + while(true){} +} \ No newline at end of file diff --git a/include/pet.ini b/include/pet.ini index ca975341..0ca739d8 100644 --- a/include/pet.ini +++ b/include/pet.ini @@ -1,6 +1,6 @@ [compilation] arch=nmos -modules=loader_0401,pet_kernal +modules=loader_0401,pet_kernal,default_panic [allocation] diff --git a/include/plus4.ini b/include/plus4.ini index 5b12893d..49f7b415 100644 --- a/include/plus4.ini +++ b/include/plus4.ini @@ -1,6 +1,6 @@ [compilation] arch=nmos -modules=c264_loader,c264_kernal,c264_hardware +modules=c264_loader,c264_kernal,c264_hardware,default_panic [allocation] diff --git a/include/stdlib.mfk b/include/stdlib.mfk index 0dda8f5c..aa8f2b4d 100644 --- a/include/stdlib.mfk +++ b/include/stdlib.mfk @@ -21,3 +21,25 @@ inline asm void enable_irq() { CLI } +asm byte hi_nibble_to_hex(byte a) { + LSR + LSR + LSR + LSR + JMP lo_nibble_to_hex +} + +asm byte lo_nibble_to_hex(byte a) { + AND #$F + CLC + ADC #$30 + CMP #$3A + BCC _lo_nibble_to_hex_lbl + ADC #$6 // carry is set +_lo_nibble_to_hex_lbl: + RTS +} + +inline asm void panic() { + JSR _panic +} \ No newline at end of file diff --git a/include/vic20.ini b/include/vic20.ini index 6c33a727..c2604ea1 100644 --- a/include/vic20.ini +++ b/include/vic20.ini @@ -1,6 +1,6 @@ [compilation] arch=nmos -modules=loader_1001,vic20_kernal +modules=loader_1001,vic20_kernal,default_panic [allocation] diff --git a/include/vic20_3k.ini b/include/vic20_3k.ini index 10088a92..66aee5e1 100644 --- a/include/vic20_3k.ini +++ b/include/vic20_3k.ini @@ -1,6 +1,6 @@ [compilation] arch=nmos -modules=loader_0401,vic20_kernal +modules=loader_0401,vic20_kernal,default_panic [allocation] diff --git a/include/vic20_8k.ini b/include/vic20_8k.ini index d7acfa58..9b029e80 100644 --- a/include/vic20_8k.ini +++ b/include/vic20_8k.ini @@ -1,6 +1,6 @@ [compilation] arch=nmos -modules=loader_1201,vic20_kernal +modules=loader_1201,vic20_kernal,default_panic [allocation] diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index be845a12..0d40e8c1 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -78,6 +78,8 @@ object CompilationFlag extends Enumeration { DetailedFlowAnalysis, DangerousOptimizations, InlineFunctions, // memory allocation options VariableOverlap, + // runtime check options + CheckIndexOutOfBounds, // warning options ExtraComparisonWarnings, RorWarning, diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 88e985fb..6d68707c 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -77,7 +77,7 @@ object Main { options = options).run() val program = if (optLevel > 0) { - OptimizationPresets.NodeOpt.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt)) + OptimizationPresets.NodeOpt.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt, options)) } else { unoptimized } @@ -191,10 +191,13 @@ object Main { }.description("Whether should prevent indirect JMP bug on page boundary.") boolean("-fdecimal-mode", "-fno-decimal-mode").action { (c, v) => c.changeFlag(CompilationFlag.DecimalMode, v) - }.description("Whether should decimal mode be available.") + }.description("Whether decimal mode should be available.") boolean("-fvariable-overlap", "-fno-variable-overlap").action { (c, v) => c.changeFlag(CompilationFlag.VariableOverlap, v) - }.description("Whether should variables overlap if their scopes do not intersect.") + }.description("Whether variables should overlap if their scopes do not intersect.") + boolean("-fbounds-checking", "-fno-bounds-checking").action { (c, v) => + c.changeFlag(CompilationFlag.VariableOverlap, v) + }.description("Whether should insert bounds checking on array access.") fluff("", "Optimization options:", "") diff --git a/src/main/scala/millfork/node/Program.scala b/src/main/scala/millfork/node/Program.scala index 89f1e12a..b3fa361e 100644 --- a/src/main/scala/millfork/node/Program.scala +++ b/src/main/scala/millfork/node/Program.scala @@ -1,11 +1,12 @@ package millfork.node +import millfork.CompilationOptions import millfork.node.opt.NodeOptimization /** * @author Karol Stasiak */ case class Program(declarations: List[DeclarationStatement]) { - def applyNodeOptimization(o: NodeOptimization) = Program(o.optimize(declarations).asInstanceOf[List[DeclarationStatement]]) + def applyNodeOptimization(o: NodeOptimization, options: CompilationOptions) = Program(o.optimize(declarations, options).asInstanceOf[List[DeclarationStatement]]) def +(p:Program): Program = Program(this.declarations ++ p.declarations) } diff --git a/src/main/scala/millfork/node/opt/NodeOptimization.scala b/src/main/scala/millfork/node/opt/NodeOptimization.scala index 94421e8e..9078813b 100644 --- a/src/main/scala/millfork/node/opt/NodeOptimization.scala +++ b/src/main/scala/millfork/node/opt/NodeOptimization.scala @@ -1,16 +1,17 @@ package millfork.node.opt +import millfork.CompilationOptions import millfork.node.{ExecutableStatement, Expression, Node, Statement} /** * @author Karol Stasiak */ trait NodeOptimization { - def optimize(nodes: List[Node]): List[Node] + def optimize(nodes: List[Node], options: CompilationOptions): List[Node] - def optimizeExecutableStatements(nodes: List[ExecutableStatement]): List[ExecutableStatement] = - optimize(nodes).asInstanceOf[List[ExecutableStatement]] + def optimizeExecutableStatements(nodes: List[ExecutableStatement], options: CompilationOptions): List[ExecutableStatement] = + optimize(nodes, options).asInstanceOf[List[ExecutableStatement]] - def optimizeStatements(nodes: List[Statement]): List[Statement] = - optimize(nodes).asInstanceOf[List[Statement]] + def optimizeStatements(nodes: List[Statement], options: CompilationOptions): List[Statement] = + optimize(nodes, options).asInstanceOf[List[Statement]] } diff --git a/src/main/scala/millfork/node/opt/UnreachableCode.scala b/src/main/scala/millfork/node/opt/UnreachableCode.scala index fbff2a7b..7715f05e 100644 --- a/src/main/scala/millfork/node/opt/UnreachableCode.scala +++ b/src/main/scala/millfork/node/opt/UnreachableCode.scala @@ -1,5 +1,6 @@ package millfork.node.opt +import millfork.CompilationOptions import millfork.node._ /** @@ -7,21 +8,21 @@ import millfork.node._ */ object UnreachableCode extends NodeOptimization { - override def optimize(nodes: List[Node]): List[Node] = nodes match { + override def optimize(nodes: List[Node], options: CompilationOptions): List[Node] = nodes match { case (x:FunctionDeclarationStatement)::xs => - x.copy(statements = x.statements.map(optimizeStatements)) :: optimize(xs) + x.copy(statements = x.statements.map(optimizeStatements(_, options))) :: optimize(xs, options) case (x:IfStatement)::xs => x.copy( - thenBranch = optimizeExecutableStatements(x.thenBranch), - elseBranch = optimizeExecutableStatements(x.elseBranch)) :: optimize(xs) + thenBranch = optimizeExecutableStatements(x.thenBranch, options), + elseBranch = optimizeExecutableStatements(x.elseBranch, options)) :: optimize(xs, options) case (x:WhileStatement)::xs => - x.copy(body = optimizeExecutableStatements(x.body)) :: optimize(xs) + x.copy(body = optimizeExecutableStatements(x.body, options)) :: optimize(xs, options) case (x:DoWhileStatement)::xs => - x.copy(body = optimizeExecutableStatements(x.body)) :: optimize(xs) + x.copy(body = optimizeExecutableStatements(x.body, options)) :: optimize(xs, options) case (x:ReturnStatement) :: xs => x :: Nil case x :: xs => - x :: optimize(xs) + x :: optimize(xs, options) case Nil => Nil } diff --git a/src/main/scala/millfork/node/opt/UnusedFunctions.scala b/src/main/scala/millfork/node/opt/UnusedFunctions.scala index bbb158f8..726245bc 100644 --- a/src/main/scala/millfork/node/opt/UnusedFunctions.scala +++ b/src/main/scala/millfork/node/opt/UnusedFunctions.scala @@ -1,5 +1,6 @@ package millfork.node.opt +import millfork.{CompilationFlag, CompilationOptions} import millfork.env._ import millfork.error.ErrorReporting import millfork.node._ @@ -9,25 +10,28 @@ import millfork.node._ */ object UnusedFunctions extends NodeOptimization { - override def optimize(nodes: List[Node]): List[Node] = { + override def optimize(nodes: List[Node], options: CompilationOptions): List[Node] = { + val panicRequired = options.flags(CompilationFlag.CheckIndexOutOfBounds) val allNormalFunctions = nodes.flatMap { - case v: FunctionDeclarationStatement => if (v.address.isDefined || v.interrupt || v.name == "main") Nil else List(v.name) + case v: FunctionDeclarationStatement => if (v.address.isDefined || v.interrupt || v.name == "main" || panicRequired && v.name == "_panic") Nil else List(v.name) case _ => Nil }.toSet val allCalledFunctions = getAllCalledFunctions(nodes).toSet val unusedFunctions = allNormalFunctions -- allCalledFunctions if (unusedFunctions.nonEmpty) { ErrorReporting.debug("Removing unused functions: " + unusedFunctions.mkString(", ")) + optimize(removeFunctionsFromProgram(nodes, unusedFunctions), options) + } else { + nodes } - removeFunctionsFromProgram(nodes, unusedFunctions) } - private def removeFunctionsFromProgram(nodes: List[Node], unusedVariables: Set[String]): List[Node] = { + private def removeFunctionsFromProgram(nodes: List[Node], unusedFunctions: Set[String]): List[Node] = { nodes match { - case (x: FunctionDeclarationStatement) :: xs if unusedVariables(x.name) => - removeFunctionsFromProgram(xs, unusedVariables) + case (x: FunctionDeclarationStatement) :: xs if unusedFunctions(x.name) => + removeFunctionsFromProgram(xs, unusedFunctions) case x :: xs => - x :: removeFunctionsFromProgram(xs, unusedVariables) + x :: removeFunctionsFromProgram(xs, unusedFunctions) case Nil => Nil } diff --git a/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala b/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala index 83025f09..46eed765 100644 --- a/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala +++ b/src/main/scala/millfork/node/opt/UnusedGlobalVariables.scala @@ -1,5 +1,6 @@ package millfork.node.opt +import millfork.CompilationOptions import millfork.env._ import millfork.error.ErrorReporting import millfork.node._ @@ -9,7 +10,7 @@ import millfork.node._ */ object UnusedGlobalVariables extends NodeOptimization { - override def optimize(nodes: List[Node]): List[Node] = { + override def optimize(nodes: List[Node], options: CompilationOptions): List[Node] = { // TODO: volatile val allNonvolatileGlobalVariables = nodes.flatMap { diff --git a/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala b/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala index 5b66d389..077e9638 100644 --- a/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala +++ b/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala @@ -1,5 +1,6 @@ package millfork.node.opt +import millfork.CompilationOptions import millfork.assembly.AssemblyLine import millfork.env._ import millfork.error.ErrorReporting @@ -10,11 +11,11 @@ import millfork.node._ */ object UnusedLocalVariables extends NodeOptimization { - override def optimize(nodes: List[Node]): List[Node] = nodes match { + override def optimize(nodes: List[Node], options: CompilationOptions): List[Node] = nodes match { case (x: FunctionDeclarationStatement) :: xs => - x.copy(statements = x.statements.map(optimizeVariables)) :: optimize(xs) + x.copy(statements = x.statements.map(optimizeVariables)) :: optimize(xs, options) case x :: xs => - x :: optimize(xs) + x :: optimize(xs, options) case Nil => Nil } diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index fdb9f42a..d9d33120 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -94,17 +94,18 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], CompilationFlag.EmitIllegals -> this.emitIllegals, CompilationFlag.DetailedFlowAnalysis -> quantum, CompilationFlag.InlineFunctions -> this.inline, + // CompilationFlag.CheckIndexOutOfBounds -> true, )) ErrorReporting.hasErrors = false ErrorReporting.verbosity = 999 - val parserF = MfParser("", source, "", options) + val parserF = MfParser("", source + "\n void _panic(){while(true){}}", "", options) parserF.toAst match { case Success(unoptimized, _) => ErrorReporting.assertNoErrors("Parse failed") // prepare - val program = nodeOptimizations.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt)) + val program = nodeOptimizations.foldLeft(unoptimized)((p, opt) => p.applyNodeOptimization(opt, options)) val callGraph = new StandardCallGraph(program) val env = new Environment(None, "") env.collectDeclarations(program, options)