mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
some script fixes and izx0 image format
This commit is contained in:
1 parent
1e2fc4d6f9
commit
c7e25e323a
9 files changed
+406
-33
No files matched your search
@@ -35,7 +35,7 @@ Follow ALL the rules below carefully.
|
||||
- `ubyte`/`uword` = unsigned; `long` = signed 4-byte; `float` = 5-byte MS format; `str` = 0-terminated ubytes (max 255 chars)
|
||||
- `pointer` = address-sized unsigned integer. **Auto-adapts to the target**: a 16-bit `uword` on the 8-bit 6502 targets (C64, CX16, C128, PET32, virtual), a 32-bit `long` on the 32-bit M68000 targets (amiga500, qemu68k). Use it for addresses/pointers that must be portable across targets instead of hardcoding `uword` or `long`.
|
||||
- **float requires `%import floats`** at top of file, else compiler errors
|
||||
- Arrays: max 256 bytes (512 for split word arrays). For larger data, use `memory()` + pointers
|
||||
- Arrays: the size limit is a **byte budget** on all targets. On 6502 targets: max 256 bytes total (512 for split word arrays), so element counts are 256/4=64 for long arrays, 256/2=128 for word arrays, 256 for byte arrays. On M68000 targets (amiga500, qemu68k): max 32768 bytes total (16384 for word arrays, 8192 for long arrays). For larger data, use `memory()` + pointers
|
||||
- `memory(name, size)` returns the address to a statically reserved block of memory. The result is `pointer`-sized: a `uword` (16-bit) on 6502 targets, a `long` (32-bit) on M68000 targets.
|
||||
- To point a typed pointer at a `memory()` block, assign the uword directly: `^^MyStruct ptr = memory("name", size)`. The `^^Type:expression` syntax (below) only works with array literals, not general uword expressions.
|
||||
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable). This `^^Type:[...]` syntax does NOT work with variables or `memory()` — only literal arrays.
|
||||
@@ -72,8 +72,8 @@ No call stack for variable storage — recursion overwrites locals. To handle it
|
||||
|
||||
## Strings, Arrays & Pointers
|
||||
- String escapes: `\\`, `\"`, `\'`, `\n`, `\r`, plus hex/unicode: `\xHH` (raw byte, no encoding applied) and `\uHHHH` (unicode codepoint)
|
||||
- `str` / array: max 256 bytes. `long[]` limited to 64 entries (64x4=256). `str[]` for string arrays: `str[5] names = ["a","b","c","d","e"]`
|
||||
- 2D arrays: `type[rows][cols] name`, access `name[r][c]`. Flat init list only (no nested `[[...]]`). Total size still ≤ 256 bytes
|
||||
- `str` / array: the limit is a **byte budget** on all targets. On 6502 targets: max 256 bytes (so `long[]` limited to 64 entries, 64x4=256; word arrays 128 entries, 128x2=256). On M68000 targets (amiga500, qemu68k): max 32768 bytes (16384 for word arrays, 8192 for long arrays). `str[]` for string arrays: `str[5] names = ["a","b","c","d","e"]`
|
||||
- 2D arrays: `type[rows][cols] name`, access `name[r][c]`. Flat init list only (no nested `[[...]]`). Total size is a byte budget: ≤ 256 bytes on 6502 targets; max 32768 bytes on M68000 targets.
|
||||
- str/array passed as pointer to subroutine (receiving subroutine gets `^^ubyte` or `^^element`)
|
||||
- **No const pointers** or pointer-to-pointer currently supported
|
||||
- **Parsing limitation**: `pointer[index].field` as assignment target needs `^^`: `pointer[index]^^.field = value`
|
||||
|
||||
@@ -55,7 +55,8 @@ Romable (%option romable)
|
||||
|
||||
IR/VM
|
||||
^^^^^
|
||||
- make long shifts with values >= 32 consistent across targets: virtual wraps the shift count mod 32 (``5 << 33`` becomes ``5 << 1`` = 10), but qemu68k saturates to 0. uword shifts with values > 15 are an error. Pick one behavior and apply uniformly (also affects the ``examples/test.p8`` bit-shift tests, which currently have to omit 33 for long to stay target-agnostic).
|
||||
- encode indexed scaling into IR (so that m68k codegen can use scale factor addressing) see ideas/scaled-indexing-IR.md
|
||||
- make long shifts with values >= 32 consistent across targets: virtual wraps the shift count mod 32 (``5 << 33`` becomes ``5 << 1`` = 10), but qemu68k saturates to 0. uword shifts with values > 15 are an error.
|
||||
- maybe change all branch instructions to have 2 exits (label if branch condition true, and label if false) instead of 1, and get rid of the implicit "next code chunk" link between chunks.
|
||||
- implement more TODOs in AssignmentGen?
|
||||
- add more optimizations in IRPeepholeOptimizer?
|
||||
|
||||
@@ -41,7 +41,7 @@ main {
|
||||
sys.exit(1)
|
||||
}
|
||||
|
||||
str imagefile = "PROGDIR:psygnosis.iff"
|
||||
str imagefile = "PROGDIR:cara.iff"
|
||||
|
||||
; open custom screen
|
||||
^^intuition.Screen myScreen = intuition.OpenScreenTagList(0, screentags)
|
||||
|
||||
+20
-17
@@ -10,6 +10,7 @@
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
; open a 320x256, 5-plane screen
|
||||
long[] screentags = [
|
||||
intuition.SA_Left, 0,
|
||||
intuition.SA_Top, 0,
|
||||
@@ -63,7 +64,9 @@ main {
|
||||
|
||||
|
||||
izx0 {
|
||||
const long HEADER_SIZE = 48
|
||||
; IZX0 header, palette, then compressed planes.
|
||||
const long HEADER_SIZE = 52
|
||||
const long TOTAL_COMPRESSED_SIZE_OFFSET = 48
|
||||
const long MAX_PALETTE_SIZE = 256 * 4
|
||||
|
||||
uword imageWidth
|
||||
@@ -93,34 +96,34 @@ izx0 {
|
||||
numColors = peekw(header + 12)
|
||||
uword paletteSize = peekw(header + 14)
|
||||
|
||||
; palette follows the header
|
||||
clearPalette()
|
||||
bytesRead = dos.Read(file, palette, paletteSize)
|
||||
if bytesRead == paletteSize {
|
||||
long largestPlane = 0
|
||||
; buffer all compressed planes before decoding
|
||||
long compressedSize = peekl(header + TOTAL_COMPRESSED_SIZE_OFFSET)
|
||||
uword plane
|
||||
for plane in 0 to numPlanes - 1 {
|
||||
long planeSize = peekl(header + 16 + plane * 4)
|
||||
if planeSize > largestPlane
|
||||
largestPlane = planeSize
|
||||
}
|
||||
|
||||
if largestPlane > 0 and bitplanepointers!=0 {
|
||||
pointer planeBuffer = exec.AllocVec(largestPlane, exec.MEMF_PUBLIC)
|
||||
if planeBuffer != 0 {
|
||||
for plane in 0 to numPlanes - 1 {
|
||||
long streamSize = peekl(header + 16 + plane * 4)
|
||||
bytesRead = dos.Read(file, planeBuffer, streamSize)
|
||||
if bytesRead == streamSize {
|
||||
if compressedSize > 0 and bitplanepointers!=0 {
|
||||
pointer compressedBuffer = exec.AllocVec(compressedSize, exec.MEMF_PUBLIC)
|
||||
if compressedBuffer != 0 {
|
||||
bytesRead = dos.Read(file, compressedBuffer, compressedSize)
|
||||
if bytesRead == compressedSize {
|
||||
pointer compressedPlane = compressedBuffer
|
||||
for plane in 0 to numPlanes - 1 {
|
||||
long streamSize = peekl(header + 16 + plane * 4)
|
||||
long bitplane = peekp(bitplanepointers)
|
||||
; decode one plane into CHIP RAM
|
||||
if bitplane!=0
|
||||
compression.unZX0(planeBuffer, bitplane)
|
||||
compression.unZX0(compressedPlane, bitplane)
|
||||
compressedPlane += streamSize
|
||||
bitplanepointers++
|
||||
}
|
||||
success = true
|
||||
}
|
||||
exec.FreeVec(planeBuffer)
|
||||
exec.FreeVec(compressedBuffer)
|
||||
}
|
||||
}
|
||||
success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
# Scaled Indexing for IR LOADX / STOREX / STOREZX
|
||||
|
||||
This document describes the design to move array index scaling out of the IR
|
||||
generator and into the code generation backends, by giving the IR instructions
|
||||
`LOADX`, `STOREX`, and `STOREZX` an explicit `scale` operand.
|
||||
|
||||
The goal is to let the m68k backend use the 68020 hardware scaled-index
|
||||
addressing mode (`(An, Xn*scale)`), while keeping the IR identical for all
|
||||
targets and fixing the same problem uniformly for every backend.
|
||||
|
||||
---
|
||||
|
||||
## 1. Current State (background)
|
||||
|
||||
The IR generator pre-scales the array index:
|
||||
|
||||
- `loadIndexReg` (`codeGenIntermediate/IRCodeGen.kt:2100-2128`) loads the
|
||||
element index and immediately multiplies it by the element size
|
||||
(`itemsize`), both on the 16-bit path (`:2115`, `multiplyByConst(UWORD, ...)`)
|
||||
and on the 8-bit path (`:2126`, `multiplyByConst(UBYTE, ...)`).
|
||||
- Therefore every consumer of `LOADX` / `STOREX` / `STOREZX` assumes the index
|
||||
register already holds a **byte offset** into the array:
|
||||
- The VM adds the raw index to the base address (`InsLOADX`/`InsSTOREX`/
|
||||
`InsSTOREZX` in `VirtualMachine.kt:576/646/693`).
|
||||
- new6502 uses the raw index as a byte offset into the array
|
||||
(`indexedLoad`/`storeExchange`/`zeroMemoryIndexed` in
|
||||
`InstrLoadStore.kt:675/753/845`).
|
||||
- m68k uses the raw index as `d0` in `(a0,d0.w)` / `(0,a0,d0.l)` forms
|
||||
(`codeGenM68k/InstrLoadStore.kt`), with comments noting "index pre-scaled".
|
||||
|
||||
Because the index is pre-scaled, the 68020's hardware scaled addressing mode
|
||||
`(An, Xn*2)` / `(An, Xn*4)` cannot be used: the value in `Xn` would already be
|
||||
scaled, so the hardware scaling would double-apply.
|
||||
|
||||
---
|
||||
|
||||
## 2. Design Decision
|
||||
|
||||
**The IR never pre-scales the index, regardless of compilation target.**
|
||||
|
||||
- The index register in `LOADX` / `STOREX` / `STOREZX` always holds the
|
||||
**element index**.
|
||||
- Each instruction carries an explicit **`scale`** operand: the element size in
|
||||
bytes.
|
||||
- The effective address is:
|
||||
|
||||
base + index*scale + labelSymbolOffset
|
||||
|
||||
- `labelSymbolOffset` already exists on IR instructions and is already resolved
|
||||
everywhere, including in the VM program loader
|
||||
(`VmProgramLoader.kt:177-201`), so the intra-element byte offsets needed by
|
||||
`setlsb` / `setmsb` ride along for free.
|
||||
|
||||
### Why an explicit operand instead of deriving it from the type
|
||||
|
||||
`setlsb` / `setmsb` store a single byte (`.b`) into word/long/float arrays where
|
||||
the element size (2/4/5/8) differs from the size of the value being stored
|
||||
(1). Only an explicit operand can express "store a byte at element index i of an
|
||||
array whose elements are N bytes wide". Deriving the scale from the value type
|
||||
would give 1 for these stores and break them.
|
||||
|
||||
### Struct fields are unaffected
|
||||
|
||||
Struct field access never goes through the scaled index register. Field offsets
|
||||
are always constant byte offsets:
|
||||
|
||||
- Pointer field access: `LOADI` / `STOREI` with `immediate = fieldOffset`
|
||||
(`ExpressionGen.kt:1886-1897`, `AssignmentGen.kt:295-315`).
|
||||
- Struct-variable field access: `LOADM` / `STOREM` with `symbolOffset`.
|
||||
- Array of structs: index is multiplied by the struct size and added to the
|
||||
pointer via `ADDR` (genuine pointer arithmetic, `ExpressionGen.kt:1866-1868`).
|
||||
|
||||
So the "absolute fixed byte index" of a struct field never appears in a scaled
|
||||
`LOADX`/`STOREX`/`STOREZX` index register. The `scale` operand applies only to
|
||||
the element-index convention of these three opcodes.
|
||||
|
||||
### Backward compatibility
|
||||
|
||||
No backward compatibility is needed for existing `.p8ir` files; the format
|
||||
changes freely. The serialized `scale` is always written for the three opcodes
|
||||
(see Section 3), so files are self-describing.
|
||||
|
||||
---
|
||||
|
||||
## 3. IR Representation
|
||||
|
||||
### 3.1 The `scale` field
|
||||
|
||||
- Add `val scale: Int = 1` to `IRInstruction`
|
||||
(`intermediate/IRInstructions.kt`, constructor around `:902`).
|
||||
- Validation:
|
||||
- `scale >= 1`.
|
||||
- `scale != 1` is only allowed on `LOADX`, `STOREX`, `STOREZX`.
|
||||
|
||||
### 3.2 Serialization
|
||||
|
||||
Format: trailing `,S=N`, always written for the three opcodes.
|
||||
|
||||
```
|
||||
loadx.w r5,r2,arr,S=2
|
||||
storx.b r1,r2,floatarray,S=5
|
||||
storezx.b r3,arr,S=1
|
||||
```
|
||||
|
||||
Parsing: `parseIRCodeLine` (`intermediate/Utils.kt:87`) strips an optional
|
||||
trailing `,S=N`; defaults to 1 when absent. The existing label + offset parsing
|
||||
(`arr+1`, `Utils.kt:242-253`) is unchanged and composes with it:
|
||||
|
||||
```
|
||||
storx.b r1,r2,arr+1,S=4
|
||||
```
|
||||
|
||||
`IRFileReader` / `IRFileWriter` need no changes; they round-trip through
|
||||
`toString()` / `parseIRCodeLine`.
|
||||
|
||||
### 3.3 Documentation updates
|
||||
|
||||
- `instructionFormats` header comment (`IRInstructions.kt`, around `:710`).
|
||||
- The 32-bit word-index note in `IRProgram.kt:315`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Scale Values by Target
|
||||
|
||||
| Element type | 6502 (cx16/c64/c128/pet32) | m68k (amiga500/qemu68k) | VM |
|
||||
|---------------|----------------------------|-------------------------|------|
|
||||
| byte / bool | 1 | 1 | 1 |
|
||||
| split word | 1 (accesses lsb/msb byte sub-arrays) | 1 | 1 |
|
||||
| word | 2 | 2 | 2 |
|
||||
| pointer | 2 (non-split), 1 (split) | 4 | 4 |
|
||||
| long | 4 | 4 | 4 |
|
||||
| float | 5 (Mflpt5) | 4 | 8 |
|
||||
|
||||
Constraints that make everything fit:
|
||||
|
||||
- 6502: `ARRAY_SIZE_LIMIT = 256` is a **byte budget** (`AstChecker.kt:1080-1099`):
|
||||
word arrays <= 128 elements, long <= 64, float <= 51. A scaled byte offset
|
||||
therefore always fits in a byte (max 50*5 + 4 = 254), so 8-bit index
|
||||
arithmetic suffices.
|
||||
- 32-bit targets: all IR registers are physically 32-bit; element index
|
||||
<= 16383 (word) / 8191 (long/float), scaled value <= 32767, which fits in a
|
||||
signed 16-bit `.w` index register.
|
||||
- 68020 scaled addressing supports `*1`, `*2`, `*4` (and `*8` with `.l`); m68k
|
||||
only ever sees scales 1/2/4.
|
||||
|
||||
---
|
||||
|
||||
## 5. Changes by Module
|
||||
|
||||
### 5.1 `intermediate`
|
||||
|
||||
1. Add `scale: Int = 1` to `IRInstruction` with the validation from Section 3.1.
|
||||
2. Serialize `,S=N` in `toString()` for `LOADX`/`STOREX`/`STOREZX`; parse it in
|
||||
`Utils.parseIRCodeLine`.
|
||||
3. Update the comments in `IRInstructions.kt` and `IRProgram.kt:315`.
|
||||
|
||||
### 5.2 `codeGenIntermediate` - stop pre-scaling
|
||||
|
||||
**`loadIndexReg`** (`IRCodeGen.kt:2100`):
|
||||
|
||||
- Delete the two `multiplyByConst` calls (`:2115` and `:2126`).
|
||||
- Drop the now-unused `itemsize` and `arrayIsSplitWords` parameters.
|
||||
- Keep the byte-to-word `EXT` so 32-bit targets get a word-sized index.
|
||||
|
||||
**Array-indexing sites - emit `scale = eltSize` on the instruction:**
|
||||
|
||||
- `ExpressionGen.kt:646` (`indexByExpression`).
|
||||
- `AssignmentGen.kt:746/766/787` (`translateRegularAssignArrayIndexed`).
|
||||
- Regular-array `for` loop (`IRCodeGen.kt:752-769`): LOADX gets
|
||||
`scale = elementSize`; the loop increment changes from
|
||||
`addConstByteToReg(indexReg, elementSize)` to `+1`; the loop compare changes
|
||||
from `lengthBytes` (= length*elementSize) to `iterableLength`.
|
||||
- `setlsb`/`setmsb`, variable-index case (`BuiltinFuncGen.kt:931/952`):
|
||||
`scale = eltSize`, `labelSymbolOffset = byteOffset(eltSize)`, and drop the
|
||||
`ADD indexReg, byteOffset` that is no longer needed. The const-index branches
|
||||
(`:923-940`, `:944-950`) keep baking the full byte offset into the `LOAD`
|
||||
immediate with default `scale = 1`.
|
||||
|
||||
**Pointer-arithmetic sites - keep the explicit multiply:**
|
||||
|
||||
These sites use `loadIndexReg` to compute a real address (element offset
|
||||
combined with a pointer via `ADDR` or `LOADI`/`STOREI`). They must multiply by
|
||||
the element size themselves after `loadIndexReg`:
|
||||
|
||||
- `ExpressionGen.kt:681` (`translatePointerIndexing`).
|
||||
- `ExpressionGen.kt:1866` (array-of-structs pointer math, struct size).
|
||||
- `AssignmentGen.kt:703/713/835` (pointer store paths).
|
||||
- `BuiltinFuncGen.kt:867` (`setlsb`/`setmsb` on a pointer target).
|
||||
|
||||
These paths do **not** use the `scale` operand; they keep genuine pointer
|
||||
arithmetic, unchanged in behavior.
|
||||
|
||||
### 5.3 `virtualmachine`
|
||||
|
||||
`InsLOADX` (`:576`), `InsSTOREX` (`:646`), `InsSTOREZX` (`:693`): multiply the
|
||||
index register by `i.scale` before adding it to the base address. The index
|
||||
register is:
|
||||
|
||||
- `reg2` for integer `LOADX` / `STOREX`,
|
||||
- `reg1` for float `LOADX` / `STOREX` and for all `STOREZX`
|
||||
(matches `instructionFormats`, `IRInstructions.kt:714/721/725`).
|
||||
|
||||
`labelSymbolOffset` is already folded into the base address by
|
||||
`VmProgramLoader.kt:177-201`, so no change is needed there.
|
||||
|
||||
### 5.4 `codeGenM68k`
|
||||
|
||||
In `InstrLoadStore.kt` (including the float variants at `:198/252/282`), after
|
||||
the index has been loaded into `d0` (`loadIndexToD0`, `AsmGen.kt:162`):
|
||||
|
||||
- **M68020 (qemu68k):** use the hardware scaled-index mode:
|
||||
- `(a0,d0.w*N)` for the integer forms,
|
||||
- `(0,a0,d0.w*N)` for the float forms (the index is a word value, always
|
||||
<= 32767, so `.w*N` is correct).
|
||||
- `labelSymbolOffset` folds into the displacement via `resolveAddress`
|
||||
(`AsmGen.kt:212`), producing `(B,a0,d0.w*N)`.
|
||||
- **M68000 (amiga500):** there is no scaled-index mode, so shift explicitly:
|
||||
- `lsl.w #1,d0` for scale 2,
|
||||
- `lsl.l #2,d0` for scale 4,
|
||||
then keep the existing `(a0,d0.w)` / `(0,a0,d0.l)` forms.
|
||||
- Refresh the stale "index pre-scaled" comments.
|
||||
- `scale = 1` paths are unchanged.
|
||||
|
||||
Gate on `AsmGen.cpu` (`AsmGen.kt:55`); only the 68020 path uses the scaled
|
||||
addressing mode.
|
||||
|
||||
### 5.5 `codeGenNew6502`
|
||||
|
||||
The index register is always a byte on the 6502, and the array byte budget
|
||||
(Section 4) guarantees the scaled offset fits in a byte.
|
||||
|
||||
A small helper multiplies the byte index by `insn.scale`:
|
||||
|
||||
- `scale` 1: no-op (byte and split arrays).
|
||||
- `scale` 2: `asl`.
|
||||
- `scale` 4: `asl` twice.
|
||||
- any other scale (notably 5, the Mflpt5 float size): `TODO` - to be
|
||||
implemented together with float array support. This leaves
|
||||
`setlsb`/`setmsb` on float arrays unimplemented in this backend for now.
|
||||
|
||||
The helper is used by `indexedLoad` (`:675`, byte path), `storeExchange`
|
||||
(`:753`), and `zeroMemoryIndexed` (`:845`). `labelSymbolOffset` folds into the
|
||||
base address via `resolveAddress`, so setlsb/setmsb needs no extra handling.
|
||||
|
||||
Float `LOADX`/`STOREX`/`STOREZX` remain `TODO` in this backend
|
||||
(`:711/861-862`), unrelated to this change.
|
||||
|
||||
---
|
||||
|
||||
## 6. Out of Scope / Notes
|
||||
|
||||
- The old SimpleAST 6502 backend (`codeGenCpu6502`) does not consume IR and is
|
||||
unaffected.
|
||||
- On the 6502, byte-size-budgeted arrays (Section 4) mean scaled offsets always
|
||||
fit in a byte; no 16-bit carry propagation is required in the scale helper.
|
||||
- The m68k regular-array `for` loop uses a BYTE-typed `INC`/`CMPI` on the loop
|
||||
index. Verify during implementation that on m68k these operate on the full
|
||||
32-bit register so comparing against `iterableLength` works for arrays with
|
||||
more than 255 elements; if not, widen the loop index register to WORD on
|
||||
32-bit targets only (the VM is unaffected). This addresses the known latent
|
||||
m68k loop-index issue; 6502 loops are fine because of the byte budget.
|
||||
- Pointer indexing keeps an explicit multiply (it is real pointer arithmetic,
|
||||
not array indexing) and does not use the `scale` operand.
|
||||
|
||||
---
|
||||
|
||||
## 7. Test Coverage
|
||||
|
||||
- `intermediate`: `.p8ir` round-trip with `,S=N`; defaults to 1 when absent.
|
||||
- qemu68k: compile word/long array loads and stores, assert the generated
|
||||
`.asm` contains `(a0,d0.w*2)` / `(a0,d0.w*4)`; setlsb/setmsb variant asserts
|
||||
`(B,a0,d0.w*N)`.
|
||||
- amiga500: same programs, assert `lsl.w #1,d0` / `lsl.l #2,d0` are present and
|
||||
that no `(a0,d0.w*...)` scaled forms appear.
|
||||
- VM (`TestCompilerVirtual`, extend the existing regression at `:930`): word /
|
||||
long / float element access with a variable index (scale 2/4/8), plus a
|
||||
`setlsb`/`setmsb` case (scale + `labelSymbolOffset`).
|
||||
- cx16 (new6502): word/long indexed access, assert the shift sequences (scale
|
||||
2/4); optional ksim65 execution test (`prog8tests.codegeneration.TestExecution6502`
|
||||
pattern).
|
||||
- Use the compile-and-grep-`.asm` test pattern from
|
||||
`TestAmigaChipramOption.kt:96-103`.
|
||||
|
||||
---
|
||||
|
||||
## 8. File Reference Map
|
||||
|
||||
| Concern | File |
|
||||
|---------|------|
|
||||
| IR instruction definition, formats, validation, serialization | `intermediate/src/prog8/intermediate/IRInstructions.kt` |
|
||||
| IR text parsing (`,S=N`) | `intermediate/src/prog8/intermediate/Utils.kt` |
|
||||
| IR comment (32-bit word index note) | `intermediate/src/prog8/intermediate/IRProgram.kt:315` |
|
||||
| IR code generator, `loadIndexReg`, for loop | `codeGenIntermediate/src/prog8/codegen/intermediate/IRCodeGen.kt` |
|
||||
| Scaled store sites | `codeGenIntermediate/src/prog8/codegen/intermediate/AssignmentGen.kt` |
|
||||
| Indexed loads, pointer math | `codeGenIntermediate/src/prog8/codegen/intermediate/ExpressionGen.kt` |
|
||||
| `setlsb`/`setmsb` | `codeGenIntermediate/src/prog8/codegen/intermediate/BuiltinFuncGen.kt` |
|
||||
| VM execution of the three opcodes | `virtualmachine/src/prog8/vm/VirtualMachine.kt` |
|
||||
| VM label + offset resolution | `virtualmachine/src/prog8/vm/VmProgramLoader.kt:177-201` |
|
||||
| m68k load/store emission | `codeGenM68k/src/prog8/codegen/m68k/InstrLoadStore.kt` |
|
||||
| m68k `cpu`, `loadIndexToD0`, `resolveAddress` | `codeGenM68k/src/prog8/codegen/m68k/AsmGen.kt` |
|
||||
| new6502 load/store emission | `codeGenNew6502/src/prog8/codegen/new6502/InstrLoadStore.kt` |
|
||||
| Array byte budget limits | `compiler/src/prog8/compiler/astprocessing/AstChecker.kt:1080-1099` |
|
||||
@@ -107,7 +107,7 @@ All have type b or w or l or f.
|
||||
load reg1, value - load immediate value into register. If you supply a symbol, loads the *address* of the symbol! (variable values are loaded from memory via the loadm instruction)
|
||||
loadm reg1, address - load reg1 with value at memory address
|
||||
loadi reg1, reg2, value - load reg1 with value in memory indirect, pointed to by reg2 + offsetvalue 0-65535 (often used to read a field from a pointer to a struct, or with offset=0 just straight from the pointer)
|
||||
loadx reg1, reg2, address - load reg1 with value at memory address indexed by value in reg2 (0-255, a byte)
|
||||
loadx reg1, reg2, address - load reg1 with value at memory address indexed by value in reg2 (a byte 0-255 on 8-bit targets, a word 0-32767 on 32-bit targets)
|
||||
loadr reg1, reg2 - load reg1 with value in register reg2, "reg1 = reg2"
|
||||
loadhr reg1, slot - load cpu hardware register from calling convention slot (s0=A, s1=X, s2=Y, s3=AX, s4=AY, s5=XY, s6=FAC1, s7=FAC2) into reg1
|
||||
loadhfaczero fpreg1 - load "cpu hardware register" fac0 into freg1.f
|
||||
@@ -116,9 +116,9 @@ storem reg1, address - store reg1 at memory address
|
||||
storei reg1, reg2, value - store reg1 in memory indirect, pointed to by reg2 + offsetvalue 0-65535 (often used to write a field from a pointer to a struct, or with offset=0 just straight to the pointer)
|
||||
storeim value, address - store an immediate value (constant) at memory address (the constant goes in the value field, NOT via a register).
|
||||
storezi reg1, value - store zero at memory pointed to by reg1 + offsetvalue 0-65535 (just like storei, but a shorthand to store a constant 0)
|
||||
storex reg1, reg2, address - store reg1 at memory address, indexed by value in reg2 (0-255, a byte)
|
||||
storex reg1, reg2, address - store reg1 at memory address, indexed by value in reg2 (a byte 0-255 on 8-bit targets, a word 0-32767 on 32-bit targets)
|
||||
storezm address - store zero at memory address
|
||||
storezx reg1, address - store zero at memory address, indexed by value in reg1 (0-255, a byte)
|
||||
storezx reg1, address - store zero at memory address, indexed by value in reg1 (a byte 0-255 on 8-bit targets, a word 0-32767 on 32-bit targets)
|
||||
storehr reg1, slot - store reg1 into cpu hardware register for calling convention slot (s0=A, s1=X, s2=Y, s3=AX, s4=AY, s5=XY, s6=FAC1, s7=FAC2)
|
||||
storehfaczero fpreg1 - store fpreg1.f into "cpu register" fac0
|
||||
storehfacone fpreg1 - store fpreg1.f into "cpu register" fac1
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
output="${1:-${root_dir}/amigaiff.adf}"
|
||||
program="${AMIGA_PROGRAM:-${root_dir}/amigaiff}"
|
||||
library="${IFFPARSE_LIBRARY:-${root_dir}/Libs/iffparse.library}"
|
||||
image="${PSYGNOSIS_IMAGE:-${root_dir}/psygnosis.iff}"
|
||||
cara_image="${CARA_IMAGE:-${root_dir}/cara.iff}"
|
||||
izx0_image="${IZX0_IMAGE:-${root_dir}/cara.izx0}"
|
||||
izx0_reader="${IZX0_READER:-${root_dir}/izx0reader}"
|
||||
|
||||
if ! command -v xdftool >/dev/null 2>&1; then
|
||||
printf '%s\n' "error: xdftool from amitools is not installed or not on PATH" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$program" ]]; then
|
||||
printf '%s\n' "error: Amiga program not found: $program" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$library" ]]; then
|
||||
printf '%s\n' "error: iffparse.library not found: $library" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$image" ]]; then
|
||||
printf '%s\n' "error: IFF image not found: $image" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$cara_image" ]]; then
|
||||
printf '%s\n' "error: cara IFF image not found: $cara_image" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$izx0_image" ]]; then
|
||||
printf '%s\n' "error: IZX0 image not found: $izx0_image" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$izx0_reader" ]]; then
|
||||
printf '%s\n' "error: IZX0 reader not found: $izx0_reader" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
xdftool -f "$output" create + \
|
||||
format PROG8 ofs + \
|
||||
boot install + \
|
||||
makedir Libs + \
|
||||
write "$library" Libs/iffparse.library + \
|
||||
write "$program" amigaiff + \
|
||||
write "$image" psygnosis.iff + \
|
||||
write "$cara_image" cara.iff + \
|
||||
write "$izx0_image" cara.izx0 + \
|
||||
write "$izx0_reader" izx0reader
|
||||
|
||||
printf 'Created FFS Amiga floppy image: %s\n' "$output"
|
||||
xdftool "$output" list / all info
|
||||
Regular → Executable
+15
-8
@@ -1,3 +1,4 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
IZX0 Image File Format Specification & Exporter Script
|
||||
======================================================
|
||||
@@ -11,7 +12,7 @@ compressed bitplane streams using the ZX0 compression algorithm (via Salvador).
|
||||
File Layout:
|
||||
------------
|
||||
+-----------------------------------------------------------------------------+
|
||||
| Fixed Binary Header (48 Bytes, Big-Endian) |
|
||||
| Fixed Binary Header (52 Bytes, Big-Endian) |
|
||||
+-----------------------------------------------------------------------------+
|
||||
| Palette Data (Variable length = palette_data_size) |
|
||||
| - ECS Mode (is_aga=0): Array of UWORD ($0RGB, 2 bytes/color) |
|
||||
@@ -24,7 +25,7 @@ File Layout:
|
||||
| - Plane N Stream (Size = plane_sizes[N-1]) |
|
||||
+-----------------------------------------------------------------------------+
|
||||
|
||||
Fixed Binary Header Memory Map (48 Bytes Total):
|
||||
Fixed Binary Header Memory Map (52 Bytes Total):
|
||||
------------------------------------------------
|
||||
Offset (Hex) Offset (Dec) Type Description
|
||||
-------------------------------------------------------------------------------
|
||||
@@ -37,8 +38,9 @@ Offset (Hex) Offset (Dec) Type Description
|
||||
0x0C 12 UWORD Number of Palette Entries (2^nPlanes)
|
||||
0x0E 14 UWORD Palette Data Size in Bytes
|
||||
0x10 16 ULONG[8] Array of Compressed Sizes (Planes 0..7)
|
||||
0x30 48 ULONG Total compressed bitmap data size
|
||||
-------------------------------------------------------------------------------
|
||||
Total Header Size: 48 Bytes (0x30)
|
||||
Total Header Size: 52 Bytes (0x34)
|
||||
|
||||
Palette Entry Specifications:
|
||||
-----------------------------
|
||||
@@ -216,13 +218,14 @@ def export_izx0(input_path, output_path, force_aga=False, max_colors=256):
|
||||
|
||||
palette_data_size = len(palette_bytes)
|
||||
|
||||
# 5. Build Fixed 48-Byte Binary Header (Big-Endian)
|
||||
# 5. Build Fixed 52-Byte Binary Header (Big-Endian)
|
||||
plane_sizes = [0] * 8
|
||||
for i in range(num_planes):
|
||||
plane_sizes[i] = len(compressed_planes[i])
|
||||
compressed_payload_size = sum(plane_sizes)
|
||||
|
||||
header = struct.pack(
|
||||
">4sHHHBB HH 8I",
|
||||
">4sHHHBB HH 9I",
|
||||
b"IZX0",
|
||||
aligned_width,
|
||||
height,
|
||||
@@ -232,6 +235,7 @@ def export_izx0(input_path, output_path, force_aga=False, max_colors=256):
|
||||
num_palette_entries, # 2^num_planes
|
||||
palette_data_size, # Byte count of palette block
|
||||
*plane_sizes,
|
||||
compressed_payload_size,
|
||||
)
|
||||
|
||||
# 6. Write Output File
|
||||
@@ -241,17 +245,16 @@ def export_izx0(input_path, output_path, force_aga=False, max_colors=256):
|
||||
for c_data in compressed_planes:
|
||||
f.write(c_data)
|
||||
|
||||
compressed_payload_size = sum(len(c) for c in compressed_planes)
|
||||
file_size = os.path.getsize(output_path)
|
||||
print(f"\nSaved '{output_path}' ({file_size} bytes total)")
|
||||
print(
|
||||
f"Header: 48B | Palette: {palette_data_size}B ({num_palette_entries} entries) | Streams: {compressed_payload_size}B"
|
||||
f"Header: 52B | Palette: {palette_data_size}B ({num_palette_entries} entries) | Streams: {compressed_payload_size}B"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="export_izx0",
|
||||
prog="amiga-export-izx0",
|
||||
description="Convert image files (PNG/BMP/etc.) into the IZX0 planar compressed Amiga format.",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
@@ -285,6 +288,10 @@ def main():
|
||||
help="Max colors for quantization palette reduction (default: 256)",
|
||||
)
|
||||
|
||||
if len(sys.argv) == 1:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.input):
|
||||
Regular → Executable
File mode changed.
Reference in new issue
Block a user