diff --git a/include/asm/lexer.hpp b/include/asm/lexer.hpp index c09233f9..8eca400f 100644 --- a/include/asm/lexer.hpp +++ b/include/asm/lexer.hpp @@ -84,6 +84,11 @@ int yylex(void); bool lexer_CaptureRept(struct CaptureBody *capture); bool lexer_CaptureMacroBody(struct CaptureBody *capture); +struct DsAlignment { + uint8_t alignment; + uint16_t alignOfs; +}; + #define INITIAL_DS_ARG_SIZE 2 struct DsArgList { size_t nbArgs; diff --git a/include/asm/section.hpp b/include/asm/section.hpp index 47ce47e0..a6547c66 100644 --- a/include/asm/section.hpp +++ b/include/asm/section.hpp @@ -47,6 +47,7 @@ void sect_EndLoadSection(void); struct Section *sect_GetSymbolSection(void); uint32_t sect_GetSymbolOffset(void); uint32_t sect_GetOutputOffset(void); +uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset); void sect_AlignPC(uint8_t alignment, uint16_t offset); void sect_StartUnion(void); diff --git a/man/rgbasm.5 b/man/rgbasm.5 index e466a4f2..c5963042 100644 --- a/man/rgbasm.5 +++ b/man/rgbasm.5 @@ -681,14 +681,14 @@ Place the section at an address whose .Ar align least-significant bits are equal to .Ar offset . -(Note that +Note that .Ic ALIGN Ns Bq Ar align is a shorthand for -.Ic ALIGN Ns Bq Ar align , No 0 ) . +.Ic ALIGN Ns Bq Ar align , No 0 . This option can be used with .Bq Ar addr , -as long as they don't contradict eachother. -It's also possible to request alignment in the middle of a section, see +as long as they don't contradict each other. +It's also possible to request alignment in the middle of a section; see .Sx Requesting alignment below. .El @@ -869,7 +869,7 @@ or .Ic ROMX . .El .Pp -Different declarations of the same unionized section are not appended, but instead overlaid on top of eachother, just like +Different declarations of the same unionized section are not appended, but instead overlaid on top of each other, just like .Sx Unions . Similarly, the size of an unionized section is the largest of all its declarations. .Ss Section fragments @@ -1433,6 +1433,27 @@ In ROM sections, it will be filled with the value passed to the .Fl p command-line option, except when using overlays with .Fl O . +.Pp +Instead of an exact number of bytes, you can specify +.Ic ALIGN Ns Bq Ar align , offset +to allocate however many bytes are required to align the subsequent data. +Thus, +.Sq Ic DS ALIGN Ns Bo Ar align , offset Bc , No ... +is equivalent to +.Sq Ic DS Ar n , No ... +followed by +.Sq Ic ALIGN Ns Bq Ar align , offset , +where +.Ar n +is the minimum value needed to satisfy the +.Ic ALIGN +constraint (see +.Sx Requesting alignment +below). +Note that +.Ic ALIGN Ns Bq Ar align +is a shorthand for +.Ic ALIGN Ns Bq Ar align , No 0 . .Ss Defining constant data in ROM .Ic DB defines a list of bytes that will be stored in the final image. @@ -2137,6 +2158,17 @@ Note that .Ic ALIGN Ar align is a shorthand for .Ic ALIGN Ar align , No 0 . +.Pp +There may be times when you don't just want to specify an alignment constraint at the current location, but also skip ahead until the constraint can be satisfied. +In that case, you can use +.Ic DS ALIGN Ns Bq Ar align , offset +to allocate however many bytes are required to align the subsequent data. +.Pp +If the constraint cannot be met by skipping any amount of space, an error is produced. +Note that +.Ic ALIGN Ns Bq Ar align +is a shorthand for +.Ic ALIGN Ns Bq Ar align , No 0 . .Sh SEE ALSO .Xr rgbasm 1 , .Xr rgblink 1 , diff --git a/src/asm/parser.y b/src/asm/parser.y index d368a888..601dacd2 100644 --- a/src/asm/parser.y +++ b/src/asm/parser.y @@ -503,6 +503,7 @@ enum { struct SectionSpec sectSpec; struct MacroArgs *macroArg; enum AssertionType assertType; + struct DsAlignment dsAlign; struct DsArgList dsArgs; struct PurgeArgList purgeArgs; struct { @@ -660,6 +661,7 @@ enum { %type sectmod %type macroargs +%type ds_align %type ds_args %type purge_args @@ -1194,6 +1196,40 @@ ds : T_POP_DS uconst { sect_Skip($2, true); } sect_RelBytes($2, $4.args, $4.nbArgs); freeDsArgList(&$4); } + | T_POP_DS ds_align trailing_comma { + uint32_t n = sect_GetAlignBytes($2.alignment, $2.alignOfs); + + sect_Skip(n, true); + sect_AlignPC($2.alignment, $2.alignOfs); + } + | T_POP_DS ds_align T_COMMA ds_args trailing_comma { + uint32_t n = sect_GetAlignBytes($2.alignment, $2.alignOfs); + + sect_RelBytes(n, $4.args, $4.nbArgs); + sect_AlignPC($2.alignment, $2.alignOfs); + freeDsArgList(&$4); + } +; + +ds_align : T_OP_ALIGN T_LBRACK uconst T_RBRACK { + if ($3 > 16) { + error("Alignment must be between 0 and 16, not %u\n", $3); + } else { + $$.alignment = $3; + $$.alignOfs = 0; + } + } + | T_OP_ALIGN T_LBRACK uconst T_COMMA uconst T_RBRACK { + if ($3 > 16) { + error("Alignment must be between 0 and 16, not %u\n", $3); + } else if ($5 >= 1 << $3) { + error("Offset must be between 0 and %u, not %u\n", + (1 << $3) - 1, $5); + } else { + $$.alignment = $3; + $$.alignOfs = $5; + } + } ; ds_args : reloc_8bit { diff --git a/src/asm/section.cpp b/src/asm/section.cpp index 986ab2c7..f822fd08 100644 --- a/src/asm/section.cpp +++ b/src/asm/section.cpp @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: MIT */ +#include #include #include #include @@ -471,6 +472,27 @@ uint32_t sect_GetOutputOffset(void) return curOffset + loadOffset; } +// Returns how many bytes need outputting for the specified alignment and offset to succeed +uint32_t sect_GetAlignBytes(uint8_t alignment, uint16_t offset) +{ + struct Section *sect = sect_GetSymbolSection(); + if (!sect) + return 0; + + bool isFixed = sect->org != (uint32_t)-1; + + // If the section is not aligned, no bytes are needed + // (fixed sections count as being maximally aligned for this purpose) + uint8_t curAlignment = isFixed ? 16 : sect->align; + if (curAlignment == 0) + return 0; + + // We need `(pcValue + curOffset + return value) % (1 << alignment) == offset` + uint16_t pcValue = isFixed ? sect->org : sect->alignOfs; + return static_cast(offset - curOffset - pcValue) + % (1u << std::min(alignment, curAlignment)); +} + void sect_AlignPC(uint8_t alignment, uint16_t offset) { if (!checksection()) @@ -480,20 +502,16 @@ void sect_AlignPC(uint8_t alignment, uint16_t offset) uint16_t alignSize = 1 << alignment; // Size of an aligned "block" if (sect->org != (uint32_t)-1) { - if ((sym_GetPCValue() - offset) % alignSize) + if ((sect->org + curOffset - offset) % alignSize) error("Section's fixed address fails required alignment (PC = $%04" PRIx32 - ")\n", sym_GetPCValue()); - } else if (sect->align != 0) { - if ((((sect->alignOfs + curOffset) % (1 << sect->align)) - offset) % alignSize) { - error("Section's alignment fails required alignment (offset from section start = $%04" - PRIx32 ")\n", curOffset); - } else if (alignment > sect->align) { - sect->align = alignment; - sect->alignOfs = (offset - curOffset) % alignSize; - } - } else { + ")\n", sect->org + curOffset); + } else if (sect->align != 0 && (((sect->alignOfs + curOffset) % (1u << sect->align)) + - offset) % alignSize) { + error("Section's alignment fails required alignment (offset from section start = $%04" + PRIx32 ")\n", curOffset); + } else if (alignment > sect->align) { sect->align = alignment; - // We need `(sect->alignOfs + curOffset) % alignSize == offset + // We need `(sect->alignOfs + curOffset) % alignSize == offset` sect->alignOfs = (offset - curOffset) % alignSize; } } diff --git a/test/asm/ds-align-min.asm b/test/asm/ds-align-min.asm new file mode 100644 index 00000000..523095b7 --- /dev/null +++ b/test/asm/ds-align-min.asm @@ -0,0 +1,7 @@ +SECTION "test", ROM0 +align 3 ; 3 < 4, so this is used in `ds align` below +db 1, 2, 5 ; three +ds align[4] ; (1 << 3) - three = 5 bytes +db 10, 20 ; two +align 4, 2 +assert @ - STARTOF("test") == 3 + 5 + 2 diff --git a/test/asm/ds-align-min.err b/test/asm/ds-align-min.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/ds-align-min.out b/test/asm/ds-align-min.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/ds-align-min.out.bin b/test/asm/ds-align-min.out.bin new file mode 100644 index 00000000..90448701 Binary files /dev/null and b/test/asm/ds-align-min.out.bin differ diff --git a/test/asm/ds-align-offset.asm b/test/asm/ds-align-offset.asm new file mode 100644 index 00000000..a26a8a4b --- /dev/null +++ b/test/asm/ds-align-offset.asm @@ -0,0 +1,7 @@ +SECTION "test", ROM0 +align 3, 3 ; 3 < 4, so this is used in `ds align` below +db 2, 22, 222 ; three +ds align[4, 4] ; (1 << 3) - 3 - three + 4 = 6 bytes +db 42 ; one +align 4, 5 +assert @ - STARTOF("test") == 3 + 6 + 1 diff --git a/test/asm/ds-align-offset.err b/test/asm/ds-align-offset.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/ds-align-offset.out b/test/asm/ds-align-offset.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/ds-align-offset.out.bin b/test/asm/ds-align-offset.out.bin new file mode 100644 index 00000000..57b4c6e7 Binary files /dev/null and b/test/asm/ds-align-offset.out.bin differ diff --git a/test/asm/ds-align.asm b/test/asm/ds-align.asm new file mode 100644 index 00000000..21d6d022 --- /dev/null +++ b/test/asm/ds-align.asm @@ -0,0 +1,25 @@ +SECTION "aligned", ROM0, ALIGN[8] +db 1, 2, 3, 4, 5 ; five +ds align[8, $ff], 6 ; (1 << 8) - five - one = 250 bytes +db 7 ; one +align 8 +assert @ - STARTOF("aligned") == 5 + 250 + 1 + +SECTION "fixed", ROM0[$100] +ds align[2] ; already aligned, 0 bytes +db 8, 9, 10 ; three +ds align[4, $e], 11 ; (1 << 4) - three - two = 11 bytes +db 12, 13 ; two +align 4 +assert @ - STARTOF("fixed") == 3 + 11 + 2 + +SECTION "floating", ROM0 +db 14, 15 ; two +ds align[4] ; not aligned, 0 bytes +align 4 ; redundant +db 16, 17, 18 ; three +align 4, 3 +ds align[5], 19, 20 ; (1 << 4) - three = 13 bytes +db 21 ; one +align 5, 1 +assert @ - STARTOF("floating") == 2 + 3 + 13 + 1 diff --git a/test/asm/ds-align.err b/test/asm/ds-align.err new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/ds-align.out b/test/asm/ds-align.out new file mode 100644 index 00000000..e69de29b diff --git a/test/asm/ds-align.out.bin b/test/asm/ds-align.out.bin new file mode 100644 index 00000000..591118b7 Binary files /dev/null and b/test/asm/ds-align.out.bin differ