document romable option and that strings+initialized arrays become read-only

This commit is contained in:
Irmen de Jong
2025-04-17 21:04:37 +02:00
parent ca7491a702
commit 9df899eb63
6 changed files with 83 additions and 18 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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 <type>

View File

@@ -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.

View File

@@ -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
...

View File

@@ -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()
}
}