Compare commits

...
55 Commits
Author SHA1 Message Date
Irmen de Jong b2520db872 add compiler version in assembly file 2026-10-22 19:26:02 +02:00
Irmen de Jong ebe26d0cf9 Fix block alias 2026-10-22 19:08:36 +02:00
Irmen de Jong 63e2a45f09 Require explicit via for linked-list for loops 2026-10-20 01:42:39 +02:00
Irmen de Jong 6076b01f6d Require explicit via for linked-list for loops
Removes the header shorthand, extracts shared validation into
LinkedForLoopChecks.kt, fixes until-validation for pointer sentinels,
and updates docs/examples/tests.
2026-10-20 00:25:27 +02:00
Irmen de Jong d0bf2c3dcb fix doc build for RTD 2026-10-19 23:07:18 +02:00
Irmen de Jong d9bf6f090e antlr4 parser tweaks 2026-10-19 22:57:49 +02:00
Irmen de Jong e08a995067 amiga: get rid of local var address escape warnings 2026-10-19 22:49:03 +02:00
Irmen de Jong da2288db93 improved variable source line position 2026-10-19 22:16:53 +02:00
Irmen de Jong 31b5cddb96 apply new linked list iteration for loop syntax 2026-10-19 22:10:19 +02:00
Irmen de Jong 264b894909 Add linked-list for-loop iteration with via/until syntax 2026-10-19 21:59:56 +02:00
Irmen de Jong 09ae0c6322 Fix 6502 codegen for pointer-to-pointer struct field double-deref 2026-10-19 19:44:44 +02:00
Irmen de Jong 9ca5db3436 fix 6502 pointer deref clobbering bare pointer variable 2026-10-19 04:01:12 +02:00
Irmen de Jong 375214b5e3 Add emudbg library module for amiga500/amiga1200 targets
New Prog8 standard library module for emulator debug output on the Amiga
targets, targeting the Copperline emulator (with partial WinUAE support).
2026-10-19 01:29:04 +02:00
Irmen de Jong 1da37e4119 skill 2026-10-17 23:45:22 +02:00
Irmen de Jong 16f7d588a7 add '-emu2' for amiga targets, -emu now launches the actual 'copperline' emulator, -emu2 still launches vamos. 2026-10-17 14:01:03 +02:00
Irmen de Jong f6c8a3f2ed defer: keep static per-subroutine mask, fix m68k carry bug, document recursion limitation
Roll back the per-activation defer-mask change because its code/RAM cost
outweighed the benefit. The pending-defer state is again a single static
mask variable per subroutine.

Keep the m68k bugfix: the handler now tests each defer bit with
`mask & bit != 0` instead of `mask >>= 1; bcc`, because the `>>=` store
on m68k uses MOVE which clears carry before the branch can read it.

Document that recursive/mutually recursive subroutines containing defer
are not supported on stack-frame targets, and remove the recursion-sound
defer todo item.
2026-10-17 05:00:02 +02:00
Irmen de Jong 5619898113 reduce test memory footprint 2026-10-17 02:43:39 +02:00
Irmen de Jong ae0414631e fix compiler crash on peek/poke/popp used in the wrong statement/expression context
fixes #248
2026-10-17 02:27:51 +02:00
Irmen de Jong e3ca9ae4a8 Fix virtual target typed pop results 2026-10-17 02:12:17 +02:00
Irmen de Jong 38c60a2d75 harden AstWalker detached-subtree skipping and extract shared canSkipExplicitCmpi helper 2026-10-17 01:46:35 +02:00
Irmen de Jong 5e83c44300 fix wrong result for short-circuit boolean and/or on the virtual target 2026-10-17 01:43:08 +02:00
Irmen de Jong 99ff5768c5 fix compiler crash on 'not' combined with or/and in one expression
cherry-picked from v12 maintenance b71787a929
2026-10-17 01:18:09 +02:00
Irmen de Jong fba4bb5272 6502 codegen bug: subtraction with a register-clobbering right operand 2026-10-16 00:58:49 +02:00
Irmen de Jong 17322a08ec 6502 math: convert lerp/lerpw/interpolate/interpolatew to 6502 assembly 2026-10-16 00:07:46 +02:00
Irmen de Jong 06d2f9ebbe when->on..goto: calibrate the jump-table sizeLimit per target 2026-10-15 22:10:52 +02:00
Irmen de Jong 3a5f2c33ab when->on..goto: allow arbitrary choice bodies by generating labels and jump tables 2026-10-15 20:23:02 +02:00
Irmen de Jong d523ecd1e1 when->on..goto: synthesize a gap label so gapped value ranges also use jumpi tables instead of the compare chain 2026-10-15 04:57:02 +02:00
Irmen de Jong 159634a99f Fix m68k frame teardown for intra-subroutine dispatch 2026-10-15 04:29:33 +02:00
Irmen de Jong 55dcb45230 when->on..goto: let the else part be any body, not just a single goto
Only the value choices have to be goto's, since those become entries in the
jump table. The else is taken over verbatim as the on..goto's else part, so a
common `else -> { ... }` body no longer disqualifies the whole when.
2026-10-15 04:05:45 +02:00
Irmen de Jong de75342198 fix when->on..goto rewrite for signed-byte conditions and non-zero-based values. Also made it smarter to allow a nonzero start value index 2026-10-15 03:29:02 +02:00
Irmen de Jong 30d3b76787 LSP: extract more document symbols
SymbolExtractor now also emits symbols for enum members, struct
fields, aliases, subroutine parameters, local variables and labels,
and finds labels/declarations nested in control-flow bodies.
Also refresh the LSP feature list in the introduction docs.
2026-10-15 01:47:45 +02:00
Irmen de Jong 8bbe5bd209 label names hash numbers are now stable 2026-10-14 19:35:49 +02:00
Irmen de Jong da7ea294ef todo cleanup 2026-10-14 01:29:22 +02:00
Irmen de Jong 05d8acc4bb optimized VM initialization and update Mordant library to fix a FFM crash 2026-10-13 22:48:29 +02:00
Irmen de Jong 6cac707fd1 code cleanups 2026-10-13 22:40:19 +02:00
Irmen de Jong dea43c99bb m68k: remove word packed args plan doc 2026-10-13 21:42:57 +02:00
Irmen de Jong 0b902f06b5 m68k: word-packed stack argument slots 2026-10-13 21:37:43 +02:00
Irmen de Jong 733a3fb752 add missing helper routines doc 2026-10-13 17:49:31 +02:00
Irmen de Jong 460adb9e02 Remove Exec-specific for-loop over linked lists, disallow pointer as struct field name 2026-10-13 01:01:24 +02:00
Irmen de Jong 3c22dfc11a fix compiler crash on indexed reads/writes through ^^pointer struct fields 2026-10-13 00:45:21 +02:00
Irmen de Jong dfb3257c04 reserve the 'pointer' datatype name, it can no longer be used as a symbol name 2026-10-13 00:00:26 +02:00
Irmen de Jong 8bcd176746 fix invalid parsing of ^^pointer 2026-10-12 20:45:12 +02:00
Irmen de Jong fd3add76e4 rename amiga_gfx to easygfx 2026-10-11 14:39:37 +02:00
Irmen de Jong 4d0a7ff8bf added amiga/tictactoe example 2026-10-11 06:42:29 +02:00
Irmen de Jong e1f2faabe7 added diskfont.p8 2026-10-11 05:33:47 +02:00
Irmen de Jong 25bd60368a fix undefined symbol error 2026-10-11 03:23:49 +02:00
Irmen de Jong f61c1dc7ef fix mouse click offsets and aspect ratio 2026-10-11 02:41:42 +02:00
Irmen de Jong eda21a6ebf Fix in operator on arrays larger than 255 elements 2026-10-11 02:05:21 +02:00
Irmen de Jong c0267e37b0 added amiga_gfx module and amiga/gfxdemo example 2026-10-11 01:03:59 +02:00
Irmen de Jong 78f9df203c inline math trig routines 2026-10-10 20:46:10 +02:00
Irmen de Jong d3e10b4974 m68k: optimize log2 and gcd routines, added log2l 2026-10-10 19:36:04 +02:00
Irmen de Jong c4679bd8af m68k: optimize lerp/interpolate/CRC routines; add interpolatew 2026-10-10 18:28:12 +02:00
Irmen de Jong e7dfe79919 m68k: add log2, diff, lerp, interpolate, gcd and crc16/32; fix qemu68k word shifts and const-eval of log2/atan2 2026-10-10 17:49:29 +02:00
Irmen de Jong 31dbbc3fe1 m68k: implement bcd library routines 2026-10-10 15:41:32 +02:00
Irmen de Jong 55b7239553 improve skills 2026-10-10 14:19:38 +02:00
192 changed files with 7225 additions and 2946 deletions

No files matched your search

+4
View File
@@ -9,6 +9,10 @@ Use 64tass syntax, not ca65 or another assembler. Keep answers concise and
practical. This applies to standalone `.asm` files and Prog8 `%asm {{ }}` or
`asmsub` blocks.
Reference documentation for the assembler is available locally in
`~/Documents/prog8-dev-resources` (the 64tass manual, `64tass.md`). Search this
folder first before looking online.
## Syntax
- Labels begin in column 1. Instructions are indented at least four spaces.
+19 -6
View File
@@ -8,6 +8,10 @@ description: Writing or understanding Motorola 68000/68020 assembly in vasm mot
Use Motorola syntax for `vasm`, not Devpac or other assemblers. Keep answers
concise and practical.
Reference documentation for the assembler and linker is available locally in
`~/Documents/prog8-dev-resources` (the vasm and vlink manuals). Search this
folder first before looking online.
## Syntax
- Labels begin in column 1. Instructions are indented at least four spaces.
@@ -105,18 +109,27 @@ when both operands are registers.
- Normal framed subroutines use `link a5,#-N` and `unlk a5`; locals are at
negative `A5` offsets and incoming parameters are at positive offsets.
- Arguments use one padded 4-byte slot each. The caller pushes arguments
left-to-right, right-justifies byte and word values in their slots, and
removes the complete argument area after the call. Parameter `i` of `N`
arguments is at `8 + 4 * (N - 1 - i)` from the callee's `A5` when `i` is zero-based.
- Arguments use word-packed slots of natural width: byte, bool, word and uword
arguments take a 2-byte slot; long, pointer and float arguments take a 4-byte
slot. The caller pushes arguments left-to-right (a byte sits at the base byte
of its 2-byte slot, the second byte is garbage; word values fill their slot)
and removes the complete argument area after the call. The area
size is always even, so the stack stays word-aligned; slot bases accumulate from
the rightmost argument at `8(a5)` according to each argument's width.
- A normal call preserves the caller's frame. A `goto` or conditional branch
leaving the current subroutine must execute `unlk a5` on the taken path before
transferring control.
- The built-in Prog8 `call(address)` is a zero-argument indirect call. It does
not marshal stack arguments. `asmsub` and `extsub` keep their explicit
register-annotated ABI, separate from normal framed calls.
- Do not push byte arguments directly with `move.b ..., -(sp)`: reserve a full
4-byte slot and store the byte at offset `3(sp)`.
- Push narrow arguments into their own 2-byte slots, each with one instruction: a
byte uses `move.b ..., -(sp)` (A7 byte predecrement steps by 2, reserving the
slot; the byte lands at the slot's base byte), a word uses a plain
`move.w ..., -(sp)`. Never push a narrow value where the callee expects a
wide (4-byte) argument: the slot sizes must line up exactly.
Wide arguments (`move.l ..., -(sp)` / `fmove.s fp0,-(sp)`) push in one step.
Use `lea N(sp),sp` for the cleanup when the call returns a status flag
(`addq` clobbers CCR).
## Prog8 Integration
+30 -48
View File
@@ -9,18 +9,14 @@ You are an expert Prog8 assistant. Keep answers concise and practical. This
skill covers `.p8` source and `.p8ir` intermediate representation for the
6502, M68000, and virtual targets.
For full language reference, link users to these project documents rather than
repeating them here:
Various reference documentation is available locally in
`~/Documents/prog8-dev-resources`:
Prog8 reference documentation, emulator manuals such as VICE and the X16
emulator, the 64tass/vasm/vlink assembler and linker docs, X16 hardware and register specs,
the Amiga NDK (docs and includes), and other resources.
Search in this folder first before looking online!
- Program structure and syntax: `docs/source/programming.rst`
- Variables, datatypes, arrays, and tags: `docs/source/variables.rst`
- Structs and pointers: `docs/source/structpointers.rst`
- Compilation and compiler options: `docs/source/compiling.rst`
- Standard library: `docs/source/libraries.rst`
- Binary libraries and `extsub`: `docs/source/binlibrary.rst`
- Target differences: `docs/source/targetsystem.rst`
- Known limitations: `docs/source/todo.rst`
- Target API signatures: `docs/source/_static/symboldumps/skeletons-<target>.txt`
## Essentials
@@ -43,7 +39,14 @@ and finish with `sys.poweroff_system()`. CBM targets use PETSCII by default;
avoid uppercase in test output unless intentional graphics. The virtual target
uses ISO when configured with `%encoding iso` and `txt.iso()`.
Do not use `%option no_sysinit` on `amiga500` or `qemu68k` targets when the goal
On any CBM machine with PETSCII (this includes the Commander X16), files
on disk must only consist of 'safe' characters (`a-z`, `0-9`, `.-+!%`).
Do NOT mix casing in filenames; in Prog8 source code, filenames should
usually be written in *lowercase* letters. Other characters (such as
`_` or uppercase mixed with lowercase) can make kernal file operations
fail, depending on the drive/host layer.
Do not use `%option no_sysinit` on `amiga500`, `amiga1200`, or `qemu68k` targets when the goal
is to create normal runnable programs. It skips critical startup logic including
library initialization (DOS, graphics, intuition, timer) and CLI argument
handling, causing programs to crash on start. It is appropriate for library
@@ -71,13 +74,15 @@ libraries.
locals at negative offsets, parameters at positive offsets. Block-level
variables, globals, `@shared`, memory slabs, and objects needing a permanent
external address stay static. Clean locals are zeroed on every call
including arrays, so the compiler warns for framed arrays over 8 bytes; use
`@dirty` when you assign first, otherwise you read stack garbage.
- Normal M68K calls use uniform 4-byte, right-justified argument slots. The caller
pushes arguments left-to-right and removes the complete argument area after the
call. Framed parameters start at positive `A5` offsets, with the first parameter
at `8 + 4 * (parameter_count - 1)`; locals use negative offsets. See
`docs/source/technical.rst` for the full convention and static fallback rules.
including arrays, so the compiler warns for framed arrays over 8 bytes; use
`@dirty` when you assign first, otherwise you read stack garbage.
- Normal M68K calls use word-packed argument slots: byte/bool/word/uword arguments
take a 2-byte slot, long/pointer/float take 4 bytes. The caller pushes arguments
left-to-right and removes the complete argument area after the call. Slot bases
accumulate right-to-left starting at positive `A5` offset 8, so a parameter's
offset depends on the widths of the parameters to its right; locals use negative
offsets. See `docs/source/technical.rst` for the full convention and static
fallback rules.
- `memory(name, size)` reserves static memory and returns a `pointer`.
- `str` and arrays have compile-time byte budgets. On 6502 targets the usual
limit is 256 bytes; M68000 targets allow up to 32768 bytes. Use `memory()`
@@ -98,8 +103,11 @@ registers survive calls. Long operations may clobber `R12-R15`.
## Syntax Pitfalls
- Hex uses `$FF`, binary uses `%1010`, and casts use `expression as type`.
- There is no automatic type widening: `byte * byte` remains a byte. Cast
explicitly when a wider result is needed.
- There is no automatic type widening in expression results: `byte * byte`
remains a byte. Cast explicitly when a wider result is needed. Assignments,
call arguments, and mixed-type expressions do widen implicitly
(`ubyte`/`byte` to `word`/`uword` to `long`); only same-width signedness
reinterpretation (`uword` to `word` and back) needs an explicit `as` cast.
- `&` is an untyped address and `&&` is a typed pointer. Complex pointer field
assignments may require `ptr^^.field`.
- `and`, `or`, `xor`, and `not` are logical operators. Use `&`, `|`, `^`, and
@@ -118,6 +126,7 @@ Prog8 supports `if`/`else`, `when`, `for`, `while`, `do`/`until`, `repeat`,
`unroll`, `break`, `continue`, `goto`, and labels. `unroll` duplicates a
constant-count body at compile time and does not support `break` or `continue`.
`repeat` is usually more efficient than `for` when no loop variable is needed.
Replace a `for` loop with a `repeat` loop when the loop variable is not used in the loop body.
Use `if_cs`, `if_cc`, `if_z`, and `if_nz` for direct CPU-flag branches.
## Subroutines and Assembly
@@ -152,35 +161,8 @@ Use `if_cs`, `if_cc`, `if_z`, and `if_nz` for direct CPU-flag branches.
## Verification
- A full `gradle build` is CPU- and IO-heavy so lower its priority on Unix like systems. Prefix
it with `nice`, e.g. `nice gradle build --console=plain`, and use `nice -n 19` for
maximum politeness. Targeted runs (`gradle :compiler:test --tests "..."`) are
cheaper but still worth lowering the priority of.
- Always pass `--console=plain`; the fancy console output interferes with agent
output parsing.
- Use the `virtual` target for behavioral tests when possible:
`prog8c -target virtual -emu program.p8`.
- Use `-check` for syntax and semantic checks without output generation.
- Use `-noopt` to determine whether a failure is optimizer-related.
- For IR execution, use `-vmtrace` when control flow needs inspection.
- Do not modify a correct test program to work around a compiler crash. Reduce
the case and fix the compiler instead.
- `gradle build` already runs the full test suite; do not run a separate
`gradle test` before it, to avoid a lengthy double run.
- `TestCompilerOnExamples` asserts exact output sizes per example. Any change to
the code generator shifts them, so expect a handful of
`expected:<N> but was:<M>` failures with no stack trace pointing at the cause.
Attribute each one before editing: rebuild with the change reverted and compare
the two generated `.asm` files, so the size delta is demonstrably yours and not a
side effect. Then update the expected size. A delta of a few bytes from removed
`ldy`/`lda` instructions is normal; a large or erratic delta means something else
changed and needs investigating.
- When running `gradle` tests, grep for `SUCCESSFUL` to check if all tests
passed, and `FAILED` to check for failures. Gradle does not print individual
`PASSED` lines by default.
- Do not write compiler unit tests that depend on the `vasm` assembler: it is
not always installed (CI only installs `64tass`). For m68k codegen tests
(`amiga500`, `qemu68k` targets) pass `writeAssembly = true, assemble = false`
to the `compileText`/`compileFile` helpers. This still generates the codegen
AST to assert on, but skips assembling, so no vasm is needed. If a test's
assertions cannot be made from the AST alone, drop the test.
+2 -2
View File
@@ -15,6 +15,6 @@
</component>
<component name="KotlinJpsPluginSettings">
<option name="externalSystemId" value="Gradle" />
<option name="version" value="2.4.20" />
<option name="version" value="2.4.21" />
</component>
</project>
</project>
+28 -4
View File
@@ -62,7 +62,7 @@ Source → parseMainModule() → processAst() → optimizeAst() → postprocessA
- **CPU instruction set differences**: Only the CommanderX16 target (cx16) can use 65C02 instructions such as STZ. The other targets (C64, C128, PET32) can only use original 6502 instructions.
- When writing or understanding 6502/65C02 assembly code, load the `asm6502-coder` skill for 64tass syntax and conventions.
- When writing or understanding M68K assembly code (amiga500, amiga1200, qemu68k targets), load the `m68k-coder` skill for vasm mot syntax and conventions.
- M68K ordinary subroutine locals and parameters use `A5`-based stack frames. Normal calls pass padded 4-byte argument slots on the stack, and the caller cleans up; see `docs/source/technical.rst` for the complete convention and frame eligibility rules.
- M68K ordinary subroutine locals and parameters use `A5`-based stack frames. Normal calls pass word-packed argument slots on the stack (2-byte slots for byte/word args, 4-byte for wide args, byte values at the slot base), and the caller cleans up; see `docs/source/technical.rst` for the complete convention and frame eligibility rules.
## DEBUGGING TIP: Use `-noopt` to isolate problems
@@ -271,12 +271,21 @@ Names follow the source file name, or the stem of the `-o` file when that option
### Automated Tests (gradle)
Always use the `--console=plain` flag with these commands to avoid messing up the agent's text interface.
- `gradle build --console=plain` - Full build including all tests (about 50s)
- `gradle :compiler:test --console=plain` - Run only compiler tests (faster)
- `gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
- `ionice -c 3 nice gradle build --console=plain` - Full build including all tests (about 50s)
- `ionice -c 3 nice gradle :compiler:test --console=plain` - Run only compiler tests (faster)
- `ionice -c 3 nice gradle :compiler:test --tests "prog8tests.compiler.TestOptimization" --console=plain` - Run specific test class
Note: By default, Gradle only shows failed tests. Passed and skipped tests are silent.
On Unix-like systems, prefix **every** gradle invocation with `nice` to lower its CPU priority,
because gradle is CPU-heavy. This applies to all gradle commands (build, compile, test, install), not
just `gradle build`. Use the same prefix for the quick compile checks and install commands in
"Building and Installing the Compiler" above.
On **Linux** additionally put `ionice -c 3` in front (idle-class I/O scheduling), so the build doesn't
compete for disk with whatever else is running: `ionice -c 3 nice gradle ...`. `ionice` is
Linux-only (util-linux); on macOS/BSD just use `nice`.
**⚠️ CRITICAL: Test Filtering Patterns - Read This First!**
Gradle's `--tests` filter has **strict rules** that are easy to get wrong:
@@ -310,6 +319,14 @@ gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit` - the latter ONLY when NOT writing for the amiga500 or qemu68k targets
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
### TestCompilerOnExamples expected sizes
`TestCompilerOnExamples` asserts exact output sizes per example. A code-generator change shifts them,
so expect a handful of `expected:<N> but was:<M>` failures with no stack trace pointing at the cause.
Attribute each one before editing: rebuild with the change reverted and compare the two generated
`.asm` files, so the size delta is demonstrably yours and not a side effect. Then update the expected
size. A delta of a few bytes from removed `ldy`/`lda` instructions is normal; a large or erratic delta
means something else changed and needs investigating.
### Compiler Unit Test Snippets
Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`) follow different rules from standalone programs:
- **Keep self-contained** — avoid `%import` directives (test setup may not have library search path configured)
@@ -317,6 +334,11 @@ Prog8 code snippets embedded in the compiler's own Kotlin unit tests (e.g., in `
- **Set `writeAssembly=false`** if the test only checks generated AST (much faster)
- **Set `optimize=false`** by default for these snippets
- **Do NOT use** the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern — that's only for real CX16 emulator runs
- **Do NOT** write compiler tests that depend on the `vasm` assembler: it is not always installed
(CI only installs `64tass`). For m68k codegen tests (`amiga500`, `qemu68k` targets) pass
`writeAssembly = true, assemble = false` to the `compileText`/`compileFile` helpers. This still
generates the codegen AST to assert on but skips assembling, so no vasm is needed. If a test's
assertions cannot be made from the AST alone, drop the test.
### Testing Assembly Output
When testing generated assembly in Kotlin tests:
@@ -333,6 +355,8 @@ When testing generated assembly in Kotlin tests:
**Testing tip**: When writing and testing Prog8 programs, **use the `virtual` target** (e.g., `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir`). This is the preferred way to test because the virtual target can easily write output to stdout, making it simple to verify program behavior and check results.
For the amiga targets, `-emu` launches FS-UAE (GUI) while `-emu2` launches Vamos (headless CLI). For automated/non-interactive testing, prefer `-emu2` (vamos) to avoid opening a GUI window.
**CX16 output verification**: Use `x16emu -echo iso -run -prg input.prg` to echo screen output to stdout **and auto-start the program**. The `-run` flag is **critical**: without it, the program loads but doesn't execute, so you'll see no output. Pipe through `strings` or `grep` to filter: `x16emu -echo iso -run -prg input.prg 2>&1 | grep -E "(PASS|FAIL)"`.
**IMPORTANT**: Always add `%encoding iso` at the top of your source file and call `txt.iso()` in `start()`. This prevents PETSCII→ISO charset translation errors that garble uppercase/special characters and make output unreadable:
```prog8
+5 -3
View File
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
kotlin("jvm") version "2.4.20"
kotlin("jvm") version "2.4.21"
}
allprojects {
@@ -72,8 +72,10 @@ subprojects {
// Enable JUnit 5 (required for Kotest)
useJUnitPlatform()
val testConcurrency = project.findProperty("testConcurrency")?.toString()?.toIntOrNull()
?: (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
// Forked test JVMs each reserve up to -PtestMaxHeap, so keep the fork count
// bounded even on many-core machines to avoid swapping during parallel builds.
val testConcurrency = (project.findProperty("testConcurrency")?.toString()?.toIntOrNull()
?: (Runtime.getRuntime().availableProcessors() / 2)).coerceIn(1, 4)
project.findProperty("testMaxHeap")?.toString()?.let { maxHeapSize = it }
// Enable concurrent test execution for Kotest
+5 -7
View File
@@ -40,12 +40,10 @@ object SymbolNames {
* See https://github.com/irmen/prog8/issues/198
*/
fun stripPrefixes(scopedName: String): String {
return scopedName.split('.')
.map { part ->
if(part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
part.drop(4)
else part
}
.joinToString(".")
return scopedName.split('.').joinToString(".") { part ->
if (part.length > 4 && part.startsWith("p8") && part[2].isLetter() && part[3] == '_')
part.drop(4)
else part
}
}
}
+7 -8
View File
@@ -323,7 +323,7 @@ class DataType private constructor(
}
fun dereference(): DataType {
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type ${this}"}
require(isPointer || isUnsignedWord) { "cannot dereference non-pointer type $this"}
return when {
isUnsignedWord -> forDt(BaseDataType.UBYTE)
sub!=null -> forDt(sub)
@@ -439,8 +439,7 @@ class DataType private constructor(
BaseDataType.STR -> targetType.base in setOf(BaseDataType.STR, BaseDataType.UWORD, BaseDataType.LONG) || (targetType.isPointer && targetType.sub==BaseDataType.UBYTE)
BaseDataType.ARRAY, BaseDataType.ARRAY_SPLITW -> {
if (isPointerArray) {
if (!targetType.isPointerArray) false
else if (subType != null && targetType.subType != null) subType!!.sameas(targetType.subType!!)
targetType.isPointerArray && if (subType != null && targetType.subType != null) subType!!.sameas(targetType.subType!!)
else if (subType == null && targetType.subType == null) pointeeSub == targetType.pointeeSub
else false
} else {
@@ -521,11 +520,11 @@ class DataType private constructor(
(memsizer.POINTER_MEM_SIZE > 2u && base == BaseDataType.LONG) ||
(base == BaseDataType.ARRAY && (sub == BaseDataType.UBYTE || sub == BaseDataType.BYTE))
fun isSplitWordArray(memsizer: IMemSizer): Boolean {
return if(base== BaseDataType.ARRAY_SPLITW)
true
else if(base==BaseDataType.ARRAY && sub==BaseDataType.POINTER)
memsizer.POINTER_MEM_SIZE<=2u
else false
return when (base) {
BaseDataType.ARRAY_SPLITW -> true
BaseDataType.ARRAY if sub == BaseDataType.POINTER -> memsizer.POINTER_MEM_SIZE <= 2u
else -> false
}
}
val isSplitUnsignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.UWORD
val isSplitSignedWordArray = base == BaseDataType.ARRAY_SPLITW && sub == BaseDataType.WORD
@@ -66,12 +66,9 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
fun hasByteAvailable() = options.zeropage != ZeropageType.DONTUSE && free.isNotEmpty()
fun hasWordAvailable(): Boolean {
if(options.zeropage== ZeropageType.DONTUSE)
return false
return free.windowed(2).any { it[0] == it[1] - 1u }
return options.zeropage != ZeropageType.DONTUSE && free.windowed(2).any { it[0] == it[1] - 1u }
}
override fun allocate(name: String,
@@ -23,7 +23,7 @@ private const val BYTE_BUDGET = 24
// high byte stale in Y).
fun byteShiftAddExpansion(value: Int): List<String>? {
if (value < 3 || value > 255)
if (value !in 3..255)
return null
val bitlen = 31 - Integer.numberOfLeadingZeros(value)
val popcount = Integer.bitCount(value)
@@ -57,29 +57,21 @@ class Amiga1200Target: ICompilationTarget,
}
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The amiga1200 target only supports the main emulator (Amitools's vamos).")
if (selectedEmulator == 1) {
CopperlineEmulator.launch(programFile, quiet, "A1200", "68020", listOf("--fast", "2M", "--fpu"))
return
}
val cpuStr = when(this.cpu) {
if (selectedEmulator != 2) {
System.err.println("The amiga1200 target supports FS-UAE (-emu) and Amitools's vamos (-emu2).")
return
}
val cpuStr = when (this.cpu) {
CpuType.M68000 -> "68000"
CpuType.M68020 -> "68020"
else -> error("invalid cpu type")
}
val cmd =
listOf(
"vamos",
"--cpu", cpuStr,
programFile.toString(),
)
if(!quiet)
println("Launching Amitools's Vamos...")
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch(_: Exception) {
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
}
VamosEmulator.launch(programFile, quiet, cpuStr)
return
}
override fun isIOAddress(address: UInt): Boolean =
@@ -97,29 +97,21 @@ class Amiga500Target: ICompilationTarget,
}
override fun launchEmulator(selectedEmulator: Int, programFile: Path, quiet: Boolean) {
if(selectedEmulator!=1) {
System.err.println("The amiga500 target only supports the main emulator (Amitools's vamos).")
if (selectedEmulator == 1) {
CopperlineEmulator.launch(programFile, quiet, "A500", "68000", listOf("--slow", "512K"))
return
}
val cpuStr = when(this.cpu) {
if (selectedEmulator != 2) {
System.err.println("The amiga500 target supports FS-UAE (-emu) and Amitools's vamos (-emu2).")
return
}
val cpuStr = when (this.cpu) {
CpuType.M68000 -> "68000"
CpuType.M68020 -> "68020"
else -> error("invalid cpu type")
}
val cmd =
listOf(
"vamos",
"--cpu", cpuStr,
programFile.toString(),
)
if(!quiet)
println("Launching Amitools's Vamos...")
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch(_: Exception) {
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
}
VamosEmulator.launch(programFile, quiet, cpuStr)
return
}
override fun isIOAddress(address: UInt): Boolean =
@@ -0,0 +1,71 @@
package prog8.code.target
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.copyTo
import kotlin.io.path.createDirectories
object CopperlineEmulator {
fun launch(programFile: Path, quiet: Boolean, model: String, cpu: String, memoryArgs: List<String>) {
val home = System.getProperty("user.home")
val sourceHdName = if (model == "A500") "Harddisk500" else "Harddisk1200"
val cmdDir = if (model == "A500") "c" else "C"
val libsDir = if (model == "A500") "libs" else "Libs"
val libs = listOf("diskfont.library", "iffparse.library", "lowlevel.library", "rexxsyslib.library", "asyncio.library", "icon.library")
val tempDir = Paths.get(System.getProperty("java.io.tmpdir"), "prog8-copperline-$model")
tempDir.createDirectories()
val progName = programFile.fileName.toString()
val stagedProg = tempDir.resolve(progName)
programFile.copyTo(stagedProg, overwrite = true)
val srcHd = File("$home/Documents/FS-UAE/Hard Drives/$sourceHdName")
if (!srcHd.isDirectory) System.err.println("Warning: source hard disk folder not found: ${srcHd}; skipping file staging.")
for (lib in libs) {
val src = File(srcHd, "$libsDir/$lib")
if (src.exists()) src.toPath().copyTo(tempDir.resolve(lib), overwrite = true)
}
val commands = if (model == "A500") listOf("Dir","List","Makedir","Assign","SetPatch","Mount","CPU","Echo","Avail") else listOf("Dir","List","MakeDir","Assign","SetPatch","Mount","CPU","Echo","Avail")
for (cmd in commands) {
val src = File(srcHd, "$cmdDir/$cmd")
if (src.exists()) src.toPath().copyTo(tempDir.resolve(cmd), overwrite = true)
}
val ramHandlerSrc = File(srcHd, "l/Ram-Handler")
if (model == "A500" && ramHandlerSrc.exists()) ramHandlerSrc.toPath().copyTo(tempDir.resolve("Ram-Handler"), overwrite = true)
val sysCfgSrc = File(srcHd, "Devs/system-configuration")
if (sysCfgSrc.exists()) sysCfgSrc.toPath().copyTo(tempDir.resolve("system-configuration"), overwrite = true)
val ksDirs = listOf(
File("${System.getProperty("user.home")}/Documents/FS-UAE/Kickstarts"),
File("${System.getProperty("user.home")}/Documents/Kickstarts")
)
val ksDir = ksDirs.firstOrNull { it.isDirectory } ?: ksDirs.first()
val candidates = ksDirs.filter { it.isDirectory }.flatMap { d -> d.listFiles()?.filter { it.name.endsWith(".rom", ignoreCase = true) && it.name.contains(model, ignoreCase = true) } ?: emptyList() }
val preferredVersion = if (model == "A500") "v1.3" else "v3.1"
val rom = candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && it.name.contains("[!", ignoreCase = true) }
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && !it.name.contains("[b", ignoreCase = true) && !it.name.contains("[h", ignoreCase = true) }
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) }
if (rom == null) {
System.err.println("Warning: no Kickstart ROM for $model found in ${ksDir}; using bundled AROS.")
}
val cmd = mutableListOf(
"copperline",
"--factory",
"--model", model,
"--cpu", cpu
)
cmd.addAll(memoryArgs)
if (rom != null) cmd.add(rom.absolutePath)
cmd.addAll(listOf(
"--run", stagedProg.toString()
))
if (!quiet) println("Launching Copperline...")
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch (_: Exception) {
System.err.println("Cannot find 'copperline'. Install it from https://copperline.dev/")
}
}
}
@@ -0,0 +1,86 @@
package prog8.code.target
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.copyTo
import kotlin.io.path.createDirectories
import kotlin.io.path.writeText
object FsUaeEmulator {
fun launch(
programFile: Path,
quiet: Boolean,
model: String,
preferredVersion: String,
romHint: String,
sourceHdName: String,
cmdDir: String,
libsDir: String,
commands: List<String>,
libs: List<String>,
extraFiles: List<Pair<String, String>>,
startupSequence: String
) {
val home = System.getProperty("user.home")
val ksDir = File("$home/Documents/FS-UAE/Kickstarts")
val candidates = ksDir.listFiles()?.filter { it.name.endsWith(".rom", ignoreCase = true) && it.name.contains(model, ignoreCase = true) } ?: emptyList()
val rom = candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && it.name.contains("[!", ignoreCase = true) }
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) && !it.name.contains("[b", ignoreCase = true) && !it.name.contains("[h", ignoreCase = true) }
?: candidates.firstOrNull { it.name.contains(preferredVersion, ignoreCase = true) }
?: candidates.firstOrNull { !it.name.contains("[b", ignoreCase = true) && !it.name.contains("[h", ignoreCase = true) }
?: candidates.firstOrNull()
?: ksDir.listFiles()?.firstOrNull { it.name.contains(romHint, ignoreCase = true) && it.name.endsWith(".rom", ignoreCase = true) }
if (rom == null) {
System.err.println("Cannot find a Kickstart ROM for $model in ${ksDir}")
return
}
val tempDir = Paths.get(System.getProperty("java.io.tmpdir"), "prog8-fsuae-$model")
tempDir.createDirectories()
val hdDir = tempDir.resolve("hd")
hdDir.resolve("S").createDirectories()
hdDir.resolve("C").createDirectories()
hdDir.resolve("L").createDirectories()
hdDir.resolve("Libs").createDirectories()
hdDir.resolve("Devs").createDirectories()
val progName = programFile.fileName.toString()
programFile.copyTo(hdDir.resolve(progName), overwrite = true)
val srcHd = File("$home/Documents/FS-UAE/Hard Drives/$sourceHdName")
for (cmd in commands) {
val src = File(srcHd, "$cmdDir/$cmd")
if (src.exists()) src.toPath().copyTo(hdDir.resolve("C/$cmd"), overwrite = true)
}
for (lib in libs) {
val src = File(srcHd, "$libsDir/$lib")
if (src.exists()) src.toPath().copyTo(hdDir.resolve("Libs/$lib"), overwrite = true)
}
for ((srcRel, dstRel) in extraFiles) {
val src = File(srcHd, srcRel)
if (src.exists()) {
val dst = hdDir.resolve(dstRel)
dst.parent.createDirectories()
src.toPath().copyTo(dst, overwrite = true)
}
}
hdDir.resolve("S/Startup-Sequence").writeText(startupSequence.replace("\$progName", progName))
val memoryArgs = if (model == "A500") listOf("--slow_memory=512") else listOf("--fast_memory=2048")
val cmd = listOf(
"fs-uae",
"--amiga_model=$model",
"--kickstart_file=${rom.absolutePath}",
"--hard_drive_0=${hdDir}",
"--automatic_input_grab=0",
"--window_width=1280",
"--window_height=1024",
"--floppy_drive_volume=8",
"--floppy_drive_volume_empty=8"
) + memoryArgs
if (!quiet) println("Launching FS-UAE...")
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch (_: Exception) {
System.err.println("Cannot find 'fs-uae'. Install it from https://fs-uae.net/")
}
}
}
@@ -0,0 +1,16 @@
package prog8.code.target
import java.nio.file.Path
object VamosEmulator {
fun launch(programFile: Path, quiet: Boolean, cpu: String) {
val cmd = listOf("vamos", "--cpu", cpu, programFile.toString())
if (!quiet) println("Launching Amitools's Vamos...")
val pb = ProcessBuilder(cmd).inheritIO()
try {
pb.start().waitFor()
} catch (_: Exception) {
System.err.println("Cannot find 'vamos' (Amitools's Amiga emulator). Install it from: https://github.com/cnvogelg/amitools")
}
}
}
@@ -737,7 +737,7 @@ class AsmGen6502Internal (
Statusflag.Pc -> out(" lda #0 | rol a | sta $tempReg")
else -> throw AssemblyError("unsupported statusflag ${fromReg.statusflag}")
}
else -> throw AssemblyError("weird CPU register ${fromReg}")
else -> throw AssemblyError("weird CPU register $fromReg")
}
// Store from temp to destination register
@@ -750,7 +750,7 @@ class AsmGen6502Internal (
Statusflag.Pc -> out(" lda $tempReg | asl a")
else -> throw AssemblyError("unsupported statusflag ${toReg.statusflag}")
}
else -> throw AssemblyError("weird CPU register ${toReg}")
else -> throw AssemblyError("weird CPU register $toReg")
}
}
}
@@ -1439,23 +1439,35 @@ $repeatLabel""")
pla
sta ($var2ZpPtrVar),y""")
} else {
if (v1.address is PtNumber) {
out(" lda ${v1.address.asConstInteger()!!.toHex()} | pha")
} else if (v1.address is PtIdentifier) {
var1ZpPtrVar = loadByteFromPointerIntoA(v1.address as PtIdentifier)
out(" pha")
} else {
TODO("swap bytes not supported for this expression. Use a simpler expression, or even just a temporary variable and assignments for now. ${v1.position}")
when (v1.address) {
is PtNumber -> {
out(" lda ${v1.address.asConstInteger()!!.toHex()} | pha")
}
is PtIdentifier -> {
var1ZpPtrVar = loadByteFromPointerIntoA(v1.address as PtIdentifier)
out(" pha")
}
else -> {
TODO("swap bytes not supported for this expression. Use a simpler expression, or even just a temporary variable and assignments for now. ${v1.position}")
}
}
if (v2.address is PtNumber) {
out(" lda ${v2.address.asConstInteger()!!.toHex()}")
} else if (v2.address is PtIdentifier) {
var2ZpPtrVar = loadByteFromPointerIntoA(
v2.address as PtIdentifier,
tempZpPtrVar = "P8ZP_SCRATCH_W1"
)
} else {
TODO("swap bytes not supported for this expression. Use a simpler expression, or even just a temporary variable and assignments for now. ${v2.position}")
when (v2.address) {
is PtNumber -> {
out(" lda ${v2.address.asConstInteger()!!.toHex()}")
}
is PtIdentifier -> {
var2ZpPtrVar = loadByteFromPointerIntoA(
v2.address as PtIdentifier,
tempZpPtrVar = "P8ZP_SCRATCH_W1"
)
}
else -> {
TODO("swap bytes not supported for this expression. Use a simpler expression, or even just a temporary variable and assignments for now. ${v2.position}")
}
}
if (v1.address is PtNumber) {
@@ -1984,8 +1996,7 @@ $repeatLabel""")
?: throw AssemblyError("expected function call for multi-value return ${ret.position}")
// Get the return register specs for the called function
val calledSub = symbolTable.lookup(fcall.name)
val calledReturnRegs = when(calledSub) {
val calledReturnRegs = when(val calledSub = symbolTable.lookup(fcall.name)) {
is StSub -> (calledSub.astNode!! as IPtSubroutine).returnsWhatWhere(options.compTarget)
is StExtSub -> calledSub.returns.map { it.register to it.type }
else -> throw AssemblyError("unexpected subroutine type for multi-value return ${ret.position}")
@@ -2280,9 +2291,7 @@ $repeatLabel""")
// optimize pointer,indexregister if possible
fun evalBytevalueWillClobberA(expr: PtExpression): Boolean {
if(!expr.type.isByte)
return true
return when(expr) {
return !expr.type.isByte || when(expr) {
is PtIdentifier -> false
is PtNumber -> false
is PtBool -> false
@@ -2691,8 +2700,7 @@ $repeatLabel""")
out(" $compare P8ZP_SCRATCH_REG")
}
val e = unwrapCasts(value)
when(e) {
when(val e = unwrapCasts(value)) {
is PtArrayIndexer -> {
val constIndex = e.index.asConstInteger()
if(constIndex!=null) {
@@ -94,7 +94,7 @@ private fun apply(modifications: List<Modification>, lines: MutableList<String>,
lines.removeAt(modification.lineIndex)
else {
val pretrim = pretrimmed.getOrNull(modification.lineIndex)
if (pretrim == null || pretrim.isBlank() || pretrim[0] == ';')
if (pretrim.isNullOrBlank() || pretrim[0] == ';')
lines.removeAt(modification.lineIndex)
else if (haslabelPretrimmed(pretrim)) {
val label = keeplabelPretrimmed(pretrim)
@@ -432,8 +432,7 @@ private fun TrimmedLine.mnemonic(): String =
/** True if the line carries a label, with or without an instruction following it. */
private fun TrimmedLine.hasLabel(): Boolean {
if (haslabelPretrimmed(trimmed)) return true
return trimmed.isNotEmpty() && (trimmed[0] == '+' || trimmed[0] == '-')
return haslabelPretrimmed(trimmed) || trimmed.isNotEmpty() && (trimmed[0] == '+' || trimmed[0] == '-')
}
/** The memory operand this instruction writes, with any +offset and ,index suffix stripped.
@@ -455,7 +454,7 @@ private fun TrimmedLine.mayWriteBase(base: String): Boolean {
return target.equals(base, ignoreCase = true)
}
/** True if [operand] is safe to read more than once: an immediate, or memory that is not an
/** True if operand is safe to read more than once: an immediate, or memory that is not an
* IO location (an IO read can return a different value without the CPU having written it). */
private fun isNonVolatileIndexOperand(indexLine: TrimmedLine, machine: ICompilationTarget, symbolTable: SymbolTable): Boolean {
val operand = indexLine.instruction.extractOperandTrimmed()
@@ -515,15 +514,15 @@ private fun optimizeSamePointerIndexingAndUselessBeq(linesByFourteen: Sequence<L
for (lines in linesByFourteen) {
val f1 = lines[0].instruction
val f2 = lines[1].instruction
val f3 = lines[2].instruction
//val f3 = lines[2].instruction
val f4 = lines[3].instruction
val f5 = lines[4].instruction
val f6 = lines[5].instruction
if(f1.startsWith("ldy ") && f2.startsWith("lda ") && f4.startsWith("ldy ") && f5.startsWith("sta ")) {
val firstvalue = f1.extractOperandTrimmed()
//val firstvalue = f1.extractOperandTrimmed()
val secondvalue = f2.extractOperandTrimmed()
val fourthvalue = f4.extractOperandTrimmed()
//val fourthvalue = f4.extractOperandTrimmed()
val fifthvalue = f5.extractOperandTrimmed()
if(secondvalue==fifthvalue && secondvalue.endsWith(",y") && fifthvalue.endsWith(",y") &&
canDropIndexReload(lines, 3, lines[0], machine, symbolTable)) {
@@ -744,11 +743,9 @@ private fun looksLikeIOAddress(operand: String, machine: ICompilationTarget): Bo
}
// Try to parse decimal address (e.g. "53280" = $d000) and delegate to machine
val decVal = operand.toUIntOrNull()
if(decVal != null)
return machine.isIOAddress(decVal)
// For unresolved symbolic names we can't determine - assume NOT IO
// (the symbol table lookup already failed, so we have no better info)
return false
return decVal != null && machine.isIOAddress(decVal)
}
private fun optimizeIncDec(linesByFour: Sequence<List<TrimmedLine>>): List<Modification> {
@@ -1020,8 +1017,7 @@ private fun scaleConstant(constStr: String, scale: Int): String {
}
private fun isArrayByteSizedAndSmall(name: String, symbolTable: SymbolTable): Boolean {
val symbol = symbolTable.flat[name]
return when (symbol) {
return when (val symbol = symbolTable.flat[name]) {
is StStaticVariable -> {
val len = symbol.length
(symbol.dt.isByteArray || symbol.dt.isString) && len != null && len <= 256u
@@ -1035,8 +1031,7 @@ private fun isArrayByteSizedAndSmall(name: String, symbolTable: SymbolTable): Bo
}
private fun isArrayWordSizedAndSmall(name: String, symbolTable: SymbolTable): Boolean {
val symbol = symbolTable.flat[name]
return when (symbol) {
return when (val symbol = symbolTable.flat[name]) {
is StStaticVariable -> {
val len = symbol.length
symbol.dt.isWordArray && len != null && len <= 256u
@@ -432,8 +432,7 @@ import prog8.codegen.cpu6502.assignment.*
}
private fun funcCallFar(fcall: PtFunctionCall): Array<RegisterOrPair> {
val targetName = asmgen.options.compTarget.name
val jsrfar = when(targetName) {
val jsrfar = when(val targetName = asmgen.options.compTarget.name) {
"cx16" -> "cx16.JSRFAR"
"c64" -> "c64.x16jsrfar"
"c128" -> "c128.x16jsrfar"
@@ -2161,7 +2160,7 @@ import prog8.codegen.cpu6502.assignment.*
" tya",
" sbc P8ZP_SCRATCH_W1+1",
" bvc $keep",
" eor #\$80",
" eor #$80",
keep,
" bpl $done",
" lda ${maxOp.first}",
@@ -2184,7 +2183,7 @@ import prog8.codegen.cpu6502.assignment.*
" tya",
" sbc P8ZP_SCRATCH_W1+1",
" bvc $keep",
" eor #\$80",
" eor #$80",
keep,
" bpl $set",
" lda P8ZP_SCRATCH_W1",
@@ -2575,9 +2574,7 @@ import prog8.codegen.cpu6502.assignment.*
internal fun funcMklong(fcall: PtFunctionCall, resultReg: RegisterOrPair, targetVarName: String? = null): Array<RegisterOrPair> {
fun isArgRegister(expression: PtExpression, reg: RegisterOrPair): Boolean {
if(expression !is PtIdentifier)
return false
return expression.name.startsWith("cx16.${reg.name.lowercase()}")
return expression is PtIdentifier && expression.name.startsWith("cx16.${reg.name.lowercase()}")
}
val (r1, r2) = when(resultReg) {
@@ -313,9 +313,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val left = condition.left
val right = condition.right
val operator = condition.operator
when (operator) {
when (val operator = condition.operator) {
"==" -> {
// if X==value
asmgen.assignExpressionToRegister(left, RegisterOrPair.A, signed)
@@ -595,9 +593,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
val left = condition.left
val right = condition.right
val operator = condition.operator
return when (operator) {
return when (val operator = condition.operator) {
"==" -> longEqualsValue(left, right, false, jumpAfterIf, stmt)
"!=" -> longEqualsValue(left, right, true, jumpAfterIf, stmt)
"<", "<=", ">", ">=" -> compareLongValues(left, operator, right, jumpAfterIf, stmt)
@@ -932,8 +928,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
asmgen.out(" lda $varname+$offset")
return
}
val e = asmgen.unwrapCasts(expr)
when (e) {
when (val e = asmgen.unwrapCasts(expr)) {
is PtArrayIndexer -> {
if (e.variable == null)
throw AssemblyError("support for ptr indexing ${e.position}")
@@ -1830,24 +1825,30 @@ internal class IfElseAsmGen(private val program: PtProgram,
block("#<${constValue}", "#>${constValue}")
} else {
val e = asmgen.unwrapCasts(expr)
if (e is PtIdentifier) {
val varname = asmgen.asmVariableName(e)
block(varname, "$varname+1")
} else if (e is PtAddressOf && !e.isFromArrayElement && e.identifier != null) {
val identifier = e.identifier!!
val varname = if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
if (e.isMsbForSplitArray) identifier.name + "_msb" else identifier.name + "_lsb"
} else {
identifier.name
when (e) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(e)
block(varname, "$varname+1")
}
is PtAddressOf if !e.isFromArrayElement && e.identifier != null -> {
val identifier = e.identifier!!
val varname = if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
if (e.isMsbForSplitArray) identifier.name + "_msb" else identifier.name + "_lsb"
} else {
identifier.name
}
block("#<$varname", "#>$varname")
}
else -> {
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
}
block("#<$varname", "#>$varname")
} else {
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
}
}
}
@@ -104,8 +104,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
}
private fun evalIfExpressionConditonAndBranchWhenFalse(stmtCondition: PtExpression, falseLabel: String) {
val condition = asmgen.unwrapCasts(stmtCondition)
when (condition) {
when (val condition = asmgen.unwrapCasts(stmtCondition)) {
is PtBinaryExpression -> {
val rightDt = condition.right.type
return when {
@@ -802,8 +801,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
translateWordExprIsZero(expr, falseLabel)
return
}
val e = asmgen.unwrapCasts(expr)
when (e) {
when (val e = asmgen.unwrapCasts(expr)) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(e)
asmgen.out("""
@@ -853,8 +851,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
translateWordExprIsNotZero(expr, falseLabel)
return
}
val e = asmgen.unwrapCasts(expr)
when (e) {
when (val e = asmgen.unwrapCasts(expr)) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(e)
asmgen.out("""
@@ -1092,8 +1089,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
asmgen.out(" lda $varname+$offset")
return
}
val e = asmgen.unwrapCasts(expr)
when (e) {
when (val e = asmgen.unwrapCasts(expr)) {
is PtArrayIndexer -> {
if (e.variable == null)
throw AssemblyError("support for ptr indexing ${e.position}")
@@ -1149,23 +1145,29 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
block("#<${constValue}", "#>${constValue}")
} else {
val e = asmgen.unwrapCasts(expr)
if (e is PtIdentifier) {
val varname = asmgen.asmVariableName(e)
block(varname, "$varname+1")
} else if (e is PtAddressOf && !e.isFromArrayElement) {
val identifier = e.identifier!!
var varname = asmgen.asmVariableName(identifier)
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
when (e) {
is PtIdentifier -> {
val varname = asmgen.asmVariableName(e)
block(varname, "$varname+1")
}
is PtAddressOf if !e.isFromArrayElement -> {
val identifier = e.identifier!!
var varname = asmgen.asmVariableName(identifier)
if (identifier.type.isSplitWordArray(asmgen.options.compTarget)) {
varname += if (e.isMsbForSplitArray) "_msb" else "_lsb"
}
block("#<$varname", "#>$varname")
}
else -> {
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
}
block("#<$varname", "#>$varname")
} else {
asmgen.saveRegisterStack(CpuRegister.A, false)
asmgen.saveRegisterStack(CpuRegister.Y, false)
asmgen.assignExpressionToVariable(expr, "P8ZP_SCRATCH_W1", expr.type)
asmgen.restoreRegisterStack(CpuRegister.Y, false)
asmgen.restoreRegisterStack(CpuRegister.A, false)
block("P8ZP_SCRATCH_W1", "P8ZP_SCRATCH_W1+1")
}
}
}
@@ -60,6 +60,7 @@ internal class ProgramAndVarsGen(
asmgen.out("; $cpu assembly code for '${program.name}'")
asmgen.out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
asmgen.out("; prog8 compiler version: ${options.compilerVersion}")
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
asmgen.out("")
@@ -678,7 +679,6 @@ internal class ProgramAndVarsGen(
private class ZpStringWithInitial(
val name: String,
val alloc: MemoryAllocator.VarAllocation,
val value: Pair<String, Encoding>
)
@@ -695,7 +695,7 @@ internal class ProgramAndVarsGen(
val scopedName = variable.key
val svar = symboltable.lookup(scopedName) as? StStaticVariable
if(svar?.initializationStringValue!=null)
result.add(ZpStringWithInitial(scopedName, variable.value, svar.initializationStringValue!!))
result.add(ZpStringWithInitial(scopedName, svar.initializationStringValue!!))
}
return result
}
@@ -1012,14 +1012,14 @@ internal class ProgramAndVarsGen(
consts.sortedBy { it.name }.forEach {
val nameWithoutColons = it.name.replace("::", "_")
if(it.dt.isFloat)
asmgen.out(" ${nameWithoutColons} = ${it.value}")
asmgen.out(" $nameWithoutColons = ${it.value}")
else {
if(it.value!=null)
asmgen.out(" ${nameWithoutColons} = ${it.value!!.toHex()}")
asmgen.out(" $nameWithoutColons = ${it.value!!.toHex()}")
else if(it.memorySlab!=null)
asmgen.out(" ${nameWithoutColons} = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
asmgen.out(" $nameWithoutColons = ${StMemorySlabBlockName}.${it.memorySlab!!.name}")
else
TODO("asm gen for ${nameWithoutColons} = memory alloc?")
TODO("asm gen for $nameWithoutColons = memory alloc?")
}
}
}
@@ -29,7 +29,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
return true
val v = symboltable.lookup(scopedName)
return if(v is StMemVar) v.address <= 255u else false
return v is StMemVar && v.address <= 255u
}
internal fun getFloatAsmConst(number: Double): String {
@@ -123,11 +123,11 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
private fun collectAllVariables(st: SymbolTable): Collection<StStaticVariable> {
val vars = mutableListOf<StStaticVariable>()
fun collect(node: StNode) {
for(child in node.children) {
if(child.value.type == StNodeType.STATICVAR)
vars.add(child.value as StStaticVariable)
for((_, value) in node.children) {
if(value.type == StNodeType.STATICVAR)
vars.add(value as StStaticVariable)
else
collect(child.value)
collect(value)
}
}
collect(st)
@@ -30,10 +30,7 @@ internal class BinaryOpAssignmentsGen(
else -> false
}
return if(translatedOk)
true
else
anyExprGen.assignAnyExpressionUsingStack(expr, assign)
return translatedOk || anyExprGen.assignAnyExpressionUsingStack(expr, assign)
}
@@ -274,7 +271,10 @@ internal class BinaryOpAssignmentsGen(
is PtIdentifier -> true
is PtIrRegister -> true
is PtNumber -> true
is PtFunctionCall -> expr.builtin && expr.name in arrayOf("lsb", "msb")
// msb/lsb only load straight into Y without touching A for a plain identifier argument;
// any other argument is evaluated via A first, which would clobber A (the left operand).
is PtFunctionCall -> expr.builtin && expr.name in arrayOf("lsb", "msb") &&
expr.args.singleOrNull() is PtIdentifier
else -> false
}
}
@@ -1236,8 +1236,7 @@ internal class BinaryOpAssignmentsGen(
return true
}
val left = expr.left
val right = expr.right
when(right) {
when(val right = expr.right) {
is PtNumber -> {
asmgen.assignExpressionTo(left, target)
val hex = right.number.toLongHex()
@@ -1318,78 +1317,87 @@ internal class BinaryOpAssignmentsGen(
}
}
is PtIdentifier -> {
if(target.kind == TargetStorageKind.VARIABLE) {
asmgen.assignExpressionTo(left, target)
val rightsym = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightsym
ldy #>$rightsym
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}""")
if (expr.operator == "+") {
asmgen.out(" jsr prog8_lib.long_add_inplace")
} else {
asmgen.out(" jsr prog8_lib.long_sub_inplace")
}
return true
} else if(target.kind == TargetStorageKind.REGISTER) {
val startreg = target.register!!.startregname()
asmgen.assignExpressionTo(left, target)
val rightsym = asmgen.asmVariableName(right)
asmgen.out(
"""
lda #<$rightsym
ldy #>$rightsym
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<cx16.$startreg
ldy #>cx16.$startreg"""
)
if (expr.operator == "+") {
asmgen.out(" jsr prog8_lib.long_add_inplace")
} else {
asmgen.out(" jsr prog8_lib.long_sub_inplace")
}
return true
} else {
// isn't an expression, so no need to preserve R12-R15
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, left.type.isSigned)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, left.type.isSigned)
if(expr.operator=="+") {
when (target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.assignExpressionTo(left, target)
val rightsym = asmgen.asmVariableName(right)
asmgen.out("""
clc
lda cx16.r12
adc cx16.r14
sta cx16.r12
lda cx16.r12+1
adc cx16.r14+1
sta cx16.r12+1
lda cx16.r12+2
adc cx16.r14+2
sta cx16.r12+2
lda cx16.r12+3
adc cx16.r14+3
sta cx16.r12+3""")
} else {
asmgen.out("""
sec
lda cx16.r12
sbc cx16.r14
sta cx16.r12
lda cx16.r12+1
sbc cx16.r14+1
sta cx16.r12+1
lda cx16.r12+2
sbc cx16.r14+2
sta cx16.r12+2
lda cx16.r12+3
sbc cx16.r14+3
sta cx16.r12+3""")
lda #<$rightsym
ldy #>$rightsym
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}"""
)
if (expr.operator == "+") {
asmgen.out(" jsr prog8_lib.long_add_inplace")
} else {
asmgen.out(" jsr prog8_lib.long_sub_inplace")
}
return true
}
TargetStorageKind.REGISTER -> {
val startreg = target.register!!.startregname()
asmgen.assignExpressionTo(left, target)
val rightsym = asmgen.asmVariableName(right)
asmgen.out(
"""
lda #<$rightsym
ldy #>$rightsym
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<cx16.$startreg
ldy #>cx16.$startreg"""
)
if (expr.operator == "+") {
asmgen.out(" jsr prog8_lib.long_add_inplace")
} else {
asmgen.out(" jsr prog8_lib.long_sub_inplace")
}
return true
}
else -> {
// isn't an expression, so no need to preserve R12-R15
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, left.type.isSigned)
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, left.type.isSigned)
if (expr.operator == "+") {
asmgen.out(
"""
clc
lda cx16.r12
adc cx16.r14
sta cx16.r12
lda cx16.r12+1
adc cx16.r14+1
sta cx16.r12+1
lda cx16.r12+2
adc cx16.r14+2
sta cx16.r12+2
lda cx16.r12+3
adc cx16.r14+3
sta cx16.r12+3"""
)
} else {
asmgen.out(
"""
sec
lda cx16.r12
sbc cx16.r14
sta cx16.r12
lda cx16.r12+1
sbc cx16.r14+1
sta cx16.r12+1
lda cx16.r12+2
sbc cx16.r14+2
sta cx16.r12+2
lda cx16.r12+3
sbc cx16.r14+3
sta cx16.r12+3"""
)
}
asmgen.assignRegister(RegisterOrPair.R12R13, target)
return true
}
asmgen.assignRegister(RegisterOrPair.R12R13, target)
return true
}
}
else -> {
@@ -1609,10 +1617,7 @@ internal class BinaryOpAssignmentsGen(
internal fun optimizedLogicalExpr(expr: PtBinaryExpression, target: AsmAssignTarget): Boolean {
fun swapOperands(): Boolean =
if(expr.right is PtIdentifier || expr.right is PtMemoryByte)
false
else
expr.left is PtIdentifier || expr.left is PtMemoryByte
!(expr.right is PtIdentifier || expr.right is PtMemoryByte) && (expr.left is PtIdentifier || expr.left is PtMemoryByte)
fun assignResultIntoA(left: PtExpression, operator: String, right: PtExpression) {
// non short-circuit evaluation it is *likely* shorter and faster because of the simple operands.
@@ -1638,26 +1643,26 @@ internal class BinaryOpAssignmentsGen(
when(right) {
is PtBool -> {
// Handle boolean literals that weren't optimized away (e.g., with -noopt)
when {
operator == "and" && right.value -> {
when (operator) {
"and" if right.value -> {
// left AND true = left (already in A, do nothing)
}
operator == "and" && !right.value -> {
"and" if !right.value -> {
// left AND false = false
asmgen.out(" lda #0")
}
operator == "or" && right.value -> {
"or" if right.value -> {
// left OR true = true
asmgen.out(" lda #1")
}
operator == "or" && !right.value -> {
"or" if !right.value -> {
// left OR false = left (already in A, do nothing)
}
operator == "xor" && right.value -> {
"xor" if right.value -> {
// left XOR true = NOT left (invert low bit)
asmgen.out(" eor #1")
}
operator == "xor" && !right.value -> {
"xor" if !right.value -> {
// left XOR false = left (already in A, do nothing)
}
}
@@ -160,7 +160,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
sta ($zpPtrVar),y""")
}
internal fun inplaceByteNegate(target: PtrTarget, scope: IPtSubroutine?) {
internal fun inplaceByteNegate(target: PtrTarget) {
val (zpPtrVar, offset) = deref(target.pointer)
if(asmgen.isTargetCpu(CpuType.CPU65C02)) {
asmgen.out("""
@@ -180,7 +180,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
}
internal fun inplaceWordNegate(target: PtrTarget, scope: IPtSubroutine?) {
internal fun inplaceWordNegate(target: PtrTarget) {
val (zpPtrVar, offset) = deref(target.pointer)
asmgen.out("""
ldy #$offset
@@ -194,7 +194,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
sta ($zpPtrVar),y""")
}
internal fun inplaceLongNegate(target: PtrTarget, scope: IPtSubroutine?) {
internal fun inplaceLongNegate(target: PtrTarget) {
val (zpPtrVar, offset) = deref(target.pointer)
asmgen.out("""
ldy #$offset
@@ -216,7 +216,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
sta ($zpPtrVar),y""")
}
internal fun inplaceFloatNegate(target: PtrTarget, scope: IPtSubroutine?) {
internal fun inplaceFloatNegate(target: PtrTarget) {
val (zpPtrVar, offset) = deref(target.pointer)
// flip sign bit in second byte of 5-byte float
asmgen.out(
@@ -230,11 +230,19 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
/**
* Walk the pointer deref chain. Always returns (zpVar, offset) such that one indirect
* access through zpVar at byte offset reaches the final thing:
* derefLast=true -> (zpVar),offset is the address of the value itself
* derefLast=false -> (zpVar),offset is the location of the field content
* This contract holds for every chain length: the final pointer (if any) is already
* followed inside deref(), so callers never need a second dereference.
* zpVar is P8ZP_SCRATCH_PTR for non-empty chains or non-zeropage pointers; it may be
* the original (zeropage) pointer variable only for an empty chain with derefLast=false.
* loadPointerFieldValue: for addOffsetToPointer=true, also load the value if the final
* field is a pointer-typed field (rather than returning its address).
*/
internal fun deref(pointer: PtPointerDeref, addOffsetToPointer: Boolean=false, loadPointerFieldValue: Boolean=false): Pair<String, UByte> {
// walk the pointer deref chain and leaves the final pointer value in a ZP var
// this will often be the temp var P8ZP_SCRATCH_PTR but can also be the original pointer variable if it is already in zeropage and there is nothing to add to it
// returns the ZP var to use as a pointer, and a Y register offset (which can be zero)
// loadPointerFieldValue: for addOffsetToPointer=true, also load the value if the final field is a pointer-typed field (rather than returning its address).
fun addFieldOffsetToScratchPointer(fieldoffset: UInt) {
if(fieldoffset==0u)
@@ -287,6 +295,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
if(pointer.derefLast) {
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
addFieldOffsetToScratchPointer(field.second.toUInt())
updateScratchPointer()
return "P8ZP_SCRATCH_PTR" to 0u
}
else
@@ -297,6 +306,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
asmgen.assignExpressionToVariable(pointer.startpointer, "P8ZP_SCRATCH_PTR", DataType.UWORD)
if(pointer.derefLast) {
addFieldOffsetToScratchPointer(field.second.toUInt())
updateScratchPointer()
return "P8ZP_SCRATCH_PTR" to 0u
}
else
@@ -346,6 +356,8 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
return !allocator.isZpVar(pointer.startpointer.name)
if(pointer.chain.size > 1 || addOffsetToPointer)
return true
// chain==1 derefLast=true now fully follows into P8ZP_SCRATCH_PTR (see deref()),
// so it always uses the scratch pointer regardless of isZpVar.
return !(allocator.isZpVar(pointer.startpointer.name) && !pointer.derefLast)
}
@@ -395,15 +407,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
}
internal fun assignPointerDerefExpression(target: AsmAssignTarget, value: PtPointerDeref) {
val (zpPtrVar, offset1) = deref(value)
val offset: UByte
if(value.derefLast) {
asmgen.loadIndirectWordAY(zpPtrVar, offset1)
asmgen.out(" sta $zpPtrVar | sty $zpPtrVar+1")
offset = 0.toUByte()
} else {
offset = offset1
}
val (zpPtrVar, offset) = deref(value)
if(value.type.isByteOrBool) {
asmgen.loadIndirectByte(zpPtrVar, offset)
asmgen.assignRegister(RegisterOrPair.A, target)
@@ -2293,7 +2293,7 @@ internal class PrimitiveAssignmentsGen(
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceByteNegate(PtrTarget(target), scope)
TargetStorageKind.POINTER -> pointergen.inplaceByteNegate(PtrTarget(target))
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
@@ -2354,7 +2354,7 @@ internal class PrimitiveAssignmentsGen(
}
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceWordNegate(PtrTarget(target), scope)
TargetStorageKind.POINTER -> pointergen.inplaceWordNegate(PtrTarget(target))
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
@@ -2385,7 +2385,7 @@ internal class PrimitiveAssignmentsGen(
ldy #>cx16.$regstart
jsr prog8_lib.long_negate_inplace""")
}
TargetStorageKind.POINTER -> pointergen.inplaceLongNegate(PtrTarget(target), scope)
TargetStorageKind.POINTER -> pointergen.inplaceLongNegate(PtrTarget(target))
TargetStorageKind.VOID -> { /* do nothing */ }
}
}
@@ -2406,7 +2406,7 @@ internal class PrimitiveAssignmentsGen(
sta ${target.asmVarname}+1""")
}
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(makePrefixedExprFromArrayExprAssign("-", assign), scope)
TargetStorageKind.POINTER -> pointergen.inplaceFloatNegate(PtrTarget(target), scope)
TargetStorageKind.POINTER -> pointergen.inplaceFloatNegate(PtrTarget(target))
else -> throw AssemblyError("weird target for in-place float negation")
}
}
@@ -655,10 +655,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
else -> throw AssemblyError("assignment value and target dt mismatch: $valueDt -> $targetDt")
}
}
fun extOpcode(src: IRDataType, dest: IRDataType, signed: Boolean): Opcode = when {
src==IRDataType.BYTE && dest==IRDataType.WORD -> if(signed) Opcode.EXTS else Opcode.EXT
src==IRDataType.WORD && dest==IRDataType.LONG -> if(signed) Opcode.EXTS else Opcode.EXT
src==IRDataType.BYTE && dest==IRDataType.LONG -> if(signed) Opcode.EXTLS else Opcode.EXTL
fun extOpcode(src: IRDataType, dest: IRDataType, signed: Boolean): Opcode = when (src) {
IRDataType.BYTE if dest==IRDataType.WORD -> if(signed) Opcode.EXTS else Opcode.EXT
IRDataType.WORD if dest==IRDataType.LONG -> if(signed) Opcode.EXTS else Opcode.EXT
IRDataType.BYTE if dest==IRDataType.LONG -> if(signed) Opcode.EXTLS else Opcode.EXTL
else -> throw AssemblyError("unsupported extension $src -> $dest")
}
if (assignment.value is PtIrRegister) {
@@ -1537,10 +1537,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
if(valueTr!=null)
addToResult(result, valueTr, -1, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
if(constFloat!=null)
it += IRInstructions.binaryImmediateFloat(divROpcode, loadReg, constFloat)
it += if(constFloat!=null)
IRInstructions.binaryImmediateFloat(divROpcode, loadReg, constFloat)
else
it += IRInstructions.binary(divROpcode, IRDataType.FLOAT, loadReg, valueTr!!.resultFpReg)
IRInstructions.binary(divROpcode, IRDataType.FLOAT, loadReg, valueTr!!.resultFpReg)
it += IRInstructions.storeMemory(Opcode.STOREX, IRDataType.FLOAT, loadReg, IRMemory.indexed(arrayVariableName, indexReg, codeGen.options.compTarget.indexRegType))
}
return result
@@ -3066,14 +3066,14 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
result += idxCode
if(zero) {
result += IRCodeChunk(null, null).also {
if(info.fieldOffset==0) it += IRInstructions.storeZero(Opcode.STOREZX, targetDt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize))
else it += IRInstructions.storeZero(Opcode.STOREZX, targetDt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
it += if(info.fieldOffset==0) IRInstructions.storeZero(Opcode.STOREZX, targetDt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize))
else IRInstructions.storeZero(Opcode.STOREZX, targetDt, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
}
} else {
if(valueReg<0) return false
result += IRCodeChunk(null, null).also {
if(info.fieldOffset==0) it += IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize))
else it += IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
it += if(info.fieldOffset==0) IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize))
else IRInstructions.storeMemory(Opcode.STOREX, targetDt, valueReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
}
}
return true
@@ -120,21 +120,21 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.WORD)
addInstr(result, IRInstructions.pop(IRDataType.WORD, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
return ExpressionCodeResult(result, IRDataType.WORD, resultRegister, -1)
}
private fun funcPopL(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.LONG)
addInstr(result, IRInstructions.pop(IRDataType.LONG, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultRegister, -1)
return ExpressionCodeResult(result, IRDataType.LONG, resultRegister, -1)
}
private fun funcPopF(): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultRegister = codeGen.registers.next(IRDataType.FLOAT)
addInstr(result, IRInstructions.pop(IRDataType.FLOAT, resultRegister), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, resultRegister)
return ExpressionCodeResult(result, IRDataType.FLOAT, -1, resultRegister)
}
private fun funcCopyFromPointer1ToPointer2(call: PtFunctionCall, type: IRDataType): ExpressionCodeResult {
@@ -261,31 +261,39 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
// Route to the return value locations expected by the caller (via returnsWhatWhere).
if(codeGen.options.compTarget.cpu.usesM68kConvention) {
// m68k: first return value in D0 (slot 10), second in D1 (slot 11)
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.hardwareStore(IRDataType.BYTE, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(10))
when (type) {
IRDataType.BYTE -> {
result += IRCodeChunk(null, null).also {
it += IRInstructions.hardwareStore(IRDataType.BYTE, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(10))
}
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.hardwareStore(IRDataType.WORD, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(10))
IRDataType.WORD -> {
result += IRCodeChunk(null, null).also {
it += IRInstructions.hardwareStore(IRDataType.WORD, remainderReg, CallingConventionSlot(11))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(10))
}
}
} else throw AssemblyError("invalid type for DIVMOD")
else -> throw AssemblyError("invalid type for DIVMOD")
}
} else {
// 6502/cx16: quotient to A (slot 0) or AY (slot 4), remainder to cx16.r15
// Note: STOREM must come before STOREHR, otherwise STOREM clobbers A before LOADHR reads it.
if(type==IRDataType.BYTE) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(0))
when (type) {
IRDataType.BYTE -> {
result += IRCodeChunk(null, null).also {
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.BYTE, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.BYTE, divisionReg, CallingConventionSlot(0))
}
}
} else if(type==IRDataType.WORD) {
result += IRCodeChunk(null, null).also {
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(4))
IRDataType.WORD -> {
result += IRCodeChunk(null, null).also {
it += IRInstructions.storeMemory(Opcode.STOREM, IRDataType.WORD, remainderReg, IRMemory.direct("cx16.r15"))
it += IRInstructions.hardwareStore(IRDataType.WORD, divisionReg, CallingConventionSlot(4))
}
}
} else throw AssemblyError("invalid type for DIVMOD")
else -> throw AssemblyError("invalid type for DIVMOD")
}
}
return ExpressionCodeResult(result, type, -1, -1)
}
@@ -996,10 +1004,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val valueTr = exprGen.translateExpression(call.args[1])
addToResult(result, valueTr, valueTr.resultReg, valueTr.resultFpReg)
result += IRCodeChunk(null, null).also {
if(dt==IRDataType.FLOAT) {
it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultFpReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
it += if(dt==IRDataType.FLOAT) {
IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultFpReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
} else {
it += IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
IRInstructions.storeMemory(Opcode.STOREX, dt, valueTr.resultReg, IRMemory.indexed(info.arrayName, indexReg, codeGen.options.compTarget.indexRegType, scale=info.structSize, displacement=info.fieldOffset))
}
}
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
@@ -22,6 +22,21 @@ internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType
}
}
/**
* True if the explicit CMPI #0 that would test a translated result value against zero can be
* skipped, because the last emitted instruction of that translation already sets the status
* bits. This is only allowed on targets that honor the "multi-byte ops set status bits based
* on the full value" contract (see CpuType.statusBitsOnMultiByteOps): on 8-bit targets,
* multi-byte ops may only set Z from the last byte, so the explicit CMPI is required there.
*
* Note: when the CMPI is skipped, a following branch tests Z meaning "the compared value is
* zero". That is only meaningful when the last instruction was itself a CMP/CMPI/SGN/BITTST
* (what OpcodesThatSetStatusbits guarantees). A future target that honors the contract while
* setting status bits with other instructions must re-check this assumption.
*/
internal fun CpuType.canSkipExplicitCmpi(lastInstr: IRInstruction?): Boolean =
statusBitsOnMultiByteOps && lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun translateExpression(expr: PtExpression): ExpressionCodeResult {
@@ -62,19 +77,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val vmDt = codeGen.irType(expr.type)
if(vmDt==IRDataType.FLOAT) {
val resultFpRegister = codeGen.registers.next(IRDataType.FLOAT)
if (isConstant) {
code += IRInstructions.loadAddress(IRDataType.FLOAT, resultFpRegister, expr.name)
code += if (isConstant) {
IRInstructions.loadAddress(IRDataType.FLOAT, resultFpRegister, expr.name)
} else {
code += IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(expr.name))
IRInstructions.loadMemory(Opcode.LOADM, IRDataType.FLOAT, resultFpRegister, IRMemory.direct(expr.name))
}
ExpressionCodeResult(code, vmDt, -1, resultFpRegister)
}
else {
val resultRegister = codeGen.registers.next(vmDt)
if (isConstant) {
code += IRInstructions.loadAddress(vmDt, resultRegister, expr.name)
code += if (isConstant) {
IRInstructions.loadAddress(vmDt, resultRegister, expr.name)
} else {
code += IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultRegister, IRMemory.direct(expr.name))
IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultRegister, IRMemory.direct(expr.name))
}
ExpressionCodeResult(code, vmDt, resultRegister, -1)
}
@@ -269,14 +284,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
// Use status flags from condition expression when possible, avoiding redundant CMPI #0
val condTr = translateExpression(ifExpr.condition)
addToResult(result, condTr, condTr.resultReg, -1)
// Only skip the CMPI if the target honors the "multi-byte ops set Z based on full
// value" contract. On 8-bit targets (6502/65C02) this is false, so we always emit
// an explicit CMPI - a multi-byte op may have set Z based on only the last byte.
// See CpuType.statusBitsOnMultiByteOps for the rationale.
val lastInstr = condTr.chunks.lastOrNull()?.instructions?.lastOrNull()
val skipCmpi = codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
&& lastInstr != null
&& lastInstr.opcode in OpcodesThatSetZeroFlagOnM68k
val skipCmpi = codeGen.options.compTarget.cpu.canSkipExplicitCmpi(lastInstr)
if (!skipCmpi) {
addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, condTr.resultReg, 0), null)
}
@@ -395,7 +404,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
if(ptr.value != null)
IRInstructions.load(vmDt, resultRegister, ptr.value!!.toInt())
else
TODO("LOAD memory() address? ${ptr}")
TODO("LOAD memory() address? $ptr")
}
else
IRInstructions.loadMemory(Opcode.LOADM, vmDt, resultRegister, IRMemory.direct(identifier.name))
@@ -528,11 +537,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.next(IRDataType.BYTE)
val lengthReg = codeGen.registers.next(IRDataType.WORD)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null)
addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
result += codeGen.makeSyscall(IMSyscall.BYTEARRAY_CONTAINS, listOf(IRDataType.BYTE to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -541,12 +550,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.next(IRDataType.BYTE)
val lengthReg = codeGen.registers.next(IRDataType.WORD)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null)
addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
val syscall = if(haystackVar.type.isSplitWordArray(codeGen.options.compTarget)) IMSyscall.SPLIT_WORDARRAY_CONTAINS else IMSyscall.WORDARRAY_CONTAINS
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -555,11 +564,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, elementTr, elementTr.resultReg, -1)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.next(IRDataType.BYTE)
val lengthReg = codeGen.registers.next(IRDataType.WORD)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.LONGARRAY_CONTAINS, listOf(IRDataType.LONG to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.LONGARRAY_CONTAINS, listOf(IRDataType.LONG to elementTr.resultReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -568,11 +577,11 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
addToResult(result, elementTr, -1, elementTr.resultFpReg)
val iterableTr = translateExpression(haystackVar)
addToResult(result, iterableTr, iterableTr.resultReg, -1)
val lengthReg = codeGen.registers.next(IRDataType.BYTE)
val lengthReg = codeGen.registers.next(IRDataType.WORD)
val resultReg = codeGen.registers.next(IRDataType.BYTE)
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
addInstr(result, IRInstructions.load(IRDataType.BYTE, lengthReg, iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, addressDt to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstructions.load(IRDataType.WORD, lengthReg, iterableLength!!), null)
result += codeGen.makeSyscall(IMSyscall.FLOATARRAY_CONTAINS, listOf(IRDataType.FLOAT to elementTr.resultFpReg, addressDt to iterableTr.resultReg, IRDataType.WORD to lengthReg), IRDataType.BYTE to resultReg)
addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, resultReg, 0), null)
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
}
@@ -1390,14 +1399,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
// Only skip the CMPI if the target honors the "multi-byte ops set Z
// based on full value" contract. On 8-bit targets a previous op like
// DEC only set Z from the last byte, so the explicit CMPI is required.
// See CpuType.statusBitsOnMultiByteOps for the rationale.
val skipCmpi = rightConst.toInt() == 0
&& codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
&& lastInstr != null
&& lastInstr.opcode in OpcodesThatSetZeroFlagOnM68k
&& codeGen.options.compTarget.cpu.canSkipExplicitCmpi(lastInstr)
if (!skipCmpi) {
addInstr(result, IRInstructions.compareImmediate(leftTr.dt, leftTr.resultReg, rightConst.toInt()), null)
}
@@ -1561,6 +1564,25 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
/**
* Emit the branch that decides if the short-circuit evaluation of a boolean `or`/`and` has to
* skip its right hand side: [branchWhenZeroTrue] selects if the branch is taken when the left
* hand side value in the register is zero (false) instead of when it is non-zero (true).
* The status bits have to be set from that register first, because only CMP/CMPI/SGN/BITTST do
* that on targets that don't honor CpuType.statusBitsOnMultiByteOps, and the value can also come
* straight from a load or a register move. [canSkipExplicitCmpi] decides if setting them can be
* skipped on this target.
*/
private fun addShortCircuitBranch(result: MutableList<IRCodeChunkBase>, leftTr: ExpressionCodeResult, shortcutLabel: String, branchWhenZeroTrue: Boolean) {
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
val skipCmpi = codeGen.options.compTarget.cpu.canSkipExplicitCmpi(lastInstr)
if (!skipCmpi) {
addInstr(result, IRInstructions.compareImmediate(IRDataType.BYTE, leftTr.resultReg, 0), null)
}
val branchOpcode = if (branchWhenZeroTrue) Opcode.BSTEQ else Opcode.BSTNE
addInstr(result, IRInstructions.branch(branchOpcode, codeLabel(shortcutLabel)), null)
}
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: IRDataType, bitwise: Boolean): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
if(!bitwise && !binExpr.right.isSimple()) {
@@ -1568,7 +1590,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val shortcutLabel = codeGen.createLabelName()
addInstr(result, IRInstructions.branch(Opcode.BSTEQ, codeLabel(shortcutLabel)), null)
addShortCircuitBranch(result, leftTr, shortcutLabel, branchWhenZeroTrue = true)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, leftTr.resultReg, -1)
result += IRCodeChunk(shortcutLabel, null)
@@ -1602,7 +1624,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val leftTr = translateExpression(binExpr.left)
addToResult(result, leftTr, leftTr.resultReg, -1)
val shortcutLabel = codeGen.createLabelName()
addInstr(result, IRInstructions.branch(Opcode.BSTNE, codeLabel(shortcutLabel)), null)
addShortCircuitBranch(result, leftTr, shortcutLabel, branchWhenZeroTrue = false)
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, leftTr.resultReg, -1)
result += IRCodeChunk(shortcutLabel, null)
@@ -958,9 +958,9 @@ class IRCodeGen(
addToResult(result, toTr, toTr.resultReg, -1)
val stepTr = expressionEval.translateExpression(iterable.step)
addToResult(result, stepTr, stepTr.resultReg, -1)
val stepReg = when {
stepTr.dt == loopvarDtIr -> stepTr.resultReg
stepTr.dt == IRDataType.WORD && loopvarDtIr == IRDataType.LONG && !iterable.step.type.isSigned -> {
val stepReg = when (stepTr.dt) {
loopvarDtIr -> stepTr.resultReg
IRDataType.WORD if loopvarDtIr == IRDataType.LONG && !iterable.step.type.isSigned -> {
val widenedReg = registers.next(IRDataType.LONG)
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.WORD, widenedReg, stepTr.resultReg), null)
widenedReg
@@ -1194,11 +1194,10 @@ class IRCodeGen(
code += IRInstructions.memoryOp(Opcode.SUBM, dt, mem(), valueReg)
}
} else {
if(value>0) {
code += IRInstructions.memoryOpImmediate(Opcode.ADDIM, dt, mem(), value)
} else {
code += IRInstructions.memoryOpImmediate(Opcode.SUBIM, dt, mem(), -value)
}
code += if(value>0)
IRInstructions.memoryOpImmediate(Opcode.ADDIM, dt, mem(), value)
else
IRInstructions.memoryOpImmediate(Opcode.SUBIM, dt, mem(), -value)
}
}
}
@@ -1237,15 +1236,14 @@ class IRCodeGen(
if(factor==1)
return code
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2==1) {
code += if(pow2==1) {
// just shift 1 bit
code += IRInstructions.unary(Opcode.LSL, irdt, reg)
}
else if(pow2>=1) {
IRInstructions.unary(Opcode.LSL, irdt, reg)
} else if(pow2>=1) {
// just shift multiple bits
code += IRInstructions.binaryImmediate(Opcode.LSLI, irdt, reg, pow2)
IRInstructions.binaryImmediate(Opcode.LSLI, irdt, reg, pow2)
} else {
code += if (factor == 0) {
if (factor == 0) {
IRInstructions.load(irdt, reg, 0)
} else {
val opcode = if(dt.isSigned) Opcode.MULS else Opcode.MUL
@@ -1318,13 +1316,13 @@ class IRCodeGen(
val pow2 = powersOfTwoInt.indexOf(factor)
if(pow2>=0 && !signed) {
// unsigned division by a power of two: logical shift right (correct)
if(pow2==1) {
code += IRInstructions.unary(Opcode.LSR, dt, reg)
code += if(pow2==1) {
IRInstructions.unary(Opcode.LSR, dt, reg)
} else if(dt == IRDataType.LONG && pow2 == 16) {
// x / 65536 for unsigned long == x >> 16 == MSIGW(x)
code += IRInstructions.binary(Opcode.MSIGW, dt, reg, reg)
IRInstructions.binary(Opcode.MSIGW, dt, reg, reg)
} else {
code += IRInstructions.binaryImmediate(Opcode.LSRI, dt, reg, pow2)
IRInstructions.binaryImmediate(Opcode.LSRI, dt, reg, pow2)
}
return code
}
@@ -1517,17 +1515,8 @@ class IRCodeGen(
// Fallback: materialize expression and branch on result
val tr = expressionEval.translateExpression(condition)
result += tr.chunks
// Only skip the CMPI #0 if BOTH:
// 1) The previous instruction is documented to set status bits (Z/N), AND
// 2) The target CPU honors the "multi-byte ops set Z based on full value" contract
// (see CpuType.statusBitsOnMultiByteOps). For 8-bit targets this contract
// is false: e.g. a 16-bit DEC only sets Z from the low byte's dec, not the
// full 16-bit value, so we MUST emit an explicit CMPI here.
val lastInstr = tr.chunks.lastOrNull()?.instructions?.lastOrNull()
val targetHonorsContract = options.compTarget.cpu.statusBitsOnMultiByteOps
val skipCmpi = targetHonorsContract
&& lastInstr != null
&& lastInstr.opcode in OpcodesThatSetZeroFlagOnM68k
val skipCmpi = options.compTarget.cpu.canSkipExplicitCmpi(lastInstr)
if (!skipCmpi) {
addInstr(result, IRInstructions.compareImmediate(tr.dt, tr.resultReg, 0), null)
}
@@ -1570,15 +1559,8 @@ class IRCodeGen(
if (number != null) {
val isComparingWithZero = number == 0
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
// Skip the CMPI only if the target honors the "multi-byte ops set Z correctly"
// contract. On 8-bit targets a 16/32-bit op like DEC only sets Z from the last
// byte, so we cannot rely on the previous instruction's status bits - the
// CMPI is required to test the full multi-byte value.
// See CpuType.statusBitsOnMultiByteOps for details.
val canSkipCmpi = isComparingWithZero
&& options.compTarget.cpu.statusBitsOnMultiByteOps
&& lastInstr != null
&& lastInstr.opcode in OpcodesThatSetZeroFlagOnM68k
&& options.compTarget.cpu.canSkipExplicitCmpi(lastInstr)
if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) {
var (opcode, useCmpi) = getIntegerComparisonBranch(condition.operator, false, signed)
@@ -1829,8 +1811,7 @@ class IRCodeGen(
result += callResult.chunks
// Get the return register specs for the called function
val calledSub = symbolTable.lookup(fcall.name)
val calledReturnRegs = when(calledSub) {
val calledReturnRegs = when(val calledSub = symbolTable.lookup(fcall.name)) {
is StSub -> (calledSub.astNode!! as IPtSubroutine).returnsWhatWhere(options.compTarget)
is StExtSub -> calledSub.returns.map { it.register to it.type }
else -> throw AssemblyError("unexpected subroutine type for multi-value return ${ret.position}")
@@ -1848,12 +1829,12 @@ class IRCodeGen(
when(fromReg.registerOrPair) {
RegisterOrPair.FAC1 -> addInstr(result, IRInstructions.unary(Opcode.LOADHFACZERO, IRDataType.FLOAT, tempFpReg), null)
RegisterOrPair.FAC2 -> addInstr(result, IRInstructions.unary(Opcode.LOADHFACONE, IRDataType.FLOAT, tempFpReg), null)
else -> throw AssemblyError("unexpected FP return register ${fromReg}")
else -> throw AssemblyError("unexpected FP return register $fromReg")
}
when(toReg.registerOrPair) {
RegisterOrPair.FAC1 -> addInstr(result, storeHwFac(Opcode.STOREHFACZERO, tempFpReg), null)
RegisterOrPair.FAC2 -> addInstr(result, storeHwFac(Opcode.STOREHFACONE, tempFpReg), null)
else -> throw AssemblyError("unexpected FP return register ${toReg}")
else -> throw AssemblyError("unexpected FP return register $toReg")
}
} else {
// For non-float returns, load from source to temp IR register, then store to destination
@@ -2177,7 +2158,7 @@ class IRCodeGen(
}
else -> throw AssemblyError("unsupported statusflag ${registerOrFlag.statusflag}")
}
else -> throw AssemblyError("weird CPU register ${registerOrFlag}")
else -> throw AssemblyError("weird CPU register $registerOrFlag")
}
return chunk
}
@@ -2258,15 +2239,19 @@ class IRCodeGen(
val indexDt = byteIndexTr.dt
if(indexDt != indexRegType) {
val newReg = registers.next(indexRegType)
when {
indexRegType == IRDataType.WORD && indexDt == IRDataType.BYTE ->
when (indexRegType) {
IRDataType.WORD if indexDt == IRDataType.BYTE ->
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, newReg, byteIndexTr.resultReg), null)
indexRegType == IRDataType.WORD && indexDt == IRDataType.LONG ->
IRDataType.WORD if indexDt == IRDataType.LONG ->
addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, newReg, byteIndexTr.resultReg), null)
indexRegType == IRDataType.BYTE && indexDt == IRDataType.WORD ->
IRDataType.BYTE if indexDt == IRDataType.WORD ->
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newReg, byteIndexTr.resultReg), null)
indexRegType == IRDataType.BYTE && indexDt == IRDataType.LONG ->
IRDataType.BYTE if indexDt == IRDataType.LONG ->
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, newReg, byteIndexTr.resultReg), null)
else -> throw IllegalArgumentException("unexpected index conversion $indexDt -> $indexRegType")
}
indexReg = newReg
@@ -2284,24 +2269,30 @@ class IRCodeGen(
if(effectiveDt == indexRegType)
return indexTr.resultReg
val newReg = registers.next(indexRegType)
when {
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.BYTE ->
when (indexRegType) {
IRDataType.WORD if indexTr.dt==IRDataType.BYTE ->
addInstr(result, IRInstructions.binary(Opcode.EXT, IRDataType.BYTE, newReg, indexTr.resultReg), null)
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.LONG ->
IRDataType.WORD if indexTr.dt==IRDataType.LONG ->
addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, newReg, indexTr.resultReg), null)
indexRegType==IRDataType.WORD && indexTr.dt==IRDataType.POINTER ->
IRDataType.WORD if indexTr.dt==IRDataType.POINTER ->
addInstr(result, IRInstructions.binary(Opcode.LSIGW, IRDataType.LONG, newReg, indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.WORD ->
IRDataType.BYTE if indexTr.dt==IRDataType.WORD ->
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newReg, indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.LONG ->
IRDataType.BYTE if indexTr.dt==IRDataType.LONG ->
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, newReg, indexTr.resultReg), null)
indexRegType==IRDataType.BYTE && indexTr.dt==IRDataType.POINTER -> {
IRDataType.BYTE if indexTr.dt==IRDataType.POINTER -> {
val ptrType = options.compTarget.pointerIRType
if(ptrType==IRDataType.WORD)
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.WORD, newReg, indexTr.resultReg), null)
else
addInstr(result, IRInstructions.binary(Opcode.LSIGB, IRDataType.LONG, newReg, indexTr.resultReg), null)
}
else -> throw IllegalArgumentException("unexpected index conversion ${indexTr.dt} -> $indexRegType")
}
return newReg
@@ -481,8 +481,7 @@ jump p8_label_gen_2
val delta = arithmeticDelta(ins)
val nextDelta = arithmeticDelta(nextInstr)
if(delta != null && nextDelta != null) {
val newDelta = delta + nextDelta
when(newDelta) {
when(val newDelta = delta + nextDelta) {
0 -> {
chunk.instructions[idx] = IRInstructions.simple(Opcode.NOP)
chunk.instructions[idx+1] = IRInstructions.simple(Opcode.NOP)
@@ -624,8 +623,7 @@ jump p8_label_gen_2
}
}
Opcode.OR -> {
val imm = ins.immediate?.integerValue
if (imm == null) return@forEach
val imm = ins.immediate?.integerValue ?: return@forEach
if (imm == 0) {
chunk.instructions.removeAt(idx)
changed = true
@@ -643,8 +641,7 @@ jump p8_label_gen_2
}
}
Opcode.XOR -> {
val imm = ins.immediate?.integerValue
if (imm == null) return@forEach
val imm = ins.immediate?.integerValue ?: return@forEach
if (imm == 0) {
chunk.instructions.removeAt(idx)
changed = true
@@ -866,7 +863,7 @@ jump p8_label_gen_2
var foundIdx: Int? = null
var foundIns: IRInstruction? = null
for((midIdx, midIns) in indexedInstructions) {
if(midIdx <= loadmIdx || midIdx >= incIdx) continue
if(midIdx !in (loadmIdx + 1)..<incIdx) continue
if(midIdx in toRemove || midIdx in toReplace) continue
if(midIns.type == IRDataType.FLOAT) continue
val indirect = midIns.memory as? MemoryReference.Indirect ?: continue
@@ -1220,8 +1217,7 @@ jump p8_label_gen_2
}
fun writesMem(ins: IRInstruction, key: MemKey): Boolean {
if(memKey(ins) != key) return false
return ins.memoryEffect == MemoryEffect.WRITE || ins.memoryEffect == MemoryEffect.READ_WRITE
return memKey(ins) == key && (ins.memoryEffect == MemoryEffect.WRITE || ins.memoryEffect == MemoryEffect.READ_WRITE)
}
val groups = mutableListOf<AddrGroup>()
@@ -1372,7 +1368,6 @@ jump p8_label_gen_2
if (stInstr.opcode != Opcode.STOREM || stInstr.srcA == null) { idx++; continue }
val loopvarSymbol = stInstr.memory?.symbolName
if (loopvarSymbol == null) { idx++; continue }
val loopvar = loopvarSymbol
val loopReg = stInstr.requireSrcA().register
val loopType = stInstr.type ?: IRDataType.WORD
// next chunk should be the loop body start with a label
@@ -1388,7 +1383,7 @@ jump p8_label_gen_2
for (k in idx + 1 until sub.chunks.size) {
val c = sub.chunks[k]
if (c !is IRCodeChunk) continue
val hasIncDec = c.instructions.any { it.opcode in setOf(Opcode.INCM, Opcode.DECM) && it.memory?.symbolName == loopvar }
val hasIncDec = c.instructions.any { it.opcode in setOf(Opcode.INCM, Opcode.DECM) && it.memory?.symbolName == loopvarSymbol }
if (!hasIncDec) continue
// old shape: INCM then (same chunk or next chunk) JUMP loopLabel
@@ -1408,7 +1403,7 @@ jump p8_label_gen_2
val i1 = nxt.instructions[0]
val i2 = nxt.instructions[1]
val i3 = nxt.instructions[2]
if (i1.opcode == Opcode.LOADM && i1.memory?.symbolName == loopvar && i1.dest != null &&
if (i1.opcode == Opcode.LOADM && i1.memory?.symbolName == loopvarSymbol && i1.dest != null &&
i2.opcode == Opcode.CMP && (i2.srcA?.register == i1.dest?.register || i2.srcB?.register == i1.dest?.register) &&
i3.opcode == Opcode.BSTNE && i3.labelTarget == loopLabel) {
tail = Tail(k, k + 1, false)
@@ -1429,7 +1424,7 @@ jump p8_label_gen_2
if (ch !is IRCodeChunk) continue
for (ins in ch.instructions) {
// any store/inc/dec that writes to loopvar memory
if (ins.memory?.symbolName == loopvar && ins.opcode in setOf(Opcode.STOREM, Opcode.STOREIM, Opcode.STOREZM, Opcode.STOREX, Opcode.STOREZX, Opcode.INCM, Opcode.DECM, Opcode.ADDIM, Opcode.SUBIM)) {
if (ins.memory?.symbolName == loopvarSymbol && ins.opcode in setOf(Opcode.STOREM, Opcode.STOREIM, Opcode.STOREZM, Opcode.STOREX, Opcode.STOREZX, Opcode.INCM, Opcode.DECM, Opcode.ADDIM, Opcode.SUBIM)) {
// exclude the initial STOREM itself (not in body) and the inc chunk (not in body)
bodyWrites = true; break
}
@@ -1449,7 +1444,7 @@ jump p8_label_gen_2
for (k in tailEnd + 1 until sub.chunks.size) {
val ch = sub.chunks[k] as? IRCodeChunk ?: continue
for (ins in ch.instructions) {
if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvar) { liveOut = true; break }
if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvarSymbol) { liveOut = true; break }
}
if (liveOut) break
}
@@ -1466,7 +1461,7 @@ jump p8_label_gen_2
val ch = sub.chunks[b] as? IRCodeChunk ?: continue
for (i in ch.instructions.indices) {
val ins = ch.instructions[i]
if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvar) {
if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvarSymbol) {
val dest = ins.requireDest().register
if (dest == loopReg) {
// Already holds the correct value; remove the reload
@@ -1482,7 +1477,7 @@ jump p8_label_gen_2
val incChunk = sub.chunks[incIdx] as IRCodeChunk
for (i in incChunk.instructions.indices) {
val ins = incChunk.instructions[i]
if (ins.memory?.symbolName == loopvar && ins.opcode in setOf(Opcode.INCM, Opcode.DECM)) {
if (ins.memory?.symbolName == loopvarSymbol && ins.opcode in setOf(Opcode.INCM, Opcode.DECM)) {
val newOpcode = if (ins.opcode == Opcode.INCM) Opcode.INC else Opcode.DEC
incChunk.instructions[i] = IRInstructions.unary(newOpcode, loopType, loopReg.num)
break
@@ -1493,7 +1488,7 @@ jump p8_label_gen_2
val cmpChunk = sub.chunks[newCmpIdx] as IRCodeChunk
for (i in cmpChunk.instructions.indices) {
val ins = cmpChunk.instructions[i]
if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvar) {
if (ins.opcode == Opcode.LOADM && ins.memory?.symbolName == loopvarSymbol) {
val dest = ins.requireDest().register
if (dest == loopReg) {
cmpChunk.instructions[i] = IRInstructions.simple(Opcode.NOP)
@@ -428,9 +428,8 @@ object RegisterPacker {
}
private fun typesCompatible(t1: IRDataType, t2: IRDataType): Boolean {
if (t1 == t2) return true
return (t1 == IRDataType.POINTER && t2 in setOf(IRDataType.WORD, IRDataType.LONG)) ||
(t2 == IRDataType.POINTER && t1 in setOf(IRDataType.WORD, IRDataType.LONG))
return t1 == t2 || (t1 == IRDataType.POINTER && t2 in setOf(IRDataType.WORD, IRDataType.LONG)) ||
(t2 == IRDataType.POINTER && t1 in setOf(IRDataType.WORD, IRDataType.LONG))
}
private fun conflictsWithRegister(
@@ -2,6 +2,7 @@ package prog8.codegen.intermediate
import prog8.code.*
import prog8.code.ast.PtStructField
import prog8.code.core.Position
import prog8.intermediate.*
/**
@@ -78,7 +79,7 @@ private class StToIrConverter(val romable: Boolean) {
val hasInit = initValue != null
val inBss = !hasInit || romable
val readonly = romable && hasInit
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly, variable.shared)
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly, variable.shared, variable.astNode?.position ?: Position.DUMMY)
}
fun convert(variable: StMemVar): IRStMemVar {
@@ -9,7 +9,8 @@ import prog8.intermediate.*
* Classifies subroutines as "frameable" and, for those:
* - moves their plain-data locals into A5-relative frame slots (negative offsets),
* - moves their parameters into the caller-pushed incoming argument area (positive offsets,
* 4 bytes per parameter, first parameter at the highest offset),
* word-packed: 2-byte slots for byte/word parameters, 4-byte slots for wide parameters,
* first parameter at the highest offset),
* - rewrites every memory reference to AddressBase.FrameSlot and every call site's argument
* locations to CallLocation.FrameSlot,
* - removes the moved variables from the static (BSS) symbol table,
@@ -40,9 +41,6 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
companion object {
const val MAX_FRAME_SIZE = 16384 // §3: per-frame limit, far below the 16-bit displacement range
/** bytes reserved per incoming parameter (padded longword slot, §6.1) */
const val PARAM_SLOT_SIZE = 4
/** virtual register numbers from here on are reserved for the runtime and cannot be relocated */
const val RESERVED_VREG_RANGE_START = 99000
@@ -342,8 +340,8 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
val (node, children) = work.last()
if (children.hasNext()) {
val next = children.next()
when {
next !in index -> {
when (next) {
!in index -> {
index[next] = counter
lowLink[next] = counter
counter++
@@ -351,7 +349,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
onStack += next
work.addLast(next to edges.getValue(next).iterator())
}
next in onStack -> {
in onStack -> {
lowLink[node] = minOf(lowLink.getValue(node), index.getValue(next))
}
}
@@ -382,13 +380,15 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
//. parameter variable name -> frame offset of its incoming slot
val paramSlots: Map<String, Int>,
// parameter variable names that keep a static cell and get an entry-time copy-in (§6.1)
val copyInParams: Set<String>
val copyInParams: Set<String>,
// total byte size of the incoming argument area (even; 2/4 bytes per parameter)
val incomingSize: Int
)
/** outcome of the frameability decision for one subroutine */
private sealed interface Frameability {
class Frameable(val layout: FrameLayoutResult) : Frameability
class NotFrameable() : Frameability
data object NotFrameable : Frameability
}
private fun frameableSubroutine(
@@ -403,7 +403,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
reachesFramePointerClobber: Set<String>,
framePointerClobberers: Set<String>
): Frameability {
val static = { Frameability.NotFrameable() }
val static = { Frameability.NotFrameable }
// a %option noframe subroutine requires the legacy static convention (it manipulates
// the machine stack itself), so it never gets a frame
if (sub.noframe) return static()
@@ -427,6 +427,8 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
val paramSlots = mutableMapOf<String, Int>()
val paramDisplacements = mutableMapOf<String, Int>()
val copyInParams = mutableSetOf<String>()
val slotSizes = mutableMapOf<String, Int>()
val slotIrTypes = mutableMapOf<String, IRDataType>()
for ((index, _) in sub.parameters.withIndex()) {
val name = scopedParamNames[index]
val v = program.st.lookup(name) as? IRStStaticVariable ?: return static()
@@ -439,15 +441,21 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
val refs = referencedIn[name]
if ((refs != null && refs.any { it != sub.label }) || isLabelReferencedInAsm(name, asmTexts))
copyInParams += name
// first parameter ends up at the highest offset, last one just above the return address
val offset = INCOMING_BASE + PARAM_SLOT_SIZE * (sub.parameters.size - 1 - index)
paramSlots[name] = offset
// narrow values are right-justified in their longword slot
paramDisplacements[name] = when (irTypeFor(elemDt)) {
IRDataType.BYTE -> 3
IRDataType.WORD -> 2
else -> 0
}
val irType = irTypeFor(elemDt) ?: error("frameable parameter $name has no IR data type")
slotIrTypes[name] = irType
slotSizes[name] = incomingSlotSize(irType, target)
}
// first parameter ends up at the highest offset, last one just above the return address;
// each slot takes its own width (2 or 4 bytes), accumulated from the last parameter backwards
var incomingSize = 0
for (name in scopedParamNames.reversed()) {
val slotSize = slotSizes.getValue(name)
paramSlots[name] = INCOMING_BASE + incomingSize
// single-instruction byte pushes land at the slot base, so all parameters
// read from their slot base without a within-slot displacement
paramDisplacements[name] = 0
incomingSize += slotSize
}
// all call sites must use the plain parameter-memory convention of this convention
@@ -497,7 +505,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
val elemDt = isFrameableVariable(v, name, addressTaken, asmTexts) ?: continue
val count = v.length?.toInt() ?: 1
val size = target.memorySize(v.dt, v.length?.toInt())
if (size <= 0 || size >= MAX_FRAME_SIZE) continue
if (size !in 1..<MAX_FRAME_SIZE) continue
val alignment = when {
size >= 4 -> 4
size >= 2 -> 2
@@ -508,7 +516,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
locals += LocalLayout(name, elemDt, count, size, alignment, v.dirty, initialized, isFloat)
if (v.length != null && size > 8 && !v.dirty && v.initializationValue == null) {
val shortName = name.substringAfterLast('.').removePrefix("p8v_")
errors.warn("local array '$shortName' ($size bytes) in subroutine '${userName(sub)}' is zero-initialized on every call which can be costly; consider @dirty if you assign it before use", sub.position)
errors.warn("local array '$shortName' ($size bytes) in subroutine '${userName(sub)}' is zero-initialized on every call which can be costly; consider @dirty if you assign it before use", v.position)
}
}
@@ -558,7 +566,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
val mem = instr.memory
val sym = mem?.symbolName
val slot = if (sym != null) slotOf(sym, slotBySymbol, paramSlots) else null
// parameters are right-justified in their padded longword slot, so include that displacement
// all parameters are read from their slot base, so no extra displacement is included
val extra = paramDisplacements[sym] ?: 0
val disp = when (mem) {
is MemoryReference.Direct -> slot?.plus(mem.displacement)?.plus(extra)
@@ -577,8 +585,8 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
return static()
}
// rewrite references: Symbol base -> FrameSlot base (parameters additionally get the
// right-justification displacement of their padded slot).
// rewrite references: Symbol base -> FrameSlot base (values are read from the slot
// base itself, so no within-slot displacement applies).
// Copy-in parameters keep their static cell for ALL references (§6.1): the entry-time
// copy-in loads the incoming value into that cell, and the body reads and writes it
// there - this also keeps the externally referencing code (defer handler, inline
@@ -633,10 +641,10 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
// a static-initialized local gets its initializer stored into the frame slot;
// this replaces the zero-clear for it
val irType = irTypeFor(fv.elemDt) ?: continue
if (irType == IRDataType.FLOAT)
prologueInstrs += IRInstructions.storeImmediateFloat(init.value, IRMemory.frameDirect(fv.slot))
prologueInstrs += if (irType == IRDataType.FLOAT)
IRInstructions.storeImmediateFloat(init.value, IRMemory.frameDirect(fv.slot))
else
prologueInstrs += IRInstructions.storeImmediate(irType, init.value.toInt(), IRMemory.frameDirect(fv.slot))
IRInstructions.storeImmediate(irType, init.value.toInt(), IRMemory.frameDirect(fv.slot))
continue
}
if (fv.dirty) continue
@@ -666,9 +674,9 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
firstChunk.instructions.addAll(0, prologueInstrs)
}
sub.frameSize = frameSize
sub.incomingSize = paramSlots.size * PARAM_SLOT_SIZE
sub.incomingSize = incomingSize
sub.frameVregSlots = vregSlots
return Frameability.Frameable(FrameLayoutResult(paramSlots, copyInParams))
return Frameability.Frameable(FrameLayoutResult(paramSlots, copyInParams, incomingSize))
}
/**
@@ -1036,6 +1044,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
if (framedSubs.isEmpty()) return
for (sub in program.allSubs()) {
val offsetsByCallee = mutableMapOf<String, List<Int>>()
val incomingSizeByCallee = mutableMapOf<String, Int>()
for ((callee, layout) in framedSubs) {
val params = calleeParameters(callee) ?: continue
if (params.isEmpty()) continue
@@ -1043,6 +1052,7 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
val scoped = if (param.startsWith("$callee.")) param else "$callee.$param"
layout.paramSlots[scoped] ?: 0
}
incomingSizeByCallee[callee] = layout.incomingSize
}
if (offsetsByCallee.isEmpty()) continue
sub.forEachChunk { chunk ->
@@ -1064,6 +1074,13 @@ class StackFrameLayout(private val program: IRProgram, private val errors: IErro
}
if (changed)
instrs[i] = instr.copy(callSite = site.copy(arguments = rewritten))
// caller/callee agreement guard: the slots the caller pushes must add up
// to exactly the callee's incoming argument area
val pushedSize = site.arguments.sumOf { incomingSlotSize(it.source.type, target) }
require(pushedSize == incomingSizeByCallee[callee]) {
"argument slot sizes of the call to '$callee' add up to $pushedSize bytes, " +
"but the callee's incoming argument area is ${incomingSizeByCallee[callee]} bytes"
}
}
}
}
@@ -24,17 +24,19 @@ internal fun extractStructArrayIndexInfo(
val baseAddrOf: PtAddressOf
val fieldOffset: Int
val unwrapped = unwrapCast(addressExpr)
when {
unwrapped is PtAddressOf -> {
when (unwrapped) {
is PtAddressOf -> {
baseAddrOf = unwrapped
fieldOffset = 0
}
unwrapped is PtBinaryExpression && unwrapped.operator == "+" -> {
is PtBinaryExpression if unwrapped.operator == "+" -> {
val leftAddr = asAddressOf(unwrapped.left) ?: return null
val rightNum = (unwrapCast(unwrapped.right) as? PtNumber)?.number?.toInt() ?: return null
baseAddrOf = leftAddr
fieldOffset = rightNum
}
else -> return null
}
if(!baseAddrOf.isFromArrayElement) return null
@@ -46,7 +48,7 @@ internal fun extractStructArrayIndexInfo(
val elemType = arrayType.elementType()
if(!elemType.isStructInstance) return null
val structSize = codeGen.program.memsizer.memorySize(elemType, null)
if(structSize <= 0 || structSize > 65535) return null
if(fieldOffset < 0 || fieldOffset > 65535) return null
if(structSize !in 1..65535) return null
if(fieldOffset !in 0..65535) return null
return StructArrayIndexInfo(arrayIdent.name, idxExpr, structSize, fieldOffset)
}
+1
View File
@@ -46,6 +46,7 @@ dependencies {
implementation(project(":codeCore"))
implementation(project(":intermediate"))
implementation("com.github.ajalt.clikt:clikt:5.1.0")
implementation("com.github.ajalt.mordant:mordant:3.1.0")
// Test dependencies - Kotest BOM is provided by root build.gradle.kts
testImplementation("io.kotest:kotest-runner-junit5")
+22 -3
View File
@@ -293,6 +293,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
// the code chunks of the subroutine currently being emitted, to tell a jump that stays
// inside it apart from one that leaves it
private var currentSubChunks: Set<IRCodeChunkBase> = emptySet()
private var previousInsn: IRInstruction? = null
// Virtual registers of the subroutine being emitted, by register number -> frame offset.
// A re-entrant subroutine (one that can have two live activations) keeps them per-activation in
@@ -317,6 +318,21 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
return target !in currentSubChunks
}
internal fun jumpTableStaysInCurrentSub(pointer: VirtualRegister): Boolean {
val load = previousInsn ?: return false
if (load.opcode != Opcode.LOADX) return false
if (load.dest?.register != pointer) return false
val indexed = load.memory as? MemoryReference.Indexed ?: return false
val symbol = indexed.base as? AddressBase.Symbol ?: return false
val variable = program.st.lookup(symbol.name) as? IRStStaticVariable ?: return false
val initializer = variable.initializationValue as? IRVariableInitializer.Array ?: return false
if (initializer.elements.isEmpty()) return false
return initializer.elements.all { element ->
val label = (element as? IRStSymbolicReference.Symbol)?.name ?: return@all false
!labelIsOutsideCurrentSub(label)
}
}
// === virtual register file layout (1, 2 or 4 bytes per slot depending on type, word-aligned) ===
private data class RegFileLayout(val offsets: Map<Int, Int>, val totalSize: Int)
@@ -452,8 +468,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
fun resolveSymbolRef(name: String): String {
val node = program.st.lookup(name)
return when (node) {
return when (val node = program.st.lookup(name)) {
is IRStMemVar -> {
val addr = node.address.toInt()
$$"$${addr.toUInt().toString(16).padStart(8, '0')}"
@@ -655,6 +670,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
val options = program.options
emitRaw("; Program: ${program.name}")
emitRaw("; Generated by prog8-m68kgen (Motorola 68000 codegen)")
emitRaw("; prog8 compiler version: ${options.compilerVersion}")
emitRaw("; Target CPU: ${cpu.name}")
emitRaw("; Output: ${options.output.name}")
emitRaw("")
@@ -916,6 +932,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
deadStoreSuppressionAllowed: Boolean = true,
preserveLoopCounter: Boolean = false
) {
previousInsn = null
invalidateD0Cache() // new basic block / branch target
emitSourceComment(chunk.sourceLinesPositions)
val callOptimizations = mutableMapOf<Int, ImmediateCallOptimization>()
@@ -960,6 +977,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
val mem = insn.memory as MemoryReference.Direct
val slot = mem.base as AddressBase.FrameSlot
emitFrameZeroLoop(slot.offset + mem.displacement, zeroRun)
previousInsn = null
index += zeroRun
continue
}
@@ -971,6 +989,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
callOptimizations[index],
index in deadLoadIndices
)
previousInsn = insn
if (clobbersLoopCounter)
emitLine("move.w (sp)+,d7", "restore loop counter after call")
index++
@@ -1467,7 +1486,7 @@ internal class AsmGen(val program: IRProgram, internal val target: ICompilationT
emitRaw("${fixNameSymbols(mv.name)} = $${mv.address.toString(16)}")
}
if (emitted.size > 0 || memvars.count() > 0)
if (emitted.isNotEmpty() || memvars.count() > 0)
emitRaw("")
}
@@ -145,6 +145,7 @@ private fun optimizeRedundantLea(allPretrimmed: List<String>): List<Modification
/** Matches a parenthesised group, for stripping addressing modes. */
private val parenthesised = Regex("""\([^)]*\)""")
private val d0IndexOperand = Regex("""\(\s*a0\s*,\s*d0\.w(?:\*\d+)?\s*\)""")
/** A single edit to apply: either remove a line (keeping its label if present) or replace it. */
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
@@ -168,7 +169,7 @@ private fun applyModifications(modifications: List<Modification>, lines: Mutable
val pretrim = pretrimmed.getOrNull(idx)
// If the line carries a label we must keep it (other code may jump to it);
// otherwise the line is dropped entirely.
if (pretrim == null || pretrim.isBlank() || pretrim.startsWith(';')) {
if (pretrim.isNullOrBlank() || pretrim.startsWith(';')) {
lines.removeAt(idx)
} else if (hasLabel(pretrim)) {
val label = keepLabel(pretrim)
@@ -671,11 +672,19 @@ private fun isSlotReferencedInLine(slot: String, line: String): Boolean {
* or for 68020+ JUMPI/CALLI:
* jmp/jsr ([p8_regfile+M]) ; memory-indirect
*
* Fused (preserves current unscaled semantics; no *4 scaling):
* The same sequence with the scale folded into the addressing mode instead of
* into d0 (68020+; this is what a pointer-array dispatch such as on..goto emits):
* lea table_label, a0
* move.l (a0,d0.w*4), d0
* move.l d0, p8_regfile+M
* movea.l p8_regfile+M, a0
* jmp/jsr (a0)
*
* Fused, keeping whichever index operand was matched so the scale is preserved:
* moveq #0, d0
* move.b p8_regfile+N, d0
* lea table_label, a0
* move.l (a0,d0.w), a0
* move.l (a0,d0.w), a0 ; or (a0,d0.w*4), a0 on 68020+
* jmp/jsr (a0)
*/
private fun optimizeFuseLoadxJumpi(allPretrimmed: List<String>, lines: MutableList<String>): List<Modification> {
@@ -686,11 +695,13 @@ private fun optimizeFuseLoadxJumpi(allPretrimmed: List<String>, lines: MutableLi
val mods = mutableListOf<Modification>()
val alreadyModified = mutableSetOf<Int>()
fun isLoadIndexed(instr: String): Boolean {
// matches "move.l (a0,d0.w), d0" with flexible spacing
if (!instr.startsWith("move.l")) return false
if ("(a0,d0.w)" !in instr) return false
return instr.trim().endsWith(", d0") || instr.trim().endsWith(",d0")
fun loadIndexedOperand(instr: String): String? {
// matches "move.l (a0,d0.w), d0" (68000, index pre-scaled into d0) and the scaled
// "move.l (a0,d0.w*4), d0" forms (68020+, scale folded into the addressing mode).
// The matched operand is returned so the fused instruction keeps the same scale.
if (!instr.startsWith("move.l")) return null
if (!(instr.trim().endsWith(", d0") || instr.trim().endsWith(",d0"))) return null
return d0IndexOperand.find(instr)?.value
}
fun isSpill(instr: String): String? {
@@ -719,7 +730,7 @@ private fun optimizeFuseLoadxJumpi(allPretrimmed: List<String>, lines: MutableLi
val loadIdx = leaIdx + 1
val spillIdx = leaIdx + 2
if (filtered[loadIdx].index in alreadyModified || filtered[spillIdx].index in alreadyModified) continue
if (!isLoadIndexed(filtered[loadIdx].instruction)) continue
val loadOperand = loadIndexedOperand(filtered[loadIdx].instruction) ?: continue
val slotM = isSpill(filtered[spillIdx].instruction) ?: continue
// Check jump/call variant: 68000 uses movea reload + jmp/jsr (a0),
@@ -746,7 +757,7 @@ private fun optimizeFuseLoadxJumpi(allPretrimmed: List<String>, lines: MutableLi
if (hasLabel(filtered[spillIdx].trimmed) || hasLabel(filtered[nextIdx].trimmed) || hasLabel(filtered[jmpIdx].trimmed)) continue
val loadIndent = lines[filtered[loadIdx].index].takeWhile { it.isWhitespace() }
mods.add(Modification(filtered[loadIdx].index, false, "${loadIndent}move.l (a0,d0.w), a0"))
mods.add(Modification(filtered[loadIdx].index, false, "${loadIndent}move.l $loadOperand, a0"))
mods.add(Modification(filtered[spillIdx].index, true, null))
mods.add(Modification(filtered[nextIdx].index, true, null))
alreadyModified.add(filtered[loadIdx].index)
@@ -759,7 +770,7 @@ private fun optimizeFuseLoadxJumpi(allPretrimmed: List<String>, lines: MutableLi
val loadIndent = lines[filtered[loadIdx].index].takeWhile { it.isWhitespace() }
val jmpIndent = lines[filtered[nextIdx].index].takeWhile { it.isWhitespace() }
val mnemonic = if (is68020MemIndirectJsr) "jsr" else "jmp"
mods.add(Modification(filtered[loadIdx].index, false, "${loadIndent}move.l (a0,d0.w), a0"))
mods.add(Modification(filtered[loadIdx].index, false, "${loadIndent}move.l $loadOperand, a0"))
mods.add(Modification(filtered[spillIdx].index, true, null))
mods.add(Modification(filtered[nextIdx].index, false, "${jmpIndent}$mnemonic (a0)"))
alreadyModified.add(filtered[loadIdx].index)
@@ -5,9 +5,11 @@ import prog8.code.core.*
import prog8.code.target.Amiga1200Target
import prog8.code.target.Amiga500Target
import prog8.code.target.Qemu68kTarget
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.deleteIfExists
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText
class AssemblyProgramM68k(override val name: String) : IAssemblyProgram {
@@ -24,7 +26,7 @@ class AssemblyProgramM68k(override val name: String) : IAssemblyProgram {
* /prog8lib/<targetname>/link.ld resource. Anything else falls back to the qemu68k layout.
*/
private fun resolveLinkerScript(target: ICompilationTarget): String {
target.libraryPath?.resolve("link.ld")?.takeIf { Files.isReadable(it) }?.let { return it.readText() }
target.libraryPath?.resolve("link.ld")?.takeIf { it.exists() }?.let { return it.readText() }
val targetResource = "/prog8lib/${target.name}/link.ld"
AssemblyProgramM68k::class.java.getResource(targetResource)?.let { return it.readText() }
return AssemblyProgramM68k::class.java.getResource("/prog8lib/${Qemu68kTarget.NAME}/link.ld")?.readText()
@@ -38,11 +40,11 @@ class AssemblyProgramM68k(override val name: String) : IAssemblyProgram {
val process = try {
proc.start()
} catch (e: Exception) {
when {
tool=="vasm" -> {
when (tool) {
"vasm" -> {
System.err.println("Cannot find '${command[0]}' (vasm assembler for m68k). Install it via your package manager if it's on there, or build it from source: http://sun.hasenbraten.de/vasm/")
}
tool=="vlink" -> {
"vlink" -> {
System.err.println("Cannot find 'vlink' (linker). Install it via your package manager if it's on there, or build it from source: http://sun.hasenbraten.de/vlink/")
}
else -> {
@@ -92,12 +94,12 @@ class AssemblyProgramM68k(override val name: String) : IAssemblyProgram {
if (!runProcess(assembleCmd, options.quiet, "vasm"))
return false
// clean up any leftover ELF/obj files from previous builds
Files.deleteIfExists(outs.binFile())
outs.binFile().deleteIfExists()
// Step 2: write linker script and link to ELF executable
val linkScript = outs.linkScript()
val elfFile = elfFile(outs)
Files.writeString(linkScript, resolveLinkerScript(options.compTarget))
linkScript.writeText(resolveLinkerScript(options.compTarget))
val linkCmd = listOf(
"vlink",
@@ -108,7 +110,7 @@ class AssemblyProgramM68k(override val name: String) : IAssemblyProgram {
objFile.toString()
)
val linkOk = runProcess(linkCmd, options.quiet, "vlink")
Files.deleteIfExists(linkScript)
linkScript.deleteIfExists()
if(linkOk && !options.quiet)
println("Executable written to '$elfFile'")
return linkOk
@@ -227,7 +227,7 @@ internal fun AsmGen.translateArithmetic(insn: IRInstruction) {
when (type) {
IRDataType.BYTE -> {
emitLoadD0(srcReg, IRDataType.BYTE)
emitLine($$"and.l #\$ff, d0", "zero extend")
emitLine($$"and.l #$ff, d0", "zero extend")
invalidateD0Cache()
emitLine("mulu.w d0, d0")
invalidateD0Cache()
@@ -195,19 +195,30 @@ private fun AsmGen.shiftOpcode(isLeft: Boolean, isArithmetic: Boolean, isRotate:
// === Shifts by 1 (constant count 1) ===
// Emit a .w count=1 memory-form shift/rotate. The absolute form (`op.w addr`)
// is one instruction and optimal, but the QEMU 68020 emulation has a bug where
// `asr.w` with absolute addressing zero-extends the 16-bit operand before the
// shift instead of sign-extending it, producing a logical shift result for
// negative values. The 68000 (vamos) and the `(a0)` / register forms are
// unaffected. Workaround for the qemu68k target only: load the address into
// a0 and use `(a0)` addressing. A0 is a scratch address register in the m68k
// codegen, so this is safe.
// is one instruction and optimal, but QEMU's 68020 emulation is broken for
// word-sized memory *right* shifts in both addressing forms:
// `asr.w addr` -> sign-extends the 16-bit operand instead of zero-extending
// it, producing a logical shift result for negative values
// `lsr.w (a0)` -> sign-extends, producing an arithmetic shift result
// The register form (`move.w addr,d0` / `op.w #1,d0` / `move.w d0,addr`) is
// correct in QEMU, and both the absolute and `(a0)` forms are correct on a real
// 68000 (vamos). Left shifts and rotates are unaffected, so they keep the
// compact memory form. d0 is scratch for the shift, matching the callers which
// already invalidate their d0 cache.
private fun AsmGen.emitMemoryWordShiftOrRotate(op: String, address: String) {
if (target.name == Qemu68kTarget.NAME) {
emitLine("lea $address, a0")
emitLine("$op.w (a0)")
} else {
emitLine("$op.w $address")
when (target.name) {
Qemu68kTarget.NAME if (op == "asr" || op == "lsr") -> {
emitLine("move.w $address, d0")
emitLine("$op.w #1, d0")
emitLine("move.w d0, $address")
}
Qemu68kTarget.NAME -> {
emitLine("lea $address, a0")
emitLine("$op.w (a0)")
}
else -> {
emitLine("$op.w $address")
}
}
}
@@ -47,8 +47,9 @@ internal fun AsmGen.translateControl(insn: IRInstruction, forwardedImmediateCall
val ref = insn.requireTarget() as? CodeReference.Indirect ?: error("JUMPI needs an indirect target register")
val reg = ref.pointer.intNumber
invalidateD0Cache()
// a dispatch jump leaves this subroutine for good: the target's rts must find our caller's return address, so our frame has to be gone first
emitFrameUnlk()
// Keep the frame for a table whose targets all belong to this subroutine.
if (!jumpTableStaysInCurrentSub(ref.pointer.register))
emitFrameUnlk()
if(program.options.compTarget.cpu >= CpuType.M68020) {
// 68020+ supports memory-indirect addressing: fetch the target address from memory directly.
emitLine("jmp ([${regAddr(reg)}])")
@@ -674,10 +675,13 @@ private fun AsmGen.translateCall(fnLabel: String, callSite: CallSite, forwardedI
}
// caller cleanup of the all-stack argument area:
// each argument was pushed into its own padded longword slot
// each argument was pushed into its own word-packed slot (2-byte slots for byte/word args,
// 4-byte slots for wide args); the total area size is always even
val pushedSlots = callSite.arguments.count { it.location is CallLocation.FrameSlot }
if (pushedSlots > 0) {
val totalSize = pushedSlots * 4
val totalSize = callSite.arguments
.filter { it.location is CallLocation.FrameSlot }
.sumOf { incomingSlotSize(it.source.type, target) }
val needsCcr = callSite.results.any { it.location is CallLocation.StatusFlag }
when {
// addq is compact but updates CCR; lea preserves CCR so status-flag returns
@@ -842,9 +846,9 @@ private fun AsmGen.translateArgument(
}
}
} else {
// Stack argument: push into a padded longword slot, right-justified.
// Stack argument: push into the parameter's word-packed slot, landing at the slot
// base byte (2-byte slots for byte/word args, 4-byte slots for wide args).
// The caller pops the whole argument area after the return, so the callee never needs to know the argument count.
// Optimizing this to use word-padding instead of long padding could be done in a future version
if (arg.location is CallLocation.FrameSlot) {
if (argReg.isFloat) {
// the FPU has no absolute-long addressing mode, so the value has to travel
@@ -854,14 +858,16 @@ private fun AsmGen.translateArgument(
emitLine("fmove.s $FP_ACC,-(sp)", paramName.orEmpty())
} else {
when (argReg.type) {
IRDataType.BYTE -> {
emitLine("subq.l #4,sp")
emitLine("move.b ${regAddr(argReg.intNumber)},3(sp)", paramName.orEmpty())
}
IRDataType.WORD -> {
emitLine("subq.l #4,sp")
emitLine("move.w ${regAddr(argReg.intNumber)},2(sp)", paramName.orEmpty())
}
IRDataType.BYTE ->
// one-instruction push: on the stack pointer a byte predecrement
// steps by 2 (not 1), so this reserves the 2-byte slot in one go;
// the byte lands at the slot's base byte, matching the zero
// within-slot displacement that byte parameters use
emitLine("move.b ${regAddr(argReg.intNumber)},-(sp)",
(if (paramName != null) "$paramName " else "") + "(note: pushed as a word)")
// a 2-byte predecrement push reserves and stores the word slot in one instruction
IRDataType.WORD ->
emitLine("move.w ${regAddr(argReg.intNumber)},-(sp)", paramName.orEmpty())
// longs and pointers fill the whole slot, so reserve and store in one push
else -> emitLine("move.l ${regAddr(argReg.intNumber)},-(sp)", paramName.orEmpty())
}
@@ -11,6 +11,7 @@ import prog8.intermediate.CallSite
import prog8.intermediate.IMSyscall
import prog8.intermediate.IRDataType
import prog8.intermediate.RegisterOperand
import prog8.intermediate.floatNumber
import prog8.intermediate.intNumber
@@ -18,7 +19,12 @@ import prog8.intermediate.intNumber
internal fun AsmGen.translateSyscall(num: Int, site: CallSite) {
when (num) {
IMSyscall.STRING_CONTAINS.number -> translateSyscallStringContains(site)
IMSyscall.BYTEARRAY_CONTAINS.number -> translateSyscallBytearrayContains(site)
IMSyscall.WORDARRAY_CONTAINS.number -> translateSyscallWordarrayContains(site)
IMSyscall.SPLIT_WORDARRAY_CONTAINS.number -> translateSyscallSplitWordarrayContains(site)
IMSyscall.LONGARRAY_CONTAINS.number -> translateSyscallLongarrayContains(site)
IMSyscall.FLOATARRAY_CONTAINS.number -> translateSyscallFloatarrayContains(site)
IMSyscall.COMPARE_STRINGS.number -> translateSyscallStringCompare(site)
IMSyscall.CLAMP_BYTE.number -> translateSyscallClamp(site, IRDataType.BYTE, true)
IMSyscall.CLAMP_UBYTE.number -> translateSyscallClamp(site, IRDataType.BYTE, false)
@@ -59,24 +65,32 @@ private fun AsmGen.loadStringArg(reg: Int, dt: IRDataType, dreg: String) {
}
}
private fun AsmGen.translateSyscallWordarrayContains(site: CallSite) {
// Shared helper for array contains syscalls that scan a contiguous array.
// The length argument is a word, so arrays up to the target's ARRAY_SIZE_LIMIT
// are scanned correctly (m68k: 32768 elements, VM: 65535 elements).
private fun AsmGen.translateSyscallArrayContains(site: CallSite, dt: IRDataType) {
val regElem = site.argument(0).intNumber
val regArr = site.argument(1).intNumber
val regLen = site.argument(2).intNumber
val resultReg = site.resultRegister(0).intNumber
val labelLoop = makeLabel(".wac_loop")
val labelFound = makeLabel(".wac_found")
val labelDone = makeLabel(".wac_done")
val labelLoop = makeLabel(".ac_loop")
val labelFound = makeLabel(".ac_found")
val labelDone = makeLabel(".ac_done")
emitLoadD0(regElem, IRDataType.WORD)
emitLoadD0(regElem, dt)
emitLine("move.l ${regAddr(regArr)}, a0")
emitLine("moveq.l #0, d1")
emitLine("move.b ${regAddrByte(regLen, 0)}, d1")
emitLine("move.w ${regAddr(regLen)}, d1")
emitLine("subq.w #1, d1")
emitLine("bmi $labelDone ; length was 0 -> not found")
emitRaw("$labelLoop:")
emitLine("cmp.w (a0)+, d0")
val cmpInstr = when (dt) {
IRDataType.BYTE -> "cmp.b"
IRDataType.WORD -> "cmp.w"
IRDataType.LONG -> "cmp.l"
else -> error("array contains: unsupported element type $dt")
}
emitLine("$cmpInstr (a0)+, d0")
emitLine("beq $labelFound")
emitLine("dbra d1, $labelLoop")
emitLine("move.b #0, ${regAddrByte(resultReg, 0)}")
@@ -86,6 +100,100 @@ private fun AsmGen.translateSyscallWordarrayContains(site: CallSite) {
emitRaw("$labelDone:")
}
private fun AsmGen.translateSyscallBytearrayContains(site: CallSite) {
translateSyscallArrayContains(site, IRDataType.BYTE)
}
private fun AsmGen.translateSyscallWordarrayContains(site: CallSite) {
translateSyscallArrayContains(site, IRDataType.WORD)
}
private fun AsmGen.translateSyscallLongarrayContains(site: CallSite) {
translateSyscallArrayContains(site, IRDataType.LONG)
}
private fun AsmGen.translateSyscallSplitWordarrayContains(site: CallSite) {
val regElem = site.argument(0).intNumber
val regArr = site.argument(1).intNumber
val regLen = site.argument(2).intNumber
val resultReg = site.resultRegister(0).intNumber
val labelLoop = makeLabel(".swc_loop")
val labelFound = makeLabel(".swc_found")
val labelDone = makeLabel(".swc_done")
emitLoadD0(regElem, IRDataType.WORD)
emitLine("move.l ${regAddr(regArr)}, a0") // a0 = lsb array start
emitLine("move.w ${regAddr(regLen)}, d1") // d1 = element count
emitLine("move.w d1, d2")
emitLine("add.l a0, d2") // d2 = msb array start (lsb + length)
emitLine("subq.w #1, d1")
emitLine("bmi $labelDone ; length was 0 -> not found")
emitRaw("$labelLoop:")
emitLine("move.b (a0,d1.w), d3") // d3 = lsb of element at index d1
emitLine("move.b (d2,d1.w), d4") // d4 = msb of element at index d1
emitLine("lsl.w #8, d4")
emitLine("or.w d3, d4") // d4 = reconstructed word
emitLine("cmp.w d0, d4")
emitLine("beq $labelFound")
emitLine("dbra d1, $labelLoop")
emitLine("move.b #0, ${regAddrByte(resultReg, 0)}")
emitLine("bra $labelDone")
emitRaw("$labelFound:")
emitLine("move.b #1, ${regAddrByte(resultReg, 0)}")
emitRaw("$labelDone:")
}
private fun AsmGen.translateSyscallFloatarrayContains(site: CallSite) {
val regNeedle = site.argument(0).floatNumber
val regArr = site.argument(1).intNumber
val regLen = site.argument(2).intNumber
val resultReg = site.resultRegister(0).intNumber
val labelLoop = makeLabel(".fac_loop")
val labelFound = makeLabel(".fac_found")
val labelDone = makeLabel(".fac_done")
emitLine("fmove.s ${floatRegFileAddr(regNeedle)}, fp0")
emitLine("move.l ${regAddr(regArr)}, a0")
emitLine("move.w ${regAddr(regLen)}, d1")
emitLine("subq.w #1, d1")
emitLine("bmi $labelDone ; length was 0 -> not found")
emitRaw("$labelLoop:")
emitLine("fcmp.s (a0)+, fp0")
emitLine("fbeq $labelFound")
emitLine("dbra d1, $labelLoop")
emitLine("move.b #0, ${regAddrByte(resultReg, 0)}")
emitLine("bra $labelDone")
emitRaw("$labelFound:")
emitLine("move.b #1, ${regAddrByte(resultReg, 0)}")
emitRaw("$labelDone:")
}
private fun AsmGen.translateSyscallStringContains(site: CallSite) {
val regChar = site.argument(0).intNumber
val regStr = site.argument(1).intNumber
val resultReg = site.resultRegister(0).intNumber
val labelLoop = makeLabel(".sc_loop")
val labelFound = makeLabel(".sc_found")
val labelDone = makeLabel(".sc_done")
emitLine("move.b ${regAddrByte(regChar, 0)}, d0")
emitLine("move.l ${regAddr(regStr)}, a0")
emitRaw("$labelLoop:")
emitLine("move.b (a0)+, d1")
emitLine("beq $labelDone ; reached null terminator -> not found")
emitLine("cmp.b d0, d1")
emitLine("beq $labelFound")
emitLine("bra $labelLoop")
emitRaw("$labelFound:")
emitLine("move.b #1, ${regAddrByte(resultReg, 0)}")
emitLine("bra $labelDone")
emitRaw("$labelDone:")
emitLine("move.b #0, ${regAddrByte(resultReg, 0)}")
}
private fun AsmGen.translateSyscallClamp(site: CallSite, dt: IRDataType, signed: Boolean) {
val valueReg = site.argument(0).intNumber
val minReg = site.argument(1).intNumber
@@ -1208,6 +1208,41 @@ class TestInstructionSelectionOptimizations : FunSpec({
lines.count { it.contains("(a0,d0.w*") } shouldBe 0
}
test("loadx+jumpi fusion removes the regfile roundtrip and keeps the scaled index operand") {
// a pointer-array dispatch (on..goto / rewritten when) emits the scale in the addressing mode
val lines = generateAsm(
tempRoot.resolve("test-m68k-fuse-loadx-jumpi-scaled"),
listOf(
IRInstructions.loadMemory(Opcode.LOADX, IRDataType.POINTER, 6, IRMemory.indexed("table", 3, IRDataType.WORD, 4)),
IRInstructions.jumpIndirect(6)
)
)
lines.any { it == "move.l (a0,d0.w*4), a0" } shouldBe true
lines.any { it == "jmp (a0)" } shouldBe true
// the spill and the reload it feeds are gone
lines.count { it.startsWith("move.l d0,p8_regfile") } shouldBe 0
lines.count { it.startsWith("movea.l") } shouldBe 0
lines.any { it.startsWith("jmp ([p8_regfile") } shouldBe false
}
test("loadx+jumpi fusion keeps the unscaled index operand on 68000") {
val lines = generateAsm(
tempRoot.resolve("test-m68k-fuse-loadx-jumpi-unscaled"),
listOf(
IRInstructions.loadMemory(Opcode.LOADX, IRDataType.POINTER, 6, IRMemory.indexed("table", 3, IRDataType.WORD, 4)),
IRInstructions.jumpIndirect(6)
),
target = Amiga500Target()
)
// on 68000 the scale is applied to d0 by hand, so the addressing mode stays unscaled
lines.any { it == "lsl.w #2,d0" } shouldBe true
lines.any { it == "move.l (a0,d0.w), a0" } shouldBe true
lines.any { it == "jmp (a0)" } shouldBe true
lines.count { it.startsWith("move.l d0,p8_regfile") } shouldBe 0
}
test("indirect memory lowering picks displacement addressing or explicit pointer adjustment") {
val lines = generateAsm(
tempRoot.resolve("test-m68k-indirect-displacement"),
@@ -1355,4 +1390,42 @@ class TestInstructionSelectionOptimizations : FunSpec({
lines.count { it.trimStart().startsWith("lea arr,a0") } shouldBe 1
lines.any { it.trimStart().startsWith("clr.w") } shouldBe true
}
test("qemu68k word right shifts avoid the memory forms its 68020 model mis-emulates") {
// QEMU's 68020 sign-extends the operand for both `asr.w <abs>` and
// `lsr.w (a0)`, so both memory forms give the wrong result. The
// register form round-trips through d0 and is correct.
val lines = generateAsm(
tempRoot.resolve("test-m68k-qemu-word-rshift"),
listOf(
IRInstructions.memoryOp(Opcode.ASRM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_value")),
IRInstructions.memoryOp(Opcode.LSRM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_other"))
),
Qemu68kTarget()
)
lines.count { it.startsWith("asr.w") } shouldBe 1
lines.count { it.startsWith("lsr.w") } shouldBe 1
lines.count { it == "asr.w #1,d0" } shouldBe 1
lines.count { it == "lsr.w #1,d0" } shouldBe 1
lines.count { it == "move.w p8b_test.p8v_value,d0" } shouldBe 1
lines.count { it == "move.w d0,p8b_test.p8v_value" } shouldBe 1
lines.count { it.startsWith("lea") } shouldBe 0
}
test("amiga500 keeps the compact word right shift memory form") {
val lines = generateAsm(
tempRoot.resolve("test-m68k-amiga-word-rshift"),
listOf(
IRInstructions.memoryOp(Opcode.ASRM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_value")),
IRInstructions.memoryOp(Opcode.LSRM, IRDataType.WORD, IRMemory.direct("p8b_test.p8v_other"))
),
Amiga500Target()
)
lines.count { it.startsWith("asr.w p8b_test.p8v_value") } shouldBe 1
lines.count { it.startsWith("lsr.w p8b_test.p8v_other") } shouldBe 1
lines.count { it == "asr.w #1,d0" } shouldBe 0
lines.count { it == "lsr.w #1,d0" } shouldBe 0
}
})
+21 -15
View File
@@ -24,7 +24,8 @@ class TestStackFrameEmission : FunSpec({
incomingSize: Int = 0,
subLabel: String = "test.start",
frameVregSlots: Map<Int, Int> = emptyMap(),
errors: ErrorReporterForTests? = null
errors: ErrorReporterForTests? = null,
params: List<IRSubroutine.IRParam>? = null
): List<String> {
val options = CompilationOptions.builder(Qemu68kTarget())
.output(OutputType.RAW)
@@ -38,10 +39,13 @@ class TestStackFrameEmission : FunSpec({
program.options.outputDir = tempRoot
val chunk = IRCodeChunk(subLabel, null)
chunk.instructions.addAll(instructions)
val params = if (incomingSize > 0)
List(incomingSize / 4) { IRSubroutine.IRParam("p8v_p$it", DataType.UBYTE) }
else emptyList()
val sub = IRSubroutine(subLabel, params, emptyList(), Position.DUMMY, frameSize, incomingSize, frameVregSlots)
// word-packed slots: give callers a way to declare each parameter's real type,
// since the default derivation from the incoming area size assumes uniform slots
val paramList = params
?: if (incomingSize > 0)
List(incomingSize / 2) { IRSubroutine.IRParam("p8v_p$it", DataType.UBYTE) }
else emptyList()
val sub = IRSubroutine(subLabel, paramList, emptyList(), Position.DUMMY, frameSize, incomingSize, frameVregSlots)
sub.chunks.add(chunk)
val block = IRBlock("test", false, IRBlock.Options(), Position.DUMMY)
block.children.add(sub)
@@ -148,7 +152,7 @@ class TestStackFrameEmission : FunSpec({
lines.any { it == "unlk a5" } shouldBe true
}
test("stack arguments are pushed right-justified and popped by the caller") {
test("stack arguments are pushed word-aligned into their slots and popped by the caller") {
val calls = listOf(
IRInstructions.call(CallSite(
target = CallTarget.Direct(CodeReference.Label("test.sub")),
@@ -161,12 +165,14 @@ class TestStackFrameEmission : FunSpec({
IRInstructions.simple(Opcode.RETURN)
)
val lines = generateAsmWithIncoming(0, calls, incomingSize = 12, subLabel = "test.caller")
lines.count { it == "subq.l #4,sp" } shouldBe 2
lines.count { it.startsWith("move.b ") && it.contains(",3(sp)") } shouldBe 1
lines.count { it.startsWith("move.w ") && it.contains(",2(sp)") } shouldBe 1
// word-packed pushes: byte arguments use the single move.b predecrement instruction
// (the byte lands at the slot's base byte), word and long push as themselves
lines.count { it.startsWith("move.b ") && it.contains(",-(sp)") && it.contains("pushed as a word") } shouldBe 1
lines.count { it.startsWith("move.w ") && it.contains(",-(sp)") } shouldBe 1
lines.count { it.startsWith("move.l ") && it.contains(",-(sp)") } shouldBe 1
lines.none { it == "subq.l #2,sp" } shouldBe true
lines.any { it.startsWith("bsr") } shouldBe true
lines.any { it == "lea 12(sp),sp" } shouldBe true
lines.any { it == "addq.l #8,sp" } shouldBe true // 2+2+4 bytes
// small argument areas use the short addq form
val smallCalls = listOf(
IRInstructions.call(CallSite(
@@ -176,7 +182,7 @@ class TestStackFrameEmission : FunSpec({
IRInstructions.simple(Opcode.RETURN)
)
val smallLines = generateAsmWithIncoming(0, smallCalls, incomingSize = 4, subLabel = "test.caller")
smallLines.any { it == "addq.l #4,sp" } shouldBe true
smallLines.any { it == "addq.l #2,sp" } shouldBe true
}
test("status-flag results use CCR-preserving cleanup") {
@@ -189,10 +195,10 @@ class TestStackFrameEmission : FunSpec({
IRInstructions.simple(Opcode.RETURN)
)
val lines = generateAsmWithIncoming(0, calls, incomingSize = 4, subLabel = "test.caller")
// a single byte argument would normally use addq.l #4,sp, but that clobbers CCR,
// so status-flag returns must use lea instead.
lines.any { it == "lea 4(sp),sp" } shouldBe true
lines.none { it == "addq.l #4,sp" } shouldBe true
// a single byte argument (2-byte slot) would normally use addq.l #2,sp, but that
// clobbers CCR, so status-flag returns must use lea instead.
lines.any { it == "lea 2(sp),sp" } shouldBe true
lines.none { it == "addq.l #2,sp" } shouldBe true
}
test("float stack arguments travel through the FPU accumulator") {
+1
View File
@@ -47,6 +47,7 @@ dependencies {
implementation(project(":intermediate"))
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
implementation("com.github.ajalt.clikt:clikt:5.1.0")
implementation("com.github.ajalt.mordant:mordant:3.1.0")
// Test dependencies - Kotest BOM is provided by root build.gradle.kts
testImplementation("io.kotest:kotest-runner-junit5")
@@ -228,8 +228,7 @@ internal class AsmGen(val program: IRProgram, private val target: ICompilationTa
name.startsWith('>') -> ">" to name.drop(1)
else -> "" to name
}
val node = program.st.lookup(rest)
return when (node) {
return when (val node = program.st.lookup(rest)) {
is IRStStructInstance -> "$prefix${structInstanceRef(node, rest)}"
else -> "$prefix${fixNameSymbols(rest)}"
}
@@ -250,8 +249,7 @@ internal class AsmGen(val program: IRProgram, private val target: ICompilationTa
}
fun resolveSymbolRef(name: String): String {
val node = program.st.lookup(name)
return when (node) {
return when (val node = program.st.lookup(name)) {
is IRStConstant -> constLabel(name)
is IRStStaticVariable, is IRStMemVar,
is IRStMemorySlab -> fixNameSymbols(name)
@@ -371,6 +369,7 @@ internal class AsmGen(val program: IRProgram, private val target: ICompilationTa
val options = program.options
emitRaw("; Program: ${program.name}")
emitRaw("; Generated by prog8-newgen (new 6502 codegen)")
emitRaw("; prog8 compiler version: ${options.compilerVersion}")
emitRaw("; Target CPU: ${cpu.name}")
emitRaw("; Output: ${options.output.name} Launcher: ${options.launcher.name}")
emitRaw("")
@@ -799,8 +799,7 @@ private fun CallSite.argumentRegister(index: Int, what: String): Int =
private fun AsmGen.translateSyscall(insn: IRInstruction) {
val site = insn.requireCallSite()
val syscallNum = (site.target as CallTarget.SystemCall).number
when (syscallNum) {
when (val syscallNum = (site.target as CallTarget.SystemCall).number) {
IMSyscall.CLAMP_UBYTE.number -> translateSyscallClampUbyte(site)
IMSyscall.CLAMP_BYTE.number -> translateSyscallClampByte(site)
IMSyscall.CLAMP_UWORD.number -> translateSyscallClampUword(site)
@@ -1430,8 +1429,7 @@ private fun AsmGen.translateFloatSign(insn: IRInstruction) {
}
private fun AsmGen.jsrfarRoutine(): String {
val targetName = program.options.compTarget.name
return when (targetName) {
return when (val targetName = program.options.compTarget.name) {
"cx16" -> "cx16.JSRFAR"
"c64" -> "c64.x16jsrfar"
"c128" -> "c128.x16jsrfar"
@@ -20,7 +20,7 @@ fun main(args: Array<String>) {
val reader = IRFileReader()
val source = Path(inputFile).readText()
val program = reader.read(source)
if(asmListfile == true)
if(asmListfile)
program.options.asmListfile = true
val target = program.options.compTarget
@@ -69,7 +69,7 @@ class ZeropageAllocator(
if (name in zeropage.allocatedVariables)
return true
val v = program.st.lookup(name)
return if (v is IRStMemVar) v.address <= 255u else false
return v is IRStMemVar && v.address <= 255u
}
/**
@@ -100,9 +100,9 @@ class ZeropageAllocator(
?: (insn.immediate as? ImmediateOperand.SymbolAddress)?.symbol
?: return
if (label !in varNames) return
val weight = when {
insn.type == IRDataType.WORD -> 2
insn.type == IRDataType.LONG -> 3
val weight = when (insn.type) {
IRDataType.WORD -> 2
IRDataType.LONG -> 3
else -> 1
}
scores[label] = (scores[label] ?: 0) + weight
@@ -319,12 +319,16 @@ class ConstExprEvaluator {
NumericLiteral(BaseDataType.BYTE, value, func.position)
}
"log2" -> {
val value = truncate(log2(args.single().number))
NumericLiteral(BaseDataType.UBYTE, value, func.position)
val arg = args.single().number
if (arg <= 0.0)
throw ExpressionError("log2 of a non-positive value is not defined", func.position)
NumericLiteral(BaseDataType.UBYTE, truncate(log2(arg)), func.position)
}
"log2w" -> {
val value = truncate(log2(args.single().number))
NumericLiteral(BaseDataType.UWORD, value, func.position)
val arg = args.single().number
if (arg <= 0.0)
throw ExpressionError("log2 of a non-positive value is not defined", func.position)
NumericLiteral(BaseDataType.UBYTE, truncate(log2(arg)), func.position)
}
"atan2" -> {
val x1f = args[0].number
@@ -334,7 +338,12 @@ class ConstExprEvaluator {
var radians = atan2(y2f-y1f, x2f-x1f)
if(radians<0)
radians+=2*PI
NumericLiteral(BaseDataType.UWORD, floor(radians/2.0/PI*256.0), func.position)
val value = floor(radians/2.0/PI*256.0)
// the routine returns a ubyte, and a near-zero negative angle can round
// up to exactly 2*PI, which lands on 256
if(value !in 0.0..255.0)
throw ExpressionError("atan2 result $value is out of range for a byte value", func.position)
NumericLiteral(BaseDataType.UBYTE, value, func.position)
}
"diff" -> {
val n1 = args[0].number
+6 -15
View File
@@ -62,10 +62,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
if (!visitedSubroutines.add(subroutine)) return
fun isBodyInlineable(stmt: Return): Boolean {
if (stmt.values.isEmpty())
return true
return stmt.values.all { value ->
return stmt.values.isEmpty() || stmt.values.all { value ->
if (isSimpleReturnExpression(value)) {
val walker = object : AstWalker() {
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
@@ -96,9 +93,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
// crash (literal argument has no identifier to assign to) or change semantics
// (assigning to the caller's variable instead of the local parameter copy).
val targetName = stmt.target.identifier?.nameInSource?.lastOrNull()
if (targetName != null && subroutine.parameters.any { it.name == targetName })
return false
return if (stmt.value.isSimple) {
return !(targetName != null && subroutine.parameters.any { it.name == targetName }) && if (stmt.value.isSimple) {
val targetInline =
if (stmt.target.identifier != null) {
makeFullyScoped(stmt.target.identifier!!)
@@ -159,9 +154,8 @@ class Inliner(private val program: Program, private val options: CompilationOpti
if (hasOnlyBodyStatements) {
if (subroutine !== program.entrypoint) {
val bodyStmt = functionalStatements.firstOrNull()
val isAutoInlineable =
when (bodyStmt) {
when (val bodyStmt = functionalStatements.firstOrNull()) {
is Return -> isBodyInlineable(bodyStmt)
is Assignment -> isBodyInlineable(bodyStmt)
is FunctionCallStatement -> isBodyInlineable(bodyStmt)
@@ -240,7 +234,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<AstModification> {
val sub = functionCallStatement.target.targetStatement(program.builtinFunctions) as? Subroutine
if (sub == null) return noModifications
?: return noModifications
if (!sub.inline) {
maybeSuggestInlineKeyword(sub)
@@ -357,9 +351,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
if (functionalCount == 2 && sub.statements.lastOrNull()?.let(::isEmptyReturn) != true)
return false
val paramNames = sub.parameters.map { it.name }.toSet()
if (!isFairlySimpleReturnExpression(returnStmt.values[0], paramNames))
return false
return true
return isFairlySimpleReturnExpression(returnStmt.values[0], paramNames)
}
private fun maybeSuggestInlineKeyword(sub: Subroutine) {
@@ -372,8 +364,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
}
override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
val sub = functionCallExpr.target.targetStatement(program.builtinFunctions) as? Subroutine
if (sub == null) return noModifications
val sub = functionCallExpr.target.targetStatement(program.builtinFunctions) as? Subroutine ?: return noModifications
if (!sub.inline) {
maybeSuggestInlineKeyword(sub)
return noModifications
@@ -591,25 +591,91 @@ class StatementOptimizer(private val program: Program,
}
}
if(whenStmt.betterAsOnGoto(program, options)) {
val indexOffset = whenStmt.onGotoIndexOffset(program, options)
if(indexOffset!=null) {
// rewrite when into a on..goto , which is faster and also smaller for ~5+ cases
var elseScope: AnonymousScope? = null
var elseJump: Jump? = null
val jumps = mutableListOf<Pair<Int, Jump>>()
val jumps = mutableListOf<Pair<Int, IdentifierReference>>()
val choiceLabels = mutableMapOf<WhenChoice, String>()
whenStmt.choices.forEach { choice ->
if(choice.values==null) {
elseJump = choice.statements.statements.single() as Jump
elseScope = choice.statements
elseJump = choice.statements.statements.singleOrNull() as? Jump
} else {
val simpleJump = choice.statements.statements.singleOrNull() as? Jump
val simpleTarget = simpleJump?.target as? IdentifierReference
val generatedLabel = if(simpleTarget==null) {
program.makeLabel("whencase").also { choiceLabels[choice] = it }
} else null
val label = simpleTarget?.copy()
?: IdentifierReference(listOf(requireNotNull(generatedLabel)), choice.position)
choice.values!!.forEach { value ->
jumps.add(value.constValue(program)!!.number.toInt() to choice.statements.statements.single() as Jump)
jumps.add(value.constValue(program)!!.number.toInt() to label)
}
}
}
if(choiceLabels.isNotEmpty() && parent !is Subroutine && parent !is Block)
return noModifications
val jumpLabels = jumps.sortedBy { it.first }.map { it.second.target as IdentifierReference }
val labelsByValue = jumps.associate { it.first to it.second.copy() }
val span = jumps.maxOf { it.first } - indexOffset + 1
val gapTarget = (elseJump?.target as? IdentifierReference)?.copy()
val elseIsEmpty = elseScope?.statements?.isEmpty() ?: true
val needsEndLabel = choiceLabels.isNotEmpty() || (span > jumps.size && elseIsEmpty && gapTarget==null)
val endLabel = if(needsEndLabel) program.makeLabel("whenend", whenStmt.position) else null
val gapLabel = if(span > jumps.size && gapTarget==null && !elseIsEmpty)
program.makeLabel("ongotogap", whenStmt.position)
else null
val effectiveGapTarget = gapTarget
?: gapLabel?.let { IdentifierReference(listOf(it.name), it.position) }
?: endLabel?.let { IdentifierReference(listOf(it.name), it.position) }
// the table spans the whole value range; a value that isn't a choice jumps to the else target.
// every slot needs its own node instance, since a choice can cover several values
val jumpLabels = (indexOffset..jumps.maxOf { it.first }).map {
labelsByValue[it]?.copy() ?: requireNotNull(effectiveGapTarget).copy()
}
val elsePart = if(elseJump==null) null else AnonymousScope(mutableListOf(elseJump), elseJump.position)
val onGoto = OnGoto(false, whenStmt.condition, jumpLabels, elsePart, whenStmt.position)
return listOf(AstReplaceNode(whenStmt, onGoto, parent))
// the jump table is indexed from the lowest choice value upwards, and unsigned arithmetic
// wraps so values below it simply fall out of the table's bounds check
val indexExpr: Expression = if(indexOffset==0) whenStmt.condition
else BinaryExpression(whenStmt.condition.copy(), "-", NumericLiteral.optimalInteger(indexOffset, whenStmt.position), whenStmt.position)
// Put the gap target in the else body when one exists. When there is no else part
// (or an empty one), generated case bodies follow the on..goto in place of it, so an
// out-of-bounds value must jump to the end label instead of falling through to them.
val elsePart = if(elseIsEmpty) {
if(choiceLabels.isNotEmpty())
AnonymousScope(mutableListOf<Statement>(program.jumpLabel(requireNotNull(endLabel))), whenStmt.position)
else
null
} else {
val s = requireNotNull(elseScope)
val statements = if(gapLabel==null)
s.statements
else
(listOf<Statement>(gapLabel) + s.statements).toMutableList()
AnonymousScope(statements, s.position)
}
val onGoto = OnGoto(false, indexExpr, jumpLabels, elsePart, whenStmt.position)
val modifications = mutableListOf<AstModification>()
var anchor: Statement = whenStmt
choiceLabels.forEach { (choice, labelName) ->
val label = Label(labelName, choice.position)
modifications += AstInsert.after(anchor, label, parent as IStatementContainer)
anchor = label
choice.statements.statements.forEach { statement ->
modifications += AstInsert.after(anchor, statement, parent as IStatementContainer)
anchor = statement
}
val endJump = program.jumpLabel(requireNotNull(endLabel))
modifications += AstInsert.after(anchor, endJump, parent as IStatementContainer)
anchor = endJump
}
if(endLabel!=null)
modifications += AstInsert.after(anchor, endLabel, parent as IStatementContainer)
modifications += AstReplaceNode(whenStmt, onGoto, parent)
return modifications
}
return noModifications
@@ -391,12 +391,10 @@ class UnusedCodeRemover(private val program: Program,
if (definingSub == null) false // global: unsafe
else {
val target = stmt.target.targetStatement(program.builtinFunctions)
if (target is Subroutine) {
!isNestedWithin(target, definingSub)
} else if (target is BuiltinFunctionPlaceholder) {
target.name !in setOf("call", "callfar", "callfar2")
} else {
true
when (target) {
is Subroutine -> !isNestedWithin(target, definingSub)
is BuiltinFunctionPlaceholder -> target.name !in setOf("call", "callfar", "callfar2")
else -> true
}
}
}
+2 -2
View File
@@ -17,7 +17,7 @@ dependencies {
implementation(project(":intermediate"))
implementation(project(":virtualmachine"))
implementation("com.github.ajalt.clikt:clikt:5.1.0")
implementation("com.github.ajalt.mordant:mordant:3.0.2")
implementation("com.github.ajalt.mordant:mordant:3.1.0")
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
testImplementation(project(":codeCore"))
@@ -52,7 +52,7 @@ fi
val marker = "@rem Execute prog8c"
val text = windowsScript.readText()
check(marker in text) { "unexpected Windows launch script layout for prog8c" }
val probe = $$"""@rem Prog8: add --enable-native-access on JVMs that support it (JDK 18+) to avoid JNA restricted-method warnings
val probe = """@rem Prog8: add --enable-native-access on JVMs that support it (JDK 18+) to avoid JNA restricted-method warnings
"%JAVA_EXE%" --enable-native-access=ALL-UNNAMED -version >NUL 2>&1
if not errorlevel 1 set JAVA_OPTS=%JAVA_OPTS% --enable-native-access=ALL-UNNAMED
@@ -0,0 +1,5 @@
%import shared_amiga_diskfont
diskfont {
%option no_symbol_prefixing, ignore_unused
}
@@ -0,0 +1,5 @@
%import shared_amiga_easygfx
easygfx {
%option ignore_unused
}
@@ -0,0 +1,5 @@
%import shared_amiga_emudbg
emudbg {
%option ignore_unused
}
+4 -3
View File
@@ -41,6 +41,7 @@ sys {
pointer @shared IFFParseBase ; loaded from disk on demand
pointer @shared AsyncIOBase ; loaded from disk on demand
pointer @shared LowLevelBase ; kickstart 2.0+, loaded from disk on demand
pointer @shared DiskfontBase ; loaded from disk on demand
pointer @shared TimerBase ; opened on demand
pointer @shared AHIBase ; ahi.device base, opened on demand by ahi.open(); for the low-level ahi.* LVO calls
@@ -156,7 +157,7 @@ sys {
sub die(uword code, str message) {
%asm {{
moveq #0,d0
move.w 14(a5),d0
move.w 12(a5),d0
lea .alert(pc),a1
lea .alert_limit(pc),a3
move.w #20,(a1)+
@@ -168,7 +169,7 @@ sys {
move.b d1,(a1)+
bra.s .copy_prefix
.copy_code:
move.w 14(a5),d2
move.w 12(a5),d2
moveq #3,d4
.copy_code_digit:
move.w d2,d3
@@ -209,7 +210,7 @@ sys {
lea .alert(pc),a0
jsr -90(a6)
moveq #0,d0
move.w 14(a5),d0
move.w 12(a5),d0
jmp sys.exit
even
+8 -4
View File
@@ -113,19 +113,23 @@ _msg:
}
sub print_w(word value) {
vprintf("%d", &value)
fmt_value_w = value as uword
vprintf("%d", &fmt_value_w)
}
sub print_l(long value) {
vprintf("%ld", &value)
fmt_value_l = value
vprintf("%ld", &fmt_value_l)
}
sub print_ulhex(long value, bool prefix) {
vprintf(if prefix then "$%08lx" else "%08lx", &value)
fmt_value_l = value
vprintf(if prefix then "$%08lx" else "%08lx", &fmt_value_l)
}
sub print_uwhex(uword value, bool prefix) {
vprintf(if prefix then "$%04x" else "%04x", &value)
fmt_value_w = value
vprintf(if prefix then "$%04x" else "%04x", &fmt_value_w)
}
sub print_ubhex(ubyte value, bool prefix) {
@@ -0,0 +1,5 @@
%import shared_amiga_diskfont
diskfont {
%option no_symbol_prefixing, ignore_unused
}
@@ -0,0 +1,5 @@
%import shared_amiga_easygfx
easygfx {
%option ignore_unused
}
+5
View File
@@ -0,0 +1,5 @@
%import shared_amiga_emudbg
emudbg {
%option ignore_unused
}
+4 -3
View File
@@ -41,6 +41,7 @@ sys {
pointer @shared IFFParseBase ; kickstart 2.0+, loaded from disk on demand
pointer @shared AsyncIOBase ; loaded from disk on demand
pointer @shared LowLevelBase ; kickstart 2.0+, loaded from disk on demand
pointer @shared DiskfontBase ; loaded from disk on demand
pointer @shared TimerBase ; opened on demand
^^ubyte @shared arguments ; CLI argument string (null-terminated), or NULL if Workbench launch
@@ -164,7 +165,7 @@ sys {
sub die(uword code, str message) {
%asm {{
moveq #0,d0
move.w 14(a5),d0
move.w 12(a5),d0
lea .alert(pc),a1
lea .alert_limit(pc),a3
move.w #20,(a1)+
@@ -176,7 +177,7 @@ sys {
move.b d1,(a1)+
bra.s .copy_prefix
.copy_code:
move.w 14(a5),d2
move.w 12(a5),d2
moveq #3,d4
.copy_code_digit:
move.w d2,d3
@@ -217,7 +218,7 @@ sys {
lea .alert(pc),a0
jsr -90(a6)
moveq #0,d0
move.w 14(a5),d0
move.w 12(a5),d0
jmp sys.exit
even
+1 -1
View File
@@ -58,7 +58,7 @@
;; as `^^MyNode`. Just ensure the allocation is suitably aligned.
;;
;; Custom nodes: you can define your own node struct for use with these
;; routines and with `for node in mylist` loops. The only requirement
;; routines. The only requirement
;; is that the link pointers are the first fields at offset 0. The
;; minimal operations (add_head/add_tail/insert/remove) only need
;; Succ/Pred; enqueue() and find_name() require the full Type/Pri/Name
+376 -81
View File
@@ -5,76 +5,153 @@
math {
%option no_symbol_prefixing, ignore_unused
asmsub sin8u(ubyte angle @A) clobbers(Y) -> ubyte @A {
%asm {{
tay
lda _sinecos8u,y
rts
_sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
; !notreached!
}}
; Sine/cosine lookup tables, one per routine (not shared).
; The tables live at block scope so the inlined
; routines below all reference the same single copy. Tables that end up
; unused (no trig routine called) are removed automatically.
private ubyte[256] sin8u_table = [$80, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad,
$b0, $b3, $b6, $b9, $bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7,
$da, $dc, $de, $e0, $e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4,
$f5, $f6, $f8, $f9, $fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff,
$ff, $ff, $ff, $ff, $fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6,
$f5, $f4, $f3, $f1, $f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc,
$da, $d7, $d5, $d3, $d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3,
$b0, $ad, $aa, $a7, $a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83,
$80, $7c, $79, $76, $73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52,
$4f, $4c, $49, $46, $43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28,
$25, $23, $21, $1f, $1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b,
$0a, $09, $07, $06, $05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00,
$00, $00, $00, $00, $01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09,
$0a, $0b, $0c, $0e, $0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23,
$25, $28, $2a, $2c, $2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c,
$4f, $52, $55, $58, $5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c]
private ubyte[256] cos8u_table = [$ff, $ff, $ff, $ff, $fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6,
$f5, $f4, $f3, $f1, $f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc,
$da, $d7, $d5, $d3, $d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3,
$b0, $ad, $aa, $a7, $a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83,
$80, $7c, $79, $76, $73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52,
$4f, $4c, $49, $46, $43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28,
$25, $23, $21, $1f, $1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b,
$0a, $09, $07, $06, $05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00,
$00, $00, $00, $00, $01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09,
$0a, $0b, $0c, $0e, $0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23,
$25, $28, $2a, $2c, $2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c,
$4f, $52, $55, $58, $5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c,
$7f, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad,
$b0, $b3, $b6, $b9, $bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7,
$da, $dc, $de, $e0, $e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4,
$f5, $f6, $f8, $f9, $fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff]
private ubyte[256] sin8_table = [$00, $03, $06, $09, $0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d,
$30, $33, $36, $39, $3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57,
$59, $5b, $5e, $60, $62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74,
$75, $76, $77, $78, $79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e,
$7f, $7e, $7e, $7e, $7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76,
$75, $74, $72, $71, $70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b,
$59, $57, $55, $52, $50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33,
$30, $2d, $2a, $27, $24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03,
$00, $fd, $fa, $f7, $f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3,
$d0, $cd, $ca, $c7, $c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9,
$a7, $a5, $a2, $a0, $9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c,
$8b, $8a, $89, $88, $87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82,
$81, $82, $82, $82, $82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a,
$8b, $8c, $8e, $8f, $90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5,
$a7, $a9, $ab, $ae, $b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd,
$d0, $d3, $d6, $d9, $dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd]
private ubyte[256] cos8_table = [$7f, $7e, $7e, $7e, $7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76,
$75, $74, $72, $71, $70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b,
$59, $57, $55, $52, $50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33,
$30, $2d, $2a, $27, $24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03,
$00, $fd, $fa, $f7, $f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3,
$d0, $cd, $ca, $c7, $c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9,
$a7, $a5, $a2, $a0, $9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c,
$8b, $8a, $89, $88, $87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82,
$81, $82, $82, $82, $82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a,
$8b, $8c, $8e, $8f, $90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5,
$a7, $a9, $ab, $ae, $b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd,
$d0, $d3, $d6, $d9, $dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd,
$00, $03, $06, $09, $0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d,
$30, $33, $36, $39, $3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57,
$59, $5b, $5e, $60, $62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74,
$75, $76, $77, $78, $79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e]
private ubyte[180] sinr8u_table = [$80, $84, $88, $8d, $91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf,
$c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0,
$f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff, $ff, $ff, $ff,
$fe, $fe, $fd, $fc, $fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7,
$e4, $e1, $de, $db, $d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af,
$ab, $a7, $a3, $9e, $9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69,
$65, $61, $5c, $58, $54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a,
$27, $24, $21, $1e, $1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05,
$04, $03, $02, $01, $01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05,
$06, $08, $09, $0b, $0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a,
$2e, $31, $35, $38, $3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69,
$6e, $72, $77, $7b]
private ubyte[180] cosr8u_table = [$ff, $ff, $ff, $fe, $fe, $fd, $fc, $fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee,
$ec, $e9, $e7, $e4, $e1, $de, $db, $d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb,
$b7, $b3, $af, $ab, $a7, $a3, $9e, $9a, $96, $91, $8d, $88, $84, $80, $7b, $77,
$72, $6e, $69, $65, $61, $5c, $58, $54, $50, $4c, $48, $44, $40, $3c, $38, $35,
$31, $2e, $2a, $27, $24, $21, $1e, $1b, $18, $16, $13, $11, $0f, $0d, $0b, $09,
$08, $06, $05, $04, $03, $02, $01, $01, $00, $00, $00, $00, $00, $01, $01, $02,
$03, $04, $05, $06, $08, $09, $0b, $0d, $0f, $11, $13, $16, $18, $1b, $1e, $21,
$24, $27, $2a, $2e, $31, $35, $38, $3c, $40, $44, $48, $4c, $50, $54, $58, $5c,
$61, $65, $69, $6e, $72, $77, $7b, $7f, $84, $88, $8d, $91, $96, $9a, $9e, $a3,
$a7, $ab, $af, $b3, $b7, $bb, $bf, $c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de,
$e1, $e4, $e7, $e9, $ec, $ee, $f0, $f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd,
$fe, $fe, $ff, $ff]
private ubyte[180] sinr8_table = [$00, $04, $08, $0d, $11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f,
$43, $47, $4a, $4e, $51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70,
$72, $74, $75, $77, $78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7f, $7e, $7e,
$7e, $7d, $7d, $7c, $7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66,
$64, $61, $5e, $5b, $58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f,
$2b, $27, $23, $1e, $1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea,
$e6, $e2, $dd, $d9, $d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac,
$a8, $a5, $a2, $9f, $9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86,
$85, $84, $83, $83, $82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86,
$88, $89, $8b, $8c, $8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac,
$af, $b2, $b6, $b9, $bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea,
$ef, $f3, $f8, $fc]
private ubyte[180] cosr8_table = [$7f, $7e, $7e, $7e, $7d, $7d, $7c, $7b, $7a, $78, $77, $75, $74, $72, $70, $6d,
$6b, $69, $66, $64, $61, $5e, $5b, $58, $54, $51, $4e, $4a, $47, $43, $3f, $3b,
$37, $33, $2f, $2b, $27, $23, $1e, $1a, $16, $11, $0d, $08, $04, $00, $fc, $f8,
$f3, $ef, $ea, $e6, $e2, $dd, $d9, $d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6,
$b2, $af, $ac, $a8, $a5, $a2, $9f, $9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b,
$89, $88, $86, $85, $84, $83, $83, $82, $82, $82, $81, $82, $82, $82, $83, $83,
$84, $85, $86, $88, $89, $8b, $8c, $8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2,
$a5, $a8, $ac, $af, $b2, $b6, $b9, $bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd,
$e2, $e6, $ea, $ef, $f3, $f8, $fc, $00, $04, $08, $0d, $11, $16, $1a, $1e, $23,
$27, $2b, $2f, $33, $37, $3b, $3f, $43, $47, $4a, $4e, $51, $54, $58, $5b, $5e,
$61, $64, $66, $69, $6b, $6d, $70, $72, $74, $75, $77, $78, $7a, $7b, $7c, $7d,
$7d, $7e, $7e, $7e]
inline sub sin8u(ubyte angle) -> ubyte {
return math.sin8u_table[angle]
}
asmsub cos8u(ubyte angle @A) clobbers(Y) -> ubyte @A {
%asm {{
tay
lda sin8u._sinecos8u+64,y
rts
}}
inline sub cos8u(ubyte angle) -> ubyte {
return math.cos8u_table[angle]
}
asmsub sin8(ubyte angle @A) clobbers(Y) -> byte @A {
%asm {{
tay
lda _sinecos8,y
rts
_sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
; !notreached!
}}
inline sub sin8(ubyte angle) -> byte {
return math.sin8_table[angle] as byte
}
asmsub cos8(ubyte angle @A) clobbers(Y) -> byte @A {
%asm {{
tay
lda sin8._sinecos8+64,y
rts
}}
inline sub cos8(ubyte angle) -> byte {
return math.cos8_table[angle] as byte
}
asmsub sinr8u(ubyte radians @A) clobbers(Y) -> ubyte @A {
%asm {{
tay
lda _sinecosR8u,y
rts
_sinecosR8u .byte trunc(128.0 + 127.5 * sin(range(180+45) * rad(360.0/180.0)))
; !notreached!
}}
inline sub sinr8u(ubyte radians) -> ubyte {
return math.sinr8u_table[radians]
}
asmsub cosr8u(ubyte radians @A) clobbers(Y) -> ubyte @A {
%asm {{
tay
lda sinr8u._sinecosR8u+45,y
rts
}}
inline sub cosr8u(ubyte radians) -> ubyte {
return math.cosr8u_table[radians]
}
asmsub sinr8(ubyte radians @A) clobbers(Y) -> byte @A {
%asm {{
tay
lda _sinecosR8,y
rts
_sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
; !notreached!
}}
inline sub sinr8(ubyte radians) -> byte {
return math.sinr8_table[radians] as byte
}
asmsub cosr8(ubyte radians @A) clobbers(Y) -> byte @A {
%asm {{
tay
lda sinr8._sinecosR8+45,y
rts
}}
inline sub cosr8(ubyte radians) -> byte {
return math.cosr8_table[radians] as byte
}
asmsub rnd() clobbers(Y) -> ubyte @A {
@@ -205,6 +282,15 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
}}
}
sub log2l(long value) -> ubyte {
; returns the integer base-2 logarithm of the signed long value (position of the highest set bit)
; returns 0 for value = 0
uword hi = (value >> 16) as uword
if hi != 0
return 16 + log2w(hi)
return log2w(value as uword)
}
asmsub mul16_last_upper() -> uword @AY {
; This routine peeks into the internal 32 bits multiplication result buffer of the
; 16*16 bits multiplication routine, to fetch the upper 16 bits of the last calculation.
@@ -559,6 +645,13 @@ log2_tab
}}
}
sub diffl(long v1, long v2) -> long {
; -- returns the (absolute) difference, or distance, between the two longs
if v1>v2
return v1-v2
return v2-v1
}
sub crc16(^^ubyte data, uword length, uword initvalue, uword xorout) -> uword {
; Calculates the CRC16 checksum of the buffer. You provide the initial start value and the final value that is xored with the result.
; For XMODEM type checksum, use initvalue=0 and xorout=0.
@@ -710,41 +803,243 @@ crchi = cx16.r15H
}
sub lerp(ubyte v0, ubyte v1, ubyte t) -> ubyte {
asmsub lerp(ubyte v0 @A, ubyte v1 @X, ubyte t @Y) clobbers(X, Y) -> ubyte @A {
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
if v1<v0
return v0 - msb(t as uword * (v0 - v1) + 255)
else
return v0 + msb(t as uword * (v1 - v0) + 255)
; original Prog8 implementation:
; if v1<v0
; return v0 - msb(t as uword * (v0 - v1) + 255)
; else
; return v0 + msb(t as uword * (v1 - v0) + 255)
%asm {{
sta P8ZP_SCRATCH_B1 ; v0
sty P8ZP_SCRATCH_W1 ; multiplier = t
txa
sec
sbc P8ZP_SCRATCH_B1 ; v1 - v0
bcs _asc
eor #$ff
clc
adc #1 ; descending: delta = v0 - v1
ldy #1
sty P8ZP_SCRATCH_PTR
jmp _have_delta
_asc:
ldy #0
sty P8ZP_SCRATCH_PTR
_have_delta:
sta P8ZP_SCRATCH_REG ; multiplicand = delta
lda #0
sta P8ZP_SCRATCH_W2 ; product low byte
ldx #8
_ml:
lsr P8ZP_SCRATCH_W1
bcc _noadd
clc
adc P8ZP_SCRATCH_REG
_noadd:
ror a
ror P8ZP_SCRATCH_W2
dex
bne _ml
ldx P8ZP_SCRATCH_W2
beq _noround
clc
adc #1 ; round up: +1 if low byte nonzero
_noround:
ldx P8ZP_SCRATCH_PTR
bne _desc
clc
adc P8ZP_SCRATCH_B1 ; v0 + inc
rts
_desc:
sta P8ZP_SCRATCH_REG
lda P8ZP_SCRATCH_B1
sec
sbc P8ZP_SCRATCH_REG ; v0 - inc
rts
}}
}
sub lerpw(uword v0, uword v1, uword t) -> uword {
asmsub lerpw(uword v0 @R0, uword v1 @R1, uword t @R2) clobbers(A, X, Y) -> uword @R0 {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535. Clobbers R15.
if v1<v0 {
t *= v0-v1
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 - cx16.r15
}
t *= v1-v0
cx16.r15 = math.mul16_last_upper()
if t!=0
cx16.r15++
return v0 + cx16.r15
; guarantees v = v1 when t = 65535
; original Prog8 implementation:
; if v1<v0 {
; t *= v0-v1
; cx16.r15 = math.mul16_last_upper()
; if t!=0
; cx16.r15++
; return v0 - cx16.r15
; }
; t *= v1-v0
; cx16.r15 = math.mul16_last_upper()
; if t!=0
; cx16.r15++
; return v0 + cx16.r15
%asm {{
lda cx16.r1
cmp cx16.r0
lda cx16.r1+1
sbc cx16.r0+1
bcs _asc
lda #1
sta P8ZP_SCRATCH_B1 ; direction = descending
lda cx16.r0
sec
sbc cx16.r1
sta prog8_math.multiply_words.multiplier
lda cx16.r0+1
sbc cx16.r1+1
sta prog8_math.multiply_words.multiplier+1
jmp _have_delta
_asc:
lda #0
sta P8ZP_SCRATCH_B1 ; direction = ascending
lda cx16.r1
sec
sbc cx16.r0
sta prog8_math.multiply_words.multiplier
lda cx16.r1+1
sbc cx16.r0+1
sta prog8_math.multiply_words.multiplier+1
_have_delta:
lda cx16.r2 ; multiplicand = t
ldy cx16.r2+1
jsr prog8_math.multiply_words
lda prog8_math.multiply_words.result
ora prog8_math.multiply_words.result+1
beq _noround
inc prog8_math.multiply_words.result+2
bne _noround
inc prog8_math.multiply_words.result+3
_noround:
lda P8ZP_SCRATCH_B1
bne _desc
clc
lda cx16.r0 ; ascending: v0 + upper word
adc prog8_math.multiply_words.result+2
sta cx16.r0
lda cx16.r0+1
adc prog8_math.multiply_words.result+3
sta cx16.r0+1
rts
_desc:
lda cx16.r0 ; descending: v0 - upper word
sec
sbc prog8_math.multiply_words.result+2
sta cx16.r0
lda cx16.r0+1
sbc prog8_math.multiply_words.result+3
sta cx16.r0+1
rts
}}
}
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
asmsub interpolate(ubyte v @R0, ubyte inputMin @R1, ubyte inputMax @R2, ubyte outputMin @R3, ubyte outputMax @R4) clobbers(X, Y) -> ubyte @A {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
; original Prog8 implementation:
; cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
; cx16.r15 *= (outputMax-outputMin)
; return cx16.r15H + outputMin
%asm {{
lda cx16.r0L
sec
sbc cx16.r1L
sta P8ZP_SCRATCH_REG ; numerator high byte = v - inputMin
lda cx16.r2L
sec
sbc cx16.r1L
sta P8ZP_SCRATCH_W1 ; divisor = inputMax - inputMin
lda cx16.r2L
sta P8ZP_SCRATCH_W2 ; numerator low byte = inputMax
lda #0
sta P8ZP_SCRATCH_PTR ; remainder
ldx #16
_div:
asl P8ZP_SCRATCH_W2
rol P8ZP_SCRATCH_REG
rol P8ZP_SCRATCH_PTR
bcs _sub
lda P8ZP_SCRATCH_PTR
cmp P8ZP_SCRATCH_W1
bcc _no
_sub:
lda P8ZP_SCRATCH_PTR
sec
sbc P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_PTR
inc P8ZP_SCRATCH_W2
_no:
dex
bne _div
lda P8ZP_SCRATCH_W2 ; quotient -> multiply_ubyte_word multiplier
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W1+1
lda cx16.r4L
sec
sbc cx16.r3L ; A = outputMax - outputMin
jsr prog8_math.multiply_ubyte_word
tya ; high byte of product
clc
adc cx16.r3L ; + outputMin
rts
}}
}
asmsub interpolatew(uword v @R0, uword inputMin @R1, uword inputMax @R2, uword outputMin @R3, uword outputMax @R4) clobbers(A, X, Y) -> uword @R0 {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; clobbers cx16.r12-cx16.r15 (multiply/division working registers) and cx16.r1, cx16.r2 (destroyed by the division helper)
; original Prog8 implementation:
; return outputMin + ((v - inputMin) as long * (outputMax - outputMin) as long / (inputMax - inputMin) as long) as uword
%asm {{
lda cx16.r4 ; outputMax - outputMin = multiplier
sec
sbc cx16.r3
sta prog8_math.multiply_words.multiplier
lda cx16.r4+1
sbc cx16.r3+1
sta prog8_math.multiply_words.multiplier+1
lda cx16.r0 ; v - inputMin = multiplicand (in AY)
sec
sbc cx16.r1
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sbc cx16.r1+1
tay
lda P8ZP_SCRATCH_B1
jsr prog8_math.multiply_words
lda prog8_math.multiply_words.result ; product -> r12/r13 (dividend)
sta cx16.r12
lda prog8_math.multiply_words.result+1
sta cx16.r12+1
lda prog8_math.multiply_words.result+2
sta cx16.r13
lda prog8_math.multiply_words.result+3
sta cx16.r13+1
lda cx16.r2 ; inputMax - inputMin = divisor
sec
sbc cx16.r1
sta cx16.r14
lda cx16.r2+1
sbc cx16.r1+1
sta cx16.r14+1
lda #0
sta cx16.r15
sta cx16.r15+1
jsr prog8_math.div_longs
lda cx16.r14 ; + outputMin, keep low word
clc
adc cx16.r3
sta cx16.r0
lda cx16.r14+1
adc cx16.r3+1
sta cx16.r0+1
rts
}}
}
sub gcd(uword @nozp a, uword @nozp b) -> uword {
+1 -1
View File
@@ -1 +1 @@
; no support for BCD
%import shared_amiga_bcd
+2 -1
View File
@@ -103,12 +103,13 @@ audio {
private ubyte[4] queued
private bool[4] play_alt
private bool[4] wait_alt
private ubyte[1] @shared channel_matrix
; ---- high level audio interface ----
sub init() -> bool {
; -- Initialize the audio device on all 4 channels.
ubyte[1] channel_matrix = [15] ; allocate all 4 channels at once
channel_matrix[0] = 15 ; allocate all 4 channels at once
for ubyte channel in 0 to 3 {
active_channels[channel] = false
queued[channel] = 0
+131 -1
View File
@@ -1 +1,131 @@
; no support for BCD
bcd {
%option merge, ignore_unused
asmsub addb(byte a @D0, byte b @D1) clobbers(D1) -> byte @D0 {
%asm {{
andi.b #$ef,ccr
abcd d1,d0
rts
}}
}
asmsub addub(ubyte a @D0, ubyte b @D1) clobbers(D1) -> ubyte @D0 {
%asm {{
andi.b #$ef,ccr
abcd d1,d0
rts
}}
}
asmsub addw(word a @D0, word b @D1) clobbers(D1) -> word @D0 {
%asm {{
andi.b #$ef,ccr
abcd d1,d0
ror.w #8,d0
ror.w #8,d1
abcd d1,d0
ror.w #8,d0
rts
}}
}
asmsub adduw(uword a @D0, uword b @D1) clobbers(D1) -> uword @D0 {
%asm {{
andi.b #$ef,ccr
abcd d1,d0
ror.w #8,d0
ror.w #8,d1
abcd d1,d0
ror.w #8,d0
rts
}}
}
asmsub addl(long a @D0, long b @D1) clobbers(D1) -> long @D0 {
%asm {{
andi.b #$ef,ccr
abcd d1,d0
rept 3
ror.l #8,d0
ror.l #8,d1
abcd d1,d0
endr
ror.l #8,d0
rts
}}
}
asmsub subb(byte a @D0, byte b @D1) clobbers(D1) -> byte @D0 {
%asm {{
andi.b #$ef,ccr
sbcd d1,d0
rts
}}
}
asmsub subub(ubyte a @D0, ubyte b @D1) clobbers(D1) -> ubyte @D0 {
%asm {{
andi.b #$ef,ccr
sbcd d1,d0
rts
}}
}
asmsub subuw(uword a @D0, uword b @D1) clobbers(D1) -> uword @D0 {
%asm {{
andi.b #$ef,ccr
sbcd d1,d0
ror.w #8,d0
ror.w #8,d1
sbcd d1,d0
ror.w #8,d0
rts
}}
}
asmsub subl(long a @D0, long b @D1) clobbers(D1) -> long @D0 {
%asm {{
andi.b #$ef,ccr
sbcd d1,d0
rept 3
ror.l #8,d0
ror.l #8,d1
sbcd d1,d0
endr
ror.l #8,d0
rts
}}
}
asmsub addtol(^^long a @A0, long b @D0) clobbers(D0,D1) {
%asm {{
move.l (a0),d1
andi.b #$ef,ccr
abcd d0,d1
rept 3
ror.l #8,d0
ror.l #8,d1
abcd d0,d1
endr
ror.l #8,d1
move.l d1,(a0)
rts
}}
}
asmsub subfroml(^^long a @A0, long b @D0) clobbers(D0,D1) {
%asm {{
move.l (a0),d1
andi.b #$ef,ccr
sbcd d0,d1
rept 3
ror.l #8,d0
ror.l #8,d1
sbcd d0,d1
endr
ror.l #8,d1
move.l d1,(a0)
rts
}}
}
}
@@ -0,0 +1,65 @@
;; Hand-maintained diskfont.library interface, trimmed to pre-3.0 content (up to Kickstart 2.0).
;; (Outline engine, diskfont control and tagged-font matching need 3.0+ and were removed.)
;; Library base: _DiskfontBase in prog8: sys.DiskfontBase
;; Bank: 13
%import exec
%import graphics
diskfont {
%option merge, no_symbol_prefixing
sub openlib() -> bool {
; not opened at startup: diskfont.library lives on disk (not in ROM),
; so it must be opened on demand and may legitimately fail
sys.DiskfontBase = exec.OpenLibrary("diskfont.library", 0)
return sys.DiskfontBase!=0
}
sub closelib() {
if sys.DiskfontBase!=0 {
exec.CloseLibrary(sys.DiskfontBase)
sys.DiskfontBase = 0
}
}
private graphics.TextAttr[1] openfont_attr ; scratch for openfont()
sub openfont(str name, uword size, ubyte style) -> ^^graphics.TextFont {
; simplified OpenDiskFont with no flags (style e.g. graphics.FS_NORMAL).
; Name is like "helvetica.font". Returns 0 when the font cannot be found.
^^graphics.TextAttr fa = &openfont_attr[0]
fa.Name = name
fa.YSize = size
fa.Style = style
fa.Flags = 0
return OpenDiskFont(fa)
}
extsub @bank 13 -30 = OpenDiskFont(^^graphics.TextAttr textAttr @A0) -> ^^graphics.TextFont @D0
extsub @bank 13 -36 = AvailFonts(pointer buffer @A0, long bufBytes @D0, long flags @D1) -> long @D0
extsub @bank 13 -42 = NewFontContents(pointer fontsLock @A0, str fontName @A1) -> pointer @D0
extsub @bank 13 -48 = DisposeFontContents(pointer fontContentsHeader @A1)
extsub @bank 13 -54 = NewScaledDiskFont(^^graphics.TextFont sourceFont @A0, ^^graphics.TextAttr destTextAttr @A1) -> pointer @D0
; ---- constants ----
const uword MAXFONTPATH = $0100
const uword FCH_ID = $0f00
const uword TFCH_ID = $0f02
const uword DFH_ID = $0f80
const ubyte MAXFONTNAME = $0020
const ubyte AFB_MEMORY = 0
const ubyte AFF_MEMORY = $0001
const ubyte AFB_DISK = 1
const ubyte AFF_DISK = $0002
const ubyte AFB_SCALED = 2
const ubyte AFF_SCALED = $0004
const ubyte AFB_BITMAP = 3
const ubyte AFF_BITMAP = $0008
const ubyte AFB_OTAG = 4
const ubyte AFF_OTAG = $0010
const ubyte AFB_CHARSET = 5
const ubyte AFF_CHARSET = $0020
const ubyte AFB_TYPE = 6
const ubyte AFF_TYPE = $0040
}
@@ -0,0 +1,407 @@
; easygfx - friendly OS graphics wrapper, shared by the amiga500 and amiga1200 targets.
;
; Hides NewScreen/NewWindow boilerplate behind a gfx_lores-style API:
; per-call pen, client-relative word coordinates, explicit open/close.
; Kickstart 1.3 compatible: uses only OpenScreen/OpenWindow (no taglists,
; no AllocBitMap, no DisplayID), except fillto() which needs SetOutlinePen (3.0+).
; All rasterization is done by
; graphics.library; filled ellipses and flood fills use AreaEllipse/Flood
; with a chip-ram TmpRas scratch buffer allocated when the window opens.
; No double buffering (use two screens/windows or raw calls for that).
; Use rastport for direct graphics.library calls, window to pump IDCMP.
%import intuition
%import graphics
%import strings
%import exec
easygfx {
%option merge, ignore_unused
uword width ; drawable client size, set by fullscreen()/windowed()
uword height
ubyte depth ; screen depth (0 = workbench default, unknown)
^^intuition.Window window
^^graphics.RastPort rastport
private ^^intuition.Screen screen
private word origin_x ; window border offset added to all coordinates
private word origin_y
private bool is_custom ; true when we opened our own screen
private ubyte aspect ; x-radius scale for non-square pixels: 0=halve, 1=as-is, 2=double, 4=quadruple
; chip-ram scratch for Flood()/AreaEnd(); the control structs may live anywhere
private graphics.TmpRas[1] tmpras_store
private graphics.AreaInfo[1] area_store
private graphics.TextAttr[1] font_store
private ubyte[20] area_vectors ; 5 bytes per vector, room for 4
private pointer tmpras_buf
private long tmpras_size
sub fullscreen(uword w, uword h, ubyte ndepth, uword viewmodes, str title) -> bool {
; fullscreen custom screen + backdrop borderless window.
; viewmodes: 0 = lores, graphics.HIRES for 640px, + graphics.INTERLACE for interlaced (doubled height).
if w == 0 or h == 0
return false
if rastport != 0
close()
bool untitled = title == 0
if not untitled
untitled = title[0] == 0
; NOTE: struct literals must be constant, so allocate zeroed and patch fields.
^^intuition.NewScreen ns = [0, 0, 0, 0, 0, ; edges, size, depth
0, 1, 0, intuition.CUSTOMSCREEN, ; pens, modes, type
0, 0, 0, 0] ; font, title, gadgets, bitmap
ns.Width = w as word
ns.Height = h as word
ns.Depth = ndepth
ns.ViewModes = viewmodes
if not untitled
ns.DefaultTitle = title
; explicit Topaz 8 screen font (ROM-resident, no diskfont needed)
^^graphics.TextAttr fa = &font_store[0]
fa.Name = "topaz.font"
fa.YSize = 8
fa.Style = 0
fa.Flags = 0
ns.Font = fa
screen = intuition.OpenScreen(ns)
if screen == 0
return false
; bar policy is applied below once the window exists: empty title hides
; the bar for true fullscreen, otherwise the window opens below it.
; null and "" both count as titleless.
word bar = 0
if not untitled {
bar = screen.WBorTop
if h - bar <= 0 {
void intuition.CloseScreen(screen)
screen = 0
return false
}
}
^^intuition.NewWindow nw = [0, 0, 0, 0, ; edges, size
0, 1, ; pens
; RAWKEY only, never add VANILLAKEY here: with both flags set Intuition
; diverts mappable keys to the vanilla channel and starves the raw one,
; so cursor keys (and on Kickstart 1.3, all keys) stop arriving as rawkey.
; Map raw codes yourself instead; ESC is $45, cursor keys are $4c-$4f.
intuition.IDCMP_MOUSEBUTTONS | intuition.IDCMP_RAWKEY,
intuition.WFLG_BACKDROP | intuition.WFLG_BORDERLESS | intuition.WFLG_ACTIVATE | intuition.WFLG_RMBTRAP | intuition.WFLG_NOCAREREFRESH,
0, 0, 0, 0, ; no gadgets, default checkmark, title, screen
0, 0, 0, 0, 0, intuition.CUSTOMSCREEN] ; bitmap, limits, type
nw.TopEdge = bar
nw.Width = w as word
nw.Height = h - bar
nw.Screen = screen
window = intuition.OpenWindow(nw)
if window == 0 {
void intuition.CloseScreen(screen)
screen = 0
return false
}
depth = ndepth
intuition.ActivateWindow(window)
finish_open(true, bar)
width = w
height = (h - bar) as uword
graphics.SetDrMd(rastport, graphics.RP_JAM1)
if untitled {
; ShowTitle(FALSE) renders the bar behind all windows; do it with
; the window present so the redisplay pass sees the real layers.
intuition.ShowTitle(screen, false)
clear(0) ; wipe the hidden bar's leftover pixels
}
return true
}
sub windowed(uword x, uword y, uword w, uword h, str title) -> bool {
; fixed-size window on the Workbench screen at position (x,y). Works everywhere,
; but depth/palette are the Workbench's: use palette() sparingly.
if w == 0 or h == 0
return false
if rastport != 0
close()
screen = 0
^^intuition.NewWindow nw = [0, 0, 0, 0, ; edges, size
255, 255, ; pens
; RAWKEY only, see fullscreen() for why.
intuition.IDCMP_CLOSEWINDOW | intuition.IDCMP_REFRESHWINDOW | intuition.IDCMP_MOUSEBUTTONS | intuition.IDCMP_RAWKEY,
intuition.WFLG_CLOSEGADGET | intuition.WFLG_DRAGBAR | intuition.WFLG_DEPTHGADGET | intuition.WFLG_ACTIVATE,
0, 0, 0, 0, ; no gadgets, default checkmark, title, screen
0, 0, 0, 0, 0, intuition.WBENCHSCREEN] ; bitmap, limits, type
nw.LeftEdge = x as word
nw.TopEdge = y as word
nw.Width = w as word
nw.Height = h as word
nw.Title = title
window = intuition.OpenWindow(nw)
if window == 0
return false
depth = 0
intuition.ActivateWindow(window)
finish_open(false, 0)
graphics.SetDrMd(rastport, graphics.RP_JAM1)
return true
}
sub close() {
; close window and screen (if ours), back to Workbench/textmode
if window != 0 {
intuition.CloseWindow(window)
window = 0
rastport = 0
}
if is_custom and screen != 0 {
void intuition.CloseScreen(screen)
}
screen = 0
is_custom = false
width = 0
height = 0
aspect = 1
if tmpras_buf != 0 {
exec.FreeMem(tmpras_buf, tmpras_size)
tmpras_buf = 0
tmpras_size = 0
}
}
sub clear(ubyte pen) {
; RectFill, not SetRast: SetRast blasts the whole bitmap, borders included
if width == 0 or height == 0
return
graphics.SetAPen(rastport, pen)
graphics.RectFill(rastport, origin_x, origin_y, (origin_x + width) - 1, (origin_y + height) - 1)
}
sub plot(word x, word y, ubyte pen) {
graphics.SetAPen(rastport, pen)
void graphics.WritePixel(rastport, origin_x+x, origin_y+y)
}
sub line(word x1, word y1, word x2, word y2, ubyte pen) {
graphics.SetAPen(rastport, pen)
graphics.Move(rastport, origin_x+x1, origin_y+y1)
graphics.Draw(rastport, origin_x+x2, origin_y+y2)
}
sub rect(word x, word y, word w, word h, ubyte pen) {
if w <= 0 or h <= 0
return
graphics.SetAPen(rastport, pen)
word xa = origin_x+x
word ya = origin_y+y
word x2 = xa+w-1
word y2 = ya+h-1
graphics.Move(rastport, xa, ya)
graphics.Draw(rastport, x2, ya)
graphics.Draw(rastport, x2, y2)
graphics.Draw(rastport, xa, y2)
graphics.Draw(rastport, xa, ya)
}
sub fillrect(word x, word y, word w, word h, ubyte pen) {
if w <= 0 or h <= 0
return
graphics.SetAPen(rastport, pen)
graphics.RectFill(rastport, origin_x+x, origin_y+y, origin_x+x+w-1, origin_y+y+h-1)
}
sub circle(word cx, word cy, word radius, ubyte pen) {
if radius == 0 {
plot(cx, cy, pen)
return
}
if radius < 0
return
graphics.SetAPen(rastport, pen)
graphics.DrawEllipse(rastport, origin_x+cx, origin_y+cy, aspect_rx(radius), radius)
}
sub oval(word cx, word cy, word rx, word ry, ubyte pen) {
if rx <= 0 or ry <= 0
return
graphics.SetAPen(rastport, pen)
graphics.DrawEllipse(rastport, origin_x+cx, origin_y+cy, aspect_rx(rx), ry)
}
sub disc(word cx, word cy, word radius, ubyte pen) {
; filled circle via the library area-fill
if radius == 0 {
plot(cx, cy, pen)
return
}
if radius < 0
return
graphics.SetAPen(rastport, pen)
do_area_ellipse(cx, cy, aspect_rx(radius), radius)
}
sub filled_oval(word cx, word cy, word rx, word ry, ubyte pen) {
; filled ellipse via the library area-fill
if rx <= 0 or ry <= 0
return
graphics.SetAPen(rastport, pen)
do_area_ellipse(cx, cy, aspect_rx(rx), ry)
}
sub text(word x, word y, str s, ubyte pen) {
; NOTE: on Kickstart 1.3 a single Text() call is limited to ~1008 pixels wide
graphics.SetAPen(rastport, pen)
graphics.Move(rastport, origin_x+x, origin_y+y)
void graphics.Text(rastport, s, strings.length(s))
}
inline sub drawmode(ubyte mode) {
; graphics.RP_JAM1, RP_JAM2, RP_COMPLEMENT, RP_INVERSVID
graphics.SetDrMd(easygfx.rastport, mode)
}
sub palette(pointer colors, uword count) {
; LoadRGB4 4-bit nibble table into the screen colormap
pointer vp = viewport()
if vp != 0
graphics.LoadRGB4(vp, colors, count as word)
}
sub setcolor(ubyte index, ubyte red, ubyte green, ubyte blue) {
; tweak a single colormap pen (4-bit nibbles)
pointer vp = viewport()
if vp != 0
graphics.SetRGB4(vp, index, red, green, blue)
}
sub scrollrect(word dx, word dy, word x, word y, word w, word h) {
; scroll a client-relative rectangle
; the rectangle should lie inside the client area.
if w <= 0 or h <= 0
return
graphics.ScrollRaster(rastport, dx, dy, origin_x+x, origin_y+y, origin_x+x+w-1, origin_y+y+h-1)
}
inline sub readpixel(word x, word y) -> long {
; pen number at the pixel, or -1 when outside
return graphics.ReadPixel(easygfx.rastport, easygfx.origin_x+x, easygfx.origin_y+y)
}
inline sub textwidth(str s) -> word {
; raster width of the string in the current font, e.g. to center text
return graphics.TextLength(easygfx.rastport, s, strings.length(s))
}
sub mouse_xy(^^intuition.IntuiMessage msg) -> word, word {
; IDCMP cursor as client-relative coords (msg.MouseX/Y include the window borders)
if msg == 0
return -1, -1
return msg.MouseX - origin_x, msg.MouseY - origin_y
}
sub imsg_wait() -> ^^intuition.IntuiMessage {
; block until the next IDCMP message arrives on our window
void exec.WaitPort(window.UserPort)
return exec.GetMsg(window.UserPort) as ^^intuition.IntuiMessage
}
sub imsg_reply(^^intuition.IntuiMessage msg) -> ^^intuition.IntuiMessage {
; reply the message and fetch the next one (0 when drained)
exec.ReplyMsg(msg as ^^exec.Message)
return exec.GetMsg(window.UserPort) as ^^intuition.IntuiMessage
}
sub fill(word x, word y, ubyte pen) {
; bucket fill: recolor the connected region holding (x,y)
do_flood(x, y, pen, 1)
}
sub fillto(word x, word y, ubyte pen, ubyte outline) {
; boundary fill: fill until the outline-colored border
void graphics.SetOutlinePen(rastport, outline)
do_flood(x, y, pen, 0)
}
private sub aspect_rx(word rx) -> word {
; x-radius corrected for non-square pixels so circles stay round
when aspect {
2 -> return rx*2
4 -> return rx*4
0 -> return rx/2
else -> return rx
}
}
private sub viewport() -> pointer {
^^intuition.Screen s = screen
if s == 0 and window != 0
s = window.WScreen
if s == 0
return 0
return intuition.GetScreenViewPort(s)
}
private sub do_flood(word x, word y, ubyte pen, long mode) {
if rastport == 0 or tmpras_buf == 0
return
graphics.SetAPen(rastport, pen)
^^graphics.TmpRas saved = rastport.TmpRas
^^graphics.TmpRas tp = &tmpras_store[0]
void graphics.InitTmpRas(tp, tmpras_buf, tmpras_size)
rastport.TmpRas = tp
void graphics.Flood(rastport, mode, origin_x+x, origin_y+y)
rastport.TmpRas = saved
}
private sub finish_open(bool custom, word bar) {
rastport = window.RPort
origin_x = window.BorderLeft
origin_y = window.BorderTop
width = (window.Width - origin_x - window.BorderRight) as uword
height = (window.Height - origin_y - window.BorderBottom) as uword
; pixel shape from the screen's ViewPort.Modes (big-endian word at offset 32)
aspect = 1
pointer vp = viewport()
if vp != 0 {
uword modes = (@(vp+32) as uword) << 8 | @(vp+33)
if (modes & graphics.SUPERHIRES) != 0 {
aspect = 4
if (modes & graphics.INTERLACE) != 0
aspect = 2
} else {
if (modes & graphics.V_HIRES) != 0 and (modes & graphics.INTERLACE) == 0
aspect = 2
if (modes & graphics.INTERLACE) != 0 and (modes & graphics.V_HIRES) == 0
aspect = 0
}
}
is_custom = custom
; bar rows the window didn't move past (backdrop positioning
; isn't always honored) still count as client offset
word extra = bar - window.TopEdge
if extra > 0
origin_y += extra
; one-plane Flood scratch sized to the rastport's bitmap
; (Flood requires the TmpRas to cover the whole bitmap, not just the client area)
if rastport.BitMap != 0 {
tmpras_size = rastport.BitMap.BytesPerRow * rastport.BitMap.Rows
tmpras_buf = exec.AllocMem(tmpras_size, exec.MEMF_CHIP)
}
}
private sub do_area_ellipse(word cx, word cy, word rx, word ry) {
if rastport == 0 or tmpras_buf == 0
return
^^graphics.TmpRas saved_tr = rastport.TmpRas
^^graphics.AreaInfo saved_ai = rastport.AreaInfo
^^graphics.TmpRas tp = &tmpras_store[0]
^^graphics.AreaInfo ai = &area_store[0]
void graphics.InitTmpRas(tp, tmpras_buf, tmpras_size)
rastport.TmpRas = tp
graphics.InitArea(ai, area_vectors, 4)
rastport.AreaInfo = ai
if graphics.AreaEllipse(rastport, origin_x+cx, origin_y+cy, rx, ry) == 0
void graphics.AreaEnd(rastport)
rastport.TmpRas = saved_tr
rastport.AreaInfo = saved_ai
}
}
@@ -0,0 +1,158 @@
; Emulator debug interface for the Amiga emulator Copperline (with partial WinUAE support).
; console_write, console_nl and exitemu use the WinUAE-compatible uaelib trap (function 86/13)
; and work on both WinUAE and Copperline. The remaining routines (console_chrout, console_value_*,
; console_hex_*, debug_arg, debug_arg_str, debug_printf) use Copperline-specific debug ports
; at $BFFF00/$BFFF04 and only work on Copperline.
; Debug output appears on the emulator's host console (as 'DBG:' lines) and in the debugger console.
; Docs: https://copperline.dev/docs/run#debugging
;
; WARNING: every routine here first probes the uaelib trap area at $F0FF60 to detect
; the emulator. That probe is harmless inside the emulator, but on real Amiga hardware
; the probe can hang the machine (A500/A2000 have no bus timeout) or cause a bus error
; (A1200). Only run code using this module inside an emulator, not on real hardware.
emudbg {
%option merge, ignore_unused
asmsub is_emulator() clobbers (D1) -> bool @D0 {
; the uaelib trap stub at $F0FF60 starts with a JSR instruction ($4EB9);
; WinUAE's alternative A-line form starts with $A00E
%asm {{
moveq #0,d0
move.w $f0ff60,d1
cmp.w #$4eb9,d1
beq.s .present
cmp.w #$a00e,d1
bne.s .done
.present
moveq #1,d0
.done
rts
}}
}
sub exitemu() {
; ask the emulator to terminate the session (uaelib ExitEmu, function 13):
; the window closes / a headless run stops and the emulator process exits with status 0
if is_emulator()
call_trap(13)
}
sub debug_arg(long value) {
; queue one 32-bit printf argument at the $BFFF00 port; consume it later with
; debug_printf(). At most 32 arguments can be queued for a single message.
if is_emulator()
write_arg(value)
}
sub debug_arg_str(str text) {
; queue a pointer to a NUL-terminated string as printf argument, for the %s conversion
if is_emulator()
write_arg_text(text)
}
sub debug_printf(str format) {
; write a pointer to a NUL-terminated printf-style format string to the $BFFF04 port:
; this prints one message with the queued arguments and clears the argument queue.
; Conversions: %d %i %u %x %X %o %p %c %s %b, use 'l' for 32-bit values, %% is a literal percent sign.
; Without 'l', the integer conversions use the low 16 bits of the argument (signed for %d/%i).
if is_emulator()
write_format(format)
}
sub console_write(str text) {
; write a string to the emulator's debug console (WinUAE-compatible via trap 86).
; Works on both WinUAE and Copperline. Adds a trailing newline.
if is_emulator()
write_trap_console(text)
}
sub console_nl() {
; write a newline to the emulator's debug console (WinUAE-compatible via trap 86)
if is_emulator()
write_trap_console("")
}
sub console_chrout(ubyte char) {
; write a single character to the emulator's debug console
if is_emulator() {
write_arg(char)
write_format("%c")
}
}
sub console_value_word(word value) {
; write a signed 16-bit value as decimal to the debug console, followed by a newline
if is_emulator() {
write_arg(value)
write_format("%d\n")
}
}
sub console_value_long(long value) {
; write a signed 32-bit value as decimal to the debug console, followed by a newline
if is_emulator() {
write_arg(value)
write_format("%ld\n")
}
}
sub console_hex_word(uword value) {
; write a 16-bit value as 4 hex digits to the debug console, followed by a newline
if is_emulator() {
write_arg(value)
write_format("%04x\n")
}
}
sub console_hex_long(long value) {
; write a 32-bit value as 8 hex digits to the debug console, followed by a newline
if is_emulator() {
write_arg(value)
write_format("%08lx\n")
}
}
private asmsub write_arg(long value @D0) {
%asm {{
move.l d0,$bfff00
rts
}}
}
private asmsub write_arg_text(str text @D0) {
%asm {{
move.l d0,$bfff00
rts
}}
}
private asmsub write_format(str format @D0) {
%asm {{
move.l d0,$bfff04
rts
}}
}
private asmsub write_trap_console(str text @D0) clobbers (D0) {
; WinUAE-compatible debug output via the uaelib trap (function 86).
; Prints "DBG: %s\n" to the host console. Works on both WinUAE and Copperline.
%asm {{
move.l d0,-(sp)
move.l #86,-(sp)
jsr $f0ff60
addq.l #8,sp
rts
}}
}
private asmsub call_trap(long functionNumber @D0) clobbers (D0) {
; call the uaelib trap like a C function: the function number is the first stack argument
%asm {{
move.l d0,-(sp)
jsr $f0ff60
addq.l #4,sp
rts
}}
}
}
@@ -8,6 +8,15 @@
graphics {
%option merge, no_symbol_prefixing
; Friendly aliases for the ViewPort.Modes bits from view.h.
; The NDK .i files only provide the V_-prefixed EQU variants
; (V_HIRES, V_LACE); these match the names used in C examples.
const uword HIRES = $8000
const uword LORES = 0
const ubyte LACE = $0004
const ubyte SUPERHIRES = $20
extsub @bank 3 -30 = BltBitMap(^^BitMap srcBitMap @A0, word xSrc @D0, word ySrc @D1, ^^BitMap destBitMap @A1, word xDest @D2, word yDest @D3, word xSize @D4, word ySize @D5, ubyte minterm @D6, ubyte mask @D7, pointer tempA @A2) -> long @D0
extsub @bank 3 -36 = BltTemplate(long source @A0, word xSrc @D0, word srcMod @D1, ^^RastPort destRP @A1, word xDest @D2, word yDest @D3, word xSize @D4, word ySize @D5)
extsub @bank 3 -42 = ClearEOL(^^RastPort rp @A1)
+7 -5
View File
@@ -66,13 +66,11 @@
;; allocation is suitably aligned (long-aligned on m68k).
;;
;; Custom nodes: you can define your own node struct for use with the
;; exec/lists routines and with `for node in mylist` loops. The only
;; exec/lists routines. The only
;; requirement is that the link pointers are the first fields at offset
;; 0 (Succ at 0, Pred at 4 on 32-bit amiga). Minimal operations only
;; need Succ/Pred; enqueue() and find_name() require the full
;; Type/Pri/Name layout of exec.Node / lists.FullNode. Define the list
;; header with your node type so `for`-iteration infers the correct
;; type. Example:
;; Type/Pri/Name layout of exec.Node / lists.FullNode. Example:
;;
;; main {
;; struct MyNode {
@@ -85,7 +83,7 @@
;; }
;; struct MyList {
;; ^^MyNode Head
;; pointer Tail
;; ^^MyNode Tail
;; ^^MyNode TailPred
;; }
;; sub start() {
@@ -95,6 +93,10 @@
;; exec.NewList(mylist as ^^exec.List) ; or lists.init(mylist as pointer)
;; lists.add_tail(mylist as pointer, n1 as pointer)
;; lists.add_tail(mylist as pointer, n2 as pointer)
;; for node in mylist.Head via .Succ until &mylist.Tail {
;; ; the last node's Succ is &mylist.Tail, not 0
;; ; use node
;; }
;; ; or directly: exec.AddTail(mylist as pointer, n1 as pointer)
;; }
;; }
+640 -145
View File
@@ -5,161 +5,153 @@
math {
%option merge, ignore_unused, no_symbol_prefixing
sub sin8u(ubyte angle) -> ubyte {
ubyte[256] sintab = [$80, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad, $b0, $b3, $b6, $b9,
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff, $ff, $ff, $ff, $ff,
$fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6, $f5, $f4, $f3, $f1,
$f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc, $da, $d7, $d5, $d3,
$d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3, $b0, $ad, $aa, $a7,
$a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83, $80, $7c, $79, $76,
$73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52, $4f, $4c, $49, $46,
$43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28, $25, $23, $21, $1f,
$1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b, $0a, $09, $07, $06,
$05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00,
$01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09, $0a, $0b, $0c, $0e,
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c]
return sintab[angle]
; Sine/cosine lookup tables, one per routine (not shared).
; The tables live at block scope so the inlined
; routines below all reference the same single copy. Tables that end up
; unused (no trig routine called) are removed automatically.
private ubyte[256] sin8u_table = [$80, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad,
$b0, $b3, $b6, $b9, $bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7,
$da, $dc, $de, $e0, $e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4,
$f5, $f6, $f8, $f9, $fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff,
$ff, $ff, $ff, $ff, $fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6,
$f5, $f4, $f3, $f1, $f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc,
$da, $d7, $d5, $d3, $d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3,
$b0, $ad, $aa, $a7, $a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83,
$80, $7c, $79, $76, $73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52,
$4f, $4c, $49, $46, $43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28,
$25, $23, $21, $1f, $1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b,
$0a, $09, $07, $06, $05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00,
$00, $00, $00, $00, $01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09,
$0a, $0b, $0c, $0e, $0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23,
$25, $28, $2a, $2c, $2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c,
$4f, $52, $55, $58, $5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c]
private ubyte[256] cos8u_table = [$ff, $ff, $ff, $ff, $fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6,
$f5, $f4, $f3, $f1, $f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc,
$da, $d7, $d5, $d3, $d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3,
$b0, $ad, $aa, $a7, $a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83,
$80, $7c, $79, $76, $73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52,
$4f, $4c, $49, $46, $43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28,
$25, $23, $21, $1f, $1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b,
$0a, $09, $07, $06, $05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00,
$00, $00, $00, $00, $01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09,
$0a, $0b, $0c, $0e, $0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23,
$25, $28, $2a, $2c, $2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c,
$4f, $52, $55, $58, $5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c,
$7f, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad,
$b0, $b3, $b6, $b9, $bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7,
$da, $dc, $de, $e0, $e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4,
$f5, $f6, $f8, $f9, $fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff]
private ubyte[256] sin8_table = [$00, $03, $06, $09, $0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d,
$30, $33, $36, $39, $3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57,
$59, $5b, $5e, $60, $62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74,
$75, $76, $77, $78, $79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e,
$7f, $7e, $7e, $7e, $7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76,
$75, $74, $72, $71, $70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b,
$59, $57, $55, $52, $50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33,
$30, $2d, $2a, $27, $24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03,
$00, $fd, $fa, $f7, $f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3,
$d0, $cd, $ca, $c7, $c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9,
$a7, $a5, $a2, $a0, $9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c,
$8b, $8a, $89, $88, $87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82,
$81, $82, $82, $82, $82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a,
$8b, $8c, $8e, $8f, $90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5,
$a7, $a9, $ab, $ae, $b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd,
$d0, $d3, $d6, $d9, $dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd]
private ubyte[256] cos8_table = [$7f, $7e, $7e, $7e, $7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76,
$75, $74, $72, $71, $70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b,
$59, $57, $55, $52, $50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33,
$30, $2d, $2a, $27, $24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03,
$00, $fd, $fa, $f7, $f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3,
$d0, $cd, $ca, $c7, $c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9,
$a7, $a5, $a2, $a0, $9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c,
$8b, $8a, $89, $88, $87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82,
$81, $82, $82, $82, $82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a,
$8b, $8c, $8e, $8f, $90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5,
$a7, $a9, $ab, $ae, $b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd,
$d0, $d3, $d6, $d9, $dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd,
$00, $03, $06, $09, $0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d,
$30, $33, $36, $39, $3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57,
$59, $5b, $5e, $60, $62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74,
$75, $76, $77, $78, $79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e]
private ubyte[180] sinr8u_table = [$80, $84, $88, $8d, $91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf,
$c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0,
$f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff, $ff, $ff, $ff,
$fe, $fe, $fd, $fc, $fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7,
$e4, $e1, $de, $db, $d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af,
$ab, $a7, $a3, $9e, $9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69,
$65, $61, $5c, $58, $54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a,
$27, $24, $21, $1e, $1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05,
$04, $03, $02, $01, $01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05,
$06, $08, $09, $0b, $0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a,
$2e, $31, $35, $38, $3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69,
$6e, $72, $77, $7b]
private ubyte[180] cosr8u_table = [$ff, $ff, $ff, $fe, $fe, $fd, $fc, $fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee,
$ec, $e9, $e7, $e4, $e1, $de, $db, $d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb,
$b7, $b3, $af, $ab, $a7, $a3, $9e, $9a, $96, $91, $8d, $88, $84, $80, $7b, $77,
$72, $6e, $69, $65, $61, $5c, $58, $54, $50, $4c, $48, $44, $40, $3c, $38, $35,
$31, $2e, $2a, $27, $24, $21, $1e, $1b, $18, $16, $13, $11, $0f, $0d, $0b, $09,
$08, $06, $05, $04, $03, $02, $01, $01, $00, $00, $00, $00, $00, $01, $01, $02,
$03, $04, $05, $06, $08, $09, $0b, $0d, $0f, $11, $13, $16, $18, $1b, $1e, $21,
$24, $27, $2a, $2e, $31, $35, $38, $3c, $40, $44, $48, $4c, $50, $54, $58, $5c,
$61, $65, $69, $6e, $72, $77, $7b, $7f, $84, $88, $8d, $91, $96, $9a, $9e, $a3,
$a7, $ab, $af, $b3, $b7, $bb, $bf, $c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de,
$e1, $e4, $e7, $e9, $ec, $ee, $f0, $f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd,
$fe, $fe, $ff, $ff]
private ubyte[180] sinr8_table = [$00, $04, $08, $0d, $11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f,
$43, $47, $4a, $4e, $51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70,
$72, $74, $75, $77, $78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7f, $7e, $7e,
$7e, $7d, $7d, $7c, $7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66,
$64, $61, $5e, $5b, $58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f,
$2b, $27, $23, $1e, $1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea,
$e6, $e2, $dd, $d9, $d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac,
$a8, $a5, $a2, $9f, $9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86,
$85, $84, $83, $83, $82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86,
$88, $89, $8b, $8c, $8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac,
$af, $b2, $b6, $b9, $bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea,
$ef, $f3, $f8, $fc]
private ubyte[180] cosr8_table = [$7f, $7e, $7e, $7e, $7d, $7d, $7c, $7b, $7a, $78, $77, $75, $74, $72, $70, $6d,
$6b, $69, $66, $64, $61, $5e, $5b, $58, $54, $51, $4e, $4a, $47, $43, $3f, $3b,
$37, $33, $2f, $2b, $27, $23, $1e, $1a, $16, $11, $0d, $08, $04, $00, $fc, $f8,
$f3, $ef, $ea, $e6, $e2, $dd, $d9, $d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6,
$b2, $af, $ac, $a8, $a5, $a2, $9f, $9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b,
$89, $88, $86, $85, $84, $83, $83, $82, $82, $82, $81, $82, $82, $82, $83, $83,
$84, $85, $86, $88, $89, $8b, $8c, $8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2,
$a5, $a8, $ac, $af, $b2, $b6, $b9, $bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd,
$e2, $e6, $ea, $ef, $f3, $f8, $fc, $00, $04, $08, $0d, $11, $16, $1a, $1e, $23,
$27, $2b, $2f, $33, $37, $3b, $3f, $43, $47, $4a, $4e, $51, $54, $58, $5b, $5e,
$61, $64, $66, $69, $6b, $6d, $70, $72, $74, $75, $77, $78, $7a, $7b, $7c, $7d,
$7d, $7e, $7e, $7e]
inline sub sin8u(ubyte angle) -> ubyte {
return math.sin8u_table[angle]
}
sub cos8u(ubyte angle) -> ubyte {
ubyte[256] costab = [$ff, $ff, $ff, $ff,
$fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6, $f5, $f4, $f3, $f1,
$f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc, $da, $d7, $d5, $d3,
$d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3, $b0, $ad, $aa, $a7,
$a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83, $80, $7c, $79, $76,
$73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52, $4f, $4c, $49, $46,
$43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28, $25, $23, $21, $1f,
$1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b, $0a, $09, $07, $06,
$05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00,
$01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09, $0a, $0b, $0c, $0e,
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c, $7f, $83, $86, $89,
$8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad, $b0, $b3, $b6, $b9,
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff ]
return costab[angle]
inline sub cos8u(ubyte angle) -> ubyte {
return math.cos8u_table[angle]
}
sub sin8(ubyte angle) -> byte {
ubyte[256] sintab = [
$00, $03, $06, $09,
$0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d, $30, $33, $36, $39,
$3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57, $59, $5b, $5e, $60,
$62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74, $75, $76, $77, $78,
$79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e, $7f, $7e, $7e, $7e,
$7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76, $75, $74, $72, $71,
$70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b, $59, $57, $55, $52,
$50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33, $30, $2d, $2a, $27,
$24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03, $00, $fd, $fa, $f7,
$f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3, $d0, $cd, $ca, $c7,
$c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9, $a7, $a5, $a2, $a0,
$9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c, $8b, $8a, $89, $88,
$87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82, $81, $82, $82, $82,
$82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a, $8b, $8c, $8e, $8f,
$90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5, $a7, $a9, $ab, $ae,
$b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd, $d0, $d3, $d6, $d9,
$dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd
]
return sintab[angle] as byte
inline sub sin8(ubyte angle) -> byte {
return math.sin8_table[angle] as byte
}
sub cos8(ubyte angle) -> byte {
ubyte[256] costab = [
$7f, $7e, $7e, $7e,
$7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76, $75, $74, $72, $71,
$70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b, $59, $57, $55, $52,
$50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33, $30, $2d, $2a, $27,
$24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03, $00, $fd, $fa, $f7,
$f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3, $d0, $cd, $ca, $c7,
$c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9, $a7, $a5, $a2, $a0,
$9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c, $8b, $8a, $89, $88,
$87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82, $81, $82, $82, $82,
$82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a, $8b, $8c, $8e, $8f,
$90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5, $a7, $a9, $ab, $ae,
$b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd, $d0, $d3, $d6, $d9,
$dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd, $00, $03, $06, $09,
$0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d, $30, $33, $36, $39,
$3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57, $59, $5b, $5e, $60,
$62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74, $75, $76, $77, $78,
$79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e
]
return costab[angle] as byte
inline sub cos8(ubyte angle) -> byte {
return math.cos8_table[angle] as byte
}
sub sinr8u(ubyte radians) -> ubyte {
ubyte[180] sintab = [
$80, $84, $88, $8d,
$91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf, $c3, $c7, $ca, $ce,
$d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0, $f2, $f4, $f6, $f7,
$f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff, $ff, $ff, $ff, $fe, $fe, $fd, $fc,
$fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7, $e4, $e1, $de, $db,
$d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af, $ab, $a7, $a3, $9e,
$9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69, $65, $61, $5c, $58,
$54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a, $27, $24, $21, $1e,
$1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05, $04, $03, $02, $01,
$01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05, $06, $08, $09, $0b,
$0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a, $2e, $31, $35, $38,
$3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69, $6e, $72, $77, $7b]
return sintab[radians]
inline sub sinr8u(ubyte radians) -> ubyte {
return math.sinr8u_table[radians]
}
sub cosr8u(ubyte radians) -> ubyte {
ubyte[180] costab = [
$ff, $ff, $ff, $fe, $fe, $fd, $fc,
$fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7, $e4, $e1, $de, $db,
$d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af, $ab, $a7, $a3, $9e,
$9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69, $65, $61, $5c, $58,
$54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a, $27, $24, $21, $1e,
$1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05, $04, $03, $02, $01,
$01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05, $06, $08, $09, $0b,
$0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a, $2e, $31, $35, $38,
$3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69, $6e, $72, $77, $7b,
$7f, $84, $88, $8d, $91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf,
$c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0,
$f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff ]
return costab[radians]
inline sub cosr8u(ubyte radians) -> ubyte {
return math.cosr8u_table[radians]
}
sub sinr8(ubyte radians) -> byte {
ubyte[180] sintab = [
$00, $04, $08, $0d,
$11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f, $43, $47, $4a, $4e,
$51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70, $72, $74, $75, $77,
$78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7f, $7e, $7e, $7e, $7d, $7d, $7c,
$7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66, $64, $61, $5e, $5b,
$58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f, $2b, $27, $23, $1e,
$1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea, $e6, $e2, $dd, $d9,
$d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac, $a8, $a5, $a2, $9f,
$9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86, $85, $84, $83, $83,
$82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86, $88, $89, $8b, $8c,
$8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac, $af, $b2, $b6, $b9,
$bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea, $ef, $f3, $f8, $fc
]
return sintab[radians] as byte
inline sub sinr8(ubyte radians) -> byte {
return math.sinr8_table[radians] as byte
}
sub cosr8(ubyte radians) -> byte {
ubyte[180] costab = [
$7f, $7e, $7e, $7e, $7d, $7d, $7c,
$7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66, $64, $61, $5e, $5b,
$58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f, $2b, $27, $23, $1e,
$1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea, $e6, $e2, $dd, $d9,
$d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac, $a8, $a5, $a2, $9f,
$9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86, $85, $84, $83, $83,
$82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86, $88, $89, $8b, $8c,
$8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac, $af, $b2, $b6, $b9,
$bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea, $ef, $f3, $f8, $fc,
$00, $04, $08, $0d, $11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f,
$43, $47, $4a, $4e, $51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70,
$72, $74, $75, $77, $78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e
]
return costab[radians] as byte
inline sub cosr8(ubyte radians) -> byte {
return math.cosr8_table[radians] as byte
}
; "X ABC" pseudo random number generator
@@ -167,10 +159,14 @@ math {
; State: 4 bytes (x1,c1,a1,b1). Default seed: $00c2, $1137.
; Very fast on 68000: only byte ops, no shifts > 1, no 32-bit multiply.
ubyte x1 = $00
ubyte c1 = $c2
ubyte a1 = $11
ubyte b1 = $37
private ubyte x1 = $00
private ubyte c1 = $c2
private ubyte a1 = $11
private ubyte b1 = $37
; streaming CRC state (m68k has no cx16.rN scratch registers)
private uword crc16_state = $0000
private long crc32_state = $00000000
sub rndw() -> uword {
%option noframe
@@ -248,4 +244,503 @@ math {
rts
}}
}
asmsub log2(ubyte value @D0) -> ubyte @D0 {
; returns the integer base-2 logarithm of the unsigned byte value (position of the highest set bit)
; returns 0 for value = 0
%asm {{
move.b d0,d1
moveq #7,d2
.loop:
btst d2,d1
bne .found
subq.b #1,d2
bcc .loop
moveq #0,d0
rts
.found:
move.b d2,d0
rts
}}
}
asmsub log2w(uword value @D0) -> ubyte @D0 {
; returns the integer base-2 logarithm of the unsigned word value (position of the highest set bit)
; returns 0 for value = 0
%asm {{
move.w d0,d1
moveq #15,d2
.loop:
btst d2,d1
bne .found
subq.b #1,d2
bcc .loop
moveq #0,d0
rts
.found:
move.b d2,d0
rts
}}
}
asmsub log2l(long value @D0) -> ubyte @D0 {
; returns the integer base-2 logarithm of the signed long value (position of the highest set bit)
; returns 0 for value = 0
%asm {{
move.l d0,d1
moveq #31,d2
.loop:
btst d2,d1
bne .found
subq.b #1,d2
bcc .loop
moveq #0,d0
rts
.found:
move.b d2,d0
rts
}}
}
inline asmsub diff(ubyte v1 @D0, ubyte v2 @D1) -> ubyte @D0 {
; -- returns the (absolute) difference, or distance, between the two bytes
%asm {{
sub.b d1,d0
bcc .done
neg.b d0
.done: local
}}
}
inline asmsub diffw(uword w1 @D0, uword w2 @D1) -> uword @D0 {
; -- returns the (absolute) difference, or distance, between the two words
%asm {{
sub.w d1,d0
bcc .done
neg.w d0
.done: local
}}
}
inline asmsub diffl(long l1 @D0, long l2 @D1) -> long @D0 {
; -- returns the (absolute) difference, or distance, between the two longs
%asm {{
sub.l d1,d0
bcc .done
neg.l d0
.done: local
}}
}
asmsub lerp(ubyte v0 @D0, ubyte v1 @D1, ubyte t @D2) -> ubyte @D0 {
; Linear interpolation (LERP)
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 255]
; guarantees v = v1 when t = 255
; original Prog8 implementation:
; if v1<v0
; return v0 - msb(t as uword * (v0 - v1) + 255)
; else
; return v0 + msb(t as uword * (v1 - v0) + 255)
%asm {{
move.b d0,d3 ; save v0
cmp.b d1,d0
bcc .descending
; ascending: v1 > v0
sub.b d0,d1 ; d1 = delta = v1 - v0
move.b d2,d0 ; d0 = t
and.w #$ff,d0
and.w #$ff,d1
mulu.w d1,d0 ; d0 = t * delta
add.w #255,d0 ; round up
lsr.w #8,d0 ; d0 = upper byte
add.b d3,d0 ; result = v0 + upper
rts
.descending:
sub.b d1,d0 ; d0 = delta = v0 - v1
move.b d2,d1 ; d1 = t
and.w #$ff,d0
and.w #$ff,d1
mulu.w d0,d1 ; d1 = t * delta
add.w #255,d1 ; round up
lsr.w #8,d1 ; d1 = upper byte
move.b d3,d0 ; d0 = v0
sub.b d1,d0 ; result = v0 - upper
rts
}}
}
asmsub lerpw(uword v0 @D0, uword v1 @D1, uword t @D2) -> uword @D0 {
; Linear interpolation (LERP) on word values
; returns an interpolation between two inputs (v0, v1) for a parameter t in the interval [0, 65535]
; guarantees v = v1 when t = 65535
; the 6502 version uses mul16_last_upper() for the high half of t*delta; here the
; full 32-bit unsigned product is computed instead, giving the same ceil(product/65536) result.
; original Prog8 implementation:
; uword delta
; bool descending
; if v1<v0 {
; delta = v0 - v1
; descending = true
; } else {
; delta = v1 - v0
; descending = false
; }
; long product = lerpw_mulu(t, delta)
; uword upper = (product >> 16) as uword
; if (product & $ffff)!=0
; upper++
; if descending
; return v0 - upper
; return v0 + upper
%asm {{
move.w d0,d3 ; save v0
cmp.w d1,d0
bcc .descending
; ascending: v1 > v0
sub.w d0,d1 ; d1 = delta = v1 - v0
move.w d2,d0 ; d0 = t
mulu.w d1,d0 ; d0 = t * delta (32-bit)
move.w d0,d1 ; d1 = low word
clr.w d0 ; d0 = high word << 16
swap d0 ; d0 = high word
tst.w d1
beq .done_asc
addq.w #1,d0 ; ceil(product / 65536)
.done_asc:
add.w d3,d0 ; result = v0 + upper
rts
.descending:
sub.w d1,d0 ; d0 = delta = v0 - v1
move.w d2,d1 ; d1 = t
mulu.w d0,d1 ; d1 = t * delta (32-bit)
move.w d1,d0 ; d0 = low word
clr.w d1 ; d1 = high word << 16
swap d1 ; d1 = high word
tst.w d0
beq .done_desc
addq.w #1,d1 ; ceil(product / 65536)
.done_desc:
move.w d3,d0 ; d0 = v0
sub.w d1,d0 ; result = v0 - upper
rts
}}
}
asmsub interpolate(ubyte v @D0, ubyte inputMin @D1, ubyte inputMax @D2, ubyte outputMin @D3, ubyte outputMax @D4) -> ubyte @D0 {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; original Prog8 implementation:
; uword tmp = ((v - inputMin) * 256 + inputMax) / (inputMax - inputMin)
; tmp *= outputMax - outputMin
; return msb(tmp) + outputMin
%asm {{
sub.b d3,d4 ; d4 = delta_out = outputMax - outputMin (byte)
sub.b d1,d0 ; d0 = diff_in = v - inputMin
and.w #$ff,d0
lsl.w #8,d0 ; d0 = diff_in * 256
add.b d2,d0 ; d0 = diff_in * 256 + inputMax (numerator)
sub.b d1,d2 ; d2 = denom = inputMax - inputMin
and.w #$ff,d2
and.l #$ffff,d0 ; clear high word for divu
divu.w d2,d0 ; d0 = [remainder:quotient]
and.w #$ff,d4
mulu.w d4,d0 ; d0 = tmp * delta_out (32-bit)
lsr.l #8,d0 ; d0 = high byte of product
add.b d3,d0 ; result = msb + outputMin
rts
}}
}
asmsub interpolatew(uword v @D0, uword inputMin @D1, uword inputMax @D2, uword outputMin @D3, uword outputMax @D4) -> uword @D0 {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; Uses a 32-bit intermediate product, so it works best when v is within the input range.
; equivalent Prog8 implementation (there was no m68k Prog8 version; this is the shared math library one):
; return outputMin + ((v - inputMin) as long * (outputMax - outputMin) as long / (inputMax - inputMin) as long) as uword
%asm {{
sub.w d3,d4 ; d4 = delta_out = outputMax - outputMin
sub.w d1,d0 ; d0 = diff_in = v - inputMin
mulu.w d4,d0 ; d0 = diff_in * delta_out (32-bit)
sub.w d1,d2 ; d2 = denom = inputMax - inputMin
divu.w d2,d0 ; d0 = [remainder:quotient]
add.w d3,d0 ; result = outputMin + quotient
rts
}}
}
asmsub gcd(uword a @D0, uword b @D1) -> uword @D0 {
; Calculate the Greatest Common Divisor of two 16-bit unsigned integers using the Binary GCD algorithm (Stein's algorithm).
%asm {{
tst.w d0
beq .done_b
tst.w d1
beq .done_a
moveq #0,d2 ; shift = 0
.shift_loop
move.w d0,d3
or.w d1,d3
btst #0,d3
bne .shift_done
lsr.w #1,d0
lsr.w #1,d1
addq.w #1,d2
bra .shift_loop
.shift_done
; remove remaining factors of 2 from a
.a_loop
btst #0,d0
bne .a_done
lsr.w #1,d0
bra .a_loop
.a_done
; loop while b != 0
.b_outer
tst.w d1
beq .finish
.b_inner
btst #0,d1
bne .b_done
lsr.w #1,d1
bra .b_inner
.b_done
cmp.w d1,d0
bls .no_swap ; if a <= b, no swap
exg d0,d1 ; swap a and b
.no_swap
sub.w d0,d1 ; b = b - a
bra .b_outer
.finish
lsl.w d2,d0 ; a << shift
.done_a
rts
.done_b
move.w d1,d0
rts
}}
}
%asm {{
SECTION .text,code
ALIGN 2
math.crc16_table:
dc.w $0000, $1021, $2042, $3063, $4084, $50A5, $60C6, $70E7
dc.w $8108, $9129, $A14A, $B16B, $C18C, $D1AD, $E1CE, $F1EF
dc.w $1231, $0210, $3273, $2252, $52B5, $4294, $72F7, $62D6
dc.w $9339, $8318, $B37B, $A35A, $D3BD, $C39C, $F3FF, $E3DE
dc.w $2462, $3443, $0420, $1401, $64E6, $74C7, $44A4, $5485
dc.w $A56A, $B54B, $8528, $9509, $E5EE, $F5CF, $C5AC, $D58D
dc.w $3653, $2672, $1611, $0630, $76D7, $66F6, $5695, $46B4
dc.w $B75B, $A77A, $9719, $8738, $F7DF, $E7FE, $D79D, $C7BC
dc.w $48C4, $58E5, $6886, $78A7, $0840, $1861, $2802, $3823
dc.w $C9CC, $D9ED, $E98E, $F9AF, $8948, $9969, $A90A, $B92B
dc.w $5AF5, $4AD4, $7AB7, $6A96, $1A71, $0A50, $3A33, $2A12
dc.w $DBFD, $CBDC, $FBBF, $EB9E, $9B79, $8B58, $BB3B, $AB1A
dc.w $6CA6, $7C87, $4CE4, $5CC5, $2C22, $3C03, $0C60, $1C41
dc.w $EDAE, $FD8F, $CDEC, $DDCD, $AD2A, $BD0B, $8D68, $9D49
dc.w $7E97, $6EB6, $5ED5, $4EF4, $3E13, $2E32, $1E51, $0E70
dc.w $FF9F, $EFBE, $DFDD, $CFFC, $BF1B, $AF3A, $9F59, $8F78
dc.w $9188, $81A9, $B1CA, $A1EB, $D10C, $C12D, $F14E, $E16F
dc.w $1080, $00A1, $30C2, $20E3, $5004, $4025, $7046, $6067
dc.w $83B9, $9398, $A3FB, $B3DA, $C33D, $D31C, $E37F, $F35E
dc.w $02B1, $1290, $22F3, $32D2, $4235, $5214, $6277, $7256
dc.w $B5EA, $A5CB, $95A8, $8589, $F56E, $E54F, $D52C, $C50D
dc.w $34E2, $24C3, $14A0, $0481, $7466, $6447, $5424, $4405
dc.w $A7DB, $B7FA, $8799, $97B8, $E75F, $F77E, $C71D, $D73C
dc.w $26D3, $36F2, $0691, $16B0, $6657, $7676, $4615, $5634
dc.w $D94C, $C96D, $F90E, $E92F, $99C8, $89E9, $B98A, $A9AB
dc.w $5844, $4865, $7806, $6827, $18C0, $08E1, $3882, $28A3
dc.w $CB7D, $DB5C, $EB3F, $FB1E, $8BF9, $9BD8, $ABBB, $BB9A
dc.w $4A75, $5A54, $6A37, $7A16, $0AF1, $1AD0, $2AB3, $3A92
dc.w $FD2E, $ED0F, $DD6C, $CD4D, $BDAA, $AD8B, $9DE8, $8DC9
dc.w $7C26, $6C07, $5C64, $4C45, $3CA2, $2C83, $1CE0, $0CC1
dc.w $EF1F, $FF3E, $CF5D, $DF7C, $AF9B, $BFBA, $8FD9, $9FF8
dc.w $6E17, $7E36, $4E55, $5E74, $2E93, $3EB2, $0ED1, $1EF0
ALIGN 2
math.crc32_table:
dc.l $00000000, $77073096, $EE0E612C, $990951BA
dc.l $076DC419, $706AF48F, $E963A535, $9E6495A3
dc.l $0EDB8832, $79DCB8A4, $E0D5E91E, $97D2D988
dc.l $09B64C2B, $7EB17CBD, $E7B82D07, $90BF1D91
dc.l $1DB71064, $6AB020F2, $F3B97148, $84BE41DE
dc.l $1ADAD47D, $6DDDE4EB, $F4D4B551, $83D385C7
dc.l $136C9856, $646BA8C0, $FD62F97A, $8A65C9EC
dc.l $14015C4F, $63066CD9, $FA0F3D63, $8D080DF5
dc.l $3B6E20C8, $4C69105E, $D56041E4, $A2677172
dc.l $3C03E4D1, $4B04D447, $D20D85FD, $A50AB56B
dc.l $35B5A8FA, $42B2986C, $DBBBC9D6, $ACBCF940
dc.l $32D86CE3, $45DF5C75, $DCD60DCF, $ABD13D59
dc.l $26D930AC, $51DE003A, $C8D75180, $BFD06116
dc.l $21B4F4B5, $56B3C423, $CFBA9599, $B8BDA50F
dc.l $2802B89E, $5F058808, $C60CD9B2, $B10BE924
dc.l $2F6F7C87, $58684C11, $C1611DAB, $B6662D3D
dc.l $76DC4190, $01DB7106, $98D220BC, $EFD5102A
dc.l $71B18589, $06B6B51F, $9FBFE4A5, $E8B8D433
dc.l $7807C9A2, $0F00F934, $9609A88E, $E10E9818
dc.l $7F6A0DBB, $086D3D2D, $91646C97, $E6635C01
dc.l $6B6B51F4, $1C6C6162, $856530D8, $F262004E
dc.l $6C0695ED, $1B01A57B, $8208F4C1, $F50FC457
dc.l $65B0D9C6, $12B7E950, $8BBEB8EA, $FCB9887C
dc.l $62DD1DDF, $15DA2D49, $8CD37CF3, $FBD44C65
dc.l $4DB26158, $3AB551CE, $A3BC0074, $D4BB30E2
dc.l $4ADFA541, $3DD895D7, $A4D1C46D, $D3D6F4FB
dc.l $4369E96A, $346ED9FC, $AD678846, $DA60B8D0
dc.l $44042D73, $33031DE5, $AA0A4C5F, $DD0D7CC9
dc.l $5005713C, $270241AA, $BE0B1010, $C90C2086
dc.l $5768B525, $206F85B3, $B966D409, $CE61E49F
dc.l $5EDEF90E, $29D9C998, $B0D09822, $C7D7A8B4
dc.l $59B33D17, $2EB40D81, $B7BD5C3B, $C0BA6CAD
dc.l $EDB88320, $9ABFB3B6, $03B6E20C, $74B1D29A
dc.l $EAD54739, $9DD277AF, $04DB2615, $73DC1683
dc.l $E3630B12, $94643B84, $0D6D6A3E, $7A6A5AA8
dc.l $E40ECF0B, $9309FF9D, $0A00AE27, $7D079EB1
dc.l $F00F9344, $8708A3D2, $1E01F268, $6906C2FE
dc.l $F762575D, $806567CB, $196C3671, $6E6B06E7
dc.l $FED41B76, $89D32BE0, $10DA7A5A, $67DD4ACC
dc.l $F9B9DF6F, $8EBEEFF9, $17B7BE43, $60B08ED5
dc.l $D6D6A3E8, $A1D1937E, $38D8C2C4, $4FDFF252
dc.l $D1BB67F1, $A6BC5767, $3FB506DD, $48B2364B
dc.l $D80D2BDA, $AF0A1B4C, $36034AF6, $41047A60
dc.l $DF60EFC3, $A867DF55, $316E8EEF, $4669BE79
dc.l $CB61B38C, $BC66831A, $256FD2A0, $5268E236
dc.l $CC0C7795, $BB0B4703, $220216B9, $5505262F
dc.l $C5BA3BBE, $B2BD0B28, $2BB45A92, $5CB36A04
dc.l $C2D7FFA7, $B5D0CF31, $2CD99E8B, $5BDEAE1D
dc.l $9B64C2B0, $EC63F226, $756AA39C, $026D930A
dc.l $9C0906A9, $EB0E363F, $72076785, $05005713
dc.l $95BF4A82, $E2B87A14, $7BB12BAE, $0CB61B38
dc.l $92D28E9B, $E5D5BE0D, $7CDCEFB7, $0BDBDF21
dc.l $86D3D2D4, $F1D4E242, $68DDB3F8, $1FDA836E
dc.l $81BE16CD, $F6B9265B, $6FB077E1, $18B74777
dc.l $88085AE6, $FF0F6A70, $66063BCA, $11010B5C
dc.l $8F659EFF, $F862AE69, $616BFFD3, $166CCF45
dc.l $A00AE278, $D70DD2EE, $4E048354, $3903B3C2
dc.l $A7672661, $D06016F7, $4969474D, $3E6E77DB
dc.l $AED16A4A, $D9D65ADC, $40DF0B66, $37D83BF0
dc.l $A9BCAE53, $DEBB9EC5, $47B2CF7F, $30B5FFE9
dc.l $BDBDF21C, $CABAC28A, $53B39330, $24B4A3A6
dc.l $BAD03605, $CDD70693, $54DE5729, $23D967BF
dc.l $B3667A2E, $C4614AB8, $5D681B02, $2A6F2B94
dc.l $B40BBE37, $C30C8EA1, $5A05DF1B, $2D02EF8D
}}
asmsub crc16(^^ubyte data @A0, uword length @D0, uword initvalue @D1, uword xorout @D2) -> uword @D0 {
; Calculates the CRC16 checksum of the buffer using a byte lookup table.
; For XMODEM type checksum, use initvalue=0 and xorout=0.
; For IBM-3740 type checksum, use initvalue=$ffff and xorout=0.
%asm {{
move.w d3,-(sp)
move.w d4,-(sp)
lea math.crc16_table,a1
move.w d1,d3 ; running crc = initvalue
.loop:
subq.w #1,d0
bcs .done
move.w d3,d1 ; copy crc
lsr.w #8,d1 ; index high byte
move.b (a0)+,d4 ; next data byte
eor.b d4,d1 ; table index
lsl.w #8,d3 ; crc <<= 8
and.w #$ff,d1
add.w d1,d1 ; word index
move.w (a1,d1.w),d1
eor.w d1,d3 ; update crc
bra .loop
.done:
move.w d3,d0
eor.w d2,d0 ; apply xorout
move.w (sp)+,d4
move.w (sp)+,d3
rts
}}
}
sub crc16_start(uword initvalue) {
; start the "streaming" crc16
; note: tracks the crc16 checksum in the module-internal crc16_state variable
crc16_state = initvalue
}
asmsub crc16_update(ubyte value @D0) {
; update the "streaming" crc16 with next byte value
; note: tracks the crc16 checksum in the module-internal crc16_state variable
%asm {{
lea math.crc16_table,a0
move.w math.crc16_state,d1
lsr.w #8,d1 ; high byte of crc
eor.b d0,d1 ; table index
move.w math.crc16_state,d0
lsl.w #8,d0 ; crc <<= 8
and.w #$ff,d1
add.w d1,d1 ; word index
move.w (a0,d1.w),d1
eor.w d1,d0 ; new crc
move.w d0,math.crc16_state
rts
}}
}
sub crc16_end(uword xorout) -> uword {
; finalize the "streaming" crc16, returns resulting crc16 value
return crc16_state ^ xorout
}
asmsub crc32(^^ubyte data @A0, uword length @D0) -> long @D0 {
; Calculates the CRC-32 (ISO-HDLC/PKZIP) checksum of the buffer using a byte lookup table.
%asm {{
move.l d2,-(sp)
lea math.crc32_table,a1
move.l #$ffffffff,d1 ; running crc
.loop:
subq.w #1,d0
bcs .done
move.b (a0)+,d2 ; next data byte
eor.b d1,d2 ; table index = (crc ^ byte) & $ff
lsr.l #8,d1 ; crc >>= 8
and.l #$ff,d2
lsl.l #2,d2 ; long index
move.l (a1,d2.l),d2
eor.l d2,d1 ; update crc
bra .loop
.done:
move.l d1,d0
eor.l #$ffffffff,d0 ; final xor
move.l (sp)+,d2
rts
}}
}
sub crc32_start() {
; start the "streaming" crc32
; note: tracks the crc32 checksum in the module-internal crc32_state variable
crc32_state = $ffffffff
}
asmsub crc32_update(ubyte value @D0) {
; update the "streaming" crc32 with next byte value
; note: tracks the crc32 checksum in the module-internal crc32_state variable
%asm {{
lea math.crc32_table,a0
move.l math.crc32_state,d1
eor.b d1,d0 ; table index = (crc ^ byte) & $ff
lsr.l #8,d1 ; crc >>= 8
and.l #$ff,d0
lsl.l #2,d0 ; long index
move.l (a0,d0.l),d0
eor.l d0,d1 ; new crc
move.l d1,math.crc32_state
rts
}}
}
sub crc32_end() -> long {
; finalize the "streaming" crc32 and return the result
return crc32_state ^ $ffffffff
}
}
+130 -160
View File
@@ -5,161 +5,114 @@
math {
%option ignore_unused
sub sin8u(ubyte angle) -> ubyte {
ubyte[256] sintab = [$80, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad, $b0, $b3, $b6, $b9,
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff, $ff, $ff, $ff, $ff,
$fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6, $f5, $f4, $f3, $f1,
$f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc, $da, $d7, $d5, $d3,
$d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3, $b0, $ad, $aa, $a7,
$a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83, $80, $7c, $79, $76,
$73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52, $4f, $4c, $49, $46,
$43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28, $25, $23, $21, $1f,
$1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b, $0a, $09, $07, $06,
$05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00,
$01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09, $0a, $0b, $0c, $0e,
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c]
return sintab[angle]
; Sine/cosine lookup tables shared between each sin/cos routine pair:
; the cos routine reads the same table at an offset (+64 / +45).
; The tables live at block scope so the inlined
; routines below all reference the same single copy. Tables that end up
; unused (no trig routine called) are removed automatically.
private ubyte[320] sincos8u_table = [$80, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad,
$b0, $b3, $b6, $b9, $bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7,
$da, $dc, $de, $e0, $e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4,
$f5, $f6, $f8, $f9, $fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff,
$ff, $ff, $ff, $ff, $fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6,
$f5, $f4, $f3, $f1, $f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc,
$da, $d7, $d5, $d3, $d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3,
$b0, $ad, $aa, $a7, $a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83,
$80, $7c, $79, $76, $73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52,
$4f, $4c, $49, $46, $43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28,
$25, $23, $21, $1f, $1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b,
$0a, $09, $07, $06, $05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00,
$00, $00, $00, $00, $01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09,
$0a, $0b, $0c, $0e, $0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23,
$25, $28, $2a, $2c, $2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c,
$4f, $52, $55, $58, $5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c,
$7f, $83, $86, $89, $8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad,
$b0, $b3, $b6, $b9, $bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7,
$da, $dc, $de, $e0, $e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4,
$f5, $f6, $f8, $f9, $fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff]
private ubyte[320] sincos8_table = [$00, $03, $06, $09, $0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d,
$30, $33, $36, $39, $3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57,
$59, $5b, $5e, $60, $62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74,
$75, $76, $77, $78, $79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e,
$7f, $7e, $7e, $7e, $7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76,
$75, $74, $72, $71, $70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b,
$59, $57, $55, $52, $50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33,
$30, $2d, $2a, $27, $24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03,
$00, $fd, $fa, $f7, $f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3,
$d0, $cd, $ca, $c7, $c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9,
$a7, $a5, $a2, $a0, $9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c,
$8b, $8a, $89, $88, $87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82,
$81, $82, $82, $82, $82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a,
$8b, $8c, $8e, $8f, $90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5,
$a7, $a9, $ab, $ae, $b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd,
$d0, $d3, $d6, $d9, $dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd,
$00, $03, $06, $09, $0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d,
$30, $33, $36, $39, $3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57,
$59, $5b, $5e, $60, $62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74,
$75, $76, $77, $78, $79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e]
private ubyte[225] sincosr8u_table = [$80, $84, $88, $8d, $91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf,
$c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0,
$f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff, $ff, $ff, $ff,
$fe, $fe, $fd, $fc, $fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7,
$e4, $e1, $de, $db, $d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af,
$ab, $a7, $a3, $9e, $9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69,
$65, $61, $5c, $58, $54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a,
$27, $24, $21, $1e, $1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05,
$04, $03, $02, $01, $01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05,
$06, $08, $09, $0b, $0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a,
$2e, $31, $35, $38, $3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69,
$6e, $72, $77, $7b, $7f, $84, $88, $8d, $91, $96, $9a, $9e, $a3, $a7, $ab, $af,
$b3, $b7, $bb, $bf, $c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de, $e1, $e4, $e7,
$e9, $ec, $ee, $f0, $f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff,
$ff]
private ubyte[225] sincosr8_table = [$00, $04, $08, $0d, $11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f,
$43, $47, $4a, $4e, $51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70,
$72, $74, $75, $77, $78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7f, $7e, $7e,
$7e, $7d, $7d, $7c, $7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66,
$64, $61, $5e, $5b, $58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f,
$2b, $27, $23, $1e, $1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea,
$e6, $e2, $dd, $d9, $d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac,
$a8, $a5, $a2, $9f, $9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86,
$85, $84, $83, $83, $82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86,
$88, $89, $8b, $8c, $8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac,
$af, $b2, $b6, $b9, $bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea,
$ef, $f3, $f8, $fc, $00, $04, $08, $0d, $11, $16, $1a, $1e, $23, $27, $2b, $2f,
$33, $37, $3b, $3f, $43, $47, $4a, $4e, $51, $54, $58, $5b, $5e, $61, $64, $66,
$69, $6b, $6d, $70, $72, $74, $75, $77, $78, $7a, $7b, $7c, $7d, $7d, $7e, $7e,
$7e]
inline sub sin8u(ubyte angle) -> ubyte {
return math.sincos8u_table[angle]
}
sub cos8u(ubyte angle) -> ubyte {
ubyte[256] costab = [$ff, $ff, $ff, $ff,
$fe, $fe, $fe, $fd, $fd, $fc, $fb, $fa, $fa, $f9, $f8, $f6, $f5, $f4, $f3, $f1,
$f0, $ee, $ed, $eb, $ea, $e8, $e6, $e4, $e2, $e0, $de, $dc, $da, $d7, $d5, $d3,
$d0, $ce, $cb, $c9, $c6, $c4, $c1, $be, $bc, $b9, $b6, $b3, $b0, $ad, $aa, $a7,
$a5, $a2, $9e, $9b, $98, $95, $92, $8f, $8c, $89, $86, $83, $80, $7c, $79, $76,
$73, $70, $6d, $6a, $67, $64, $61, $5d, $5a, $58, $55, $52, $4f, $4c, $49, $46,
$43, $41, $3e, $3b, $39, $36, $34, $31, $2f, $2c, $2a, $28, $25, $23, $21, $1f,
$1d, $1b, $19, $17, $15, $14, $12, $11, $0f, $0e, $0c, $0b, $0a, $09, $07, $06,
$05, $05, $04, $03, $02, $02, $01, $01, $01, $00, $00, $00, $00, $00, $00, $00,
$01, $01, $01, $02, $02, $03, $04, $05, $05, $06, $07, $09, $0a, $0b, $0c, $0e,
$0f, $11, $12, $14, $15, $17, $19, $1b, $1d, $1f, $21, $23, $25, $28, $2a, $2c,
$2f, $31, $34, $36, $39, $3b, $3e, $41, $43, $46, $49, $4c, $4f, $52, $55, $58,
$5a, $5d, $61, $64, $67, $6a, $6d, $70, $73, $76, $79, $7c, $7f, $83, $86, $89,
$8c, $8f, $92, $95, $98, $9b, $9e, $a2, $a5, $a7, $aa, $ad, $b0, $b3, $b6, $b9,
$bc, $be, $c1, $c4, $c6, $c9, $cb, $ce, $d0, $d3, $d5, $d7, $da, $dc, $de, $e0,
$e2, $e4, $e6, $e8, $ea, $eb, $ed, $ee, $f0, $f1, $f3, $f4, $f5, $f6, $f8, $f9,
$fa, $fa, $fb, $fc, $fd, $fd, $fe, $fe, $fe, $ff, $ff, $ff ]
return costab[angle]
inline sub cos8u(ubyte angle) -> ubyte {
; uword index: angle+64 can exceed 255 and must not wrap (cos8u(192) reads entry 256)
return math.sincos8u_table[(angle as uword)+64]
}
sub sin8(ubyte angle) -> byte {
ubyte[256] sintab = [
$00, $03, $06, $09,
$0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d, $30, $33, $36, $39,
$3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57, $59, $5b, $5e, $60,
$62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74, $75, $76, $77, $78,
$79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e, $7f, $7e, $7e, $7e,
$7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76, $75, $74, $72, $71,
$70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b, $59, $57, $55, $52,
$50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33, $30, $2d, $2a, $27,
$24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03, $00, $fd, $fa, $f7,
$f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3, $d0, $cd, $ca, $c7,
$c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9, $a7, $a5, $a2, $a0,
$9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c, $8b, $8a, $89, $88,
$87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82, $81, $82, $82, $82,
$82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a, $8b, $8c, $8e, $8f,
$90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5, $a7, $a9, $ab, $ae,
$b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd, $d0, $d3, $d6, $d9,
$dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd
]
return sintab[angle] as byte
inline sub sin8(ubyte angle) -> byte {
return math.sincos8_table[angle] as byte
}
sub cos8(ubyte angle) -> byte {
ubyte[256] costab = [
$7f, $7e, $7e, $7e,
$7e, $7e, $7d, $7d, $7c, $7b, $7b, $7a, $79, $78, $77, $76, $75, $74, $72, $71,
$70, $6e, $6c, $6b, $69, $67, $66, $64, $62, $60, $5e, $5b, $59, $57, $55, $52,
$50, $4e, $4b, $49, $46, $43, $41, $3e, $3b, $39, $36, $33, $30, $2d, $2a, $27,
$24, $21, $1e, $1b, $18, $15, $12, $0f, $0c, $09, $06, $03, $00, $fd, $fa, $f7,
$f4, $f1, $ee, $eb, $e8, $e5, $e2, $df, $dc, $d9, $d6, $d3, $d0, $cd, $ca, $c7,
$c5, $c2, $bf, $bd, $ba, $b7, $b5, $b2, $b0, $ae, $ab, $a9, $a7, $a5, $a2, $a0,
$9e, $9c, $9a, $99, $97, $95, $94, $92, $90, $8f, $8e, $8c, $8b, $8a, $89, $88,
$87, $86, $85, $85, $84, $83, $83, $82, $82, $82, $82, $82, $81, $82, $82, $82,
$82, $82, $83, $83, $84, $85, $85, $86, $87, $88, $89, $8a, $8b, $8c, $8e, $8f,
$90, $92, $94, $95, $97, $99, $9a, $9c, $9e, $a0, $a2, $a5, $a7, $a9, $ab, $ae,
$b0, $b2, $b5, $b7, $ba, $bd, $bf, $c2, $c5, $c7, $ca, $cd, $d0, $d3, $d6, $d9,
$dc, $df, $e2, $e5, $e8, $eb, $ee, $f1, $f4, $f7, $fa, $fd, $00, $03, $06, $09,
$0c, $0f, $12, $15, $18, $1b, $1e, $21, $24, $27, $2a, $2d, $30, $33, $36, $39,
$3b, $3e, $41, $43, $46, $49, $4b, $4e, $50, $52, $55, $57, $59, $5b, $5e, $60,
$62, $64, $66, $67, $69, $6b, $6c, $6e, $70, $71, $72, $74, $75, $76, $77, $78,
$79, $7a, $7b, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7e, $7e
]
return costab[angle] as byte
inline sub cos8(ubyte angle) -> byte {
; uword index: angle+64 can exceed 255 and must not wrap (cos8(192) reads entry 256)
return math.sincos8_table[(angle as uword)+64] as byte
}
sub sinr8u(ubyte radians) -> ubyte {
ubyte[180] sintab = [
$80, $84, $88, $8d,
$91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf, $c3, $c7, $ca, $ce,
$d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0, $f2, $f4, $f6, $f7,
$f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff, $ff, $ff, $ff, $fe, $fe, $fd, $fc,
$fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7, $e4, $e1, $de, $db,
$d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af, $ab, $a7, $a3, $9e,
$9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69, $65, $61, $5c, $58,
$54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a, $27, $24, $21, $1e,
$1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05, $04, $03, $02, $01,
$01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05, $06, $08, $09, $0b,
$0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a, $2e, $31, $35, $38,
$3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69, $6e, $72, $77, $7b]
return sintab[radians]
inline sub sinr8u(ubyte radians) -> ubyte {
return math.sincosr8u_table[radians]
}
sub cosr8u(ubyte radians) -> ubyte {
ubyte[180] costab = [
$ff, $ff, $ff, $fe, $fe, $fd, $fc,
$fb, $fa, $f9, $f7, $f6, $f4, $f2, $f0, $ee, $ec, $e9, $e7, $e4, $e1, $de, $db,
$d8, $d5, $d1, $ce, $ca, $c7, $c3, $bf, $bb, $b7, $b3, $af, $ab, $a7, $a3, $9e,
$9a, $96, $91, $8d, $88, $84, $80, $7b, $77, $72, $6e, $69, $65, $61, $5c, $58,
$54, $50, $4c, $48, $44, $40, $3c, $38, $35, $31, $2e, $2a, $27, $24, $21, $1e,
$1b, $18, $16, $13, $11, $0f, $0d, $0b, $09, $08, $06, $05, $04, $03, $02, $01,
$01, $00, $00, $00, $00, $00, $01, $01, $02, $03, $04, $05, $06, $08, $09, $0b,
$0d, $0f, $11, $13, $16, $18, $1b, $1e, $21, $24, $27, $2a, $2e, $31, $35, $38,
$3c, $40, $44, $48, $4c, $50, $54, $58, $5c, $61, $65, $69, $6e, $72, $77, $7b,
$7f, $84, $88, $8d, $91, $96, $9a, $9e, $a3, $a7, $ab, $af, $b3, $b7, $bb, $bf,
$c3, $c7, $ca, $ce, $d1, $d5, $d8, $db, $de, $e1, $e4, $e7, $e9, $ec, $ee, $f0,
$f2, $f4, $f6, $f7, $f9, $fa, $fb, $fc, $fd, $fe, $fe, $ff, $ff ]
return costab[radians]
inline sub cosr8u(ubyte radians) -> ubyte {
return math.sincosr8u_table[radians+45]
}
sub sinr8(ubyte radians) -> byte {
ubyte[180] sintab = [
$00, $04, $08, $0d,
$11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f, $43, $47, $4a, $4e,
$51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70, $72, $74, $75, $77,
$78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e, $7f, $7e, $7e, $7e, $7d, $7d, $7c,
$7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66, $64, $61, $5e, $5b,
$58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f, $2b, $27, $23, $1e,
$1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea, $e6, $e2, $dd, $d9,
$d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac, $a8, $a5, $a2, $9f,
$9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86, $85, $84, $83, $83,
$82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86, $88, $89, $8b, $8c,
$8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac, $af, $b2, $b6, $b9,
$bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea, $ef, $f3, $f8, $fc
]
return sintab[radians] as byte
inline sub sinr8(ubyte radians) -> byte {
return math.sincosr8_table[radians] as byte
}
sub cosr8(ubyte radians) -> byte {
ubyte[180] costab = [
$7f, $7e, $7e, $7e, $7d, $7d, $7c,
$7b, $7a, $78, $77, $75, $74, $72, $70, $6d, $6b, $69, $66, $64, $61, $5e, $5b,
$58, $54, $51, $4e, $4a, $47, $43, $3f, $3b, $37, $33, $2f, $2b, $27, $23, $1e,
$1a, $16, $11, $0d, $08, $04, $00, $fc, $f8, $f3, $ef, $ea, $e6, $e2, $dd, $d9,
$d5, $d1, $cd, $c9, $c5, $c1, $bd, $b9, $b6, $b2, $af, $ac, $a8, $a5, $a2, $9f,
$9c, $9a, $97, $95, $93, $90, $8e, $8c, $8b, $89, $88, $86, $85, $84, $83, $83,
$82, $82, $82, $81, $82, $82, $82, $83, $83, $84, $85, $86, $88, $89, $8b, $8c,
$8e, $90, $93, $95, $97, $9a, $9c, $9f, $a2, $a5, $a8, $ac, $af, $b2, $b6, $b9,
$bd, $c1, $c5, $c9, $cd, $d1, $d5, $d9, $dd, $e2, $e6, $ea, $ef, $f3, $f8, $fc,
$00, $04, $08, $0d, $11, $16, $1a, $1e, $23, $27, $2b, $2f, $33, $37, $3b, $3f,
$43, $47, $4a, $4e, $51, $54, $58, $5b, $5e, $61, $64, $66, $69, $6b, $6d, $70,
$72, $74, $75, $77, $78, $7a, $7b, $7c, $7d, $7d, $7e, $7e, $7e
]
return costab[radians] as byte
inline sub cosr8(ubyte radians) -> byte {
return math.sincosr8_table[radians+45] as byte
}
sub rnd() -> ubyte {
@@ -208,29 +161,35 @@ math {
alias randrangew_rom = math.randrangew
sub log2(ubyte value) -> ubyte {
ubyte result = 7
ubyte compare = $80
repeat {
if value&compare!=0
return result
result--
if_z
return 0
compare >>= 1
ubyte result = 0
repeat 7 {
value >>= 1
if value != 0
result++
}
return result
}
sub log2w(uword value) -> ubyte {
ubyte result = 15
uword compare = $8000
repeat {
if value&compare!=0
return result
result--
if_z
return 0
compare >>= 1
ubyte result = 0
repeat 15 {
value >>= 1
if value != 0
result++
}
return result
}
sub log2l(long value) -> ubyte {
; returns the integer base-2 logarithm of the signed long value (position of the highest set bit)
; returns 0 for value = 0
ubyte result = 0
repeat 31 {
value >>= 1
if value != 0
result++
}
return result
}
sub direction(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
@@ -322,6 +281,13 @@ math {
return w2-w1
}
sub diffl(long v1, long v2) -> long {
; -- returns the (absolute) difference, or distance, between the two longs
if v1>v2
return v1-v2
return v2-v1
}
sub crc16(^^ubyte data, uword length, uword initvalue, uword xorout) -> uword {
; Calculates the CRC16 checksum of the buffer. You provide the initial start value and the final value that is xored with the result.
; For XMODEM type checksum, use initvalue=0 and xorout=0.
@@ -435,12 +401,16 @@ math {
sub interpolate(ubyte v, ubyte inputMin, ubyte inputMax, ubyte outputMin, ubyte outputMax) -> ubyte {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
; Clobbers R15.
; (There is no version for words because of lack of precision in the fixed point calculation there)
cx16.r15 = ((v-inputMin)*256+inputMax) / (inputMax-inputMin)
cx16.r15 *= (outputMax-outputMin)
return cx16.r15H + outputMin
}
sub interpolatew(uword v, uword inputMin, uword inputMax, uword outputMin, uword outputMax) -> uword {
; Interpolate a value v in interval [inputMin, inputMax] to output interval [outputMin, outputMax]
return outputMin + ((v - inputMin) as long * (outputMax - outputMin) as long / (inputMax - inputMin) as long) as uword
}
sub gcd(uword aa, uword bb) -> uword {
; Calculate the Greatest Common Divisor of two 16-bit unsigned integers using the Binary GCD algorithm (Stein's algorithm).
+89 -89
View File
@@ -130,22 +130,22 @@ private fun compileMain(args: Array<String>): Boolean {
// Single presenter for all user-facing output in this invocation.
// It only wraps a real Mordant Terminal on an interactive console;
// everywhere else it is plain println output with no native code involved.
val presenter = createPresenter(plainText==true)
if(version==true) {
val presenter = createPresenter(plainText)
if(version) {
banner(presenter)
return true
}
if(quietAll!=true)
if(!quietAll)
banner(presenter)
if(libDump!=null) {
scanLibraryFiles(libDump, null, plainText==true)
scanLibraryFiles(libDump, null, plainText)
return true
}
if(libSearch!=null) {
scanLibraryFiles(null, libSearch, plainText==true)
scanLibraryFiles(null, libSearch, plainText)
return true
}
@@ -163,7 +163,7 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
if(profilingInstrumentation==true && compilationTarget!=Cx16Target.NAME) {
if(profilingInstrumentation && compilationTarget!=Cx16Target.NAME) {
presenter.printErrorLine("Profiling instrumentation is only available on the cx16 target.")
return false
}
@@ -217,11 +217,11 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
if(varsGolden==true && varsHighBank!=null) {
if(varsGolden && varsHighBank!=null) {
presenter.printErrorLine("Either use -varsgolden or -varshigh, not both.")
return false
}
if(varsAddress!=null && (varsGolden==true || varsHighBank!=null)) {
if(varsAddress!=null && (varsGolden || varsHighBank!=null)) {
presenter.printErrorLine("Either use -varsaddress or -varsgolden/-varshigh, not both.")
return false
}
@@ -230,8 +230,8 @@ private fun compileMain(args: Array<String>): Boolean {
// checked in the compiler where the compilation target is resolved: it knows the actual
// CpuType and pointer size, which also covers custom targets defined by a config file.
if(startVm==true) {
return runVm(moduleFiles.first(), quietAll==true, vmTrace==true, presenter)
if(startVm) {
return runVm(moduleFiles.first(), quietAll, vmTrace, presenter)
}
val processedSymbols = processSymbolDefs(symbolDefs, presenter) ?: return false
@@ -240,7 +240,7 @@ private fun compileMain(args: Array<String>): Boolean {
outputFile?.let { pathFrom(it).parent?.createDirectories() }
outputPath.createDirectories()
if(watchMode==true) {
if(watchMode) {
val watchservice = FileSystems.getDefault().newWatchService()
val allImportedFiles = mutableSetOf<Path>()
val results = mutableListOf<CompilationResult>()
@@ -250,43 +250,43 @@ private fun compileMain(args: Array<String>): Boolean {
results.clear()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val txtcolors = if(plainText==true) ErrorReporter.PlainText else ErrorReporter.AnsiColors
val txtcolors = if(plainText) ErrorReporter.PlainText else ErrorReporter.AnsiColors
val compilerArgs = CompilerArguments(
filepath,
if(checkSource==true) false else dontOptimize != true,
if(checkSource==true) false else dontWriteAssembly != true,
warnSymbolShadowing == true,
warnImplicitTypeCasts == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
newCodegen == true,
dumpVariables == true,
dumpSymbols == true,
!checkSource && !dontOptimize,
!checkSource && !dontWriteAssembly,
warnSymbolShadowing,
warnImplicitTypeCasts,
quietAll,
quietAll || quietAssembler,
showTimings,
asmListfile,
!dontIncludeSourcelines,
newCodegen,
dumpVariables,
dumpSymbols,
varsHighBank,
varsGolden == true,
varsGolden,
varsAddress,
compilationTarget!!,
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
printAst1,
printAst2,
printCompileInfo = true,
ignoreFootguns == true,
profilingInstrumentation == true,
traceImports == true,
ignoreFootguns,
profilingInstrumentation,
traceImports,
processedSymbols,
srcdirs,
outputPath,
outputFile = outputFile?.let { pathFrom(it) },
errors = ErrorReporter(txtcolors),
assemble = true,
generateDocumentation = generateDocumentation == true
generateDocumentation = generateDocumentation
)
val compilationResult = compileProgram(compilerArgs)
if(checkSource==true)
if(checkSource)
println("No output produced.")
if(compilationResult!=null)
@@ -331,40 +331,40 @@ private fun compileMain(args: Array<String>): Boolean {
presenter.clearScreen()
}
} else if (daemonMode == true) {
if((startEmulator1==true || startEmulator2==true) && moduleFiles.size>1) {
} else if (daemonMode) {
if((startEmulator1 || startEmulator2) && moduleFiles.size>1) {
System.err.println("can't start emulator when multiple module files are specified")
return false
}
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize().toAbsolutePath()
val txtcolors = if(plainText==true) ErrorReporter.PlainText else ErrorReporter.AnsiColors
val txtcolors = if(plainText) ErrorReporter.PlainText else ErrorReporter.AnsiColors
val absoluteSrcDirs = srcdirs.map { Path.of(it).toAbsolutePath().toString() }
val compilerArgs = CompilerArguments(
filepath,
if(checkSource==true) false else dontOptimize != true,
if(checkSource==true) false else dontWriteAssembly != true,
warnSymbolShadowing == true,
warnImplicitTypeCasts == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
newCodegen == true,
dumpVariables == true,
dumpSymbols == true,
!checkSource && !dontOptimize,
!checkSource && !dontWriteAssembly,
warnSymbolShadowing,
warnImplicitTypeCasts,
quietAll,
quietAll || quietAssembler,
showTimings,
asmListfile,
!dontIncludeSourcelines,
newCodegen,
dumpVariables,
dumpSymbols,
varsHighBank,
varsGolden == true,
varsGolden,
varsAddress,
compilationTarget!!,
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
printAst1,
printAst2,
true,
ignoreFootguns == true,
profilingInstrumentation == true,
traceImports == true,
ignoreFootguns,
profilingInstrumentation,
traceImports,
processedSymbols,
absoluteSrcDirs,
outputPath,
@@ -372,35 +372,35 @@ private fun compileMain(args: Array<String>): Boolean {
cwd = Path.of(System.getProperty("user.dir")),
errors = ErrorReporter(txtcolors),
assemble = true,
generateDocumentation = generateDocumentation == true
generateDocumentation = generateDocumentation
)
val response = compileViaDaemon(compilerArgs, plainText == true)
val response = compileViaDaemon(compilerArgs, plainText)
if (response == null || !response.ok)
return false
if (startEmulator1 == true || startEmulator2 == true) {
if (startEmulator1 || startEmulator2) {
if (response.outputFiles.isEmpty()) {
println("\nCan't start emulator because no program was assembled.")
} else {
val programFile = Path.of(response.outputFiles.first())
val target = getCompilationTargetByName(compilationTarget!!)
if (startEmulator1 == true) {
if (startEmulator1) {
if (target is VMTarget) {
if(!launchVirtualMachine(target, programFile, quietAll==true, vmTrace==true, presenter))
if(!launchVirtualMachine(target, programFile, quietAll, vmTrace, presenter))
return false
} else {
target.launchEmulator(1, programFile, quietAll==true)
target.launchEmulator(1, programFile, quietAll)
}
} else {
target.launchEmulator(2, programFile, quietAll==true)
target.launchEmulator(2, programFile, quietAll)
}
}
}
}
return true
} else {
if((startEmulator1==true || startEmulator2==true) && moduleFiles.size>1) {
if((startEmulator1 || startEmulator2) && moduleFiles.size>1) {
System.err.println("can't start emulator when multiple module files are specified")
return false
}
@@ -408,43 +408,43 @@ private fun compileMain(args: Array<String>): Boolean {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
val txtcolors = if(plainText==true) ErrorReporter.PlainText else ErrorReporter.AnsiColors
val txtcolors = if(plainText) ErrorReporter.PlainText else ErrorReporter.AnsiColors
val compilerArgs = CompilerArguments(
filepath,
if(checkSource==true) false else dontOptimize != true,
if(checkSource==true) false else dontWriteAssembly != true,
warnSymbolShadowing == true,
warnImplicitTypeCasts == true,
quietAll == true,
quietAll == true || quietAssembler == true,
showTimings == true,
asmListfile == true,
dontIncludeSourcelines != true,
newCodegen == true,
dumpVariables == true,
dumpSymbols==true,
!checkSource && !dontOptimize,
!checkSource && !dontWriteAssembly,
warnSymbolShadowing,
warnImplicitTypeCasts,
quietAll,
quietAll || quietAssembler,
showTimings,
asmListfile,
!dontIncludeSourcelines,
newCodegen,
dumpVariables,
dumpSymbols,
varsHighBank,
varsGolden == true,
varsGolden,
varsAddress,
compilationTarget!!,
breakpointCpuInstruction,
printAst1 == true,
printAst2 == true,
printAst1,
printAst2,
true,
ignoreFootguns == true,
profilingInstrumentation == true,
traceImports == true,
ignoreFootguns,
profilingInstrumentation,
traceImports,
processedSymbols,
srcdirs,
outputPath,
outputFile = outputFile?.let { pathFrom(it) },
errors = ErrorReporter(txtcolors),
assemble = true,
generateDocumentation = generateDocumentation == true
generateDocumentation = generateDocumentation
)
val result = compileProgram(compilerArgs)
if(checkSource==true)
if(checkSource)
println("No output produced.")
if(result==null)
@@ -455,7 +455,7 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
if(startEmulator1==true || startEmulator2==true) {
if(startEmulator1 || startEmulator2) {
if (compilationResult.compilerAst.name.isEmpty()) {
println("\nCan't start emulator because no program was assembled.")
return true
@@ -468,16 +468,16 @@ private fun compileMain(args: Array<String>): Boolean {
compareIrFiles(outs.ir(), Path(compareIR!!), presenter)
}
if (startEmulator1 == true) {
if (startEmulator1) {
if (compilationResult.compilationOptions.compTarget is VMTarget) {
if(!launchVirtualMachine(compilationResult.compilationOptions.compTarget as VMTarget,
outs.programFile, quietAll==true, vmTrace==true, presenter))
outs.programFile, quietAll, vmTrace, presenter))
return false
} else {
compilationResult.compilationOptions.compTarget.launchEmulator(1, outs.programFile, quietAll==true)
compilationResult.compilationOptions.compTarget.launchEmulator(1, outs.programFile, quietAll)
}
} else if (startEmulator2 == true)
compilationResult.compilationOptions.compTarget.launchEmulator(2, outs.programFile, quietAll==true)
} else if (startEmulator2)
compilationResult.compilationOptions.compTarget.launchEmulator(2, outs.programFile, quietAll)
}
}
@@ -928,7 +928,7 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
return response
}
System.out.print(response.stdout)
print(response.stdout)
System.out.flush()
System.err.print(response.stderr)
System.err.flush()
+3 -7
View File
@@ -151,7 +151,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
symbolDefs = args.symbolDefs
}
// apply custom target default for vars address now so it participates in the ROMable check
if(compilationOptions.varsAddress==null && compilationOptions.varsGolden==false && compilationOptions.varsHighBank==null) {
if(compilationOptions.varsAddress==null && !compilationOptions.varsGolden && compilationOptions.varsHighBank==null) {
(compilationOptions.compTarget as? ConfigFileTarget)?.varsAddress?.let {
compilationOptions.varsAddress = it
}
@@ -603,7 +603,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
.launcher(launcherType)
.zeropage(zpType)
.zpReserved(zpReserved)
.zpAllowed(if (zpAllowed.isEmpty()) CompilationOptions.AllZeropageAllowed else zpAllowed)
.zpAllowed(zpAllowed.ifEmpty { CompilationOptions.AllZeropageAllowed })
.floats(floatsEnabled)
.noSysInit(noSysInit)
.romable(romable)
@@ -932,10 +932,6 @@ private fun createAssemblyAndAssemble(program: PtProgram,
val chunkCount = assembly?.irChunkCount ?: 0
val registerCount = assembly?.irRegisterCount ?: 0
val success = if(assembly!=null && errors.noErrors()) {
if(assemble) assembly.assemble(compilerOptions, errors) else true
} else {
false
}
val success = assembly!=null && errors.noErrors() && (!assemble || assembly.assemble(compilerOptions, errors))
return AssemblyResult(success, instructionCount, chunkCount, registerCount)
}
@@ -49,7 +49,7 @@ internal class CompilerDaemon(private val socketPath: Path) {
System.err.println(buildString {
append("prog8c daemon")
if (pid != null) append(" (pid $pid)")
append(" running at ${socketPath}")
append(" running at $socketPath")
})
var lastRequestTime = System.currentTimeMillis()
@@ -103,7 +103,7 @@ internal object DaemonProtocol {
fun decodeResponse(json: String): DaemonResponse {
val map = parseJsonObject(json)
val outputFiles = (map["outputFiles"] as? List<*>)?.map { it as String } ?: emptyList<String>()
val outputFiles = (map["outputFiles"] as? List<*>)?.map { it as String } ?: emptyList()
val importedFiles = (map["importedFiles"] as? List<*>)?.map { it as String } ?: emptyList()
return DaemonResponse(
ok = map["ok"] as Boolean,
@@ -202,9 +202,9 @@ class ModuleImporter(private val program: Program,
val file = it.resolve(fileName)
val isLib = file.parent in libraryPathsSet // a file may be found here before reaching the libraryPaths loop, but still resides in a library dir (customtargets)
val source = ImportFileSystem.getFile(file, isLib)
val origin = when {
it == Path("").absolute() -> "CWD"
it in sourcePaths -> "srcdirs"
val origin = when (it) {
Path("").absolute() -> "CWD"
in sourcePaths -> "srcdirs"
else -> "neighboring directory"
}
trace("Importing module '$name' from file: $file (found in $origin) - imported by '$requestedBy'")
@@ -222,11 +222,61 @@ internal class AstChecker(private val program: Program,
errors.warn("condition is always ${constvalue.asBooleanValue}", ifElse.condition.position)
}
checkIfElseChainAsWhen(ifElse)
super.visit(ifElse)
}
private fun checkIfElseChainAsWhen(ifElse: IfElse) {
// only consider the outermost link of a chain, otherwise a single long chain reports repeatedly
val scope = ifElse.parent
if(scope is AnonymousScope && scope.parent is IfElse && (scope.parent as IfElse).elsepart===scope)
return
// walk down the `else if` chain, gathering the constants that are compared against one and the same value
val testedValues = mutableSetOf<Double>()
var tested: Expression? = null
var node: IfElse? = ifElse
var choices = 0
while(node!=null) {
val binExpr = node.condition as? BinaryExpression ?: return
val rightConst = binExpr.right as? NumericLiteral ?: return
if(binExpr.operator!="==")
return
// a side-effecting expression is re-evaluated by every link in the chain, but only once by a when
if(binExpr.left.hasSideEffects(options.compTarget))
return
if(tested==null)
tested = binExpr.left
else if(!(binExpr.left isSameAs tested))
return
// a repeated constant isn't expressible as a when choice (it would be a compile error)
if(!testedValues.add(rightConst.number))
return
choices++
node = node.elsepart.statements.singleOrNull() as? IfElse
}
if(choices>=3)
errors.info("chain of if-else on the same value is better written as a when statement", ifElse.position)
}
override fun visit(forLoop: ForLoop) {
if (forLoop.linkedVia != null) {
val loopVarDecl = forLoop.loopVar.targetVarDecl()
if (loopVarDecl == null || !loopVarDecl.datatype.isPointer) {
errors.err("linked-list for loop requires a pointer loop variable", forLoop.position)
} else {
checkLinkedForLoop(forLoop, loopVarDecl.datatype, program, errors)
}
super.visit(forLoop)
return
}
if (forLoop.linkedUntil != null) {
errors.err("'until' can only be used in linked-list for loops", forLoop.position)
}
fun checkUnsignedLoopDownto0(range: RangeExpression?) {
if(range==null)
return
@@ -238,7 +288,7 @@ internal class AstChecker(private val program: Program,
}
}
// step validation for non-range iterables (AST desugaring, see ideas/list-iteration.md)
// step validation for non-range iterables
if(forLoop.step != null) {
if(forLoop.iterable is RangeExpression) {
errors.err("step on for-loop cannot be combined with range step - use 'to/downto ... step ...' inside the range", forLoop.position)
@@ -247,25 +297,25 @@ internal class AstChecker(private val program: Program,
if(stepVal==null) {
errors.err("step for non-range iterable must be a constant 1 or -1", forLoop.position)
} else if(stepVal != 1.0 && stepVal != -1.0) {
errors.err("step for non-range iterable must be 1 or -1 (arrays, strings and lists have no stride)", forLoop.position)
errors.err("step for non-range iterable must be 1 or -1 (arrays and strings have no stride)", forLoop.position)
}
}
}
val iterableDt = forLoop.iterable.inferType(program).getOrUndef()
val isTypedForLoop = forLoop.loopVarType != null
val isList = isListIterable(iterableDt)
if(forLoop.iterable is IFunctionCall) {
errors.err("can not loop over function call return value", forLoop.position)
} else if(!(iterableDt.isIterable) && forLoop.iterable !is RangeExpression && !isList) {
errors.err("can only loop over an iterable type", forLoop.position)
} else if(!(iterableDt.isIterable) && forLoop.iterable !is RangeExpression) {
if (iterableDt.isPointer)
errors.err("pointer-linked for loop requires an explicit 'via .field'", forLoop.position)
else
errors.err("can only loop over an iterable type", forLoop.position)
} else {
val loopvar = forLoop.loopVar.targetVarDecl()
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position)
} else if(isList) {
validateListForLoop(forLoop, loopvar, iterableDt, isTypedForLoop)
} else {
when (loopvar.datatype.base) {
BaseDataType.UBYTE -> {
@@ -356,54 +406,6 @@ internal class AstChecker(private val program: Program,
super.visit(forLoop)
}
private fun isListIterable(dt: DataType): Boolean = ListIterationHelper.isListIterable(dt, program)
private fun structDeclFor(dt: DataType): StructDecl? = ListIterationHelper.structDeclFor(dt, program)
private fun isNodeStruct(decl: StructDecl): Boolean = decl.isNodeStruct()
private fun validateListForLoop(forLoop: ForLoop, loopvar: VarDecl, iterableDt: DataType, isTyped: Boolean) {
// loop var must be pointer to node
if(!loopvar.datatype.isPointer) {
errors.err("for loop over list requires a pointer variable (^^Node)", forLoop.position)
return
}
val listDecl = structDeclFor(iterableDt) ?: run {
errors.err("unable to resolve list type", forLoop.position)
return
}
val expectedNodeDecl = structDeclFor(listDecl.fields[0].type) ?: run {
errors.err("list Head field must be a typed pointer", forLoop.position)
return
}
// if explicit type given on for (^^Type var), forLoop.loopVarType holds it, but loopvar.datatype is the actual var's type.
// We validate that loopvar's type is compatible with expected node type, or if explicit type differs, validate that explicit node is also a valid node.
val loopVarNodeDecl = structDeclFor(loopvar.datatype)
if(loopVarNodeDecl==null) {
errors.err("loop variable must be a typed pointer to a node struct with Succ/Pred", forLoop.position)
return
}
if(!isNodeStruct(loopVarNodeDecl)) {
errors.err("loop variable's struct must have Succ/Pred (or Next/Prev) pointer fields at offset 0", forLoop.position)
return
}
// if explicit for type differs from list's node, we allow it as reinterpretation but still require valid node layout (already checked)
// otherwise enforce that loop var type matches list's node type when not explicitly typed differently
if(!isTyped) {
// inferred case: require exact match to list's node type
if(loopVarNodeDecl.name != expectedNodeDecl.name) {
errors.err("loop variable type ^^${loopVarNodeDecl.name} does not match list's node type ^^${expectedNodeDecl.name} - use explicit 'for ^^${expectedNodeDecl.name} ${forLoop.loopVar.nameInSource.single()}' or declare variable as ^^${expectedNodeDecl.name}", forLoop.position)
}
} else {
// explicit typed loop: check that explicit type matches loopvar's declared type
val explicitDt = forLoop.loopVarType!!
val explicitDecl = structDeclFor(explicitDt)
if(explicitDecl!=null && explicitDecl.name != loopVarNodeDecl.name) {
errors.err("explicit for loop type ^^${explicitDecl.name} does not match loop variable's declared type ^^${loopVarNodeDecl.name}", forLoop.position)
}
if(explicitDecl!=null && !isNodeStruct(explicitDecl)) {
errors.err("explicit loop type must be a node struct with Succ/Pred", forLoop.position)
}
}
}
override fun visit(jump: Jump) {
val ident = jump.target as? IdentifierReference
@@ -1027,7 +1029,11 @@ internal class AstChecker(private val program: Program,
}
private fun checkCondition(condition: Expression) {
if(!condition.inferType(program).isBool)
val inferred = condition.inferType(program)
if(inferred.isUnknown)
return // type cannot be determined (yet), e.g. referencing an undefined symbol;
// the proper error is reported by the identifier check
if(!inferred.isBool)
errors.err("condition should be a boolean", condition.position)
val cast = condition as? TypecastExpression
if(cast!=null && cast.type.isBool) {
@@ -2381,8 +2387,7 @@ internal class AstChecker(private val program: Program,
val constIdx = arrayIndexedExpression.indexer.constIndex()
if(constIdx!=null) {
val deref = arrayIndexedExpression.pointerderef!!
val target = deref.definingScope.lookup(deref.chain)
val arraysize: Int? = when(target) {
val arraysize: Int? = when(val target = deref.definingScope.lookup(deref.chain)) {
is StructFieldRef -> target.field.constSize()
is VarDecl -> target.arraysize?.constIndex()
else -> null
@@ -2456,9 +2461,6 @@ internal class AstChecker(private val program: Program,
if(whenStmt.condition.constValue(program)!=null)
errors.warn("when-value is a constant and will always result in the same choice", whenStmt.condition.position)
if(whenStmt.betterAsOnGoto(program, options))
errors.info("when statement can be replaced with on..goto", whenStmt.position)
super.visit(whenStmt)
}
@@ -2646,24 +2648,22 @@ internal class AstChecker(private val program: Program,
}
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
return if (targetDt.isString) {
when {
value.value.length > 255 -> {
errors.err("string length must be 0-255", value.position)
false
}
value.value.isEmpty() -> {
val decl = value.parent as? VarDecl
if(decl!=null && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)) {
errors.err("string in Zeropage must be non-empty", value.position)
false
}
else true
}
else -> true
return targetDt.isString && when {
value.value.length > 255 -> {
errors.err("string length must be 0-255", value.position)
false
}
value.value.isEmpty() -> {
val decl = value.parent as? VarDecl
if(decl!=null && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)) {
errors.err("string in Zeropage must be non-empty", value.position)
false
} else true
}
else -> true
}
else false
}
private fun checkValueTypeAndRangeArray(targetDt: DataType, arrayspec: ArrayIndex, value: ArrayLiteral) : Boolean {
@@ -2689,9 +2689,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize>byteLimit)
return err("boolean array length must be 1-$byteLimit")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
return arraySize == expectedSize || err("array size mismatch (expecting $expectedSize, got $arraySize)")
}
return err("invalid boolean array size, must be 1-$byteLimit")
}
@@ -2708,9 +2706,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize>byteLimit)
return err("byte array length must be 1-$byteLimit")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
return arraySize == expectedSize || err("array size mismatch (expecting $expectedSize, got $arraySize)")
}
return err("invalid byte array size, must be 1-$byteLimit")
}
@@ -2728,9 +2724,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize>maxLength)
return err("array length must be 1-$maxLength")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
return arraySize == expectedSize || err("array size mismatch (expecting $expectedSize, got $arraySize)")
}
return err("invalid array size, must be 1-$maxLength")
}
@@ -2744,9 +2738,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize>maxLength)
return err("array length must be 1-$maxLength")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
return arraySize == expectedSize || err("array size mismatch (expecting $expectedSize, got $arraySize)")
}
return err("invalid array size, must be 1-$maxLength")
}
@@ -2769,9 +2761,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range
val doubles = value.value.map { it.constValue(program)?.number!! }.toDoubleArray()
if(doubles.any { it < options.compTarget.FLOAT_MAX_NEGATIVE || it > options.compTarget.FLOAT_MAX_POSITIVE })
return err("floating point value overflow")
return true
return !doubles.any { it < options.compTarget.FLOAT_MAX_NEGATIVE || it > options.compTarget.FLOAT_MAX_POSITIVE } || err("floating point value overflow")
}
return err("invalid float array initialization value ${value.type}, expected $targetDt")
}
@@ -249,10 +249,7 @@ internal fun Program.variousCleanups(errors: IErrorReporter, options: Compilatio
internal fun IdentifierReference.isSubroutineParameter(): Boolean {
val vardecl = this.targetVarDecl()
if(vardecl!=null && vardecl.origin==VarDeclOrigin.SUBROUTINEPARAM) {
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
}
return false
return vardecl!=null && vardecl.origin==VarDeclOrigin.SUBROUTINEPARAM && vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
}
internal fun Subroutine.hasRtsInAsm(checkOnlyLastInstruction: Boolean): Boolean {
@@ -274,9 +271,7 @@ internal fun Subroutine.hasRtsInAsm(checkOnlyLastInstruction: Boolean): Boolean
val lastLine = lastAsm.assembly.lineSequence().map { it.trim() }.last {
it.isNotBlank() && (!it.startsWith(';') || it.contains("!notreached!"))
}
if(lastLine.contains("!notreached!"))
return true
return allAsms.any { it.hasReturnOrRts() }
return lastLine.contains("!notreached!") || allAsms.any { it.hasReturnOrRts() }
}
}
@@ -51,8 +51,11 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
override fun visit(decl: VarDecl) {
if(decl.name in BuiltinFunctions)
val names = decl.names.ifEmpty { listOf(decl.name) }
if(names.any { it in BuiltinFunctions })
errors.err("builtin function cannot be redefined", decl.position)
else if(names.any { it == "pointer" })
errors.err("variable name cannot be a keyword", decl.position) // 'pointer' is the only datatype name the grammar accepts as an identifier
fun checkNameForErrors(name: String) {
val existingInSameScope = decl.definingScope.lookup(listOf(name))
@@ -98,6 +101,8 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
struct.fields.forEach { field ->
if(field.type.isArray && field.type.sub==BaseDataType.STRUCT_INSTANCE)
errors.err("arrays of struct instances are currently not supported as struct fields at '${field.name}'", struct.position)
else if(field.name == "pointer")
errors.err("struct field name cannot be a keyword", struct.position)
}
super.visit(struct)
}
@@ -106,6 +111,8 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", subroutine.position)
} else if(subroutine.name == "pointer") {
errors.err("subroutine name cannot be a keyword", subroutine.position)
} else {
// already reported elsewhere:
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
@@ -147,15 +154,23 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
}
override fun visit(alias: Alias) {
val existing = alias.definingScope.lookup(listOf(alias.alias))
if(existing != null && existing !== alias)
nameError(alias.alias, alias.position, existing)
if(alias.alias in BuiltinFunctions)
errors.err("builtin function cannot be redefined", alias.position)
else if(alias.alias == "pointer")
errors.err("alias name cannot be a keyword", alias.position)
else {
val existing = alias.definingScope.lookup(listOf(alias.alias))
if(existing != null && existing !== alias)
nameError(alias.alias, alias.position, existing)
}
super.visit(alias)
}
override fun visit(label: Label) {
if(label.name in BuiltinFunctions) {
errors.err("builtin function cannot be redefined", label.position)
} else if(label.name == "pointer") {
errors.err("label name cannot be a keyword", label.position)
} else {
val existing = (label.definingSubroutine ?: label.definingBlock).getAllLabels(label.name)
for(el in existing) {
@@ -544,6 +544,14 @@ class AstPreprocessor(val program: Program,
}
override fun after(enum: Enumeration, parent: Node): Iterable<AstModification> {
if(enum.name in BuiltinFunctions) {
errors.err("builtin function cannot be redefined", enum.position)
return noModifications
}
if(enum.name == "pointer") {
errors.err("enum name cannot be a keyword", enum.position)
return noModifications
}
if (enum.members.isEmpty()) {
errors.err("enumeration must contain at least one member", enum.position)
}
@@ -137,7 +137,11 @@ if not CONDITION
}
private fun checkCondition(condition: Expression): String? {
if(!condition.inferType(program).isBool)
val inferred = condition.inferType(program)
if(inferred.isUnknown)
return null // type cannot be determined (yet), e.g. referencing an undefined symbol;
// the proper error is reported by the identifier check later
if(!inferred.isBool)
return "condition should be a boolean"
val cast = condition as? TypecastExpression
if(cast!=null && cast.type.isBool) {
@@ -206,12 +210,19 @@ _after:
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<AstModification> {
val outerFunc = functionCall.target.nameInSource
if(outerFunc==listOf("peek")) {
// A desugar replacement that produces an Expression can only be applied to a
// FunctionCallExpression, and one that produces a Statement (Assignment or
// FunctionCallStatement) only to a FunctionCallStatement. Applying the wrong kind
// would make the parent's replaceChildNode reject it (FatalAstException). Leaving
// the node un-desugared in the wrong context lets the regular type checks produce
// a proper error message instead.
if(outerFunc==listOf("peek") && functionCall is FunctionCallExpression) {
// peek(a) is synonymous with @(a)
val memread = DirectMemoryRead(functionCall.args.single(), position)
return listOf(AstReplaceNode(functionCall as Node, memread, parent))
}
if(outerFunc==listOf("poke") && parent !is Assignment) {
if(outerFunc==listOf("poke") && functionCall is FunctionCallStatement && parent !is Assignment) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(
null,
@@ -225,7 +236,7 @@ _after:
return listOf(AstReplaceNode(functionCall as Node, assign, parent))
}
if(outerFunc==listOf("pokew") || outerFunc==listOf("pokel") || outerFunc==listOf("pokef")) {
if((outerFunc==listOf("pokew") || outerFunc==listOf("pokel") || outerFunc==listOf("pokef")) && functionCall is FunctionCallStatement) {
val innercall = functionCall.args[1] as? IFunctionCall
val innerFunc = innercall?.target?.nameInSource
val peekname = "peek" + outerFunc[0].substring(4)
@@ -254,7 +265,7 @@ _after:
}
}
if(outerFunc==listOf("pushp")) {
if(outerFunc==listOf("pushp") && functionCall is FunctionCallStatement) {
// pushp adapts to pointer size: pushw on 16-bit targets, pushl on 32-bit
val pushName = if(target.POINTER_MEM_SIZE > 2u) "pushl" else "pushw"
val newCall = FunctionCallStatement(
@@ -265,13 +276,19 @@ _after:
}
if(outerFunc==listOf("popp")) {
val popName = if(target.POINTER_MEM_SIZE > 2u) "popl" else "popw"
val innerTarget = IdentifierReference(listOf(popName), position)
if(functionCall is FunctionCallStatement) {
// popp() used as a statement only discards its result, so it has to desugar
// into a call statement again - not into an expression
return listOf(AstReplaceNode(functionCall, FunctionCallStatement(innerTarget, mutableListOf(), false, position), parent))
}
val newCall = FunctionCallExpression(
IdentifierReference(listOf(popName), position),
innerTarget,
mutableListOf(), position
)
return listOf(AstReplaceNode(functionCall as Node, newCall, parent))
}
if(outerFunc==listOf("pokep")) {
if(outerFunc==listOf("pokep") && functionCall is FunctionCallStatement) {
val pokeName = if(target.POINTER_MEM_SIZE > 2u) "pokel" else "pokew"
val newCall = FunctionCallStatement(
IdentifierReference(listOf(pokeName), position),
@@ -905,8 +922,9 @@ _after:
val conditionVar: VarDecl?
val assignIndex: Assignment?
// put condition in temp var, if it is not simple; to avoid evaluating expression multiple times
if (ongoto.index.isSimple) {
// put condition in temp var, unless it is just a plain variable or constant,
// to avoid evaluating a more complex expression multiple times
if (ongoto.index is IdentifierReference || ongoto.index is NumericLiteral) {
indexValue = ongoto.index
assignIndex = null
conditionVar = null
@@ -991,24 +1009,22 @@ _after:
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<AstModification> {
// Desugar list iteration and reverse array/string iteration (ideas/list-iteration.md Option A)
// - for x in list [step -1] -> while cursor.Succ/Pred !=0 with next/prev saved
if (forLoop.linkedVia != null) {
// leave the invalid step combination for AstChecker, desugaring would hide it
if (forLoop.step != null) return noModifications
return desugarLinkedForLoop(forLoop, parent)
}
// Desugar reverse array/string iteration
// - for x in array step -1 -> index loop len-1 downto 0
// - for c in string step -1 -> similar via index
val iterableDt = forLoop.iterable.inferType(program).getOrUndef()
val isList = isListIterable(iterableDt)
val isArray = iterableDt.isArray
val isString = iterableDt.isString
val stepVal = forLoop.step?.constValue(program)?.number
val isReverse = stepVal == -1.0
val isForward = forLoop.step==null || stepVal==1.0
if(!isList && !(isArray || isString)) return noModifications
if(isList) {
// only 1 / -1 already validated in AstChecker
if(!isForward && !isReverse) return noModifications
return desugarListForLoop(forLoop, parent, isReverse)
}
if((isArray || isString) && isReverse) {
if(!(isArray || isString)) return noModifications
if(isReverse) {
// Keep this in desugaring: backend support would duplicate descending
// index handling for every array/string kind in every code generator,
// for a relatively uncommon use case.
@@ -1018,65 +1034,60 @@ _after:
return noModifications
}
private fun isListIterable(dt: DataType): Boolean = ListIterationHelper.isListIterable(dt, program)
private fun structDeclFor(dt: DataType): StructDecl? = ListIterationHelper.structDeclFor(dt, program)
private fun desugarListForLoop(forLoop: ForLoop, parent: Node, reverse: Boolean): Iterable<AstModification> {
private fun desugarLinkedForLoop(forLoop: ForLoop, parent: Node): Iterable<AstModification> {
val pos = forLoop.position
// need list struct to know Head/TailPred field names and node link names
val iterableDt = forLoop.iterable.inferType(program).getOrUndef()
val listDecl = structDeclFor(iterableDt) ?: return noModifications
val nodeDecl = structDeclFor(listDecl.fields[0].type) ?: return noModifications
val linkSuccName = nodeDecl.fields[0].name // Succ or Next
val linkPredName = nodeDecl.fields[1].name // Pred or Prev
val linkName = if(reverse) linkPredName else linkSuccName
val headField = if(reverse) "TailPred" else "Head"
// Instead create a synthetic pointer variable in the enclosing scope - typed to the list's node
// For simplicity, create a new VarDecl for cursor in the same block as the for loop
val cursorDt = DataType.pointer(nodeDecl)
val loopVarName = forLoop.loopVar.nameInSource.singleOrNull() ?: return noModifications
// ensure loop var exists (implicit var may not have been created if AstChecker/Implicit pass missed it)
val existingLoopVar = forLoop.loopVar.targetVarDecl()
val loopVarDecl: VarDecl? = if(existingLoopVar==null) {
val effType = forLoop.loopVarType ?: DataType.pointer(nodeDecl)
VarDecl.builder(effType, pos).names(loopVarName).type(VarDeclType.VAR).build()
} else null
// make cursor/next names unique per for-loop position to avoid hoisting collisions in the same subroutine
val uniq = "${pos.line}_${pos.startCol}"
val cursorVar = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, cursorDt, ZeropageWish.DONTCARE, SplitWish.DONTCARE, null, null, "list_cursor_${loopVarName}_${uniq}", emptyList(), null, false, 0u, false, null, pos)
// Build: cursor = iterable.Head (or TailPred) - use IdentifierReference with dotted name for struct field
val iterableRefCopy = forLoop.iterable.copy()
val iterableName = (iterableRefCopy as? IdentifierReference)?.nameInSource ?: return noModifications
val headAccess: Expression = IdentifierReference(iterableName + headField, pos)
val cursorAssign = Assignment(AssignTarget(IdentifierReference(listOf(cursorVar.name), pos), null, null, null, false, position=pos), headAccess, AssignmentOrigin.OPTIMIZER, pos)
// Stop at the embedded list sentinel rather than relying on a null link.
val sentinel: Expression = if(reverse) {
// AddressOf a pointer field is represented as the target's raw address.
AddressOf(IdentifierReference(iterableName + "Head", pos), null, null, false, false, pos)
} else {
AddressOf(IdentifierReference(iterableName + "Tail", pos), null, null, false, false, pos)
val nodeRef = forLoop.loopVar.copy()
val startExpr = forLoop.iterable
val linkField = forLoop.linkedVia ?: error("linked loop desugared without a via field")
val sentinelExpr = forLoop.linkedUntil ?: NumericLiteral.optimalInteger(0, pos)
// Declare the loop variable if it is not already declared.
val statements = mutableListOf<Statement>()
val names = forLoop.loopVar.nameInSource
val scope = forLoop.definingScope
val existing = if (names.size == 1) scope.lookup(names.single()) else null
if (existing !is VarDecl) {
val iterableType = forLoop.iterable.inferType(program).getOrUndef()
val effectiveType = forLoop.loopVarType ?: iterableType
val decl = VarDecl.builder(effectiveType, pos)
.names(names.single())
.type(VarDeclType.VAR)
.build()
statements.add(decl)
}
val condition = BinaryExpression(IdentifierReference(listOf(cursorVar.name), pos), "!=", sentinel, pos)
// use IdentifierReference with dotted access; later desugarer pass will convert pointer field access to PtrDereference
val linkFieldAccessForNext = IdentifierReference(listOf(cursorVar.name, linkName), pos)
// next/prev temp - also typed to node pointer for dereference
val nextDt = DataType.pointer(nodeDecl)
val nextVar = VarDecl(VarDeclType.VAR, VarDeclOrigin.USERCODE, nextDt, ZeropageWish.DONTCARE, SplitWish.DONTCARE, null, null, "list_next_${loopVarName}_${uniq}", emptyList(), null, false, 0u, false, null, pos)
val nextAssign = Assignment(AssignTarget(IdentifierReference(listOf(nextVar.name), pos), null, null, null, false, position=pos), linkFieldAccessForNext, AssignmentOrigin.OPTIMIZER, pos)
val loopVarAssign = Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, null, false, position=pos), IdentifierReference(listOf(cursorVar.name), pos), AssignmentOrigin.OPTIMIZER, pos)
val cursorUpdate = Assignment(AssignTarget(IdentifierReference(listOf(cursorVar.name), pos), null, null, null, false, position=pos), IdentifierReference(listOf(nextVar.name), pos), AssignmentOrigin.OPTIMIZER, pos)
val whileBody = AnonymousScope(mutableListOf<Statement>(nextAssign, loopVarAssign).apply { addAll(forLoop.body.statements) }.apply { add(cursorUpdate) }, pos)
val initAssign = Assignment(
AssignTarget(nodeRef.copy(), null, null, null, false, position = pos),
startExpr,
AssignmentOrigin.OPTIMIZER,
pos
)
statements.add(initAssign)
val condition = BinaryExpression(nodeRef.copy(), "!=", sentinelExpr, pos)
val fieldAccess = BinaryExpression(nodeRef.copy(), ".", IdentifierReference(listOf(linkField), pos), pos)
val tailAssign = Assignment(
AssignTarget(nodeRef.copy(), null, null, null, false, position = pos),
fieldAccess,
AssignmentOrigin.OPTIMIZER,
pos
)
val whileBody = AnonymousScope(
mutableListOf<Statement>().apply {
addAll(forLoop.body.statements)
add(tailAssign)
},
pos
)
val whileLoop = WhileLoop(condition, whileBody, pos)
val stmts = mutableListOf<Statement>()
if(loopVarDecl!=null) stmts.add(loopVarDecl)
stmts.add(cursorVar)
stmts.add(nextVar)
stmts.add(cursorAssign)
stmts.add(whileLoop)
val replacement = AnonymousScope(stmts, pos)
// set parents
statements.add(whileLoop)
val replacement = AnonymousScope(statements, pos)
replacement.linkParents(parent)
return listOf(AstReplaceNode(forLoop, replacement, parent))
}
private fun desugarReverseArrayStringForLoop(forLoop: ForLoop, parent: Node, iterableDt: DataType): Iterable<AstModification> {
val pos = forLoop.position
// Create index variable - make unique per loop position to avoid hoisting collisions
@@ -1129,7 +1140,7 @@ _after:
val idxDec = Assignment(AssignTarget(IdentifierReference(listOf(idxName), pos), null, null, null, false, position=pos), BinaryExpression(IdentifierReference(listOf(idxName), pos), "-", NumericLiteral.optimalInteger(1, pos), pos), AssignmentOrigin.OPTIMIZER, pos)
val whileBody = AnonymousScope(mutableListOf<Statement>(elementAssign).apply { addAll(forLoop.body.statements) }.apply { add(idxDec) }, pos)
val whileLoop = WhileLoop(condition, whileBody, pos)
val replacement = AnonymousScope(mutableListOf<Statement>(wordIdxVar, wordIdxInit, whileLoop), pos)
val replacement = AnonymousScope(mutableListOf(wordIdxVar, wordIdxInit, whileLoop), pos)
replacement.linkParents(parent)
return listOf(AstReplaceNode(forLoop, replacement, parent))
} else if(iterableDt.isString) {
@@ -1143,7 +1154,7 @@ _after:
val lenInit = Assignment(AssignTarget(IdentifierReference(listOf(lenName), pos), null, null, null, false, position=pos), NumericLiteral.optimalInteger(0, pos), AssignmentOrigin.OPTIMIZER, pos)
val lenCond = BinaryExpression(ArrayIndexedExpression(iterableRef.copy(), null, null, ArrayIndex(IdentifierReference(listOf(lenName), pos), pos), pos), "!=", NumericLiteral.optimalInteger(0, pos), pos)
val lenInc = Assignment(AssignTarget(IdentifierReference(listOf(lenName), pos), null, null, null, false, position=pos), BinaryExpression(IdentifierReference(listOf(lenName), pos), "+", NumericLiteral.optimalInteger(1, pos), pos), AssignmentOrigin.OPTIMIZER, pos)
val lenLoop = WhileLoop(lenCond, AnonymousScope(mutableListOf<Statement>(lenInc), pos), pos)
val lenLoop = WhileLoop(lenCond, AnonymousScope(mutableListOf(lenInc), pos), pos)
val idxInit = Assignment(AssignTarget(IdentifierReference(listOf(idxName), pos), null, null, null, false, position=pos), BinaryExpression(IdentifierReference(listOf(lenName), pos), "-", NumericLiteral.optimalInteger(1, pos), pos), AssignmentOrigin.OPTIMIZER, pos)
// unsigned wrap condition: idx < len (start len-1, wraps to 65535 after 0 and exits)
val cond = BinaryExpression(IdentifierReference(listOf(idxName), pos), "<", IdentifierReference(listOf(lenName), pos), pos)
@@ -1218,7 +1229,7 @@ _after:
fieldDt.isPointer -> {
// the field is itself a pointer: load it, then index into what it points to
val elemDt = fieldDt.dereference()
if(!elemDt.isNumericOrBool) {
if(!elemDt.isNumericOrBool && !elemDt.isPointer) {
errors.err("cannot index into field '${field.first}' of struct '${struct.name}' here", deref.position)
return null
}
@@ -1231,7 +1242,7 @@ _after:
}
fieldDt.isArray -> {
val elemDt = fieldDt.elementType()
if(!elemDt.isNumericOrBool) {
if(!elemDt.isNumericOrBool && !elemDt.isPointer) {
errors.err("cannot index into field '${field.first}' of struct '${struct.name}' here", deref.position)
return null
}
@@ -1255,9 +1266,8 @@ _after:
fun buildChainFromDotExpression(expr: BinaryExpression): List<Pair<String, ArrayIndex?>>? {
if(expr.operator!=".") return null
val right = expr.right as? IdentifierReference ?: return null
val left = expr.left
// left could be ArrayIndexedExpression (for ptr[idx]) or IdentifierReference (for ptr)
return when(left) {
return when(val left = expr.left) {
is ArrayIndexedExpression -> {
val ptrName = left.plainarrayvar?.nameInSource ?: return null
listOf(Pair(ptrName[0], left.indexer), Pair(right.nameInSource[0], null))
@@ -1287,7 +1297,7 @@ _after:
// Handle "ptr[idx].field" represented as BinaryExpression with "." operator
if(value is BinaryExpression && value.operator==".") {
val chain = buildChainFromDotExpression(value)
if(chain != null && chain == origDeref.chain && origDeref.derefLast == false) {
if(chain != null && chain == origDeref.chain && !origDeref.derefLast) {
return DirectMemoryRead(address.copy(), value.position)
}
}
@@ -1328,7 +1338,8 @@ _after:
dt.isSignedWord -> "pokew" to DataType.UWORD
dt.isLong -> "pokel" to null
dt.isFloat -> "pokef" to null
else -> throw FatalAstException("can only deref a numeric or boolean pointer here")
dt.isPointer -> "pokep" to null
else -> throw FatalAstException("cannot poke into a value of type $dt")
}
}
@@ -1437,7 +1448,8 @@ _after:
elemDt.isSignedWord -> "peekw" to DataType.WORD
elemDt.isLong -> "peekl" to null
elemDt.isFloat -> "peekf" to null
else -> return noModifications // guarded in helper; unreachable
elemDt.isPointer -> "peekp" to null
else -> return noModifications
}
val peekCall = FunctionCallExpression(IdentifierReference(listOf(peekFunc), deref.position), mutableListOf(address), deref.position)
val replacement: Expression = if(cast==null) peekCall else TypecastExpression(peekCall, cast, true, deref.position)
@@ -22,6 +22,11 @@ import prog8.code.source.SourceCode
* Limitations:
* - Maximum 8 defers per subroutine on 6502/virtual, 16 on m68k (due to UBYTE/UWORD mask)
* - Only simple return values can be deferred (complex expressions need push/pop)
* - Recursive or mutually recursive subroutines that contain `defer` are rejected on stack-frame
* targets (amiga500, amiga1200, qemu68k). The pending-defer mask is a single static variable per
* subroutine; multiple live activations would share it, so an inner activation's registration or
* handler could clobber/clear the outer activation's pending defers. A per-activation mask would
* cost extra RAM and code on every target for a rare case, so the compiler rejects it instead.
*/
internal object DeferProcessor {
@@ -215,6 +220,10 @@ internal object DeferProcessor {
return emptyMap()
}
// Define one pending-defer mask variable per subroutine. This is intentionally simple
// and compact, but it cannot distinguish multiple live activations of the same sub;
// recursive or mutually recursive deferring subs are therefore rejected on stack-frame
// targets (see StackFrameLayout.reportUnsoundStaticState).
// define the bitmask variable and set it to zero
val deferVariable = PtVariable(
maskVarName,
@@ -450,29 +459,29 @@ internal object DeferProcessor {
exitCall.parent.add(idx, unwindCall)
}
// generate per-sub handlers (same as before)
// generate per-sub handlers. Test each defer's bit directly with `mask & bit != 0`
// instead of shifting and branching on carry: on m68k the `>>=` store uses MOVE,
// which clears the carry bit before any conditional branch can observe it.
for( (sub, defers) in subdefers) {
val defersRoutine = PtSub(invokeDefersRoutineName, emptyList(), emptyList(), sub.position)
defersRoutine.parent=sub
for((idx, defer) in defers.reversed().withIndex()) {
val shift = PtAugmentedAssign(">>=", defer.position)
shift.add(PtAssignTarget(false, defer.position).also {
it.add(PtIdentifier(sub.scopedName+"."+maskVarName, maskType, defer.position))
})
val baseType = if (maskType == DataType.UWORD) BaseDataType.UWORD else BaseDataType.UBYTE
shift.add(PtNumber(baseType, 1.0, defer.position))
defersRoutine.add(shift)
val skiplabel = "prog8_defer_skip_${idx+1}"
val branchcc = PtConditionalBranch(BranchCondition.CC, defer.position)
branchcc.add(PtNodeGroup().also {
val jump = PtJump(defer.position)
jump.add(PtIdentifier(defersRoutine.scopedName+"."+skiplabel, DataType.UBYTE, defer.position))
it.add(jump)
})
branchcc.add(PtNodeGroup())
defersRoutine.add(branchcc)
transferChildren(defer, defersRoutine, true)
defersRoutine.add(PtLabel(skiplabel, defer.position))
val baseType = if (maskType == DataType.UWORD) BaseDataType.UWORD else BaseDataType.UBYTE
for(deferIndex in defers.indices.reversed()) {
val defer = defers[deferIndex]
// if mask & bit != 0 { <defer body> }
val bitTest = PtBinaryExpression("&", maskType, defer.position)
bitTest.add(PtIdentifier(sub.scopedName+".${maskVarName}", maskType, defer.position))
bitTest.add(PtNumber(baseType, (1 shl (defers.size-1 - deferIndex)).toDouble(), defer.position))
val cond = PtBinaryExpression("!=", DataType.BOOL, defer.position)
cond.add(bitTest)
cond.add(PtNumber(baseType, 0.0, defer.position))
val ifNode = PtIfElse(defer.position)
ifNode.add(cond)
val trueScope = PtNodeGroup()
transferChildren(defer, trueScope, true)
ifNode.add(trueScope)
ifNode.add(PtNodeGroup())
defersRoutine.add(ifNode)
}
defersRoutine.add(PtReturn(sub.position))
sub.add(defersRoutine)
@@ -34,19 +34,23 @@ class ImplicitForIteratorDecls(
if (!inferredIterableType.isKnown)
return emptyList()
val iterableType = inferredIterableType.getOrUndef()
val isList = isListIterable(iterableType)
if (!iterableType.isIterable && forLoop.iterable !is RangeExpression && !isList)
val isLinked = forLoop.linkedVia != null
if (!iterableType.isIterable && forLoop.iterable !is RangeExpression && !isLinked)
return emptyList()
val elementType = if(isList) {
val listDecl = structDeclFor(iterableType) ?: return emptyList()
val nodeType = listDecl.fields[0].type
// Head field is ^^Node, use that as element type
// Resolve it in the list declaration's own scope: the field type may not
// have been processed yet if its module hasn't been visited (and the name
// may not even be visible from the scope using the list).
resolveDt(nodeType, listDecl.definingScope, forLoop.position)
} else iterableType.elementType()
val elementType = if (isLinked) {
iterableType.takeIf { it.isPointer } ?: return emptyList()
} else {
iterableType.elementType()
}
if (isLinked) {
checkLinkedForLoop(forLoop, elementType, program, errors)
if (!errors.noErrors())
return emptyList()
}
val scope = forLoop.definingScope
val existing = scope.lookup(names)
@@ -120,8 +124,7 @@ class ImplicitForIteratorDecls(
return listOf(AstInsert.before(forLoop, declaration, container))
}
private fun isListIterable(dt: DataType): Boolean = ListIterationHelper.isListIterable(dt, program)
private fun structDeclFor(dt: DataType): prog8.ast.statements.StructDecl? = ListIterationHelper.structDeclFor(dt, program)
private fun resolveDt(dt: DataType, scope: INameScope, position: Position): DataType {
val antlrName = dt.subTypeFromAntlr ?: return dt
val resolved = StructTypeResolver.resolve(scope, program, antlrName, position, errors) ?: return dt
@@ -131,4 +134,5 @@ class ImplicitForIteratorDecls(
else -> dt
}
}
}
@@ -0,0 +1,139 @@
package prog8.compiler.astprocessing
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.ForLoop
import prog8.code.core.DataType
import prog8.code.core.IErrorReporter
import prog8.code.core.ISubType
import prog8.code.core.Position
internal fun checkLinkedForLoop(
forLoop: ForLoop,
nodeTypeIn: DataType,
program: Program,
errors: IErrorReporter
): Boolean {
val nodeType = resolveDt(nodeTypeIn, forLoop.definingScope, program)
if (!nodeType.isPointer) {
errors.err("linked-list for loop requires a pointer loop variable", forLoop.position)
return false
}
val iterableTypeRaw = forLoop.iterable.inferType(program).getOrUndef()
val iterableType = resolveDt(iterableTypeRaw, forLoop.definingScope, program)
if (!iterableType.isPointer) {
errors.err("linked-list for loop start must be a pointer", forLoop.position)
return false
}
if (iterableType != nodeType) {
errors.err("linked-list for loop start must be a pointer to the same node type", forLoop.position)
return false
}
val nodeStruct = nodeType.dereference().subType
if (nodeStruct == null) {
errors.err("linked-list for loop node type must be a struct", forLoop.position)
return false
}
val linkField = forLoop.linkedVia!!
val fieldType = nodeStruct.getFieldType(linkField)?.let {
resolveDeferredType(it, nodeStruct, forLoop.definingScope, program, errors, forLoop.position)
}
if (fieldType == null || !fieldType.isPointer || fieldType != nodeType) {
errors.err("linked-list for loop 'via' field must be a pointer to the same node type", forLoop.position)
return false
}
if (forLoop.step != null) {
errors.err("linked-list for loop cannot use 'step'", forLoop.position)
}
forLoop.linkedUntil?.let { untilExpr ->
if (!isValidLinkedUntil(untilExpr, nodeType, program)) {
errors.err("linked-list for loop 'until' must be a compatible node pointer or 0", forLoop.position)
}
}
return errors.noErrors()
}
private fun isValidLinkedUntil(untilExpr: Expression, nodeType: DataType, program: Program): Boolean {
if (untilExpr is NumericLiteral && untilExpr.number == 0.0)
return true
val untilType = untilExpr.inferType(program).getOrUndef()
if (untilType == nodeType)
return true
if (untilExpr is AddressOf) {
val addressOfTargetType = untilExpr.identifier?.inferType(program)?.getOrUndef()
if (addressOfTargetType == nodeType)
return true
// Exec-style sentinel: address of an untyped pointer field.
// The plain `pointer` base type is normalized to the target's pointer-sized integer (uword/long),
// so accept that as well.
if (addressOfTargetType?.base == program.target.pointerBaseType)
return true
}
return false
}
internal fun resolveDeferredType(
dt: DataType,
nodeStruct: ISubType,
scope: INameScope,
program: Program,
errors: IErrorReporter,
position: Position
): DataType {
val antlrName = dt.subTypeFromAntlr ?: return dt
val structScope = nodeStruct.scopedNameString.split('.').dropLast(1)
val name = if (antlrName.size == 1 && structScope.isNotEmpty()) structScope + antlrName else antlrName
StructTypeResolver.resolve(scope, program, name, position, errors)?.let(dt::setActualSubType)
return dt
}
private fun resolveDt(dt: DataType, scope: INameScope, program: Program): DataType {
val antlrName = dt.subTypeFromAntlr ?: return dt
// Try the scope first, then search all modules/blocks for a struct with that name.
var resolved = scope.lookup(antlrName) as? ISubType
if (resolved == null && antlrName.size == 1) {
for (module in program.modules) {
val block = findStructInScope(module, antlrName[0])
if (block != null) {
resolved = block
break
}
}
}
return if (resolved != null) {
when {
dt.isPointer -> DataType.pointer(resolved)
dt.isStructInstance -> DataType.structInstance(resolved)
else -> dt
}
} else {
dt
}
}
private fun findStructInScope(scope: INameScope, name: String): ISubType? {
val direct = scope.lookup(name) as? ISubType
if (direct != null) return direct
for (stmt in scope.statements) {
if (stmt is INameScope) {
val found = findStructInScope(stmt, name)
if (found != null) return found
}
}
return null
}
@@ -1,55 +0,0 @@
package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.statements.StructDecl
import prog8.code.core.DataType
/**
* Shared helpers for list-iteration feature (see ideas/list-iteration.md).
* Structural contract:
* - List: Head, Tail, TailPred in order; Head/TailPred are typed pointers to same node type; Tail is plain pointer sentinel.
* - Node: Succ/Pred or Next/Prev at offset 0, both typed self-pointers, not mixed.
* NDK reference: /mnt/nfs/biggie/Storage/Retro/Amiga/AmigaNDK_headers Include_H/exec/lists.h, Include_I/exec/lists.i
*/
object ListIterationHelper {
fun structDeclFor(dt: DataType, program: Program): StructDecl? {
val sub = dt.subType as? StructDecl
if (sub != null) return sub
val nameList = dt.subTypeFromAntlr ?: return null
val simpleName = nameList.last()
// search recursively - structs can be inside blocks, subroutines, etc. (e.g. local MyNode inside main.start)
fun findInContainer(container: prog8.ast.IStatementContainer): StructDecl? {
for (stmt in container.statements) {
if (stmt is StructDecl && stmt.name == simpleName) return stmt
if (stmt is prog8.ast.statements.Block) {
findInContainer(stmt)?.let { return it }
}
if (stmt is prog8.ast.statements.Subroutine) {
// Subroutine is also a container via its statements
for (s in stmt.statements) if (s is StructDecl && s.name == simpleName) return s
// also check nested AnonymousScope inside subroutine via walk?
}
if (stmt is prog8.ast.statements.AnonymousScope) {
findInContainer(stmt)?.let { return it }
}
}
return null
}
for (mod in program.modules) {
findInContainer(mod)?.let { return it }
for (stmt in mod.statements) {
if (stmt is prog8.ast.statements.Block) {
findInContainer(stmt)?.let { return it }
}
}
}
// fallback: try fully qualified lookup via first block's scope
return null
}
fun isListIterable(dt: DataType, program: Program): Boolean {
val decl = structDeclFor(dt, program) ?: return false
return decl.isListStruct()
}
}
@@ -43,9 +43,7 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
else -> null
}
if(baseDt != null) {
val arraySize = field.arraySize?.indexExpr?.constValue(program)?.number?.toInt()
if(arraySize == null)
return noModifications
val arraySize = field.arraySize?.indexExpr?.constValue(program)?.number?.toInt() ?: return noModifications
val encoded = program.target.encodeString(string.value, string.encoding)
if(encoded.size > arraySize) {
errors.err("string literal (${encoded.size} bytes) does not fit in ${baseDt.name.lowercase()} array field '${field.name}' of size $arraySize", string.position)
@@ -222,7 +220,7 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
}
}
if(target==null && identifier.nameInSource.size>1) {
if(identifier.nameInSource.size>1) {
// First component might be an alias
val tgt2 = identifier.definingScope.lookup(identifier.nameInSource[0]) as? Alias
if(tgt2!=null && parent !is Alias) {
@@ -282,10 +280,7 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
}
}
}
val targetStatement = resolveAliasTarget(target)
if(targetStatement == null) {
return noModifications // alias loop or unresolved
}
val targetStatement = resolveAliasTarget(target) ?: return noModifications // alias loop or unresolved
val replacement: IdentifierReference =
when (targetStatement) {
is StructFieldRef -> {
@@ -21,10 +21,8 @@ internal class NamedStructInitializerFlattener(private val errors: IErrorReporte
return noModifications
val struct = initializer.structname.targetStructDecl()
if(struct==null) {
// Struct name not resolvable yet; leave named args for later error reporting.
?: // Struct name not resolvable yet; leave named args for later error reporting.
return noModifications
}
// Unions do not support named-field initialization; the checker rejects those.
if(struct.isUnion)
@@ -1280,7 +1280,7 @@ class SimplifiedAstMaker(private val program: Program, private val errors: IErro
private fun transform(srcCast: TypecastExpression): PtTypeCast {
val inner = transformExpression(srcCast.expression)
require(inner !is PtNumber) {
"numeric literal typecast should have been resolved at the compiler Ast level, not at simplified Ast: ${srcCast}"
"numeric literal typecast should have been resolved at the compiler Ast level, not at simplified Ast: $srcCast"
}
val cast = PtTypeCast(srcCast.type, srcCast.implicit, srcCast.position)
cast.add(inner)
@@ -66,10 +66,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
return listOf(AstReplaceNode(decl, vardecl, parent))
}
if(valueDt.largerSizeThan(decl.datatype)) {
val constValue = decl.value!!.constValue(program)
if(constValue==null) {
return noModifications
}
val constValue = decl.value!!.constValue(program) ?: return noModifications
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
} else if(decl.datatype.largerSizeThan(valueDt)) {
// don't make it signed if it was unsigned and vice versa, except when it is a long const declaration
@@ -506,6 +503,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
override fun after(ifElse: IfElse, parent: Node): Iterable<AstModification> {
if(ifElse.truepart.isEmpty() && ifElse.elsepart.isEmpty()) {
if(ifElse.condition.inferType(program).isUnknown)
return noModifications // keep it so the identifier check can report the unresolved symbol
errors.info("removing empty if-else statement", ifElse.position)
return listOf(AstRemove(ifElse, parent as IStatementContainer))
}
@@ -606,7 +605,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
override fun after(deref: PtrDereference, parent: Node): Iterable<AstModification> {
fun peekFunc(dt: BaseDataType): Pair<String, DataType?> {
fun peekFunc(dt: BaseDataType): Pair<String, DataType?>? {
return when {
dt.isBool -> "peekbool" to null
dt.isUnsignedByte -> "peek" to null
@@ -615,7 +614,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
dt.isSignedWord -> "peekw" to DataType.WORD
dt.isLong -> "peekl" to null
dt.isFloat -> "peekf" to null
else -> throw FatalAstException("unexpected peek type $dt")
dt.isPointer -> "peekp" to null
else -> null
}
}
@@ -630,7 +630,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// ptr1.field[0] --> peek(ptr.field)
val dt=deref.inferType(program).getOrUndef()
if(dt.sub!=null) {
val (peek, valueCast) = peekFunc(dt.sub!!)
// other pointee types (such as ^^str) are left alone for later validation
val (peek, valueCast) = peekFunc(dt.sub!!) ?: return noModifications
val peekF = FunctionCallExpression(IdentifierReference(listOf(peek), deref.position), mutableListOf(replaceDerefWithIdentifier(deref)), deref.position)
val typedPeek = if(valueCast==null) peekF else TypecastExpression(peekF, valueCast, true, deref.position)
return listOf(AstReplaceNode(parent, typedPeek, parent.parent))
@@ -639,7 +640,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// ptr1.field[index] --> peek(ptr.field + index) (making sure the index is uword typed)
val dt=deref.inferType(program).getOrUndef()
if(dt.sub!=null) {
val (peek, valueCast) = peekFunc(dt.sub!!)
// other pointee types (such as ^^str) are left alone for later validation
val (peek, valueCast) = peekFunc(dt.sub!!) ?: return noModifications
val indexer = if(constIndex==null) {
if(parent.indexer.indexExpr.inferType(program).isUnsignedWord)
parent.indexer.indexExpr
@@ -658,7 +660,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
// ptr1.field^^ --> peek(ptr1.field)
val dt=deref.inferType(program).getOrUndef()
if(!dt.isUndefined && dt.base.isNumericOrBool) {
val (peek, valueCast) = peekFunc(dt.base)
val (peek, valueCast) = peekFunc(dt.base) ?: return noModifications
val peekF = FunctionCallExpression(IdentifierReference(listOf(peek), deref.position), mutableListOf(replaceDerefWithIdentifier(deref)), deref.position)
val typedPeek = if(valueCast==null) peekF else TypecastExpression(peekF, valueCast, true, deref.position)
return listOf(AstReplaceNode(deref, typedPeek, parent))
@@ -679,8 +681,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
private fun replaceDerefWithIdentifier(deref: PtrDereference): IdentifierReference {
val ident = IdentifierReference(deref.chain, deref.position)
val target = deref.definingScope.lookup(ident.nameInSource)
when (target) {
when (val target = deref.definingScope.lookup(ident.nameInSource)) {
is VarDecl -> require(target.datatype.isPointer || target.datatype == options.compTarget.pointerType)
is StructFieldRef -> require(target.type.isPointer || target.type == options.compTarget.pointerType)
else -> throw FatalAstException("requires pointer or uword dereference target at ${deref.position}")
+55 -55
View File
@@ -145,23 +145,23 @@ class TestCompilerOnExamplesC64: FunSpec({
val onlyC64 = cartesianProduct(
listOf(
ExampleSizes("balloonflight", c64SizeOptimized=1674, c64SizeUnoptimized=1733),
ExampleSizes("balloonflight", c64SizeOptimized=1674, c64SizeUnoptimized=3481),
ExampleSizes("banking", c64SizeOptimized=680, c64SizeUnoptimized=1356),
ExampleSizes("bdmusic-irq", c64SizeOptimized=1001, c64SizeUnoptimized=1019),
ExampleSizes("bdmusic", c64SizeOptimized=938, c64SizeUnoptimized=1011),
ExampleSizes("charset", c64SizeOptimized=835, c64SizeUnoptimized=860),
ExampleSizes("cube3d-sprites", c64SizeOptimized=2664, c64SizeUnoptimized=2641),
ExampleSizes("cube3d-sprites", c64SizeOptimized=2846, c64SizeUnoptimized=4075),
ExampleSizes("fileselector", c64SizeOptimized=3523, c64SizeUnoptimized=4510),
ExampleSizes("library/main", c64SizeOptimized=321, c64SizeUnoptimized=1147),
ExampleSizes("multiplexer", c64SizeOptimized=1805, c64SizeUnoptimized=1909),
ExampleSizes("petswirl", c64SizeOptimized=939, c64SizeUnoptimized=1005),
ExampleSizes("plasma", c64SizeOptimized=1613, c64SizeUnoptimized=1654),
ExampleSizes("rasterbars", c64SizeOptimized=845, c64SizeUnoptimized=892),
ExampleSizes("multiplexer", c64SizeOptimized=1987, c64SizeUnoptimized=3333),
ExampleSizes("petswirl", c64SizeOptimized=1121, c64SizeUnoptimized=2435),
ExampleSizes("plasma", c64SizeOptimized=1532, c64SizeUnoptimized=3085),
ExampleSizes("rasterbars", c64SizeOptimized=776, c64SizeUnoptimized=2319),
ExampleSizes("simplemultiplexer", c64SizeOptimized=843, c64SizeUnoptimized=900),
ExampleSizes("sprites", c64SizeOptimized=915, c64SizeUnoptimized=1028),
ExampleSizes("sprites", c64SizeOptimized=915, c64SizeUnoptimized=2772),
ExampleSizes("starfield", c64SizeOptimized=737, c64SizeUnoptimized=758),
ExampleSizes("turtle-gfx", c64SizeOptimized=2701, c64SizeUnoptimized=2812),
ExampleSizes("wizzine", c64SizeOptimized=1124, c64SizeUnoptimized=1224),
ExampleSizes("wizzine", c64SizeOptimized=1063, c64SizeUnoptimized=2653),
// boingball, cube3d, tehtriz are in BothC64andCx16
),
listOf(false, true)
@@ -196,8 +196,8 @@ class TestCompilerOnExamplesCx16: FunSpec({
ExampleSizes("pcmaudio/stream-simple-aflow", cx16SizeOptimized=1842, cx16SizeUnoptimized=3612),
ExampleSizes("pcmaudio/stream-simple-poll", cx16SizeOptimized=1556, cx16SizeUnoptimized=3322),
ExampleSizes("pcmaudio/vumeter", cx16SizeOptimized=3862, cx16SizeUnoptimized=6221),
ExampleSizes("sprites/dragon", cx16SizeOptimized=1858, cx16SizeUnoptimized=4318),
ExampleSizes("sprites/dragons", cx16SizeOptimized=1503, cx16SizeUnoptimized=3626),
ExampleSizes("sprites/dragon", cx16SizeOptimized=1785, cx16SizeUnoptimized=5746),
ExampleSizes("sprites/dragons", cx16SizeOptimized=1503, cx16SizeUnoptimized=5361),
ExampleSizes("zsmkit_v1/demo1", cx16SizeOptimized=7472, cx16SizeUnoptimized=7910),
ExampleSizes("zsmkit_v1/demo2", cx16SizeOptimized=708, cx16SizeUnoptimized=2516),
// zsmkit_v1/zsmkit_high and zsmkit_v1/zsmkit_low don't compile (empty source files)
@@ -208,43 +208,43 @@ class TestCompilerOnExamplesCx16: FunSpec({
ExampleSizes("fileselector/standalone", cx16SizeOptimized=4135, cx16SizeUnoptimized=6070),
ExampleSizes("fileselector/main", cx16SizeOptimized=480, cx16SizeUnoptimized=2314),
// fileselector/fselector and fileselector/namesorting don't compile (empty source files)
ExampleSizes("pointers/fountain-cx16", cx16SizeOptimized=1239, cx16SizeUnoptimized=1582),
ExampleSizes("amiga", cx16SizeOptimized=7603, cx16SizeUnoptimized=11610),
ExampleSizes("pointers/fountain-cx16", cx16SizeOptimized=1239, cx16SizeUnoptimized=3326),
ExampleSizes("amiga", cx16SizeOptimized=7619, cx16SizeUnoptimized=11610),
ExampleSizes("audioroutines", cx16SizeOptimized=948, cx16SizeUnoptimized=1244),
ExampleSizes("automatons", cx16SizeOptimized=1037, cx16SizeUnoptimized=1342),
ExampleSizes("balloonflight", cx16SizeOptimized=3178, cx16SizeUnoptimized=4196),
ExampleSizes("automatons", cx16SizeOptimized=1037, cx16SizeUnoptimized=3094),
ExampleSizes("balloonflight", cx16SizeOptimized=3178, cx16SizeUnoptimized=5936),
ExampleSizes("bdmusic", cx16SizeOptimized=2339, cx16SizeUnoptimized=2704),
ExampleSizes("bobs", cx16SizeOptimized=2794, cx16SizeUnoptimized=3414),
ExampleSizes("bobs", cx16SizeOptimized=2976, cx16SizeUnoptimized=4840),
ExampleSizes("bubbleuniverse", cx16SizeOptimized=1367, cx16SizeUnoptimized=1883),
ExampleSizes("charfade", cx16SizeOptimized=1054, cx16SizeUnoptimized=1376),
ExampleSizes("charfade", cx16SizeOptimized=1054, cx16SizeUnoptimized=3120),
ExampleSizes("charsets", cx16SizeOptimized=1125, cx16SizeUnoptimized=1417),
ExampleSizes("circles", cx16SizeOptimized=2679, cx16SizeUnoptimized=4837),
ExampleSizes("cobramk3-cpu", cx16SizeOptimized=7608, cx16SizeUnoptimized=10327),
ExampleSizes("cobramk3-verafx", cx16SizeOptimized=4951, cx16SizeUnoptimized=5393),
ExampleSizes("colorbars", cx16SizeOptimized=1606, cx16SizeUnoptimized=1904),
ExampleSizes("cxlogo", cx16SizeOptimized=599, cx16SizeUnoptimized=895),
ExampleSizes("diskspeed", cx16SizeOptimized=6111, cx16SizeUnoptimized=8085),
ExampleSizes("circles", cx16SizeOptimized=2679, cx16SizeUnoptimized=6581),
ExampleSizes("cobramk3-cpu", cx16SizeOptimized=7787, cx16SizeUnoptimized=11761),
ExampleSizes("cobramk3-verafx", cx16SizeOptimized=5133, cx16SizeUnoptimized=6829),
ExampleSizes("colorbars", cx16SizeOptimized=1606, cx16SizeUnoptimized=3648),
ExampleSizes("cxlogo", cx16SizeOptimized=599, cx16SizeUnoptimized=2639),
ExampleSizes("diskspeed", cx16SizeOptimized=6111, cx16SizeUnoptimized=9829),
ExampleSizes("fileseek", cx16SizeOptimized=2462, cx16SizeUnoptimized=4291),
ExampleSizes("floatparse", cx16SizeOptimized=8303, cx16SizeUnoptimized=9171),
ExampleSizes("interpolation", cx16SizeOptimized=3824, cx16SizeUnoptimized=4744),
ExampleSizes("kefrenbars", cx16SizeOptimized=2386, cx16SizeUnoptimized=4623),
ExampleSizes("interpolation", cx16SizeOptimized=3606, cx16SizeUnoptimized=6247),
ExampleSizes("kefrenbars", cx16SizeOptimized=2566, cx16SizeUnoptimized=6053),
ExampleSizes("keyboardhandler", cx16SizeOptimized=412, cx16SizeUnoptimized=717),
ExampleSizes("landscape", cx16SizeOptimized=1634, cx16SizeUnoptimized=1988),
ExampleSizes("life", cx16SizeOptimized=1320, cx16SizeUnoptimized=1663),
ExampleSizes("landscape", cx16SizeOptimized=1634, cx16SizeUnoptimized=3730),
ExampleSizes("life", cx16SizeOptimized=1320, cx16SizeUnoptimized=3407),
ExampleSizes("mandelbrot", cx16SizeOptimized=1120, cx16SizeUnoptimized=1434),
ExampleSizes("multi-irq-new", cx16SizeOptimized=1073, cx16SizeUnoptimized=1519),
ExampleSizes("multi-irq-old", cx16SizeOptimized=962, cx16SizeUnoptimized=1410),
ExampleSizes("plasma", cx16SizeOptimized=1660, cx16SizeUnoptimized=1991),
ExampleSizes("rasterbars", cx16SizeOptimized=1268, cx16SizeUnoptimized=1685),
ExampleSizes("plasma", cx16SizeOptimized=1579, cx16SizeUnoptimized=3424),
ExampleSizes("rasterbars", cx16SizeOptimized=1200, cx16SizeUnoptimized=3112),
ExampleSizes("serialdownload", cx16SizeOptimized=2887, cx16SizeUnoptimized=4034),
ExampleSizes("showbmx", cx16SizeOptimized=2756, cx16SizeUnoptimized=5106),
ExampleSizes("snow", cx16SizeOptimized=3099, cx16SizeUnoptimized=5254),
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=3857),
ExampleSizes("spotlight", cx16SizeOptimized=908, cx16SizeUnoptimized=1358),
ExampleSizes("starszoom", cx16SizeOptimized=2317, cx16SizeUnoptimized=4627),
ExampleSizes("test_gfx_hires", cx16SizeOptimized=4819, cx16SizeUnoptimized=7441),
ExampleSizes("test_gfx_lores", cx16SizeOptimized=5899, cx16SizeUnoptimized=7115),
ExampleSizes("testmonogfx", cx16SizeOptimized=10148, cx16SizeUnoptimized=11371),
ExampleSizes("snow", cx16SizeOptimized=3099, cx16SizeUnoptimized=6998),
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=5601),
ExampleSizes("spotlight", cx16SizeOptimized=908, cx16SizeUnoptimized=3116),
ExampleSizes("starszoom", cx16SizeOptimized=2498, cx16SizeUnoptimized=6061),
ExampleSizes("test_gfx_hires", cx16SizeOptimized=5017, cx16SizeUnoptimized=8875),
ExampleSizes("test_gfx_lores", cx16SizeOptimized=6077, cx16SizeUnoptimized=8552),
ExampleSizes("testmonogfx", cx16SizeOptimized=10330, cx16SizeUnoptimized=12805),
ExampleSizes("textspotlight", cx16SizeOptimized=3431, cx16SizeUnoptimized=3788),
),
listOf(false, true)
@@ -273,30 +273,30 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
val bothCx16AndC64 = cartesianProduct(
listOf(
ExampleSizes("balls", c64SizeOptimized=885, c64SizeUnoptimized=927, cx16SizeOptimized=874, cx16SizeUnoptimized=1200),
ExampleSizes("balls", c64SizeOptimized=885, c64SizeUnoptimized=2671, cx16SizeOptimized=874, cx16SizeUnoptimized=2948),
ExampleSizes("boingball", c64SizeOptimized=1487, c64SizeUnoptimized=1553, cx16SizeOptimized=1463, cx16SizeUnoptimized=1944),
ExampleSizes("cube3d", c64SizeOptimized=2360, c64SizeUnoptimized=2386, cx16SizeOptimized=2299, cx16SizeUnoptimized=2595),
ExampleSizes("cube3d", c64SizeOptimized=2538, c64SizeUnoptimized=3820, cx16SizeOptimized=2477, cx16SizeUnoptimized=4031),
ExampleSizes("cube3d-float", c64SizeOptimized=2390, c64SizeUnoptimized=2441, cx16SizeOptimized=2283, cx16SizeUnoptimized=2606),
ExampleSizes("cube3d-gfx", c64SizeOptimized=3468, c64SizeUnoptimized=3540, cx16SizeOptimized=2138, cx16SizeUnoptimized=2422),
ExampleSizes("cube3d-gfx", c64SizeOptimized=3648, c64SizeUnoptimized=4966, cx16SizeOptimized=2318, cx16SizeUnoptimized=3852),
ExampleSizes("dirlist", c64SizeOptimized=1515, c64SizeUnoptimized=2291, cx16SizeOptimized=1525, cx16SizeUnoptimized=3270),
ExampleSizes("fibonacci", c64SizeOptimized=582, c64SizeUnoptimized=601, cx16SizeOptimized=634, cx16SizeUnoptimized=933),
ExampleSizes("fractal-tree", c64SizeOptimized=2778, c64SizeUnoptimized=2821, cx16SizeOptimized=1168, cx16SizeUnoptimized=1462),
ExampleSizes("maze", c64SizeOptimized=2994, c64SizeUnoptimized=3183, cx16SizeOptimized=2793, cx16SizeUnoptimized=3155),
ExampleSizes("maze", c64SizeOptimized=2994, c64SizeUnoptimized=4931, cx16SizeOptimized=2793, cx16SizeUnoptimized=4899),
ExampleSizes("mandelbrot", c64SizeOptimized=1173, c64SizeUnoptimized=1213, cx16SizeOptimized=1120, cx16SizeUnoptimized=1434),
ExampleSizes("mandelbrot-gfx", c64SizeOptimized=1492, c64SizeUnoptimized=1514, cx16SizeOptimized=957, cx16SizeUnoptimized=1250),
ExampleSizes("multitasking", c64SizeOptimized=1900, c64SizeUnoptimized=1955, cx16SizeOptimized=1804, cx16SizeUnoptimized=2137),
ExampleSizes("numbergame", c64SizeOptimized=1034, c64SizeUnoptimized=1050, cx16SizeOptimized=1079, cx16SizeUnoptimized=1382),
ExampleSizes("numbergame", c64SizeOptimized=1034, c64SizeUnoptimized=2792, cx16SizeOptimized=1079, cx16SizeUnoptimized=3128),
ExampleSizes("primes", c64SizeOptimized=520, c64SizeUnoptimized=541, cx16SizeOptimized=565, cx16SizeUnoptimized=870),
ExampleSizes("queens", c64SizeOptimized=1053, c64SizeUnoptimized=1085, cx16SizeOptimized=1103, cx16SizeUnoptimized=1419),
ExampleSizes("screencodes", c64SizeOptimized=639, c64SizeUnoptimized=655, cx16SizeOptimized=647, cx16SizeUnoptimized=951),
ExampleSizes("swirl", c64SizeOptimized=828, c64SizeUnoptimized=839, cx16SizeOptimized=857, cx16SizeUnoptimized=1154),
ExampleSizes("swirl", c64SizeOptimized=1010, c64SizeUnoptimized=2273, cx16SizeOptimized=1039, cx16SizeUnoptimized=2584),
ExampleSizes("swirl-float", c64SizeOptimized=664, c64SizeUnoptimized=690, cx16SizeOptimized=528, cx16SizeUnoptimized=838),
ExampleSizes("tehtriz", c64SizeOptimized=4325, c64SizeUnoptimized=4600, cx16SizeOptimized=6154, cx16SizeUnoptimized=6681),
ExampleSizes("tehtriz", c64SizeOptimized=4325, c64SizeUnoptimized=6349, cx16SizeOptimized=6154, cx16SizeUnoptimized=8438),
ExampleSizes("textelite", c64SizeOptimized=11227, c64SizeUnoptimized=12146, cx16SizeOptimized=10746, cx16SizeUnoptimized=12788),
ExampleSizes("pointers/animalgame", c64SizeOptimized=1965, c64SizeUnoptimized=2521, cx16SizeOptimized=1991, cx16SizeUnoptimized=2801),
ExampleSizes("pointers/binarytree", c64SizeOptimized=2672, c64SizeUnoptimized=2735, cx16SizeOptimized=2175, cx16SizeUnoptimized=2487),
ExampleSizes("pointers/hashtable", c64SizeOptimized=3388, c64SizeUnoptimized=4217, cx16SizeOptimized=2911, cx16SizeUnoptimized=3788),
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1353, c64SizeUnoptimized=1393, cx16SizeOptimized=1291, cx16SizeUnoptimized=1610),
ExampleSizes("pointers/hashtable", c64SizeOptimized=3388, c64SizeUnoptimized=4207, cx16SizeOptimized=2911, cx16SizeUnoptimized=3782),
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1353, c64SizeUnoptimized=3137, cx16SizeOptimized=1291, cx16SizeUnoptimized=3358),
ExampleSizes("pointers/sorting", c64SizeOptimized=1776, c64SizeUnoptimized=2523, cx16SizeOptimized=1788, cx16SizeUnoptimized=2653),
),
listOf(false, true),
@@ -329,17 +329,17 @@ class TestCompilerOnExamplesVirtual: FunSpec({
val onlyVirtual = cartesianProduct(
listOf(
ExampleSizes("bouncegfx", virtualInstrCountOptimized=219, virtualInstrCountUnoptimized=740, virtualRegCountOptimized=90, virtualRegCountUnoptimized=325),
ExampleSizes("bsieve", virtualInstrCountOptimized=262, virtualInstrCountUnoptimized=1890, virtualRegCountOptimized=74, virtualRegCountUnoptimized=800),
ExampleSizes("fountain", virtualInstrCountOptimized=198, virtualInstrCountUnoptimized=719, virtualRegCountOptimized=80, virtualRegCountUnoptimized=318),
ExampleSizes("bouncegfx", virtualInstrCountOptimized=219, virtualInstrCountUnoptimized=770, virtualRegCountOptimized=90, virtualRegCountUnoptimized=342),
ExampleSizes("bsieve", virtualInstrCountOptimized=262, virtualInstrCountUnoptimized=1900, virtualRegCountOptimized=74, virtualRegCountUnoptimized=800),
ExampleSizes("fountain", virtualInstrCountOptimized=198, virtualInstrCountUnoptimized=749, virtualRegCountOptimized=80, virtualRegCountUnoptimized=335),
ExampleSizes("pixelshader", virtualInstrCountOptimized=84, virtualInstrCountUnoptimized=210, virtualRegCountOptimized=17, virtualRegCountUnoptimized=60),
ExampleSizes("sincos", virtualInstrCountOptimized=334, virtualInstrCountUnoptimized=826, virtualRegCountOptimized=148, virtualRegCountUnoptimized=357),
ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=437, virtualInstrCountUnoptimized=2066, virtualRegCountOptimized=158, virtualRegCountUnoptimized=882),
ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=655, virtualInstrCountUnoptimized=2233, virtualRegCountOptimized=264, virtualRegCountUnoptimized=968),
ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=677, virtualInstrCountUnoptimized=2231, virtualRegCountOptimized=290, virtualRegCountUnoptimized=987),
ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=400, virtualInstrCountUnoptimized=2385, virtualRegCountOptimized=133, virtualRegCountUnoptimized=1034),
ExampleSizes("pointers/fountain-virtual", virtualInstrCountOptimized=216, virtualInstrCountUnoptimized=741, virtualRegCountOptimized=88, virtualRegCountUnoptimized=320),
ExampleSizes("pointers/sorting", virtualInstrCountOptimized=479, virtualInstrCountUnoptimized=2181, virtualRegCountOptimized=159, virtualRegCountUnoptimized=931)
ExampleSizes("sincos", virtualInstrCountOptimized=326, virtualInstrCountUnoptimized=856, virtualRegCountOptimized=141, virtualRegCountUnoptimized=374),
ExampleSizes("pointers/animalgame", virtualInstrCountOptimized=437, virtualInstrCountUnoptimized=2076, virtualRegCountOptimized=158, virtualRegCountUnoptimized=882),
ExampleSizes("pointers/binarytree", virtualInstrCountOptimized=655, virtualInstrCountUnoptimized=2239, virtualRegCountOptimized=264, virtualRegCountUnoptimized=968),
ExampleSizes("pointers/hashtable", virtualInstrCountOptimized=677, virtualInstrCountUnoptimized=2241, virtualRegCountOptimized=290, virtualRegCountUnoptimized=987),
ExampleSizes("pointers/sortedlist", virtualInstrCountOptimized=400, virtualInstrCountUnoptimized=2418, virtualRegCountOptimized=133, virtualRegCountUnoptimized=1047),
ExampleSizes("pointers/fountain-virtual", virtualInstrCountOptimized=216, virtualInstrCountUnoptimized=768, virtualRegCountOptimized=88, virtualRegCountUnoptimized=336),
ExampleSizes("pointers/sorting", virtualInstrCountOptimized=479, virtualInstrCountUnoptimized=2189, virtualRegCountOptimized=159, virtualRegCountUnoptimized=931)
),
listOf(false, true)
)
@@ -370,7 +370,7 @@ class TestCompilerOnExamplesPET32: FunSpec({
val onlyPET32 = cartesianProduct(
listOf(
ExampleSizes("boingball", pet32SizeOptimized=1490, pet32SizeUnoptimized=1546),
ExampleSizes("petswirl", pet32SizeOptimized=895, pet32SizeUnoptimized=961),
ExampleSizes("petswirl", pet32SizeOptimized=1077, pet32SizeUnoptimized=2391),
ExampleSizes("music", pet32SizeOptimized=876, pet32SizeUnoptimized=925),
),
listOf(false, true)
+1 -1
View File
@@ -12,7 +12,7 @@ class TestLaunchEmu: FunSpec({
val target = VMTarget()
val tmpfile = kotlin.io.path.createTempFile(suffix=".p8ir")
tmpfile.writeText("""<?xml version="1.0" encoding="utf-8"?>
<PROGRAM NAME="test" COMPILERVERSION="99.99" IRFORMAT="4">
<PROGRAM NAME="test" COMPILERVERSION="99.99" IRFORMAT="8">
<OPTIONS>
</OPTIONS>
+378
View File
@@ -0,0 +1,378 @@
package prog8tests.compiler
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import prog8.ast.Node
import prog8.ast.ParentSentinel
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Block
import prog8.ast.statements.ForLoop
import prog8.ast.statements.Subroutine
import prog8.ast.walk.AstModification
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import prog8.code.core.Position
import prog8.code.source.SourceCode
import prog8.code.target.VMTarget
import prog8.parser.Prog8Parser.parseModule
import prog8.vm.VmRunner
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
import kotlin.io.path.readText
class TestLinkedForLoops: FunSpec({
val outputDir = tempdir().toPath()
test("parser keeps step on linked loops with via and until") {
val src = """
main {
sub start() {
for n in head via .next step 1 {
}
for n2 in head via .next until sentinel step 2 {
}
}
}
"""
val module = parseModule(SourceCode.Text(src), VMTarget())
val start = module.statements.filterIsInstance<Block>().single { it.name == "main" }
.statements.filterIsInstance<Subroutine>().single { it.name == "start" }
val loops = start.statements.filterIsInstance<ForLoop>()
loops.size shouldBe 2
loops[0].linkedVia shouldBe "next"
loops[0].linkedUntil shouldBe null
loops[0].step shouldNotBe null
loops[1].linkedVia shouldBe "next"
loops[1].linkedUntil shouldNotBe null
loops[1].step shouldNotBe null
}
test("linked for loop with via cannot use step") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
main {
struct Node {
^^Node next
ubyte value
}
sub start() {
^^Node head = memory("head", sizeof(Node), 0)
for ^^Node n in head via .next step 1 {
n.value = 1
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
errors.errors.any { it.contains("linked-list for loop cannot use 'step'") } shouldBe true
}
test("pointer-linked for loop requires an explicit via field") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
main {
struct Node {
^^Node next
ubyte value
}
sub start() {
^^Node head = memory("head", sizeof(Node), 0)
for ^^Node n in head {
n.value = 1
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
errors.errors.any { it.contains("pointer-linked for loop requires an explicit 'via .field'") } shouldBe true
}
test("linkedUntil is traversed by IAstVisitor and AstWalker") {
val pos = Position.DUMMY
val loop = ForLoop(
IdentifierReference(listOf("node"), pos),
IdentifierReference(listOf("head"), pos),
AnonymousScope.empty(),
null,
null,
pos,
linkedVia = "next",
linkedUntil = IdentifierReference(listOf("sentinel"), pos)
)
val seenByVisitor = mutableListOf<String>()
val visitor = object : IAstVisitor {
override fun visit(identifier: IdentifierReference) {
seenByVisitor += identifier.nameInSource.single()
}
}
loop.accept(visitor)
seenByVisitor shouldContain "sentinel"
val seenByWalker = mutableListOf<String>()
val walker = object : AstWalker() {
override fun before(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
seenByWalker += identifier.nameInSource.single()
return noModifications
}
}
loop.linkParents(ParentSentinel)
loop.accept(walker, ParentSentinel)
seenByWalker shouldContain "sentinel"
}
test("explicit custom sentinel identifier terminates traversal") {
val src = $$"""
%zeropage basicsafe
main {
struct Node {
^^Node next
ubyte value
}
sub start() {
^^Node n1 = memory("n1", sizeof(Node), 0)
^^Node n2 = memory("n2", sizeof(Node), 0)
^^Node sentinel = memory("sentinel", sizeof(Node), 0)
n1.value = 11
n2.value = 22
sentinel.value = 99
n1.next = n2
n2.next = sentinel
ubyte @shared sum = 0
for ^^Node n in n1 via .next until sentinel {
sum += n.value
}
if sum != 33 { sys.exit(7) }
pokew($4400, $c0ba)
sys.exit(0)
}
}
"""
val result = compileText(VMTarget(), false, src, outputDir)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText(), true) { vm ->
vm.memory.getUW(0x4400u).toInt() shouldBe 0xc0ba
}
}
test("explicit header traversal uses tail sentinel") {
val src = $$"""
%zeropage basicsafe
main {
struct Node {
^^Node next
ubyte value
}
struct List {
^^Node head
^^Node last
}
sub start() {
^^Node n1 = memory("n1", sizeof(Node), 0)
^^Node n2 = memory("n2", sizeof(Node), 0)
^^Node sentinel = memory("sentinel", sizeof(Node), 0)
^^List list = memory("list", sizeof(List), 0)
n1.value = 11
n2.value = 22
sentinel.value = 99
n1.next = n2
n2.next = sentinel
list.head = n1
list.last = sentinel
ubyte @shared sum = 0
for ^^Node n in list.head via .next until list.last {
sum += n.value
}
if sum != 33 { sys.exit(7) }
pokew($4400, $c0ba)
sys.exit(0)
}
}
"""
val result = compileText(VMTarget(), false, src, outputDir)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText(), true) { vm ->
vm.memory.getUW(0x4400u).toInt() shouldBe 0xc0ba
}
}
test("explicit node traversal terminates at zero by default") {
val src = $$"""
%zeropage basicsafe
main {
struct Node {
^^Node next
ubyte value
}
sub start() {
^^Node n1 = memory("n1", sizeof(Node), 0)
^^Node n2 = memory("n2", sizeof(Node), 0)
n1.value = 11
n2.value = 22
n1.next = n2
n2.next = 0
^^Node @shared head = n1
ubyte @shared sum = 0
for ^^Node n in head via .next {
sum += n.value
}
if sum != 33 { sys.exit(7) }
pokew($4400, $c0ba)
sys.exit(0)
}
}
"""
val result = compileText(VMTarget(), false, src, outputDir)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText(), true) { vm ->
vm.memory.getUW(0x4400u).toInt() shouldBe 0xc0ba
}
}
test("ordinary array, string and range for loops still compile") {
val result = compileText(VMTarget(), false, """
main {
sub start() {
ubyte[] arr = [1,2,3,4]
ubyte x
for x in arr {
x++
}
for x in arr step -1 {
x++
}
str s = "abcd"
ubyte c
for c in s {
c++
}
for c in s step -1 {
c++
}
for x in 0 to 3 {
x++
}
}
}
""", outputDir, writeAssembly = false)
result shouldNotBe null
}
test("until in ordinary for loop is rejected") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
main {
sub start() {
ubyte[] arr = [1,2,3]
ubyte x
for x in arr until 2 {
x++
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
errors.errors.any { it.contains("'until' can only be used in linked-list for loops") } shouldBe true
}
test("explicit linked traversal requires a pointer link field") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
main {
struct Node {
ubyte value
}
sub start() {
^^Node head = memory("head", sizeof(Node), 0)
for n in head via .value {
n.value = 1
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
errors.errors.any { it.contains("linked-list for loop 'via' field must be a pointer to the same node type") } shouldBe true
}
test("explicit until accepts address-of header field") {
val src = $$"""
%zeropage basicsafe
main {
struct Node {
^^Node next
ubyte value
}
struct List {
^^Node head
^^Node tail
}
sub start() {
^^Node n1 = memory("n1", sizeof(Node), 0)
^^Node n2 = memory("n2", sizeof(Node), 0)
^^List list = memory("list", sizeof(List), 0)
n1.value = 11
n2.value = 22
n1.next = n2
n2.next = &list.tail
list.head = n1
list.tail = n2
ubyte @shared sum = 0
for ^^Node n in list.head via .next until &list.tail {
sum += n.value
}
if sum != 33 { sys.exit(7) }
pokew($4400, $c0ba)
sys.exit(0)
}
}
"""
val result = compileText(VMTarget(), false, src, outputDir)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText(), true) { vm ->
vm.memory.getUW(0x4400u).toInt() shouldBe 0xc0ba
}
}
test("explicit until accepts address-of untyped pointer header field") {
val src = $$"""
%zeropage basicsafe
main {
struct Node {
^^Node next
ubyte value
}
struct List {
^^Node head
pointer tail
}
sub start() {
^^Node n1 = memory("n1", sizeof(Node), 0)
^^Node n2 = memory("n2", sizeof(Node), 0)
^^List list = memory("list", sizeof(List), 0)
n1.value = 11
n2.value = 22
n1.next = n2
n2.next = &list.tail
list.head = n1
list.tail = n2
ubyte @shared sum = 0
for ^^Node n in list.head via .next until &list.tail {
sum += n.value
}
if sum != 33 { sys.exit(7) }
pokew($4400, $c0ba)
sys.exit(0)
}
}
"""
val result = compileText(VMTarget(), false, src, outputDir)!!
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
VmRunner().runAndTestProgram(virtfile.readText(), true) { vm ->
vm.memory.getUW(0x4400u).toInt() shouldBe 0xc0ba
}
}
})
-465
View File
@@ -1,465 +0,0 @@
package prog8tests.compiler
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.target.Amiga500Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestListIteration: FunSpec({
val outputDir = tempdir().toPath()
// ---------- virtual target - portable list ----------
test("portable list forward iteration - explicit node type") {
val result = compileText(VMTarget(), false, """
%import lists
main {
sub start() {
^^lists.List mylist = memory("mylist", sizeof(lists.List), 0)
^^lists.Node n1 = memory("n1", sizeof(lists.Node), 0)
^^lists.Node n2 = memory("n2", sizeof(lists.Node), 0)
^^lists.Node n3 = memory("n3", sizeof(lists.Node), 0)
lists.init(mylist)
lists.add_tail(mylist, n1)
lists.add_tail(mylist, n2)
lists.add_tail(mylist, n3)
for ^^lists.Node node in mylist {
if node==0 { break }
}
}
}
""", outputDir)
result shouldNotBe null
}
test("portable list forward iteration - inferred node type") {
val result = compileText(VMTarget(), false, """
%import lists
main {
sub start() {
^^lists.List mylist = memory("mylist", sizeof(lists.List), 0)
^^lists.Node n1 = memory("n1", sizeof(lists.Node), 0)
lists.init(mylist)
lists.add_tail(mylist, n1)
for node in mylist {
if node==0 { break }
}
}
}
""", outputDir)
result shouldNotBe null
// node should be inferred as ^^lists.Node via Head field
val forNode = result!!.compilerAst.entrypoint.statements
.flatMap { (it as? prog8.ast.statements.Subroutine)?.statements ?: emptyList() }
// just verify compilation succeeded - deeper type check is in AstChecker
}
test("portable list reverse iteration step -1") {
val result = compileText(VMTarget(), false, """
%import lists
main {
sub start() {
^^lists.List mylist = memory("mylist", sizeof(lists.List), 0)
^^lists.Node n1 = memory("n1", sizeof(lists.Node), 0)
^^lists.Node n2 = memory("n2", sizeof(lists.Node), 0)
lists.init(mylist)
lists.add_tail(mylist, n1)
lists.add_tail(mylist, n2)
for ^^lists.Node node in mylist step -1 {
if node==0 { break }
}
}
}
""", outputDir)
result shouldNotBe null
}
test("portable list reverse iteration execution - order fwd vs rev") {
val result = compileText(VMTarget(), false, """
%import lists
main {
sub start() {
struct MyNode {
^^MyNode Succ
^^MyNode Pred
ubyte value
}
struct MyList {
^^MyNode Head
pointer Tail
^^MyNode TailPred
}
^^MyList mylist = memory("mylist", sizeof(MyList), 0)
mylist.Head = &mylist.Tail as ^^MyNode
mylist.Tail = 0
mylist.TailPred = &mylist.Head as ^^MyNode
^^MyNode n1 = memory("n1", sizeof(MyNode), 0)
^^MyNode n2 = memory("n2", sizeof(MyNode), 0)
^^MyNode n3 = memory("n3", sizeof(MyNode), 0)
ubyte @shared failures = 0
n1.value = 11
n2.value = 22
n3.value = 33
n1.Succ = &mylist.Tail as ^^MyNode
n1.Pred = mylist.TailPred
mylist.TailPred.Succ = n1
mylist.TailPred = n1
n2.Succ = &mylist.Tail as ^^MyNode
n2.Pred = mylist.TailPred
mylist.TailPred.Succ = n2
mylist.TailPred = n2
n3.Succ = &mylist.Tail as ^^MyNode
n3.Pred = mylist.TailPred
mylist.TailPred.Succ = n3
mylist.TailPred = n3
ubyte @shared idx = 0
ubyte[] @shared fwd = [0,0,0]
for ^^MyNode node in mylist {
fwd[idx] = node.value
idx++
}
if fwd[0]!=11 { failures++ }
if fwd[1]!=22 { failures++ }
if fwd[2]!=33 { failures++ }
idx = 0
ubyte[] @shared rev = [0,0,0]
for ^^MyNode node in mylist step -1 {
rev[idx] = node.value
idx++
}
if rev[0]!=33 { failures++ }
if rev[1]!=22 { failures++ }
if rev[2]!=11 { failures++ }
}
}
""", outputDir)!!
result.codegenAst shouldNotBe null
}
test("portable list empty and single node iteration") {
val result = compileText(VMTarget(), false, """
%import lists
main {
sub start() {
ubyte @shared failures = 0
^^lists.List mylist = memory("mylist", sizeof(lists.List), 0)
lists.init(mylist)
; empty
for ^^lists.Node node in mylist {
failures++
}
for ^^lists.Node node in mylist step -1 {
failures++
}
; single
^^lists.Node n1 = memory("n1", sizeof(lists.Node), 0)
lists.add_tail(mylist, n1)
for ^^lists.Node node in mylist {
if node!=n1 { failures++ }
}
for ^^lists.Node node in mylist step -1 {
if node!=n1 { failures++ }
}
}
}
""", outputDir)
result shouldNotBe null
}
test("list Next/Prev alias iteration") {
val result = compileText(VMTarget(), false, """
main {
sub start() {
struct MyNode {
^^MyNode Next
^^MyNode Prev
ubyte value
}
struct MyList {
^^MyNode Head
pointer Tail
^^MyNode TailPred
}
^^MyList mylist = memory("mylist", sizeof(MyList), 0)
mylist.Head = &mylist.Tail as ^^MyNode
mylist.Tail = 0
mylist.TailPred = &mylist.Head as ^^MyNode
^^MyNode n1 = memory("n1", sizeof(MyNode), 0)
^^MyNode n2 = memory("n2", sizeof(MyNode), 0)
n1.value = 1
n2.value = 2
n1.Next = &mylist.Tail as ^^MyNode
n1.Prev = mylist.TailPred
mylist.TailPred.Next = n1
mylist.TailPred = n1
n2.Next = &mylist.Tail as ^^MyNode
n2.Prev = mylist.TailPred
mylist.TailPred.Next = n2
mylist.TailPred = n2
for ^^MyNode node in mylist {
if node==0 { break }
}
for ^^MyNode node in mylist step -1 {
if node==0 { break }
}
}
}
""", outputDir)
result shouldNotBe null
}
test("list mixed Next/Pred should fail") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
main {
sub start() {
struct BadNode {
^^BadNode Succ
^^BadNode Prev
}
struct BadList {
^^BadNode Head
pointer Tail
^^BadNode TailPred
}
^^BadList mylist = memory("mylist", sizeof(BadList), 0)
for ^^BadNode node in mylist {
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
// BadNode has Succ/Pred? Actually Succ with Prev is mixed? Our isNodeStruct rejects Succ+Prev (should be Succ+Pred or Next+Prev)
// So this should be an error - either "can only loop over an iterable type" or node struct validation
(errors.errors.isNotEmpty() || errors.warnings.isNotEmpty()) shouldBe true
}
test("amiga lists module struct aliases resolve in type positions") {
// Reproduces the qualified struct-alias bug: ^^lists.List and ^^lists.Node
// should resolve to exec.MinList and exec.MinNode, but currently remain unresolved.
val result = compileText(Amiga500Target(), false, """
%import lists
main {
sub start() {
^^lists.List lst = memory("lst", sizeof(lists.List), 0)
^^lists.Node node = memory("node", sizeof(lists.Node), 0)
lists.init(lst)
lists.add_tail(lst, node)
^^lists.Node current = lst.Head
}
}
""", outputDir, writeAssembly=false)
result shouldNotBe null
}
test("amiga lists module struct aliases resolve in for loop") {
val result = compileText(Amiga500Target(), false, """
%import lists
main {
sub start() {
^^lists.List lst = memory("lst", sizeof(lists.List), 0)
^^lists.Node node = memory("node", sizeof(lists.Node), 0)
lists.init(lst)
lists.add_tail(lst, node)
for ^^lists.Node current in lst {
break
}
}
}
""", outputDir, writeAssembly=false)
result shouldNotBe null
}
test("list raw pointer Head should fail - not iterable") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
main {
sub start() {
struct BadNode {
^^BadNode Succ
^^BadNode Pred
}
struct BadList {
pointer Head
pointer Tail
pointer TailPred
}
^^BadList mylist = memory("mylist", sizeof(BadList), 0)
for ^^BadNode node in mylist {
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
errors.errors[0] shouldContain "can only loop over an iterable type"
}
test("list step 2 should fail") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
%import lists
main {
sub start() {
^^lists.List mylist = memory("mylist", sizeof(lists.List), 0)
lists.init(mylist)
for ^^lists.Node node in mylist step 2 {
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
errors.errors[0] shouldContain "step for non-range iterable must be 1 or -1"
}
test("list step 0 should fail") {
val errors = ErrorReporterForTests()
compileText(VMTarget(), false, """
%import lists
main {
sub start() {
^^lists.List mylist = memory("mylist", sizeof(lists.List), 0)
lists.init(mylist)
for ^^lists.Node node in mylist step 0 {
}
}
}
""", outputDir, errors, writeAssembly = false) shouldBe null
errors.errors[0] shouldContain "step for non-range iterable must be 1 or -1"
}
test("cx16 local list forward iteration - inferred node type") {
// regression: the sentinel comparison in the desugared loop used to crash the 6502 code generator
val result = compileText(Cx16Target(), false, """
main {
struct MyNode {
^^MyNode Succ
^^MyNode Pred
ubyte value
}
struct MyList {
^^MyNode Head
pointer Tail
^^MyNode TailPred
}
sub start() {
^^MyList mylist = memory("mylist", sizeof(MyList), 0)
mylist.Head = &mylist.Tail as ^^MyNode
mylist.Tail = 0
mylist.TailPred = &mylist.Head as ^^MyNode
for node in mylist {
if node==0 { break }
}
}
}
""", outputDir)
result shouldNotBe null
}
// ---------- amiga500 target - exec lists ----------
test("amiga500 exec.List forward iteration") {
val result = compileText(Amiga500Target(), false, """
%import exec
main {
sub start() {
^^exec.List mylist = memory("mylist", sizeof(exec.List), 0)
^^exec.Node n1 = memory("n1", sizeof(exec.Node), 0)
^^exec.Node n2 = memory("n2", sizeof(exec.Node), 0)
exec.NewList(mylist)
exec.AddTail(mylist, n1)
exec.AddTail(mylist, n2)
for ^^exec.Node node in mylist {
if node==0 { break }
}
}
}
""", outputDir, writeAssembly=false)
result shouldNotBe null
}
test("amiga500 exec.List reverse iteration") {
val result = compileText(Amiga500Target(), false, """
%import exec
main {
sub start() {
^^exec.List mylist = memory("mylist", sizeof(exec.List), 0)
^^exec.Node n1 = memory("n1", sizeof(exec.Node), 0)
exec.NewList(mylist)
exec.AddTail(mylist, n1)
for ^^exec.Node node in mylist step -1 {
if node==0 { break }
}
}
}
""", outputDir, writeAssembly=false)
result shouldNotBe null
}
test("amiga500 exec.MinList forward iteration") {
val result = compileText(Amiga500Target(), false, """
%import exec
main {
sub start() {
^^exec.MinList mylist = memory("mylist", sizeof(exec.MinList), 0)
^^exec.MinNode n1 = memory("n1", sizeof(exec.MinNode), 0)
exec.NewList(mylist as ^^exec.List)
exec.AddTail(mylist as ^^exec.List, n1 as pointer)
for ^^exec.MinNode node in mylist {
if node==0 { break }
}
}
}
""", outputDir, writeAssembly=false)
result shouldNotBe null
}
test("amiga500 exec.List inferred node type") {
val result = compileText(Amiga500Target(), false, """
%import exec
main {
sub start() {
^^exec.List mylist = memory("mylist", sizeof(exec.List), 0)
exec.NewList(mylist)
for node in mylist {
if node==0 { break }
}
}
}
""", outputDir, writeAssembly=false)
result shouldNotBe null
}
test("amiga500 exec.Message list") {
val result = compileText(Amiga500Target(), false, """
%import exec
main {
sub start() {
exec.Message msg1
exec.Message msg2
^^exec.List mylist = memory("mylist", sizeof(exec.List), 0)
exec.NewList(mylist)
; use messages as nodes - need pointers
^^exec.Message p1 = &msg1
^^exec.Message p2 = &msg2
exec.AddTail(mylist, p1 as pointer)
exec.AddTail(mylist, p2 as pointer)
for ^^exec.Message msg in mylist {
if msg==0 { break }
}
}
}
""", outputDir, writeAssembly=false)
// This will fail struct instance check for msg1/msg2 direct, but we just check that iteration syntax itself is accepted
// So we allow it to maybe fail for other reasons, but not for list iteration detection
// We just verify that if it compiles, it is ok, otherwise check that error is not about list iteration
if(result==null) {
// if it failed, ensure it's not due to list iteration detection but struct instance decl
} else {
result shouldNotBe null
}
}
})
+159 -8
View File
@@ -508,8 +508,8 @@ thing {
sub new() -> ^^Node {
cx16.r0++
^^Node pointer = 2000
return pointer
^^Node ptr = 2000
return ptr
}
}"""
@@ -526,9 +526,9 @@ main {
^^MNode next
}
sub func(^^MNode pointer) -> ^^MNode {
sub func(^^MNode ptr) -> ^^MNode {
cx16.r0++
return pointer.next
return ptr.next
}
sub start() {
@@ -546,9 +546,9 @@ thing {
^^Node next
}
sub func(^^Node pointer) -> ^^Node {
sub func(^^Node ptr) -> ^^Node {
cx16.r0++
return pointer.next
return ptr.next
}
}"""
@@ -558,6 +558,72 @@ thing {
compileText(Cx16Target(), false, src, outputDir) shouldNotBe null
}
test("indexing a pointer-to-pointer struct field reads via peekp") {
val src="""
main {
struct Selector {
ubyte index
^^pointer choices
}
sub start() {
^^Selector item = []
item.index = 1
pointer p = item.choices[item.index]
pointer q = item.choices[0]
if p==q {
cx16.r0++
}
}
}"""
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
val start = result.compilerAst.allBlocks.first { it.name=="main" }
.statements.filterIsInstance<Subroutine>().first { it.name=="start" }
// NB: CodeDesugarer rewrites peekp into peekl/peekw depending on the target's pointer size
fun peekCalls(sub: Subroutine, name: String): List<FunctionCallExpression> =
sub.statements.filterIsInstance<Assignment>()
.mapNotNull { it.value as? FunctionCallExpression }
.filter { it.target.nameInSource == listOf(name) }
peekCalls(start, "peekl").size shouldBe 2
val resultCx16 = compileText(Cx16Target(), false, src, outputDir, writeAssembly = false)!!
val startCx16 = resultCx16.compilerAst.allBlocks.first { it.name=="main" }
.statements.filterIsInstance<Subroutine>().first { it.name=="start" }
peekCalls(startCx16, "peekw").size shouldBe 2
compileText(C64Target(), false, src, outputDir) shouldNotBe null
}
test("writing through pointer-to-pointer struct fields") {
val src = $$"""
%option no_sysinit
%launcher none
main {
struct Selector {
ubyte index
^^pointer choices
}
pointer[2] @shared @nosplit paddrs
uword @shared v0 = $aaaa
uword @shared v1 = $bbbb
&ubyte probe = $02
sub start() {
^^Selector pp = []
^^Selector item = []
pp^^.choices = &paddrs
item.choices = &paddrs
pp^^.choices[0] = &v0
item.choices[1] = &v1
if paddrs[0]==&v0 and paddrs[1]==&v1
probe = 1
}
}""".trimIndent()
val result = compileText(Cx16Target(), true, src, outputDir)!!
val machine = result.simulate()
machine.assertMemory(0x02, 0x01)
}
test("string comparisons still calling string compare routine") {
val src="""
main {
@@ -3114,8 +3180,6 @@ main {
val a3 = st[3] as Assignment
val a4 = st[4] as Assignment
val a5 = st[5] as Assignment
val a6 = st[7] as Assignment
val a7 = st[9] as Assignment
a1.target.arrayIndexedDereference shouldBe null
a1.target.pointerDereference!!.chain shouldBe listOf("sprptr", "y")
@@ -3982,4 +4046,91 @@ main {
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "no such field"
}
test("double pointer dereference in while loop (code gen fix)") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
%zeropage basicsafe
main {
uword[4] @shared @nosplit table
sub count() -> ubyte {
ubyte num
^^pointer temp = &table
while temp^^ != $0000 {
temp++
num++
}
return num
}
&ubyte probe = $02
&ubyte poweroff = $f203
sub start() {
table[0] = &probe
table[1] = &probe
table[2] = $0000
probe = count()
poweroff = 1
}
}""".trimIndent()
val result = compileText(C64Target(), false, src, outputDir)!!
val machine = result.simulate()
machine.assertMemory(0x02, 0x02)
}
test("pointer-to-pointer struct field deref still double-derefs on 6502") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
%zeropage basicsafe
main {
struct S {
^^pointer bar
}
&ubyte probe = $02
&ubyte poweroff = $f203
sub start() {
^^S foo = memory("foo", 4, 0)
foo.bar = $2000
pokew($2000, $3000)
pointer result = foo.bar^^
probe = result as ubyte
poweroff = 1
}
}""".trimIndent()
val result = compileText(C64Target(), false, src, outputDir)!!
val machine = result.simulate()
machine.assertMemory(0x02, 0x00) // low byte of $3000
}
test("chain-1 derefLast write stores through followed pointer on 6502") {
val src = $$"""
%option no_sysinit
%launcher none
%address $1000
%zeropage basicsafe
main {
struct S {
^^pointer bar
}
&ubyte probe = $02
&ubyte poweroff = $f203
sub start() {
^^S foo = memory("foo", 4, 0)
foo.bar = $2000
foo.bar^^ = $5678
probe = peekw($2000) as ubyte
poweroff = 1
}
}""".trimIndent()
val result = compileText(C64Target(), false, src, outputDir)!!
val machine = result.simulate()
machine.assertMemory(0x02, 0x78) // low byte of $5678
}
})
+29 -26
View File
@@ -87,27 +87,28 @@ main {
// incoming-only frame: no locals, but the parameters need a frame pointer
lines.any { it == "link a5,#0" } shouldBe true
lines.any { it == "unlk a5" } shouldBe true
// narrow parameters are right-justified inside their longword slot (offsets 8/12/16)
lines.any { it.contains("19(a5)") } shouldBe true // ubyte at slot 16 + 3
lines.any { it.contains("14(a5)") } shouldBe true // word at slot 12 + 2
// word-packed slots: long at 8, word slot at 12, byte slot at 14 with the byte at 14
lines.any { it.contains("14(a5)") } shouldBe true // ubyte at slot 14 base byte
lines.any { it.contains("12(a5)") } shouldBe true // word at slot 12
lines.any { it.contains("8(a5)") } shouldBe true // long at slot 8
// parameters are no longer static storage
lines.none { "p8v_a:" in it || "p8v_b:" in it || "p8v_c:" in it } shouldBe true
// the caller pushes every argument into its own slot, then pops the whole area
lines.count { it == "subq.l #4,sp" } shouldBe 2
lines.count { it.startsWith("move.b ") && it.contains(",3(sp)") } shouldBe 1
lines.count { it.startsWith("move.w ") && it.contains(",2(sp)") } shouldBe 1
// the caller pushes every argument into its own slot (byte: single one-instruction
// push carrying a "pushed as a word" note), then pops the whole area
lines.count { it.startsWith("move.b ") && it.contains(",-(sp)") && it.contains("pushed as a word") } shouldBe 1
lines.count { it.startsWith("move.w ") && it.contains(",-(sp)") } shouldBe 1
lines.count { it == "subq.l #2,sp" } shouldBe 0
// long/pointer slots push with a single move.l ...,-(sp), same shape as PUSH ops elsewhere,
// so only require at least the one argument push (exact isolation is covered in TestStackFrameEmission)
lines.count { it.startsWith("move.l ") && it.contains(",-(sp)") } shouldBeGreaterThanOrEqual 1
lines.any { it == "lea 12(sp),sp" } shouldBe true
lines.any { it == "addq.l #8,sp" } shouldBe true
// IR carries the incoming area size and the frame-slot argument locations
ir.contains("INCOMING=\"12\"") shouldBe true
ir.contains("frame:16=") shouldBe true
ir.contains("INCOMING=\"8\"") shouldBe true
ir.contains("frame:14") shouldBe true
val reloaded = IRFileReader().read(ir)
val sub = reloaded.allSubs().first { it.label.endsWith("p8s_withp") }
sub.frameSize shouldBe 0
sub.incomingSize shouldBe 12
sub.incomingSize shouldBe 8
}
test("many parameters use one cleanup for the whole argument area") {
@@ -123,13 +124,15 @@ main {
}
"""
val (lines, ir) = compile(src)
lines.count { it == "subq.l #4,sp" } shouldBe 4
lines.any { it == "lea 20(sp),sp" } shouldBe true // 20 bytes: the addq form maxes out at 8
// 5 parameters: slots at 24/20/16/12/8, narrow values right-justified inside their slot
lines.any { it.contains("27(a5)") } shouldBe true // ubyte at slot 24 + 3
lines.any { it.contains("22(a5)") } shouldBe true // uword at slot 20 + 2
lines.any { it.contains("16(a5)") } shouldBe true // long at slot 16
ir.contains("INCOMING=\"20\"") shouldBe true
// no byte slot reservations; byte pushes use a single move.b predecrement
lines.count { it == "subq.l #2,sp" } shouldBe 0
lines.any { it == "lea 12(sp),sp" } shouldBe true // 12 bytes: the addq form maxes out at 8
// 5 parameters, word-packed slots, accumulated right-to-left: byte slot at 18,
// word slot at 16, long slot at 12, byte slot at 10, word slot at 8
lines.any { it.contains("18(a5)") } shouldBe true // ubyte at slot 18 base byte
lines.any { it.contains("16(a5)") } shouldBe true // uword at slot 16
lines.any { it.contains("12(a5)") } shouldBe true // long at slot 12
ir.contains("INCOMING=\"12\"") shouldBe true
}
test("subroutine that calls another is framed when it is not recursive") {
@@ -494,7 +497,7 @@ main {
val parm = subAssembly(lines, "p8b_main.p8s_parm")
parm.any { it == "link a5,#0" } shouldBe true
// the entry-time copy-in is still performed
parm.any { it.startsWith("move.b 11(a5),") } shouldBe true
parm.any { it.startsWith("move.b 8(a5),") } shouldBe true
// the body's write goes to the static cell, not to the incoming frame slot
parm.any { it.contains("move.b") && it.contains("p8b_main.p8s_parm.p8v_x") && it.contains("#99") } shouldBe true
// the defer handler reads the static cell, so it observes the body's written value
@@ -679,7 +682,7 @@ main {
parm.any { it == "link a5,#0" } shouldBe true
parm.any { it == "unlk a5" } shouldBe true
// entry-time copy-in: load the incoming slot, store into the static cell, before any clearing
parm.any { it.startsWith("move.b 11(a5),") } shouldBe true
parm.any { it.startsWith("move.b 8(a5),") } shouldBe true
parm.any { it.contains("p8b_main.p8s_parm.p8v_x") } shouldBe true
// the static cell is kept so the block-scope defer handler can still see it
lines.any { "p8b_main.p8s_parm.p8v_x:" in it } shouldBe true
@@ -705,7 +708,7 @@ main {
val (lines, _) = compile(src)
val rec = subAssembly(lines, "p8b_main.p8s_rec")
rec.any { it == "link a5,#0" } shouldBe true
rec.any { it.startsWith("move.b 11(a5),") } shouldBe true
rec.any { it.startsWith("move.b 8(a5),") } shouldBe true
rec.any { it.contains("p8b_main.p8s_rec.p8v_x") } shouldBe true
// the static cell is kept for the assembly reference; the body itself also uses the
// static cell (the entry-time copy-in made it the current value)
@@ -1255,11 +1258,11 @@ main {
val (lines, _) = compile(src)
val start = subAssembly(lines, "p8b_main.p8s_start")
// each push store is annotated with the short parameter name it fills;
// the slot-reserving subq lines stay bare
start.any { it.contains(",3(sp)") && it.endsWith("; first") } shouldBe true
start.any { it.contains(",2(sp)") && it.endsWith("; second") } shouldBe true
start.any { it.contains(",-(sp)") && it.endsWith("; third") } shouldBe true
start.count { it == "subq.l #4,sp" } shouldBe 2
// the byte slot-reserving subq lines stay absent (byte pushes are single moves)
start.any { it.startsWith("move.b ") && it.contains(",-(sp)") && it.contains("; first") && it.contains("pushed as a word") } shouldBe true
start.any { it.startsWith("move.w ") && it.contains(",-(sp)") && it.endsWith("; second") } shouldBe true
start.any { it.startsWith("move.l ") && it.contains(",-(sp)") && it.endsWith("; third") } shouldBe true
start.count { it == "subq.l #2,sp" } shouldBe 0
}
test("re-entrant subroutines that share data are rejected in plain language") {
+113
View File
@@ -0,0 +1,113 @@
package prog8tests.compiler
import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.shouldBe
import prog8.code.ast.PtNamedNode
import prog8.code.ast.PtNode
import prog8.code.core.ICompilationTarget
import prog8.code.target.Cx16Target
import prog8.code.target.Qemu68kTarget
import prog8.code.target.VMTarget
import prog8tests.helpers.compileText
private fun PtNode.allNames(): List<String> {
val names = mutableListOf<String>()
if(this is PtNamedNode)
names.add(name)
children.forEach { names.addAll(it.allNames()) }
return names
}
private val trigSubs = setOf("sin8u", "cos8u", "sin8", "cos8", "sinr8u", "cosr8u", "sinr8", "cosr8")
private val sharedTrigTables = setOf("sincos8u_table", "sincos8_table", "sincosr8u_table", "sincosr8_table")
private val separateTrigTables = setOf("sin8u_table", "cos8u_table", "sin8_table", "cos8_table", "sinr8u_table", "cosr8u_table", "sinr8_table", "cosr8_table")
class TestTrigTables: FunSpec({
val outputDir = tempdir().toPath()
// Uses math but no trig routines. @shared defeats const-folding.
val noTrigSrc = """
%import math
main {
ubyte @shared b1
ubyte @shared b2
ubyte @shared result
sub start() {
b1 = 10
b2 = 30
result = math.diff(b1, b2)
}
}
"""
// Uses all trig routines with simple args so every call gets inlined.
// Distinct result vars to avoid duplicate-assignment cleanup.
val trigSrc = """
%import math
main {
ubyte @shared angle
ubyte @shared radians
ubyte @shared u1
ubyte @shared u2
ubyte @shared ur1
ubyte @shared ur2
byte @shared s1
byte @shared s2
byte @shared sr1
byte @shared sr2
sub start() {
angle = 64
radians = 45
u1 = math.sin8u(angle)
u2 = math.cos8u(angle)
s1 = math.sin8(angle)
s2 = math.cos8(angle)
ur1 = math.sinr8u(radians)
ur2 = math.cosr8u(radians)
sr1 = math.sinr8(radians)
sr2 = math.cosr8(radians)
}
}
"""
// vasm is not available everywhere; the codegen AST is still produced with assemble=false
fun compileFor(target: ICompilationTarget, src: String) =
compileText(target, optimize=true, src, outputDir, writeAssembly=true, assemble=target !is Qemu68kTarget)!!
val targets = listOf(VMTarget() to sharedTrigTables, Cx16Target() to separateTrigTables, Qemu68kTarget() to separateTrigTables)
test("unused trig tables are absent from simpleAst") {
for((target, trigTables) in targets) {
val names = compileFor(target, noTrigSrc).codegenAst!!.allNames()
withClue("no trig table should remain on ${target.name}, found: ${names.filter { it.substringAfterLast('.') in trigTables }}") {
names.none { it.substringAfterLast('.') in trigTables } shouldBe true
}
withClue("no trig subroutine should remain on ${target.name}") {
names.none { it.substringAfterLast('.') in trigSubs } shouldBe true
}
}
}
test("used trig tables appear exactly once in simpleAst") {
for((target, trigTables) in targets) {
val names = compileFor(target, trigSrc).codegenAst!!.allNames()
for(table in trigTables) {
withClue("table $table should occur exactly once on ${target.name}, found: ${names.filter { table in it }}") {
names.count { table in it } shouldBe 1
}
}
withClue("all trig calls should be inlined on ${target.name}, no trig subroutine should remain") {
names.none { it.substringAfterLast('.') in trigSubs } shouldBe true
}
val otherTables = (sharedTrigTables + separateTrigTables) - trigTables
withClue("other table layout should be absent on ${target.name}") {
names.none { it.substringAfterLast('.') in otherTables } shouldBe true
}
}
}
})
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff. Show more