mirror of
https://github.com/irmen/prog8.git
synced 2026-10-24 03:02:20 +00:00
Compare commits
55
Commits
v14.0-BETA2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2520db872 | ||
|
|
ebe26d0cf9 | ||
|
|
63e2a45f09 | ||
|
|
6076b01f6d | ||
|
|
d0bf2c3dcb | ||
|
|
d9bf6f090e | ||
|
|
e08a995067 | ||
|
|
da2288db93 | ||
|
|
31b5cddb96 | ||
|
|
264b894909 | ||
|
|
09ae0c6322 | ||
|
|
9ca5db3436 | ||
|
|
375214b5e3 | ||
|
|
1da37e4119 | ||
|
|
16f7d588a7 | ||
|
|
f6c8a3f2ed | ||
|
|
5619898113 | ||
|
|
ae0414631e | ||
|
|
e3ca9ae4a8 | ||
|
|
38c60a2d75 | ||
|
|
5e83c44300 | ||
|
|
99ff5768c5 | ||
|
|
fba4bb5272 | ||
|
|
17322a08ec | ||
|
|
06d2f9ebbe | ||
|
|
3a5f2c33ab | ||
|
|
d523ecd1e1 | ||
|
|
159634a99f | ||
|
|
55dcb45230 | ||
|
|
de75342198 | ||
|
|
30d3b76787 | ||
|
|
8bbe5bd209 | ||
|
|
da7ea294ef | ||
|
|
05d8acc4bb | ||
|
|
6cac707fd1 | ||
|
|
dea43c99bb | ||
|
|
0b902f06b5 | ||
|
|
733a3fb752 | ||
|
|
460adb9e02 | ||
|
|
3c22dfc11a | ||
|
|
dfb3257c04 | ||
|
|
8bcd176746 | ||
|
|
fd3add76e4 | ||
|
|
4d0a7ff8bf | ||
|
|
e1f2faabe7 | ||
|
|
25bd60368a | ||
|
|
f61c1dc7ef | ||
|
|
eda21a6ebf | ||
|
|
c0267e37b0 | ||
|
|
78f9df203c | ||
|
|
d3e10b4974 | ||
|
|
c4679bd8af | ||
|
|
e7dfe79919 | ||
|
|
31dbbc3fe1 | ||
|
|
55b7239553 |
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
Generated
+2
-2
@@ -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>
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
%import shared_amiga_emudbg
|
||||
|
||||
emudbg {
|
||||
%option ignore_unused
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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 @@
|
||||
; no support for BCD
|
||||
%import shared_amiga_bcd
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
;; }
|
||||
;; }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
+3
-8
@@ -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}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
@@ -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") {
|
||||
|
||||
@@ -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
Reference in new issue
Block a user