From d702ace09f1555acae1aedcc7aef6d87da0d98f9 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Thu, 13 Aug 2026 22:11:09 +0200 Subject: [PATCH] add support for pointer sizes >2 to configurable targets --- .../src/prog8/code/target/ConfigFileTarget.kt | 21 ++++-- .../codecore/TestConfigFileTarget.kt | 66 +++++++++++++++++++ docs/source/targetsystem.rst | 3 + docs/source/todo.rst | 3 +- .../targetconfigs/atari.properties | 1 + .../targetconfigs/f256.properties | 1 + .../targetconfigs/neo6502.properties | 1 + .../targetconfigs/tinyc64.properties | 1 + .../targetconfigs/tinycx16.properties | 1 + .../targetconfigs/tinypet.properties | 1 + 10 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 codeCore/test/prog8tests/codecore/TestConfigFileTarget.kt diff --git a/codeCore/src/prog8/code/target/ConfigFileTarget.kt b/codeCore/src/prog8/code/target/ConfigFileTarget.kt index 7ff4d2426..b46bb044f 100644 --- a/codeCore/src/prog8/code/target/ConfigFileTarget.kt +++ b/codeCore/src/prog8/code/target/ConfigFileTarget.kt @@ -38,8 +38,13 @@ class ConfigFileTarget( val virtualregistersStart: UInt, val zpFullsafe: List, val zpKernalsafe: List, - val zpBasicsafe: List -): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8) { + val zpBasicsafe: List, + val pointerSize: Int = 2 +): ICompilationTarget, IStringEncoding by Encoder(true), IMemSizer by NormalMemSizer(8, pointerSize) { + + init { + require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" } + } companion object { @@ -66,6 +71,12 @@ class ConfigFileTarget( return value.toUInt() } + private fun parsePointerSize(props: Properties): Int { + val pointerSize = parseInt(props.getProperty("pointer_size", "2")).toInt() + require(pointerSize == 2 || pointerSize == 4) { "unsupported pointer size: $pointerSize" } + return pointerSize + } + private fun parseAddressRanges(key: String, props: Properties): List { val rangesStr = props.getString(key) if(rangesStr.isBlank()) @@ -111,6 +122,7 @@ class ConfigFileTarget( (customLauncherStr+"\n").lines().map { it.trimEnd() } else emptyList() val assemblerOptionsStr = props.getProperty("assembler_options", "").trim() + val pointerSize = parsePointerSize(props) val outputTypeString = props.getProperty("output_type", "PRG") val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase()) val launcherString = props.getProperty("launcher", "BASIC") @@ -146,6 +158,7 @@ class ConfigFileTarget( zpFullsafe, zpKernalsafe, zpBasicsafe, + pointerSize, ) } } @@ -154,8 +167,8 @@ class ConfigFileTarget( override val FLOAT_MAX_POSITIVE = 9.999999999e97 override val FLOAT_MAX_NEGATIVE = -9.999999999e97 override val FLOAT_MEM_SIZE = 8u - override val POINTER_MEM_SIZE = 2u // TODO should support other pointer size as well - override val ARRAY_SIZE_LIMIT = 256u // TODO should support other size as well + override val POINTER_MEM_SIZE = pointerSize.toUInt() + override val ARRAY_SIZE_LIMIT = 256u override lateinit var zeropage: Zeropage override fun getFloatAsmBytes(num: Number) = TODO("floats") diff --git a/codeCore/test/prog8tests/codecore/TestConfigFileTarget.kt b/codeCore/test/prog8tests/codecore/TestConfigFileTarget.kt new file mode 100644 index 000000000..43c1a960f --- /dev/null +++ b/codeCore/test/prog8tests/codecore/TestConfigFileTarget.kt @@ -0,0 +1,66 @@ +package prog8tests.codecore + +import io.kotest.core.spec.style.FunSpec +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import prog8.code.core.BaseDataType +import prog8.code.target.ConfigFileTarget +import java.nio.file.Files +import kotlin.io.path.writeText + +class TestConfigFileTarget: FunSpec({ + fun loadTarget(extraProperties: String = ""): ConfigFileTarget { + val directory = Files.createTempDirectory("prog8-config-target") + val library = Files.createDirectories(directory.resolve("library")) + val config = directory.resolve("target.properties") + config.writeText( + """ + cpu = 65C02 + encoding = iso + load_address = 2048 + memtop = 65535 + bss_highram_start = 0 + bss_highram_end = 0 + bss_goldenram_start = 0 + bss_goldenram_end = 0 + zp_scratch_b1 = 240 + zp_scratch_reg = 241 + zp_scratch_w1 = 242 + zp_scratch_w2 = 244 + zp_scratch_ptr = 246 + virtual_registers = 2 + zp_fullsafe = 34-239 + zp_kernalsafe = 34-239 + zp_basicsafe = 34-239 + io_regions = + """.trimIndent() + "\nlibrary = $library\n$extraProperties" + ) + return try { + ConfigFileTarget.fromConfigFile(config) + } finally { + directory.toFile().deleteRecursively() + } + } + + test("legacy config defaults to 2-byte pointers and 256 element arrays") { + val target = loadTarget() + + target.POINTER_MEM_SIZE shouldBe 2u + target.memorySize(BaseDataType.POINTER) shouldBe 2 + target.ARRAY_SIZE_LIMIT shouldBe 256u + } + + test("config supports 4-byte pointers") { + val target = loadTarget("pointer_size = 4") + + target.POINTER_MEM_SIZE shouldBe 4u + target.memorySize(BaseDataType.POINTER) shouldBe 4 + target.ARRAY_SIZE_LIMIT shouldBe 256u + } + + test("config rejects unsupported pointer sizes") { + shouldThrow { + loadTarget("pointer_size = 3") + } + } +}) diff --git a/docs/source/targetsystem.rst b/docs/source/targetsystem.rst index 31cf5ff89..3bac6dfbd 100644 --- a/docs/source/targetsystem.rst +++ b/docs/source/targetsystem.rst @@ -73,6 +73,9 @@ The ``launcher`` property lets you set the default launcher type for all program without requiring a ``%launcher`` directive in every source file. Valid values are ``basic`` (CBM-BASIC SYS launcher) and ``none`` (no launcher). If omitted, the default is ``basic``. +The optional ``pointer_size`` property sets the size of pointers in bytes. It defaults to ``2`` for compatibility +with existing configuration files. The supported values are ``2`` and ``4``. + Memory Model ============ diff --git a/docs/source/todo.rst b/docs/source/todo.rst index cb471bc79..eefc74320 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -18,9 +18,8 @@ Future Things and Ideas - struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid between IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator" - struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field) - add documentation for more library modules instead of just linking to the source code -- add support for pointer size >2 to configurable compiler targets. - add float support to the configurable compiler targets. Restrictions: just have "cbm-style floats" as an option (to that it can slot into the current float codegen), where all you have to specify is the addresses of AYINT and GIVAYF and FADDT and all their friends. -- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first), maybe only when qualified symbol starts with '.' such as: .local.value = 33, or the other way around? i.e. require new syntax to explicitly look up from global scope, ::globalmodule.var = 99 ? consider pros cons +- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first), maybe only when qualified symbol starts with '.' such as: .local.value = 33, or the other way around? i.e. require new syntax to explicitly look up from global scope. That would give a backwards compatible solution. - implement the signed remainder byte and word routines on 6502 (virtual target already has them working) - implement the signed divmod byte and word routines on 6502 (virtual target already has them working) - make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type diff --git a/examples/customtarget/targetconfigs/atari.properties b/examples/customtarget/targetconfigs/atari.properties index 1f3d25c11..a6b48e4e7 100644 --- a/examples/customtarget/targetconfigs/atari.properties +++ b/examples/customtarget/targetconfigs/atari.properties @@ -12,6 +12,7 @@ bss_highram_end = 0 bss_goldenram_start = 0 bss_goldenram_end = 0 supports_banked_calls = false +# pointer_size = 2 # optional, defaults to 2 bytes # io_regions specifies memory-mapped I/O registers that should be treated differentely. # it can be zero or more memory address ranges (inclusive) separated by comma diff --git a/examples/customtarget/targetconfigs/f256.properties b/examples/customtarget/targetconfigs/f256.properties index 77cdc447d..f315563d4 100644 --- a/examples/customtarget/targetconfigs/f256.properties +++ b/examples/customtarget/targetconfigs/f256.properties @@ -15,6 +15,7 @@ bss_highram_end = 0 bss_goldenram_start = 0 bss_goldenram_end = 0 supports_banked_calls = true +# pointer_size = 2 # optional, defaults to 2 bytes # io_regions specifies memory-mapped I/O registers that should be treated differentely. # it can be zero or more memory address ranges (inclusive) separated by comma diff --git a/examples/customtarget/targetconfigs/neo6502.properties b/examples/customtarget/targetconfigs/neo6502.properties index c26c427e2..aec2e2474 100644 --- a/examples/customtarget/targetconfigs/neo6502.properties +++ b/examples/customtarget/targetconfigs/neo6502.properties @@ -12,6 +12,7 @@ bss_highram_end = 0 bss_goldenram_start = 0 bss_goldenram_end = 0 supports_banked_calls = false +# pointer_size = 2 # optional, defaults to 2 bytes # io_regions specifies memory-mapped I/O registers that should be treated differentely. # it can be zero or more memory address ranges (inclusive) separated by comma diff --git a/examples/customtarget/targetconfigs/tinyc64.properties b/examples/customtarget/targetconfigs/tinyc64.properties index 83b341707..d3aec6c01 100644 --- a/examples/customtarget/targetconfigs/tinyc64.properties +++ b/examples/customtarget/targetconfigs/tinyc64.properties @@ -11,6 +11,7 @@ bss_highram_end = $cfdf bss_goldenram_start = 0 bss_goldenram_end = 0 supports_banked_calls = true +# pointer_size = 2 # optional, defaults to 2 bytes # io_regions specifies memory-mapped I/O registers that should be treated differentely. # it can be zero or more memory address ranges (inclusive) separated by comma diff --git a/examples/customtarget/targetconfigs/tinycx16.properties b/examples/customtarget/targetconfigs/tinycx16.properties index 24dc03a39..68e79bc26 100644 --- a/examples/customtarget/targetconfigs/tinycx16.properties +++ b/examples/customtarget/targetconfigs/tinycx16.properties @@ -11,6 +11,7 @@ bss_highram_end = $bfff bss_goldenram_start = $0400 bss_goldenram_end = $07ff supports_banked_calls = true +# pointer_size = 2 # optional, defaults to 2 bytes # io_regions specifies memory-mapped I/O registers that should be treated differentely. # it can be zero or more memory address ranges (inclusive) separated by comma diff --git a/examples/customtarget/targetconfigs/tinypet.properties b/examples/customtarget/targetconfigs/tinypet.properties index c952d97fb..0053de267 100644 --- a/examples/customtarget/targetconfigs/tinypet.properties +++ b/examples/customtarget/targetconfigs/tinypet.properties @@ -11,6 +11,7 @@ bss_highram_end = 0 bss_goldenram_start = 0 bss_goldenram_end = 0 supports_banked_calls = false +# pointer_size = 2 # optional, defaults to 2 bytes # io_regions specifies memory-mapped I/O registers that should be treated differentely. # it can be zero or more memory address ranges (inclusive) separated by comma