remove "@split" tag

The default is to split word arrays. If you need your word array to not be split, use @nosplit on the array.
This commit is contained in:
Irmen de Jong
2025-10-05 15:56:23 +02:00
parent 3e1386a987
commit 4ed92d71a7
16 changed files with 45 additions and 70 deletions

View File

@@ -530,6 +530,5 @@ enum class ZeropageWish {
enum class SplitWish {
DONTCARE,
SPLIT,
NOSPLIT
}

View File

@@ -1261,12 +1261,12 @@ $repeatLabel""")
// print a message when more optimal code is possible for 65C02 cpu
val variable = symbolTable.lookup(arrayVariable.name)!!
if(variable is StStaticVariable && variable.length!!<=128u)
errors.info("the jump address array is @split, but @nosplit would create more efficient code here", jump.position)
errors.info("the jump address array is split, but @nosplit would create more efficient code here", jump.position)
}
} else {
// print a message when more optimal code is possible for 6502 cpu
if(!arrayIdx.splitWords)
errors.info("the jump address array is @nosplit, but @split would create more efficient code here", jump.position)
errors.info("the jump address array is @nosplit, but split would create more efficient code here", jump.position)
}
}
// we can do the address evaluation right now and just use a temporary pointer variable

View File

@@ -1038,7 +1038,7 @@ _doplot beq +
; y*40 lookup table. Pretty compact because it all fits in a word and we only need 240 y positions.
; a y*80 lookup table would be very large (lo,mid,hi for 480 values...)
uword[240] @split @shared times40 = [
uword[240] @shared times40 = [
0, 40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600,
640, 680, 720, 760, 800, 840, 880, 920, 960, 1000, 1040, 1080, 1120, 1160,
1200, 1240, 1280, 1320, 1360, 1400, 1440, 1480, 1520, 1560, 1600, 1640, 1680,

View File

@@ -1090,8 +1090,8 @@ internal class AstChecker(private val program: Program,
}
if(decl.datatype.isPointerArray) {
if(decl.splitwordarray!= SplitWish.SPLIT)
errors.err("pointer arrays can only be @split", decl.position)
if(decl.splitwordarray==SplitWish.NOSPLIT)
errors.err("pointer arrays can only be split", decl.position)
}
if(decl.datatype.isStructInstance && decl.origin!=VarDeclOrigin.SUBROUTINEPARAM) {

View File

@@ -71,12 +71,11 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// check splitting of word arrays
if(decl.splitwordarray != SplitWish.DONTCARE && !decl.datatype.isWordArray && !decl.datatype.isPointerArray) {
if(decl.origin != VarDeclOrigin.ARRAYLITERAL)
errors.err("@split and @nosplit are for word or pointer arrays only", decl.position)
errors.err("@nosplit is for word or pointer arrays only", decl.position)
}
if(decl.datatype.isWordArray) {
var changeDataType: DataType?
var changeSplit: SplitWish = decl.splitwordarray
when(decl.splitwordarray) {
SplitWish.DONTCARE -> {
changeDataType = if(decl.datatype.isSplitWordArray) null else {
@@ -86,16 +85,6 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
else
DataType.arrayFor(eltDt.base)
}
changeSplit = SplitWish.SPLIT
}
SplitWish.SPLIT -> {
changeDataType = if(decl.datatype.isSplitWordArray) null else {
val eltDt = decl.datatype.elementType()
if(eltDt.isPointer)
TODO("convert array of pointers to split words array type")
else
DataType.arrayFor(eltDt.base)
}
}
SplitWish.NOSPLIT -> {
changeDataType = if(decl.datatype.isSplitWordArray && !decl.datatype.elementType().isPointer)
@@ -109,7 +98,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
value = ArrayLiteral(InferredTypes.knownFor(changeDataType), value.value, value.position)
}
val newDecl = VarDecl(decl.type, decl.origin, changeDataType, decl.zeropage,
changeSplit, decl.arraysize, decl.name, decl.names,
decl.splitwordarray, decl.arraysize, decl.name, decl.names,
value, decl.sharedWithAsm, decl.alignment, decl.dirty, decl.position)
return listOf(IAstModification.ReplaceNode(decl, newDecl, parent))
}

View File

@@ -145,7 +145,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
if(addrOf!=null) {
val identType = addrOf.identifier?.inferType(program)?.getOrUndef()
if(identType?.isSplitWordArray==true) {
return Pair("argument ${mismatch + 1} type mismatch, was: $actual (because arg is a @split word array) expected: $expected", call.args[mismatch].position)
return Pair("argument ${mismatch + 1} type mismatch, was: $actual (because arg is a split word array) expected: $expected", call.args[mismatch].position)
}
}
}

View File

@@ -1951,7 +1951,7 @@ main {
val src="""
main {
sub start() {
uword[10] @split @shared splitarray
uword[10] @shared splitarray
uword[10] @nosplit @shared nosplitarray
^^uword ptr1 = &&splitarray
@@ -1992,9 +1992,9 @@ main {
errors.errors.size shouldBe 3
errors.warnings.size shouldBe 0
errors.infos.size shouldBe 0
errors.errors[0] shouldContain "pointer arrays can only be @split"
errors.errors[1] shouldContain "was: ^^ubyte (because arg is a @split word array) expected: ^^main.Node"
errors.errors[2] shouldContain "was: ^^ubyte (because arg is a @split word array) expected: ^^main.Node"
errors.errors[0] shouldContain "pointer arrays can only be split"
errors.errors[1] shouldContain "was: ^^ubyte (because arg is a split word array) expected: ^^main.Node"
errors.errors[2] shouldContain "was: ^^ubyte (because arg is a split word array) expected: ^^main.Node"
}
test("passing split array of structpointers to a subroutine in various forms should be param type ptr to ubyte (the lsb part of the split array)") {
@@ -2005,7 +2005,7 @@ main {
}
sub start() {
^^Node[10] @split nodearray
^^Node[10] nodearray
func(&&nodearray)
func(&nodearray)
func(nodearray)

View File

@@ -1129,30 +1129,26 @@ thing {
}
}
test("pointer arrays are always split") {
test("pointer arrays are split by default") {
val src="""
%option enable_floats
main {
sub start() {
^^bool[10] @split @shared barray
^^word[10] @split @shared warray
^^float[10] @split @shared farray
^^bool[10] @shared barrayns
^^word[10] @shared warrayns
^^float[10] @shared farrayns
^^bool[10] @shared barray
^^word[10] @shared warray
^^float[10] @shared farray
}
}"""
val result = compileText(VMTarget(), optimize=false, src, outputDir, writeAssembly=false)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 7
st.size shouldBe 4
val decls = st.filterIsInstance<VarDecl>()
decls.size shouldBe 6
decls.size shouldBe 3
decls.all { it.datatype.sub!=null } shouldBe true
decls.all { it.datatype.isPointerArray } shouldBe true
decls.all { it.datatype.isPointerArray } shouldBe true
decls.all { it.datatype.isSplitWordArray } shouldBe true
}
test("on..call in nested scope compiles correctly with temp variable introduced") {

View File

@@ -80,8 +80,8 @@ def make_test_array(datatype, comparison: C):
numbers = testnumbers[datatype]
print(" sub test_cmp_array() {")
print(f""" {datatype} @shared x
{datatype}[] @split values = [0, 0]
{datatype}[] @split sources = [0, 0]
{datatype}[] values = [0, 0]
{datatype}[] sources = [0, 0]
success = 0""")
expected = 0
test_index = 0

View File

@@ -36,9 +36,9 @@ main {{
test_is_var()
txt.print("\\n!=var: ")
test_not_var()
txt.print("\\n==array[] @split: ")
txt.print("\\n==array[] split: ")
test_is_array_splitw()
txt.print("\\n!=array[] @split: ")
txt.print("\\n!=array[] split: ")
test_not_array_splitw()
txt.print("\\n==expr: ")
test_is_expr()
@@ -97,7 +97,7 @@ nonzero_values = {
def make_test_is_zero(datatype):
print(f"""
sub test_is_zero() {{
{datatype}[] @split sources = [9999, 9999]
{datatype}[] sources = [9999, 9999]
success = 0
sources[1]={zero_values[datatype]}
@@ -155,7 +155,7 @@ skip4:
def make_test_not_zero(datatype):
print(f"""
sub test_not_zero() {{
{datatype}[] @split sources = [9999, 9999]
{datatype}[] sources = [9999, 9999]
success = 0
sources[1]={nonzero_values[datatype]}
@@ -229,7 +229,7 @@ testnumbers = {
def make_test_is_number(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_number() {" if equals else " sub test_not_number() {")
print(f""" {datatype}[] @split sources = [9999, 9999]
print(f""" {datatype}[] sources = [9999, 9999]
success = 0""")
expected = 0
test_index = 0
@@ -286,8 +286,8 @@ skip{test_index}b:
def make_test_is_var(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_var() {" if equals else " sub test_not_var() {")
print(f""" {datatype}[] @split sources = [9999, 9999]
{datatype}[] @split values = [8888,8888]
print(f""" {datatype}[] sources = [9999, 9999]
{datatype}[] values = [8888,8888]
success = 0""")
expected = 0
test_index = 0
@@ -346,8 +346,8 @@ def make_test_is_array(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_array_splitw() {" if equals else " sub test_not_array_splitw() {")
print(f"""
{datatype}[] @split values = [9999, 8888]
{datatype}[] @split sources = [9999, 8888]
{datatype}[] values = [9999, 8888]
{datatype}[] sources = [9999, 8888]
success = 0""")
expected = 0
test_index = 0
@@ -441,7 +441,7 @@ skip{test_index}d:
def make_test_is_expr(datatype, equals):
numbers = testnumbers[datatype]
print(" sub test_is_expr() {" if equals else " sub test_not_expr() {")
print(f""" {datatype}[] @split sources = [9999, 9999]
print(f""" {datatype}[] sources = [9999, 9999]
cx16.r4 = 1
cx16.r5 = 1
success = 0""")

View File

@@ -137,7 +137,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
override fun visitVardecl(ctx: VardeclContext): VarDecl {
val tags = ctx.TAG().map { it.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@nosplit", "@shared", "@alignword", "@alignpage", "@align64", "@dirty")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid variable tag '$tag'", ctx.toPosition())
@@ -167,17 +167,12 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
DataType.arrayFor(baseDt.base, split!=SplitWish.NOSPLIT)
}
val splitWords = if(split==SplitWish.DONTCARE) {
if(dt.isPointerArray) SplitWish.SPLIT // pointer arrays are always @split by default
else split
} else split
return VarDecl(
VarDeclType.VAR, // can be changed to MEMORY or CONST as required
VarDeclOrigin.USERCODE,
dt,
zp,
splitWords,
split,
arrayIndex,
name,
if(identifiers.size==1) emptyList() else identifiers,
@@ -486,7 +481,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
override fun visitSub_param(pctx: Sub_paramContext): SubroutineParameter {
val decl = pctx.vardecl()
val tags = decl.TAG().map { t -> t.text }
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@split", "@nosplit", "@shared")
val validTags = arrayOf("@zp", "@requirezp", "@nozp", "@nosplit", "@shared")
for(tag in tags) {
if(tag !in validTags)
throw SyntaxError("invalid parameter tag '$tag'", pctx.toPosition())
@@ -724,7 +719,6 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
private fun getSplitOption(tags: List<String>): SplitWish {
return when {
"@nosplit" in tags -> SplitWish.NOSPLIT
"@split" in tags -> SplitWish.SPLIT
else -> SplitWish.DONTCARE
}
}

View File

@@ -305,7 +305,7 @@ class VarDecl(
fun createAutoOptionalSplit(array: ArrayLiteral): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val arrayDt = array.type.getOrElse { throw FatalAstException("unknown dt") }
val split = if(arrayDt.isSplitWordArray) SplitWish.SPLIT else if(arrayDt.isWordArray) SplitWish.NOSPLIT else SplitWish.DONTCARE
val split = if(arrayDt.isSplitWordArray) SplitWish.DONTCARE else if(arrayDt.isWordArray) SplitWish.NOSPLIT else SplitWish.DONTCARE
val arraysize = ArrayIndex.forArray(array)
return VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, arrayDt, ZeropageWish.NOT_IN_ZEROPAGE,
split, arraysize, autoVarName, emptyList(), array,

View File

@@ -1,10 +1,6 @@
TODO
====
remove "@split" tag SplitWish.NOSPLIT
LONG TYPE
---------
- implement the other comparison operators (<,>,<=,>=) on longs

View File

@@ -358,7 +358,7 @@ See also :ref:`pointervars` and the chapter about it :ref:`pointers`.
**LSB/MSB split word and str arrays:**
As an optimization, (u)word arrays and str arrays are split by the compiler in memory as two separate arrays,
As an optimization, (u)word arrays, pointer arrays, and str arrays are split by the compiler in memory as two separate arrays,
one with the LSBs and one with the MSBs of the word values. This is more efficient to access by the 6502 cpu.
It also allows a maximum length of 256 for word arrays, where normally it would have been 128.
@@ -366,13 +366,14 @@ For normal prog8 array indexing, the compiler takes care of the distiction for y
*But for assembly code, or code that otherwise accesses the array elements directly, you have to be aware of the distinction from 'normal' arrays.*
In the assembly code, the array is generated as two byte arrays namely ``name_lsb`` and ``name_msb``, immediately following eachother in memory.
The ``@nosplit`` tag can be added to the variable declaration to *never* split the array. This is useful for compatibility with
The ``@nosplit`` tag can be added to the variable declaration to *not* split the array. This is useful for compatibility with
code that expects the words to be sequentially in memory (such as the cx16.FB_set_palette routine).
.. note::
Most but not all array operations are supported yet on "split word arrays".
If you get a compiler error message, simply revert to a regular sequential word array using ``@nosplit``,
and please report the issue.
Some obscure array operations may not yet be supported on "split word arrays".
If you get a compiler error message hinting that this is the case,
simply revert to a regular sequential word array using ``@nosplit``, and please report the issue so that
the missing function can be added.
.. note::
Array literals are stored as split arrays if they're initializing a split word array, otherwise,

View File

@@ -12,7 +12,7 @@
<option name="HAS_STRING_ESCAPES" value="true" />
</options>
<keywords keywords="&amp;;&amp;&amp;;&amp;&lt;;&amp;&gt;;-&gt;;@;^^;alias;and;as;asmsub;break;clobbers;continue;do;downto;else;extsub;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;on;or;repeat;return;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%jmptable;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nosplit;@nozp;@requirezp;@shared;@split;@zp;atascii:;c64os:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords2 keywords="%address;%align;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%jmptable;%launcher;%memtop;%option;%output;%zeropage;%zpallowed;%zpreserved;@align64;@alignpage;@alignword;@bank;@dirty;@nosplit;@nozp;@requirezp;@shared;@zp;atascii:;c64os:;cp437:;default:;iso16:;iso5:;iso:;kata:;petscii:;sc:" />
<keywords3 keywords="^^bool;^^byte;^^float;^^long;^^str;^^ubyte;^^uword;^^word;bool;byte;const;float;long;str;struct;ubyte;uword;void;word" />
<keywords4 keywords="abs;bmx;call;callfar;callfar2;cbm;clamp;cmp;conv;cx16;defer;diskio;divmod;floats;len;lsb;lsw;math;max;memory;min;mkword;msb;msw;offsetof;peek;peekbool;peekf;peekw;poke;pokebool;pokef;pokew;psg;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt;strings;sys;txt;verafx" />
</highlighting>
@@ -20,4 +20,4 @@
<mapping ext="p8" />
<mapping ext="prog8" />
</extensionMap>
</filetype>
</filetype>

View File

@@ -43,7 +43,7 @@ syn region prog8ArrayType matchgroup=prog8Type
\ start="\<\%(u\?byte\|u\?word\|float\|str\|bool\)\[" end="\]"
\ transparent
syn keyword prog8StorageClass const
syn match prog8StorageClass "\(^\|\s\)\(@zp\|@bank\|@shared\|@split\|@nosplit\|@nozp\|@requirezp\|@align64\|@alignword\|@alignpage\|@dirty\)\>"
syn match prog8StorageClass "\(^\|\s\)\(@zp\|@bank\|@shared\|@nosplit\|@nozp\|@requirezp\|@align64\|@alignword\|@alignpage\|@dirty\)\>"
syn region prog8Block start="{" end="}" transparent
syn region prog8Expression start="(" end=")" transparent