diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 3e793fdd5..4819a0c1c 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -111,6 +111,15 @@ fun compileProgram(args: CompilerArguments): CompilationResult? { resultingProgram = program importedFiles = imported + if(compilationOptions.romable) { + if (!compilationOptions.varsGolden && compilationOptions.varsHighBank==null) + args.errors.err("When ROMable code is selected, variables should be moved to a RAM memory region using either -varsgolden or -varshigh option", program.toplevelModule.position) + if (!compilationOptions.slabsGolden && compilationOptions.slabsHighBank==null) + args.errors.err("When ROMable code is selected, memory() blocks should be moved to a RAM memory region using either -slabsgolden or -slabshigh option", program.toplevelModule.position) + args.errors.report() + } + + processAst(program, args.errors, compilationOptions) // println("*********** COMPILER AST RIGHT BEFORE OPTIMIZING *************") // printProgram(program) @@ -246,7 +255,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? { internal fun determineProgramLoadAddress(program: Program, options: CompilationOptions, errors: IErrorReporter) { val specifiedAddress = program.toplevelModule.loadAddress - var loadAddress: UInt? = null + var loadAddress: UInt? if(specifiedAddress!=null) loadAddress = specifiedAddress.first else @@ -360,7 +369,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat val allOptions = program.modules.flatMap { it.options() }.toSet() val floatsEnabled = "enable_floats" in allOptions var noSysInit = "no_sysinit" in allOptions - var rombale = "romable" in allOptions + val rombale = "romable" in allOptions var zpType: ZeropageType = if (zpoption == null) if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE @@ -490,7 +499,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e if(errors.noErrors()) { // certain optimization steps could have introduced a "not" in an if statement, postprocess those again. - var changer = NotExpressionAndIfComparisonExprChanger(program, errors, compilerOptions.compTarget) + val changer = NotExpressionAndIfComparisonExprChanger(program, errors, compilerOptions.compTarget) changer.visit(program) if(errors.noErrors()) changer.applyModifications() diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index ecf761c54..2538aab7c 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -694,7 +694,7 @@ internal class AstChecker(private val program: Program, val decl = idx.arrayvar.targetVarDecl(program)!! if(decl.type!=VarDeclType.MEMORY && decl.zeropage!=ZeropageWish.REQUIRE_ZEROPAGE) { // memory mapped arrays are assumed to be in RAM. If they're not.... well, POOF - errors.err("cannot assign to an array or string that is located in ROM", assignTarget.position) + errors.err("cannot assign to an array or string that is located in ROM (option romable is enabled)", assignTarget.position) } } } @@ -1898,7 +1898,7 @@ internal class AstChecker(private val program: Program, return err("invalid float array size, must be 1-51") // check if the floating point values are all within range - val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray() + val doubles = value.value.map { it.constValue(program)?.number!! }.toDoubleArray() if(doubles.any { it < compilerOptions.compTarget.FLOAT_MAX_NEGATIVE || it > compilerOptions.compTarget.FLOAT_MAX_POSITIVE }) return err("floating point value overflow") return true diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 59dc7e825..5a77a5c54 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -430,7 +430,8 @@ Directives - ``ignore_unused`` (block or module) suppress warnings about unused variables and subroutines. Instead, these will be silently stripped. This option is useful in library modules that contain many more routines beside the ones that you actually use. - ``verafxmuls`` (block, cx16 target only) uses Vera FX hardware word multiplication on the CommanderX16 for all word multiplications in this block. Warning: this may interfere with IRQs and other Vera operations, so use this only when you know what you're doing. It's safer to explicitly use ``verafx.muls()``. - - ``romable`` (module) *WORK-IN-PROGRESS/EXPERIMENTAL* make sure that the generated code is suitable for running in ROM (so no self-modifying code and such) + - ``romable`` (module) *WORK-IN-PROGRESS/EXPERIMENTAL* make sure that the generated code is suitable for running in ROM (so no self-modifying code and such, which is normally used to generate smaller/more optimized code) + See :ref:`romable` for more details. .. data:: %output diff --git a/docs/source/technical.rst b/docs/source/technical.rst index 8023a6d52..56f31cb94 100644 --- a/docs/source/technical.rst +++ b/docs/source/technical.rst @@ -301,3 +301,37 @@ The tool isn't powerful enough to see what routine the variables or instructions You can see in the example above that the variables that are among the most used are neatly placed in zeropage already. If you see for instance a variable that is heavily used and that is *not* in zeropage, you could consider adding ``@zp`` to that variable's declaration to prioritize it to be put into zeropage. + + +.. _romable: + +ROM-able programs +----------------- + +Normally Prog8 will use some tricks to generate the smallest and most optimized code it can. +This includes the following techniques that by default prevent generated program code from running in ROM: + +self-modifying code + This is program code that actually modifies itself during program execution (instructions or operands are modified) + When the program is in ROM, such modifications are impossible, so the program will not execute correctly. + +inline variables + These are variables that are located in the same memory region that the program code is in (or even interleaved within the program code). + Again, writing to such variables will not work when it is in ROM, so the program will not execute correctly. + +(Not all prog8 source code will end up using these techniques but you should not depend on it.) + +The directive ``%option romable`` changes this behavior. +It tells the compiler to no longer generate code using these two tricks, and instead revert to slightly slower running code (or needing more instructions) +but which *is* able to run from ROM. +There are a few things to note: + +- string variables and array variables that are initialized with something other than just zeros, *are no longer mutable*. + This is because both of these will still end up as part of the same memory region the program code is in (which will be ROM). + The compiler will try to detect writes to them and give an error if these occur. However it cannot detect all such writes, so beware. +- arrays without an initialization literal will be placed into the memory region for variables instead which can and should be placed in RAM, + so those arrays *are* mutable as usual. +- the same holds for memory blocks allocated using the ``memory`` function; nothing changes for them. +- the memory region for variables and memory blocks (BSS sections) should be explicitly placed in RAM memory. + You can do this with the ``-varsgolden`` or ``-varshigh``, and ``-slabsgolden`` or ``-slabshigh`` command line options. + TODO: maybe in the future an option will be added to choose a memory address for those manually. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index a1d52b3c5..fb1cf08a7 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -1,11 +1,6 @@ TODO ==== -document romable option and that strings+initialized arrays become read-only - -also support 'heavy' version of the unicode box characters like https://www.compart.com/en/unicode/U+250F as characters in strings - - ... diff --git a/examples/test.p8 b/examples/test.p8 index 1b17253f5..58f3328f9 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,12 +1,38 @@ -%import graphics +%import textio %zeropage basicsafe -%option no_sysinit +%option no_sysinit, romable -main { +main $9000 { sub start() { - graphics.enable_bitmap_mode() - for cx16.r11L in 0 to 110 { - graphics.horizontal_line(cx16.r11L+10, cx16.r11L+20, cx16.r11L+5) - } + ubyte[] filled_array = [11,22,33,44] + ubyte[10] empty_array + + uword block = memory("block", 100, 0) + sys.memset(block, 100, 0) + str name = "irmen" + ubyte @shared number + txt.print(name) + txt.spc() + number++ + txt.print_ub(number) + txt.spc() + number++ + txt.print_ub(number) + txt.nl() + txt.print_ub(block[10]) + txt.spc() + block[10]++ + txt.print_ub(block[10]) + txt.nl() + + txt.print_ub(filled_array[2]) + txt.spc() + ;;empty_array[2]=0 ; TODO should not give error! + txt.print_ub(empty_array[2]) + txt.spc() + ;;empty_array[2]++ ; TODO should not give error! + txt.print_ub(empty_array[2]) + txt.nl() + } }