mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4dedc27e5b | ||
|
|
5ba494c5e5 | ||
|
|
5c16ebe377 | ||
|
|
b5dfc0cd97 | ||
|
|
d276fb9ed5 | ||
|
|
ec9567a669 | ||
|
|
70d81298c1 | ||
|
|
1de1c10b41 | ||
|
|
25f883bb85 | ||
|
|
adc6d6626a | ||
|
|
90ff904b6e | ||
|
|
ba54601eab | ||
|
|
1c71f702f4 | ||
|
|
3d3c567ea6 | ||
|
|
539c75bbf6 | ||
|
|
2683b6388a | ||
|
|
a919ea63e4 | ||
|
|
07910fc543 | ||
|
|
13e032e1a3 | ||
|
|
c3b5ee3dfd | ||
|
|
b0e0058310 | ||
|
|
9cbbf85a85 | ||
|
|
9936a855ab | ||
|
|
bb1d1e2007 | ||
|
|
0aa6c130e8 | ||
|
|
51257e47da | ||
|
|
67ddd97f3d | ||
|
|
abea7bf329 | ||
|
|
bc3f49dc13 | ||
|
|
cf2a5ae4f9 | ||
|
|
e5865a1446 | ||
|
|
5b5f553d37 | ||
|
|
b9fe1f2057 | ||
|
|
cfd1f951f0 | ||
|
|
b0e55eb25b | ||
|
|
b405ffb8bd | ||
|
|
0fa08310e7 | ||
|
|
968d9819db | ||
|
|
f85c59b12c | ||
|
|
34eedd1fe7 | ||
|
|
2c10e2b102 | ||
|
|
05bea61a69 | ||
|
|
2a8b24074b | ||
|
|
1f184a14ae | ||
|
|
8be1cd1090 | ||
|
|
c3ec400ee7 | ||
|
|
5130a74754 | ||
|
|
e61db5ed3f | ||
|
|
cc5ef21fd6 | ||
|
|
f1f925ecee | ||
|
|
ab422c0ebc | ||
|
|
05a13463f4 | ||
|
|
19238cc366 | ||
|
|
e3d4401d99 | ||
|
|
5da4efc2a3 | ||
|
|
c0c44156f1 | ||
|
|
8645833319 | ||
|
|
6a867d98eb | ||
|
|
58363ff4e4 | ||
|
|
2555aa72ca | ||
|
|
3b94fd4c4b | ||
|
|
95f500c32f | ||
|
|
870c2e8827 | ||
|
|
af2360648f | ||
|
|
bb95d40d6d | ||
|
|
55ea599fca | ||
|
|
fd5fdf3d13 | ||
|
|
ca129edce4 | ||
|
|
c1694a4b08 | ||
|
|
a348bbbebe | ||
|
|
e6b0a89d64 | ||
|
|
9a4821d1e3 | ||
|
|
71583a5b93 | ||
|
|
407ba34c72 | ||
|
|
fc4782a35b | ||
|
|
7a83b7db1b | ||
|
|
a827f4c4ce | ||
|
|
01c5794799 | ||
|
|
1c093cca2d | ||
|
|
a85b267872 | ||
|
|
5e0c1c9794 | ||
|
|
887df44359 | ||
|
|
a5fc162c71 | ||
|
|
9d3fc99d71 | ||
|
|
a871713907 | ||
|
|
2a19e5682a | ||
|
|
22d4fa73d6 | ||
|
|
2ed95c52ef | ||
|
|
73fb04aaf5 | ||
|
|
f97a7f426c | ||
|
|
c1189617f3 | ||
|
|
78d43c3cb6 | ||
|
|
8df2bb8d2e | ||
|
|
c8c51245af | ||
|
|
02110de844 | ||
|
|
7d77a0eca6 | ||
|
|
e6e4714b02 | ||
|
|
d83ece397d | ||
|
|
a3c768bc6b | ||
|
|
a3d14003cb | ||
|
|
8c92663824 | ||
|
|
10d86eb4d8 | ||
|
|
ee5e734f23 | ||
|
|
6b233b7da3 | ||
|
|
a140519374 | ||
|
|
2b118f2e42 | ||
|
|
be9d9fe377 | ||
|
|
d1345aafec | ||
|
|
d5932a7a57 | ||
|
|
5751beb72f | ||
|
|
02194a735a | ||
|
|
b5fc25b2f4 | ||
|
|
63c425f247 | ||
|
|
69261c51b3 | ||
|
|
63d7ef7105 | ||
|
|
88a3e5b23f | ||
|
|
1b5efe1e5d | ||
|
|
2b5a15a178 | ||
|
|
a9f22d9375 | ||
|
|
f9b3514f2a | ||
|
|
be12fb507e | ||
|
|
b2dcd02ee4 | ||
|
|
5c23471254 | ||
|
|
67e284b680 | ||
|
|
1e5ba48fe0 | ||
|
|
8e6a27d7c1 | ||
|
|
ccd3d94ec0 | ||
|
|
98355d3850 | ||
|
|
0e53113b85 | ||
|
|
c55f5e4b50 | ||
|
|
532bf13057 | ||
|
|
de706e79de | ||
|
|
a19f337ebe | ||
|
|
041a904e44 | ||
|
|
97fb5298a8 | ||
|
|
b580a177d4 | ||
|
|
a0b1ca5d9f | ||
|
|
3a291c56b4 | ||
|
|
ed41d5dbc5 | ||
|
|
628bd41740 | ||
|
|
de988f72c6 | ||
|
|
869c5ef544 | ||
|
|
17ee233e8b |
No files matched your search
@@ -1,27 +1,29 @@
|
||||
---
|
||||
name: asm6502-coder
|
||||
description: Write 6502/65C02 assembly code using 64tass syntax, for use within Prog8 programs
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
description: Writing or understanding 6502/65C02 assembly code using 64tass syntax, for use as stand alone .asm or inline within Prog8 programs
|
||||
---
|
||||
|
||||
# 65(C)02 Assembly Coder Skill
|
||||
|
||||
You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.asm` files or embedded in a Prog8 program (inside `%asm {{ }}` blocks or `asmsub` routines). Follow all rules below.
|
||||
|
||||
## Git Operations
|
||||
- When moving, renaming, or deleting git-tracked files, **always use `git mv` or `git rm`** instead of plain `mv`/`rm`. This preserves history and properly stages the change. Plain `mv`/`rm` causes git to see them as delete+add (losing history).
|
||||
|
||||
## Debugging Generated Assembly
|
||||
- The prog8 compiler outputs `*.asm` (assembly source) and (with -asmlist option) also `*.list` (full listing with address/symbols) when compiling. Inspect these to debug generated code, verify optimizations, and trace instruction sequences.
|
||||
You are an expert MOS 6502 assembly development assistant. Keep responses concise and practical — prefer short, correct code examples over lengthy prose.
|
||||
You are working with **6502/65C02 assembly** using **64tass syntax**, in separate `*.asm` files or embedded in a Prog8 program (inside `%asm {{ }}` blocks or `asmsub` routines). Follow all rules below.
|
||||
|
||||
## Assembler: 64tass Syntax
|
||||
### Formatting Rules
|
||||
- Labels start in the first column of the line.
|
||||
- Instructions are indented at least 4 spaces.
|
||||
- Two spaces between the instruction opcode and its operand (e.g., `lda #1`, `sta $d020`).
|
||||
- End-of-line comments are preceded by two spaces before the `;` (e.g., `sta $d020 ; border color`).
|
||||
- If a comment is the only thing on a line, it starts in the first column (no indentation).
|
||||
- Opcodes and operands are written in lowercase (e.g., `lda`, not `LDA`; `$d020`, not `$D020`).
|
||||
|
||||
- **NOT ca65/cc65** or other assemblers. Key differences:
|
||||
- `.proc` / `.pend` for procedures (scoping)
|
||||
- `_label` for local labels (prefixed with underscore, scoped to `.proc`)
|
||||
- **Symbol aliases**: inside a `.proc`, use `_name = P8ZP_SCRATCH_B1` to give a scratch variable a descriptive name.
|
||||
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line. Reference them in branches using `+`, `++`, `+++` etc. (first/second/third *upcoming* anonymous forward label) and `-`, `--`, `---` etc. (first/second/third *preceding* anonymous backward label).
|
||||
- Crucial: `+` refers to the NEXT upcoming `+` label, `++` refers to the ONE AFTER that, etc.
|
||||
- **Anonymous labels**: defined as `+` (forward) or `-` (backward) at the start of a line.
|
||||
- **CRITICAL DISTINCTION**: Label **definitions** are always just a single `+` or `-`. Label **references** use `+`, `++`, `+++` to refer to the 1st, 2nd, 3rd upcoming forward label, and `-`, `--`, `---` for the 1st, 2nd, 3rd preceding backward label.
|
||||
- `+` refers to the NEXT upcoming `+` label, `++` refers to the ONE AFTER that, etc.
|
||||
- `-` refers to the MOST RECENT `-` label, `--` to the one before that, etc.
|
||||
- When a `+` label is passed, the forward-reference count resets (so `++` then refers to the next one after that new label)
|
||||
- Example:
|
||||
@@ -32,10 +34,10 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
+ dex ; forward label '+'
|
||||
bne + ; branch to this same '+' (forward)
|
||||
sta $400
|
||||
+ lda #0 ; second '+'
|
||||
bne ++ ; branch to the '++' below
|
||||
+ lda #0 ; second '+' label definition (just '+')
|
||||
bne ++ ; branch to the '++' below (third forward '+')
|
||||
rts
|
||||
++ inc $d020 ; third forward label
|
||||
+ inc $d020 ; third forward label definition (just '+', referred to as '++')
|
||||
```
|
||||
- Data directives: `.byte`, `.word`, `.dword`, `.fill` (allocate space)
|
||||
- Equates: `label = value` (not `label .equ value` or `#define`)
|
||||
@@ -67,6 +69,17 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
- Check the target before using 65C02-specific instructions
|
||||
- **6502 / 65C02 instruction reference table**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=4 (provides exact instruction details for all opcodes: operation, addressing modes, byte length, and cycle count).
|
||||
|
||||
## Commander X16 Memory Map
|
||||
```
|
||||
Bank Offset Content
|
||||
$0000-$9EFF Fixed/System RAM
|
||||
$9F00-$9FFF I/O Area (VIA, VERA, YM2151)
|
||||
$00-$FF:$A000-$BFFF Banked RAM (max 2 MiB, often 512 KiB) (256x8K banks)
|
||||
$00-$1F:$C000-$FFFF System ROM (512 KiB) (32x16K banks)
|
||||
```
|
||||
- Bank registers (in zeropage): `$00` selects the current RAM bank (0-255), `$01` selects the current ROM bank (0-31). For JSRFAR/banked calls from Prog8, use `extsub @bank ...` instead of manipulating these directly.
|
||||
- Full hardware specs at https://ayce.dev/emptyx16.html#emptyx16---x16-hardware-specs
|
||||
|
||||
## Calling Convention / Register Conventions
|
||||
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
|
||||
- **X register**: 8-bit, often used for indexing, loop counters
|
||||
@@ -144,6 +157,7 @@ All Prog8 symbols are prefixed when accessed from assembly:
|
||||
- Virtual registers `cx16.r0`-`cx16.r15` are available on ALL targets, but only on CX16 in zeropage
|
||||
- Assume the scratch variables are not consecutive in zeropage. The CX16 virtual registers ARE consecutive in memory though.
|
||||
- For additional temporary storage, allocate regular variables in BSS
|
||||
- **CRITICAL: NEVER use hardcoded zeropage addresses** (like `$1e`, `$22`, `$7c`, etc.) to reference P8ZP_SCRATCH registers or cx16 virtual registers. Their addresses differ per compilation target. **ALWAYS** use the symbolic names (`P8ZP_SCRATCH_W1`, `cx16.r14`, `cx16.r14+1`, etc.) so the assembler resolves the correct address for the target.
|
||||
|
||||
## Common 6502 Patterns
|
||||
|
||||
@@ -165,6 +179,9 @@ loop ; do work here
|
||||
bne loop ; loop while X != 0 (runs 'count' times)
|
||||
```
|
||||
|
||||
### Self-Modifying Code (SMC) — Avoid When Possible
|
||||
**Do NOT use self-modifying code unless absolutely necessary** — self-modifying code writes to the code segment at runtime, which means it **cannot run from ROM** and is generally fragile, hard to debug, and violates the expected behavior of modern tooling (emulators, debuggers, etc.). Prefer lookup tables, indirect jumps via a vector in RAM, or alternative algorithms that keep code in place.
|
||||
|
||||
### Self-Modifying Code (SMC) Detection
|
||||
Look for `sta`, `stx`, or `sty` pointing into code labels:
|
||||
```asm
|
||||
@@ -253,9 +270,6 @@ Call them with `#pushax` and `#popax`.
|
||||
|
||||
## IRQ Handler Best Practices
|
||||
- **Clear the Decimal Flag (`cld`)**: On the original 6502, the decimal flag (`D`) is **not** automatically cleared when an interrupt occurs. If the interrupted code was in decimal mode, your handler will also run in decimal mode, causing arithmetic errors. Always call `cld` at the beginning of your handler. The 65C02 clears it automatically, but `cld` is still recommended for portability.
|
||||
- Keep handlers extremely short and fast — they run with interrupts disabled and steal cycles from the main program.
|
||||
- Do NOT do lengthy processing, I/O, or complex subroutine calls inside the handler.
|
||||
- Instead, set a boolean flag or semaphore that the main loop checks periodically, and do the actual work there.
|
||||
|
||||
## Invoking the Assembler (64tass)
|
||||
If you need to manually invoke `64tass` to assemble a generated `.asm` file, you should be aware of the default arguments that `prog8c` supplies to ensure compatibility with the generated code:
|
||||
|
||||
@@ -1,39 +1,23 @@
|
||||
---
|
||||
name: prog8-coder
|
||||
description: Write Prog8 programs (6502-targeted language with 8-bit retro systems as targets)
|
||||
license: MIT
|
||||
compatibility: opencode
|
||||
description: Writing or understanding Prog8 programs
|
||||
---
|
||||
|
||||
# Prog8 Coder Skill
|
||||
|
||||
You are an expert retro system development assistant, versed in the Prog8 programming language. Keep responses concise and practical — prefer short, correct code examples over lengthy prose.
|
||||
You are working with **Prog8** source code (`.p8` files) or its Intermediate Representation (`.p8ir` files). Prog8 targets 8-bit systems (C64, CX16, C128, PET32) with the 6502 CPU, plus a `virtual` target for testing.
|
||||
|
||||
Follow ALL the rules below carefully.
|
||||
|
||||
## General & Setup
|
||||
- **Git operations**: When moving, renaming, or deleting git-tracked files, **always use `git mv` or `git rm`** instead of plain `mv`/`rm`. This preserves history and properly stages the change. Plain `mv`/`rm` causes git to see them as delete+add (losing history).
|
||||
- A program = a `main` block containing a `start` subroutine entry point, plus optional other subroutines/blocks
|
||||
- Add `%zeropage basicsafe` at the top of your program to allow clean return on exit (instead of resetting the machine/emulator)
|
||||
- Module imports: `%import modulename` — no `as` aliasing. Use the module's defined prefix (e.g., `%import textio` → `txt.xxx`)
|
||||
- **Virtual target for testing**: Use `prog8c -target virtual -emu input.p8` or `prog8c -vm input.p8ir` for fast testing
|
||||
- **Fast syntax check**: `prog8c -check input.p8` — does NOT produce output files (.prg, .asm, etc.), just errors
|
||||
- **Output directory**: `prog8c -out outdir input.p8` (default: same dir as source)
|
||||
- **Compilation outputs**: `*.prg` (program binary), `*.asm` (assembly listing), `*.list` (full listing), `*.p8ir` (IR for VM), `*.vice-mon-list` (Vice debug symbols)
|
||||
- `.p8ir` files contain the **Intermediate Representation** — a sequence of named chunks, each containing typed instructions and virtual registers. This is a target-independent representation of the program, executable by the built-in Virtual Machine via `prog8c -vm file.p8ir`. Useful for debugging the compiler's code generation path without involving 6502 assembly.
|
||||
- **Debugging switches**: `-noopt` (disable optimizations), `-printast1` (parsed AST), `-printast2` (optimized simple AST), `-compareir` (compare IR outputs), `-dumpsymbols` (print all symbols), `-dumpvars` (print all variables)
|
||||
- **Library search (preferred)**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded library files. Extremely useful to quickly find library routines, variables, strings, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines; `prog8c -libsearch "sin"` finds math functions)
|
||||
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection (less common, use `-libsearch` first)
|
||||
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive to speed up multiple successive compilations — must be passed on every `prog8c` invocation)
|
||||
- **Test programs**: add `%zeropage basicsafe` and `%option no_sysinit` at top
|
||||
- **Compiler unit test snippets**: These are **prog8 code snippets embedded in the Prog8 compiler's own Kotlin unit tests** (e.g., in `TestAstChecks.kt`, `TestOptimization.kt`, etc.). They are NOT standalone Prog8 programs:
|
||||
- Keep them self-contained — avoid `%import` directives so they don't depend on library files (the test setup may not have the library search path configured)
|
||||
- Use the `cx16` target (not `virtual`, unless specifically testing the VM)
|
||||
- Consider setting `writeAssembly=false` if the test only needs to check the generated AST (much faster)
|
||||
- Use `optimize=false` by default for these snippets
|
||||
- Do NOT use the `%encoding iso` / `txt.iso()` / `sys.poweroff_system()` pattern in these snippets — that's only for real CX16 emulator runs
|
||||
- `.p8ir` files are the **Intermediate Representation** — target-independent, executable via `prog8c -vm file.p8ir`
|
||||
- **Library search**: `prog8c -libsearch <regex>` to find routines in the embedded stdlib (e.g., `prog8c -libsearch "txt\."`)
|
||||
- **Library dump**: `prog8c -libdump <dir>` to extract all embedded library source files
|
||||
- **`sys` module**: always available, no import needed
|
||||
- **CX16 debugging**: Add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`. For emulator: `x16emu -echo iso -run -prg input.prg 2>&1 | grep ...`
|
||||
- **CX16 programs**: add `%encoding iso`, call `txt.iso()` in `start()`, end with `sys.poweroff_system()`
|
||||
|
||||
## Datatypes & Variables
|
||||
- Primitives: `bool`, `byte`, `ubyte`, `word`, `uword`, `long`, `float`, `str`
|
||||
@@ -44,7 +28,7 @@ Follow ALL the rules below carefully.
|
||||
- To point a typed pointer at a `memory()` block, assign the uword directly: `^^MyStruct ptr = memory("name", size)`. The `^^Type:expression` syntax (below) only works with array literals, not general uword expressions.
|
||||
- Struct initialization: `^^StructType ptr = ^^StructType:[val1,val2,...]` (the `^^StructType:` can be omitted if inferable). This `^^Type:[...]` syntax does NOT work with variables or `memory()` — only literal arrays.
|
||||
- Struct definitions must be inside a block, not at file level.
|
||||
- Struct fields: only simple types + `str` allowed. NO arrays as fields. `str` in struct = `^^ubyte`
|
||||
- Struct fields: simple types, `str`, and fixed-size arrays. Array fields: `ubyte[108] name`. `str` in struct = `^^ubyte`
|
||||
- Word/pointer arrays split into LSB/MSB by default. Override with `@nosplit`
|
||||
- **No call stack**: all variables statically allocated. No recursion without manual stack management
|
||||
- Variables zero-initialized (globals at start, locals on subroutine entry)
|
||||
@@ -230,26 +214,10 @@ Used to call routines at fixed memory addresses (like ROM KERNAL routines or thi
|
||||
extsub @bank 10 $C09F = audio_init() clobbers(A, X, Y) -> bool @Pc
|
||||
```
|
||||
|
||||
## Assembly Programming Details
|
||||
- **Symbol prefixes**: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
|
||||
- **Fully qualified names**: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
|
||||
- **Within a `.proc`**, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
|
||||
- **Split word arrays**: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
|
||||
- **CX16 target**: use 65C02 instructions (STZ, PHX, etc). Others: 6502 only
|
||||
- **Assembly syntax**: 64tass. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
|
||||
- **Instructions** like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`
|
||||
- **Anonymous labels**: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
|
||||
- **Register Aliases**: It's common to define aliases at the start of an `asmsub`:
|
||||
```prog8
|
||||
asmsub my_routine(uword ptr @AX) {
|
||||
%asm {{
|
||||
ptr_lo = p8zp_scratch_w1
|
||||
ptr_hi = p8zp_scratch_w1+1
|
||||
sta ptr_lo
|
||||
stx ptr_hi
|
||||
}}
|
||||
}
|
||||
```
|
||||
## Inline Assembly
|
||||
When writing inline assembly (`%asm {{ }}` blocks or `asmsub` routines), load the **asm6502-coder** skill for formatting rules, symbol references, anonymous labels, and 64tass syntax details.
|
||||
|
||||
|
||||
|
||||
## Standard Library
|
||||
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
|
||||
@@ -272,6 +240,7 @@ Used to call routines at fixed memory addresses (like ROM KERNAL routines or thi
|
||||
- **No bare `{ }` blocks** like C/Java
|
||||
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
|
||||
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
|
||||
- **Test programs for CBM targets must only use lowercase text in string literals** (the C64/C128/PET32/CX16 targets all use PETSCII). In PETSCII, uppercase letters A-Z are the *graphics* symbols, so any uppercase text you put in a string literal is rendered as unpredictable graphics characters instead of readable letters. Use only lowercase (a-z) for any printed test output, labels, or expected-match strings on these targets; reserve uppercase for intentional PETSCII graphics. The `virtual` target (ISO) is unaffected and may use either case.
|
||||
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. They are syntactic sugar for a list of `const` declarations, not a type. Use enums for related values, `const` for standalone
|
||||
- Avoid `globals.XXXX` — move constants closer to where they're used
|
||||
|
||||
@@ -27,11 +27,17 @@ jobs:
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Build and test with Gradle
|
||||
- name: Compile
|
||||
run: ./gradlew compileKotlin compileTestKotlin --console=plain
|
||||
|
||||
- name: Test
|
||||
run: ./gradlew test --continue --console=plain
|
||||
|
||||
- name: Package
|
||||
run: |
|
||||
./gradlew build shadowJar --no-daemon --console=plain
|
||||
./gradlew shadowJar --console=plain
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
@@ -40,3 +46,10 @@ jobs:
|
||||
compiler/build/libs/*-all.jar
|
||||
compiler/build/libs/hash.txt
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: test-results
|
||||
path: compiler/build/test-results/test/
|
||||
|
||||
@@ -17,6 +17,7 @@ out-old/
|
||||
*.prg
|
||||
*.bin
|
||||
*.p8ir
|
||||
*.bankedcalls
|
||||
*.labels.txt
|
||||
*.vm.txt
|
||||
*.vice-mon-list
|
||||
|
||||
Generated
+11
-1
@@ -2,6 +2,16 @@
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="11">
|
||||
<module name="prog8.codeGenExperimental" target="17" />
|
||||
<module name="prog8.codeGenExperimental.main" target="17" />
|
||||
<module name="prog8.codeGenExperimental.test" target="17" />
|
||||
<module name="prog8.codeGenM68k" target="17" />
|
||||
<module name="prog8.codeGenM68k.main" target="17" />
|
||||
<module name="prog8.codeGenM68k.test" target="17" />
|
||||
<module name="prog8.codeGenNew6502" target="17" />
|
||||
<module name="prog8.codeGenNew6502.main" target="17" />
|
||||
<module name="prog8.codeGenNew6502.test" target="17" />
|
||||
</bytecodeTargetLevel>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+29
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/codeCore" />
|
||||
<option value="$PROJECT_DIR$/codeGenCpu6502" />
|
||||
<option value="$PROJECT_DIR$/codeGenIntermediate" />
|
||||
<option value="$PROJECT_DIR$/codeOptimizers" />
|
||||
<option value="$PROJECT_DIR$/compiler" />
|
||||
<option value="$PROJECT_DIR$/compilerAst" />
|
||||
<option value="$PROJECT_DIR$/intermediate" />
|
||||
<option value="$PROJECT_DIR$/languageServer" />
|
||||
<option value="$PROJECT_DIR$/parser" />
|
||||
<option value="$PROJECT_DIR$/simpleAst" />
|
||||
<option value="$PROJECT_DIR$/virtualmachine" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="myGradleHome" value="/usr/share/java/gradle" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+5
-4
@@ -7,13 +7,14 @@
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
<component name="KotlinCommonCompilerArguments">
|
||||
<option name="apiVersion" value="2.3" />
|
||||
<option name="languageVersion" value="2.3" />
|
||||
<option name="apiVersion" value="2.4" />
|
||||
<option name="languageVersion" value="2.4" />
|
||||
</component>
|
||||
<component name="KotlinCompilerSettings">
|
||||
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.3.21" />
|
||||
<option name="externalSystemId" value="Gradle" />
|
||||
<option name="version" value="2.4.10" />
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
||||
Generated
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.21" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
-18
@@ -1,18 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.13.2">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.13.2/antlr4-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.13.2/antlr4-runtime-4.13.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-13
@@ -1,13 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="eclipse.lsp4j" type="repository">
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:1.0.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/1.0.0/org.eclipse.lsp4j-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/1.0.0/org.eclipse.lsp4j.jsonrpc-1.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.14.0/gson-2.14.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.48.0/error_prone_annotations-2.48.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
Generated
-14
@@ -1,14 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.irmen.ksim65" type="repository">
|
||||
<properties maven-id="com.github.irmen:ksim65:v2.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.1/ksim65-v2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.framework.datatest" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-framework-datatest:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest/5.9.1/kotest-framework-datatest-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-datatest-jvm/5.9.1/kotest-framework-datatest-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="io.kotest.runner.junit5.jvm" type="repository">
|
||||
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.1/kotest-runner-junit5-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.1/kotest-framework-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.1/kotest-assertions-shared-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.1/kotest-common-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.1/kotest-framework-engine-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.1/kotest-framework-discovery-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.1/kotest-assertions-core-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.1/kotest-assertions-api-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.1/kotest-extensions-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.1/kotest-framework-concurrency-jvm-5.9.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.2/apiguardian-api-1.1.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
|
||||
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.6" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.6/kotlinx-cli-jvm-0.3.6.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
<component name="libraryTable">
|
||||
<library name="michael.bull.kotlin.result.jvm" type="repository">
|
||||
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.3.1/kotlin-result-jvm-2.3.1-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.10/kotlin-stdlib-2.3.10-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+1
@@ -20,6 +20,7 @@
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11" />
|
||||
</component>
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
|
||||
Generated
-14
@@ -3,22 +3,8 @@
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/benchmark-program/benchmark-program.iml" filepath="$PROJECT_DIR$/benchmark-program/benchmark-program.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" filepath="$PROJECT_DIR$/codeGenIntermediate/codeGenIntermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/languageServer/languageServer.iml" filepath="$PROJECT_DIR$/languageServer/languageServer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
@@ -30,7 +30,7 @@
|
||||
- The prog8 compiler is written mostly in Kotlin, those files have the .kt extension.
|
||||
- The standard library is mostly written in Prog8 and assembly code, and can be found in the "compiler" module, in the 'res/prog8lib' directory.
|
||||
- **Library Reference**: For a quick overview of all available modules, routines, and their signatures, **consult the skeleton files (symboldumps)** in `docs/source/_static/symboldumps/`. They are named `skeletons-<target>.txt`.
|
||||
- Kotlin version 2.3 is used for the compiler implementation.
|
||||
- Kotlin version 2.4 is used for the compiler implementation.
|
||||
- Java 17 is used as Java runtime version.
|
||||
- ANTLR4 version 4.13 is used for the parser implementation.
|
||||
- Dependent library versions can be found in 'build.gradle.kts' and in the IntelliJ IDEA configuration files in .idea/libraries
|
||||
@@ -127,6 +127,12 @@ Steps when encountering a compiler crash:
|
||||
### Code Style Guidelines
|
||||
**Minimal comments when making changes**: When modifying existing code, add only essential comments that explain *why* a change was made or document non-obvious behavior. **Do not add verbose comments** that restate what the code does; let the code speak for itself. Existing extensive comments should be preserved, but new changes should have minimal commentary.
|
||||
|
||||
**Kotlin string `$` handling**: The `$` character is special in Kotlin string literals (string interpolation). If you need a literal `$` character, use Kotlin's `$$"..."` interpolation prefix instead of backslash escaping (`\$`) or `${'$'}`. The `$$"..."` syntax is a fairly recent Kotlin feature that avoids nasty escaping. For example:
|
||||
```kotlin
|
||||
val bill = $$"The value is $3.99"
|
||||
```
|
||||
|
||||
|
||||
## Prog8 language information
|
||||
|
||||
When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir` (Prog8 intermediate representation) files, **load the `prog8-coder` skill** — it contains the full language reference, syntax rules, and standard library guidance.
|
||||
@@ -153,18 +159,6 @@ When the task involves writing or understanding `.p8` (Prog8 source) or `.p8ir`
|
||||
- **git log/history queries can be useful** for understanding when/why a feature was added or tracking down when a bug was introduced, but for locating code use grep_search or glob instead.
|
||||
- Architecture decisions: separation of frontend/parser, IR intermediate representation, multiple backends
|
||||
|
||||
## Junie Agent Commitments
|
||||
These instructions apply specifically to the Junie agent:
|
||||
|
||||
* **Code Discovery and Navigation**: Use the `get_file_structure` tool to understand the API and structure of files before reading or editing them.
|
||||
* **Symbol-Aware Searching**: Use the `search_project` tool as the primary method for finding symbol definitions (classes, methods, variables) across the project.
|
||||
* **Project-Wide Refactoring**: Exclusively use the `rename_element` tool for any symbol renames to ensure all references, imports, and documentation are updated correctly.
|
||||
* **Structured Test Execution**: Use the `run_test` tool for rapid, isolated verification of Kotlin logic changes.
|
||||
* **Contextual Exploration**: Use the `open` tool with specific `line_number` parameters to navigate directly to relevant code sections.
|
||||
* **Gradle Console Output**: Always use the `--console=plain` flag with `gradle` commands. Fancy console output (like progress bars) will mess up the coding agent's text interface.
|
||||
|
||||
**Note on Terminal usage**: Junie uses `gradle` and terminal commands primarily for project lifecycle tasks (e.g., `gradle installdist` to re-embed standard library resources), manual compiler verification with `prog8c`, and full regression testing before submission. When using `gradle`, always append `--console=plain`.
|
||||
|
||||
# Dev environment tips
|
||||
|
||||
## Development Workflows
|
||||
@@ -202,9 +196,10 @@ These instructions apply specifically to the Junie agent:
|
||||
- This compiles AND installs, but still skips running tests (faster than `gradle build`)
|
||||
|
||||
### When to run full build with tests
|
||||
- **Before committing changes**: Always run `gradle build` to ensure all tests pass
|
||||
- **Before final verification**: Always run `gradle build` to ensure all tests pass
|
||||
- **After major refactoring**: Run `gradle build` to catch regressions
|
||||
- **When debugging test failures**: Use `gradle test --tests "*TestName*"` for specific tests
|
||||
- **When working on a standalone codegen module** (one that reads `.p8ir` files): Run `gradle :<module>:test --console=plain` instead of full `gradle build`. Compiler unit tests are irrelevant for IR-level changes. Do run full `gradle build` before final verification.
|
||||
|
||||
### Build command summary
|
||||
|
||||
@@ -225,6 +220,7 @@ These instructions apply specifically to the Junie agent:
|
||||
- **the `-printast1` switch prints out the internal Compiler AST** after parsing and semantic analysis.
|
||||
- **the `-printast2` switch prints out the optimized Simple AST** just before it goes to the code generator. This is useful for debugging optimizer issues.
|
||||
- **the `-out outdir` switch sets an alternative output directory** for compiled files (.prg, .asm, .list, etc.). **By default, output files are written to the same directory as the source file**.
|
||||
- **Other useful flags**: `-quiet` (suppress messages), `-warnimplicitcasts` (warn on implicit type widening), `-daemon` (keep a background compiler process alive — must be passed on every invocation)
|
||||
|
||||
### Compilation Output Files
|
||||
- `*.prg` - The final compiled program file for the target system (e.g., Commander X16)
|
||||
@@ -238,6 +234,10 @@ These instructions apply specifically to the Junie agent:
|
||||
- `prog8c -target targetname -emu input.p8` - Compile and execute a prog8 file in the emulator for the given target (cx16, c64, pet32, c128, virtual)
|
||||
- `prog8c -vm input.p8ir` - Execute an existing prog8 program, compiled in IR form, in the Virtual Machine
|
||||
|
||||
### Library Tools
|
||||
- **Library search**: `prog8c -libsearch <regex>` — search for a regex pattern in the embedded stdlib files. Extremely useful to quickly find library routines, variables, or signatures (e.g., `prog8c -libsearch "txt\."` lists all textio routines)
|
||||
- **Library dump**: `prog8c -libdump <dir>` — extract all embedded library source files into a directory for direct inspection
|
||||
|
||||
## Testing and Verification
|
||||
|
||||
- Never assume your changes simply work, always test!
|
||||
@@ -284,6 +284,25 @@ gradle :compiler:compileTestKotlin --info 2>&1 | grep "^e:"
|
||||
- **When writing test programs**, add at the top: `%zeropage basicsafe` and `%option no_sysinit`
|
||||
- When a test fails, the output shows "There were failing tests. See the report at:" - **read that HTML report**
|
||||
|
||||
### 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)
|
||||
- **Use `cx16` target** (not `virtual`, unless specifically testing the VM)
|
||||
- **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
|
||||
|
||||
### Testing Assembly Output
|
||||
When testing generated assembly in Kotlin tests:
|
||||
- **NEVER dump raw assembly in string assertions** (e.g., `asm shouldContain "jsr label"`). On failure, this dumps the ENTIRE assembly listing into test logs.
|
||||
- **Instead, parse into lines and check specific properties:**
|
||||
```kotlin
|
||||
val lines = asm.lines().map { it.trim() }
|
||||
lines.any { it == "lda #42" } shouldBe true
|
||||
lines.count { it.startsWith("jsr") } shouldBe 0
|
||||
```
|
||||
- Prefer `lines.any { ... }` for existence checks and `lines.count { ... } shouldBe 0` for absence checks.
|
||||
|
||||
### Manual Verification & Emulators
|
||||
|
||||
**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.
|
||||
|
||||
@@ -113,8 +113,8 @@ Additional required tools
|
||||
-------------------------
|
||||
|
||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
||||
If you're on Windows, download a recent .exe from the official repository's file section <https://sourceforge.net/projects/tass64/files/binaries/>.
|
||||
For other systems (Mac, Linux) it is very easy to get it via your package manager or to compile it yourself.
|
||||
|
||||
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
|
||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.3.21"
|
||||
kotlin("jvm") version "2.4.10"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -52,7 +52,7 @@ subprojects {
|
||||
// Common test dependencies via Kotest BOM (Bill of Materials)
|
||||
// This manages all Kotest module versions centrally
|
||||
dependencies {
|
||||
testImplementation(platform("io.kotest:kotest-bom:5.9.1"))
|
||||
testImplementation(platform("io.kotest:kotest-bom:6.2.3"))
|
||||
// implementation("com.github.irmen:ksim65:v2.1")
|
||||
}
|
||||
|
||||
|
||||
@@ -8,5 +8,4 @@ dependencies {
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -111,7 +111,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"prog8_lib_structalloc" to FSignature(true, arrayOf(BaseDataType.UWORD)),
|
||||
"prog8_lib_copylong" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"prog8_lib_copyfloat" to FSignature(false, emptyArray(), FParam("pointer1", BaseDataType.UWORD), FParam("pointer2", BaseDataType.UWORD)),
|
||||
"abs" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
|
||||
"abs" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"abs__byte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.BYTE)),
|
||||
"abs__word" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.WORD)),
|
||||
"abs__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG)),
|
||||
@@ -120,7 +120,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"sizeof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("object", *(BaseDataType.entries - BaseDataType.STRUCT_INSTANCE).toTypedArray())),
|
||||
"offsetof" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("field", BaseDataType.UBYTE)),
|
||||
"sgn" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, emptyArray(), FParam("value", *NumericDatatypes)),
|
||||
"sqrt" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", *NumericDatatypes)),
|
||||
"sqrt__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE)),
|
||||
"sqrt__uword" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UWORD)),
|
||||
"sqrt__long" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.LONG)),
|
||||
@@ -140,19 +140,19 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"mkword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("msb", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msb", BaseDataType.UBYTE), FParam("b2", BaseDataType.UBYTE), FParam("b1", BaseDataType.UBYTE), FParam("lsb", BaseDataType.UBYTE)),
|
||||
"mklong2" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("msw", BaseDataType.UWORD), FParam("lsw", BaseDataType.UWORD)),
|
||||
"clamp" to FSignature(true, emptyArray(), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("value", BaseDataType.BYTE), FParam("minimum", BaseDataType.BYTE), FParam("maximum", BaseDataType.BYTE)),
|
||||
"clamp__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("value", BaseDataType.UBYTE), FParam("minimum", BaseDataType.UBYTE), FParam("maximum", BaseDataType.UBYTE)),
|
||||
"clamp__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("value", BaseDataType.WORD), FParam("minimum", BaseDataType.WORD), FParam("maximum", BaseDataType.WORD)),
|
||||
"clamp__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("value", BaseDataType.UWORD), FParam("minimum", BaseDataType.UWORD), FParam("maximum", BaseDataType.UWORD)),
|
||||
"clamp__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("value", BaseDataType.LONG), FParam("minimum", BaseDataType.LONG), FParam("maximum", BaseDataType.LONG)),
|
||||
"min" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"min__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"min__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
"min__uword" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("val1", BaseDataType.UWORD), FParam("val2", BaseDataType.UWORD)),
|
||||
"min__long" to FSignature(true, arrayOf(BaseDataType.LONG), FParam("val1", BaseDataType.LONG), FParam("val2", BaseDataType.LONG)),
|
||||
"max" to FSignature(true, emptyArray(), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max" to FSignature(true, arrayOf(BaseDataType.UNDEFINED), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__byte" to FSignature(true, arrayOf(BaseDataType.BYTE), FParam("val1", BaseDataType.BYTE), FParam("val2", BaseDataType.BYTE)),
|
||||
"max__ubyte" to FSignature(true, arrayOf(BaseDataType.UBYTE), FParam("val1", BaseDataType.UBYTE), FParam("val2", BaseDataType.UBYTE)),
|
||||
"max__word" to FSignature(true, arrayOf(BaseDataType.WORD), FParam("val1", BaseDataType.WORD), FParam("val2", BaseDataType.WORD)),
|
||||
|
||||
@@ -25,7 +25,6 @@ class CompilationOptions(val output: OutputType,
|
||||
var includeSourcelines: Boolean = false,
|
||||
var dumpVariables: Boolean = false,
|
||||
var dumpSymbols: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var varsHighBank: Int? = null,
|
||||
var varsGolden: Boolean = false,
|
||||
var slabsHighBank: Int? = null,
|
||||
@@ -66,7 +65,6 @@ class CompilationOptions(val output: OutputType,
|
||||
private var includeSourcelines: Boolean = false
|
||||
private var dumpVariables: Boolean = false
|
||||
private var dumpSymbols: Boolean = false
|
||||
private var experimentalCodegen: Boolean = false
|
||||
private var varsHighBank: Int? = null
|
||||
private var varsGolden: Boolean = false
|
||||
private var slabsHighBank: Int? = null
|
||||
@@ -97,7 +95,6 @@ class CompilationOptions(val output: OutputType,
|
||||
fun includeSourcelines(includeSourcelines: Boolean) = apply { this.includeSourcelines = includeSourcelines }
|
||||
fun dumpVariables(dumpVariables: Boolean) = apply { this.dumpVariables = dumpVariables }
|
||||
fun dumpSymbols(dumpSymbols: Boolean) = apply { this.dumpSymbols = dumpSymbols }
|
||||
fun experimentalCodegen(experimentalCodegen: Boolean) = apply { this.experimentalCodegen = experimentalCodegen }
|
||||
fun varsHighBank(varsHighBank: Int?) = apply { this.varsHighBank = varsHighBank }
|
||||
fun varsGolden(varsGolden: Boolean) = apply { this.varsGolden = varsGolden }
|
||||
fun slabsHighBank(slabsHighBank: Int?) = apply { this.slabsHighBank = slabsHighBank }
|
||||
@@ -113,7 +110,7 @@ class CompilationOptions(val output: OutputType,
|
||||
return CompilationOptions(
|
||||
output, launcher, zeropage, zpReserved, zpAllowed, floats, noSysInit, romable, compTarget, compilerVersion,
|
||||
loadAddress, memtopAddress, warnSymbolShadowing, warnImplicitTypeCast, optimize, asmQuiet, asmListfile,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, experimentalCodegen, varsHighBank, varsGolden,
|
||||
includeSourcelines, dumpVariables, dumpSymbols, varsHighBank, varsGolden,
|
||||
slabsHighBank, slabsGolden, breakpointCpuInstruction, ignoreFootguns, outputDir, quiet,
|
||||
profilingInstrumentation, symbolDefs
|
||||
)
|
||||
|
||||
@@ -98,3 +98,24 @@ fun String.unescape(): String {
|
||||
}
|
||||
return result.joinToString("")
|
||||
}
|
||||
|
||||
// === Symbol name prefixing ===
|
||||
// These are used by code generators to add p8b_/p8s_/p8v_/etc. prefixes to symbol names.
|
||||
// The prefix character depends on the symbol type (block='b', sub='s', variable='v', etc.)
|
||||
// Scoped names (containing dots) have each part prefixed according to its role.
|
||||
|
||||
fun prefixScopedName(name: String, type: Char): String {
|
||||
val generatedPrefix = "p8_label_gen_"
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(generatedPrefix))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(generatedPrefix)) lastPart else "p8${type}_$lastPart"
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
@@ -32,8 +32,8 @@ enum class BaseDataType {
|
||||
this == LONG -> other.isByteOrBool || other.isWord
|
||||
this == STR && other == UWORD || this == UWORD && other == STR -> false
|
||||
this.isArray && other.isArray -> false
|
||||
this.isArray -> other != FLOAT
|
||||
this == STR -> other != FLOAT
|
||||
this.isArray -> other.isByteOrBool
|
||||
this == STR -> other.isByteOrBool
|
||||
this.isPointer -> other.isByteOrBool
|
||||
else -> true
|
||||
}
|
||||
|
||||
@@ -2,10 +2,48 @@ package prog8.code.core
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65C02,
|
||||
VIRTUAL
|
||||
/**
|
||||
* Identifies the target CPU type and its key characteristics that affect IR semantics.
|
||||
*
|
||||
* The most important property is [statusBitsOnMultiByteOps], which controls the IR
|
||||
* status-bit contract (see IRInstructions.kt for the full spec):
|
||||
*
|
||||
* - When `true`, the IR generator is allowed to skip emitting explicit CMP/CMPI before
|
||||
* branches that depend on the result of a previous "status-setting" instruction
|
||||
* (LOAD, INC, DEC, AND, OR, XOR, etc.). The contract is: "this instruction sets
|
||||
* Z and N based on the full multi-byte value." Backends for these targets must
|
||||
* honor this contract (e.g., M68000's `move.w #imm,d0` naturally sets Z based on
|
||||
* the 16-bit value, so the IR can rely on it).
|
||||
*
|
||||
* - When `false`, multi-byte operations on this target do NOT set status bits
|
||||
* correctly for the full value (e.g., 6502's `dec lo` only sets Z based on the
|
||||
* low byte of a 16-bit decrement). The IR generator must therefore always emit
|
||||
* an explicit CMP/CMPI before any branch that depends on the result. Backends
|
||||
* for these targets should NOT add `ora` postambles to set Z; instead they
|
||||
* rely on the explicit CMP/CMPI emitted by the IR generator.
|
||||
*
|
||||
* For 8-bit CPUs (6502, 65C02) the natural status flags from a multi-byte
|
||||
* cascade are misleading: Z is set based on the LAST byte operated on, not
|
||||
* the full value. So we treat the contract as "no instruction sets status
|
||||
* bits except CMP/CMPI/SEC/CLC/SGN/BITTST" and have the IR generator emit
|
||||
* explicit compares. This avoids the trap of "this works for BYTE but
|
||||
* silently breaks for WORD/LONG".
|
||||
*/
|
||||
enum class CpuType(val statusBitsOnMultiByteOps: Boolean) {
|
||||
/** 8-bit NMOS 6502. Multi-byte ops don't set Z based on full value. */
|
||||
CPU6502(false),
|
||||
|
||||
/** 8-bit CMOS 65C02. Same status-bit issue as plain 6502. */
|
||||
CPU65C02(false),
|
||||
|
||||
/**
|
||||
* Virtual machine target. We use `false` here so the IR generator always
|
||||
* emits explicit CMP/CMPI; the VM's setResultReg then does NOT set Z/N
|
||||
* flags (only CMP/CMPI/SEC/CLC/SGN/BITTST do). This matches the VM's
|
||||
* existing documentation (VirtualMachine.kt top comment) and keeps the
|
||||
* behavior consistent across all current targets.
|
||||
*/
|
||||
VIRTUAL(false)
|
||||
}
|
||||
|
||||
interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
@@ -14,7 +52,6 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: UInt
|
||||
val STARTUP_CODE_RESERVED_SIZE: UInt // this is here, so that certain compiler targets are able to tune this
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val PROGRAM_MEMTOP_ADDRESS: UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
@@ -28,6 +65,8 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val customLauncher: List<String>
|
||||
val additionalAssemblerOptions: List<String>
|
||||
val defaultOutputType: OutputType
|
||||
val defaultLauncherType: CbmPrgLauncherType
|
||||
val supportsBankedCalls: Boolean
|
||||
|
||||
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
|
||||
fun getFloatAsmBytes(num: Number): String
|
||||
|
||||
@@ -11,11 +11,13 @@ class C128Target: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c128"
|
||||
@@ -27,7 +29,6 @@ class C128Target: ICompilationTarget,
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xc000u
|
||||
|
||||
|
||||
@@ -12,11 +12,13 @@ class C64Target: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "c64"
|
||||
@@ -30,7 +32,6 @@ class C64Target: ICompilationTarget,
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xcfe0u // $a000 if floats are used
|
||||
// note that at $cfe0-$cfff are the 16 'virtual registers' R0-R15
|
||||
|
||||
@@ -15,16 +15,17 @@ import kotlin.io.path.nameWithoutExtension
|
||||
|
||||
class ConfigFileTarget(
|
||||
override val name: String,
|
||||
override val supportsBankedCalls: Boolean,
|
||||
override val defaultEncoding: Encoding,
|
||||
override val cpu: CpuType,
|
||||
override val PROGRAM_LOAD_ADDRESS: UInt,
|
||||
override val PROGRAM_MEMTOP_ADDRESS: UInt,
|
||||
override val STARTUP_CODE_RESERVED_SIZE: UInt,
|
||||
override val BSSHIGHRAM_START: UInt,
|
||||
override val BSSHIGHRAM_END: UInt,
|
||||
override val BSSGOLDENRAM_START: UInt,
|
||||
override val BSSGOLDENRAM_END: UInt,
|
||||
override val defaultOutputType: OutputType,
|
||||
override val defaultLauncherType: CbmPrgLauncherType,
|
||||
override val libraryPath: Path,
|
||||
override val customLauncher: List<String>,
|
||||
override val additionalAssemblerOptions: List<String>,
|
||||
@@ -112,19 +113,26 @@ class ConfigFileTarget(
|
||||
val assemblerOptionsStr = props.getProperty("assembler_options", "").trim()
|
||||
val outputTypeString = props.getProperty("output_type", "PRG")
|
||||
val defaultOutputType = OutputType.valueOf(outputTypeString.uppercase())
|
||||
val launcherString = props.getProperty("launcher", "BASIC")
|
||||
val defaultLauncherType = try {
|
||||
CbmPrgLauncherType.valueOf(launcherString.uppercase())
|
||||
} catch (_: IllegalArgumentException) {
|
||||
CbmPrgLauncherType.BASIC
|
||||
}
|
||||
|
||||
return ConfigFileTarget(
|
||||
configfile.nameWithoutExtension,
|
||||
props.getProperty("supports_banked_calls", "false").toBoolean(),
|
||||
Encoding.entries.first { it.prefix==props.getString("encoding") },
|
||||
cpuType,
|
||||
props.getInteger("load_address"),
|
||||
props.getInteger("memtop"),
|
||||
0u, // used only in a very specific error condition check in a certain scenario...
|
||||
props.getInteger("bss_highram_start"),
|
||||
props.getInteger("bss_highram_end"),
|
||||
props.getInteger("bss_goldenram_start"),
|
||||
props.getInteger("bss_goldenram_end"),
|
||||
defaultOutputType,
|
||||
defaultLauncherType,
|
||||
libraryPath,
|
||||
customLauncher,
|
||||
if(assemblerOptionsStr=="") emptyList() else assemblerOptionsStr.split(" "),
|
||||
|
||||
@@ -11,11 +11,13 @@ class Cx16Target: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = true
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "cx16"
|
||||
@@ -27,7 +29,6 @@ class Cx16Target: ICompilationTarget,
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x9f00u
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ internal class NormalMemSizer(val floatsize: Int): IMemSizer {
|
||||
dt.isFloat -> floatsize * (numElements ?: 1)
|
||||
dt.isLong -> 4 * (numElements ?: 1)
|
||||
dt.isPointer -> 2 // pointer is just a uword
|
||||
dt.isStructInstance -> dt.subType!!.memsize(this)
|
||||
dt.isStructInstance -> dt.subType?.memsize(this) ?: 0
|
||||
dt.isUndefined -> throw IllegalArgumentException("undefined has no memory size")
|
||||
else -> 2 * (numElements ?: 1)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,13 @@ class PETTarget: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(Mflpt5.FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val defaultEncoding = Encoding.PETSCII
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "pet32"
|
||||
@@ -26,7 +28,6 @@ class PETTarget: ICompilationTarget,
|
||||
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
|
||||
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 20u
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0401u
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0x8000u
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ class VMTarget: ICompilationTarget,
|
||||
IMemSizer by NormalMemSizer(FLOAT_MEM_SIZE) {
|
||||
|
||||
override val name = NAME
|
||||
override val supportsBankedCalls = false
|
||||
override val defaultEncoding = Encoding.ISO
|
||||
override val libraryPath = null
|
||||
override val customLauncher = emptyList<String>()
|
||||
override val additionalAssemblerOptions = emptyList<String>()
|
||||
override val defaultOutputType = OutputType.PRG
|
||||
override val defaultLauncherType = CbmPrgLauncherType.BASIC
|
||||
|
||||
companion object {
|
||||
const val NAME = "virtual"
|
||||
@@ -29,7 +31,6 @@ class VMTarget: ICompilationTarget,
|
||||
override val FLOAT_MAX_POSITIVE = Double.MAX_VALUE
|
||||
override val FLOAT_MAX_NEGATIVE = -Double.MAX_VALUE
|
||||
override val FLOAT_MEM_SIZE = VMTarget.FLOAT_MEM_SIZE.toUInt()
|
||||
override val STARTUP_CODE_RESERVED_SIZE = 0u // not actually used
|
||||
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
|
||||
override val PROGRAM_MEMTOP_ADDRESS = 0xffffu // not actually used
|
||||
|
||||
|
||||
@@ -32,7 +32,10 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
}
|
||||
ZeropageType.FLOATSAFE,
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0xb1u..0xbau) // TODO more?
|
||||
// Only scratch register area is safe for transient use.
|
||||
// $b5 and $ba are clobbered by PET BASIC/Kernal calls like CHROUT ($ffd2).
|
||||
// User variables requiring persistent storage must NOT be placed here.
|
||||
free.addAll(listOf(0xb1u, 0xb2u, 0xb3u, 0xb4u, 0xb6u, 0xb7u, 0xb8u, 0xb9u, 0xfbu, 0xfcu, 0xfdu, 0xfeu))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
@@ -83,4 +86,4 @@ class PETZeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
allocatedVariables["cx16.r12r13sl"] = VarAllocation((base+6*4).toUInt(), DataType.LONG, 4)
|
||||
allocatedVariables["cx16.r14r15sl"] = VarAllocation((base+7*4).toUInt(), DataType.LONG, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ class ErrorReporterForTests(
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
val infos = mutableListOf<String>()
|
||||
val printedErrors = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
val text = "${position.toClickableStr()} $msg"
|
||||
@@ -47,7 +48,7 @@ class ErrorReporterForTests(
|
||||
!errors.any { ":${position.line}:" in it }
|
||||
|
||||
override fun printSingleError(errormessage: String) {
|
||||
// prints nothing in tests
|
||||
printedErrors.add(errormessage)
|
||||
}
|
||||
|
||||
override fun report() {
|
||||
|
||||
@@ -229,6 +229,16 @@ class TestDataType: FunSpec({
|
||||
BaseDataType.WORD.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.LONG.largerSizeThan(BaseDataType.WORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.BOOL).shouldBeTrue()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.UWORD).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.POINTER).shouldBeFalse()
|
||||
BaseDataType.STR.largerSizeThan(BaseDataType.LONG).shouldBeFalse()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UBYTE).shouldBeTrue()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.BOOL).shouldBeTrue()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.UWORD).shouldBeFalse()
|
||||
BaseDataType.POINTER.largerSizeThan(BaseDataType.STR).shouldBeFalse()
|
||||
}
|
||||
|
||||
test("BaseDataType.equalsSize") {
|
||||
@@ -236,6 +246,11 @@ class TestDataType: FunSpec({
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.BYTE).shouldBeTrue()
|
||||
BaseDataType.WORD.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.UBYTE.equalsSize(BaseDataType.WORD).shouldBeFalse()
|
||||
BaseDataType.STR.equalsSize(BaseDataType.STR).shouldBeTrue()
|
||||
BaseDataType.STR.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.STR.equalsSize(BaseDataType.BYTE).shouldBeFalse()
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.UWORD).shouldBeTrue()
|
||||
BaseDataType.POINTER.equalsSize(BaseDataType.STR).shouldBeFalse()
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -256,16 +256,17 @@ class TestZeropageFreeList: FunSpec({
|
||||
zp.availableBytes() shouldBe 0
|
||||
}
|
||||
|
||||
test("BASICSAFE should have minimal locations") {
|
||||
test("BASICSAFE should have minimal free locations") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE has minimal locations (0xB1-0xBA per code, minus scratch locations)
|
||||
zp.free.isNotEmpty().shouldBeTrue()
|
||||
// After scratch removal, some locations in 0xb1-0xba range should still be free
|
||||
// Just verify the free list is small but non-empty
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After scratch removal, $fb-$fe remain usable.
|
||||
zp.free.size shouldBeLessThan 20
|
||||
zp.free.contains(0xfbu) shouldBe true
|
||||
zp.free.contains(0xfcu) shouldBe true
|
||||
zp.free.contains(0xfdu) shouldBe true
|
||||
zp.free.contains(0xfeu) shouldBe true
|
||||
|
||||
verifyScratchLocationsReserved(zp)
|
||||
}
|
||||
@@ -279,6 +280,9 @@ class TestZeropageFreeList: FunSpec({
|
||||
|
||||
// FLOATSAFE and BASICSAFE should have same free locations for PET
|
||||
floatZp.free.size shouldBe basicZp.free.size
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
floatZp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE should exclude IRQ locations") {
|
||||
@@ -628,13 +632,17 @@ class TestZeropageFreeList: FunSpec({
|
||||
}
|
||||
}
|
||||
|
||||
test("BASICSAFE - minimal locations around 0xB1-0xBA") {
|
||||
test("BASICSAFE - extra fb-fe free after scratch removal") {
|
||||
val options = createOptions(PETTarget(), ZeropageType.BASICSAFE)
|
||||
val zp = PETZeropage(options)
|
||||
|
||||
// BASICSAFE has minimal locations (0xB1-0xBA minus scratch)
|
||||
zp.free.size shouldBeGreaterThan 0
|
||||
// BASICSAFE includes scratch register addresses ($b1-$b4, $b6-$b9)
|
||||
// and $fb-$fe. After removing scratch registers from the pool,
|
||||
// only $fb-$fe remain free.
|
||||
zp.free.size shouldBeLessThan 15
|
||||
setOf(0xfbu, 0xfcu, 0xfdu, 0xfeu).forEach { addr ->
|
||||
zp.free.contains(addr) shouldBe true
|
||||
}
|
||||
}
|
||||
|
||||
test("KERNALSAFE - excludes IRQ locations") {
|
||||
|
||||
@@ -9,5 +9,4 @@ dependencies {
|
||||
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -15,7 +15,10 @@ import kotlin.io.path.writeLines
|
||||
internal const val subroutineFloatEvalResultVar1 = "prog8_float_eval_result1"
|
||||
internal const val subroutineFloatEvalResultVar2 = "prog8_float_eval_result2"
|
||||
|
||||
class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSequenceNr: Int): ICodeGeneratorBackend {
|
||||
class AsmGen6502(val prefixSymbols: Boolean,
|
||||
private val lastGeneratedLabelSequenceNr: Int,
|
||||
private val preassignedCallSiteIds: Map<PtAsmSub, UByte> = emptyMap()
|
||||
): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
@@ -23,319 +26,29 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
||||
errors: IErrorReporter,
|
||||
): IAssemblyProgram? {
|
||||
val st = if(prefixSymbols) prefixSymbols(program, options, symbolTable) else symbolTable
|
||||
val asmgen = AsmGen6502Internal(program, st, options, errors, lastGeneratedLabelSequenceNr)
|
||||
val asmgen = AsmGen6502Internal(program, st, options, errors, lastGeneratedLabelSequenceNr, preassignedCallSiteIds)
|
||||
return asmgen.compileToAssembly()
|
||||
}
|
||||
|
||||
private fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
|
||||
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val expressionsToFixSubtype = mutableListOf<PtExpression>()
|
||||
val variablesToFixSubtype = mutableListOf<IPtVariable>()
|
||||
|
||||
fun prefixNamedNode(node: PtNamedNode) {
|
||||
when(node) {
|
||||
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
||||
is PtBlock -> node.name = "p8b_${node.name}"
|
||||
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // only prefix user defined labels
|
||||
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
||||
is PtStructDecl -> node.name = "p8t_${node.name}"
|
||||
}
|
||||
}
|
||||
|
||||
fun prefixSymbols(node: PtNode) {
|
||||
when(node) {
|
||||
is PtAsmSub -> {
|
||||
prefixNamedNode(node)
|
||||
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
|
||||
if(node.address?.varbank!=null) {
|
||||
node.address!!.varbank = node.address!!.varbank!!.prefix(node, st)
|
||||
}
|
||||
}
|
||||
is PtSub -> prefixNamedNode(node)
|
||||
is PtFunctionCall -> {
|
||||
if(node.builtin) {
|
||||
// could be a struct instance creation
|
||||
if(node.name=="prog8_lib_structalloc") {
|
||||
val struct = node.type.subType!!
|
||||
if(struct is StStruct) {
|
||||
// update link to active symboltable node
|
||||
node.type.subType = st.lookup(struct.scopedNameString) as StStruct
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if (stNode.astNode?.definingBlock()?.options?.noSymbolPrefixing != true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
// check if the identifier is part of a pointer dereference (which means you cannot look it up in the symboltable because it's a struct field)
|
||||
val pexpr = node.parent as? PtBinaryExpression
|
||||
if(pexpr?.operator==".") {
|
||||
if(pexpr.left is PtArrayIndexer) {
|
||||
val arrayvar = (pexpr.left as PtArrayIndexer).variable!!
|
||||
if(arrayvar.type.subType!=null) {
|
||||
// don't prefix a struct field name here, we take care of that at asm generation time, which was a lot easier
|
||||
//nodesToPrefix += node.parent to node.parent.children.indexOf(node)
|
||||
return
|
||||
} else
|
||||
throw AssemblyError("can only use deref expression on struct type")
|
||||
} else {
|
||||
TODO("handle other left operand ${pexpr.left }in dereferencing of struct fields ${node.position}")
|
||||
}
|
||||
}
|
||||
// normal (non pointer deref) expression
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?:
|
||||
throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
is PtBlock -> prefixNamedNode(node)
|
||||
is PtConstant -> node.name = "p8c_${node.name}"
|
||||
is PtLabel -> prefixNamedNode(node)
|
||||
is PtMemMapped -> prefixNamedNode(node)
|
||||
is PtSubroutineParameter -> prefixNamedNode(node)
|
||||
is PtVariable -> {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
is PtStructDecl -> prefixNamedNode(node) // note: field names are not prefixed here, we take care of that at asm generation time, which was a lot easier
|
||||
else -> { }
|
||||
}
|
||||
|
||||
if(node is IPtVariable) {
|
||||
if(node.type.subType!=null)
|
||||
variablesToFixSubtype.add(node)
|
||||
} else if(node is PtExpression) {
|
||||
if(node.type.subType!=null)
|
||||
expressionsToFixSubtype.add(node)
|
||||
}
|
||||
|
||||
node.children.forEach { prefixSymbols(it) }
|
||||
}
|
||||
|
||||
fun maybePrefixFunctionCallsAndIdentifierReferences(node: PtNode) {
|
||||
if(node is PtFunctionCall && !node.builtin) {
|
||||
// function calls to subroutines defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the call site
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
else if (node is PtIdentifier) {
|
||||
// identifier references to things defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the referencing point
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
node.children.forEach { maybePrefixFunctionCallsAndIdentifierReferences(it) }
|
||||
}
|
||||
|
||||
program.allBlocks().forEach { block ->
|
||||
if (!block.options.noSymbolPrefixing) {
|
||||
prefixSymbols(block)
|
||||
} else {
|
||||
maybePrefixFunctionCallsAndIdentifierReferences(block)
|
||||
}
|
||||
}
|
||||
|
||||
nodesToPrefix.forEach { (parent, index) ->
|
||||
when(val node = parent.children[index]) {
|
||||
is PtIdentifier -> parent.setChild(index, node.prefix(parent, st))
|
||||
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
|
||||
is PtVariable -> parent.setChild(index, node.prefix(parent, st))
|
||||
else -> throw AssemblyError("weird node to prefix $node")
|
||||
}
|
||||
}
|
||||
|
||||
// reversed so inner calls (such as arguments to a function call) get processed before the actual function call itself
|
||||
functionCallsToPrefix.reversed().forEach { (parent, index) ->
|
||||
val node = parent.children[index]
|
||||
if(node is PtFunctionCall) {
|
||||
val prefixedName = PtIdentifier(node.name, DataType.UNDEFINED, node.position).prefix(parent, st)
|
||||
val prefixedNode = node.withNewName(prefixedName.name)
|
||||
parent.setChild(index, prefixedNode)
|
||||
} else {
|
||||
throw AssemblyError("expected PtFunctionCall")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val updatedSt = SymbolTableMaker(program, options).make()
|
||||
|
||||
fun findSubtypeReplacement(sub: ISubType): StStruct? {
|
||||
if(updatedSt.lookup(sub.scopedNameString)!=null)
|
||||
return null
|
||||
val old = st.lookup(sub.scopedNameString) ?: throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
|
||||
|
||||
val prefixed = ArrayDeque<String>()
|
||||
var node: StNode = old
|
||||
while(node.type!= StNodeType.GLOBAL) {
|
||||
val typeChar = typePrefixChar(node.type)
|
||||
prefixed.addFirst("p8${typeChar}_${node.name}")
|
||||
node=node.parent
|
||||
}
|
||||
return updatedSt.lookup(prefixed.joinToString(".")) as StStruct
|
||||
}
|
||||
|
||||
expressionsToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
variablesToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
|
||||
return updatedSt
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun prefixScopedName(name: String, type: Char): String {
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(GENERATED_LABEL_PREFIX))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
|
||||
// the parts in between are assumed to be subroutine scopes.
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
|
||||
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
name = prefixScopedName(name, 'v')
|
||||
if(value==null)
|
||||
return this
|
||||
|
||||
val arrayValue = value as? PtArray
|
||||
return if(arrayValue!=null && arrayValue.children.any { it !is PtNumber && it !is PtBool } ) {
|
||||
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
||||
arrayValue.children.forEach { elt ->
|
||||
when(elt) {
|
||||
is PtBool -> newValue.add(elt)
|
||||
is PtNumber -> newValue.add(elt)
|
||||
is PtConstant -> newValue.add(elt)
|
||||
is PtAddressOf -> {
|
||||
if(elt.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
newValue.add(elt)
|
||||
else {
|
||||
val newAddr = PtAddressOf(elt.type, false, elt.position)
|
||||
newAddr.add(elt.identifier!!.prefix(newAddr, st))
|
||||
if (elt.arrayIndexExpr != null)
|
||||
newAddr.add(elt.arrayIndexExpr!!)
|
||||
newAddr.parent = arrayValue
|
||||
newValue.add(newAddr)
|
||||
}
|
||||
}
|
||||
is PtFunctionCall if elt.builtin -> {
|
||||
if (elt.name != "prog8_lib_structalloc")
|
||||
throw AssemblyError("weird array value element $elt")
|
||||
else
|
||||
newValue.add(elt)
|
||||
}
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
val result = PtVariable(name, type, zeropage, align, dirty, newValue, arraySize, position)
|
||||
result.parent = parent
|
||||
result
|
||||
}
|
||||
else this
|
||||
}
|
||||
|
||||
//private fun PtFunctionCall.prefix(targetType: Char): PtFunctionCall {
|
||||
// val newName = prefixScopedName(name, targetType)
|
||||
// return this.withNewName(newName)
|
||||
//}
|
||||
|
||||
private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
|
||||
val call = PtFunctionCall(name, builtin, hasNoSideEffects, returntypes, position)
|
||||
call.addAll(children)
|
||||
call.children.forEach { it.parent = call }
|
||||
call.parent = parent
|
||||
return call
|
||||
}
|
||||
|
||||
private fun PtIdentifier.prefix(parent: PtNode, st: SymbolTable): PtIdentifier {
|
||||
val targetNt: StNodeType
|
||||
val target = st.lookup(name)
|
||||
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
|
||||
if(target==null) {
|
||||
if(name.endsWith("_lsb") || name.endsWith("_msb")) {
|
||||
val target2 = st.lookup(name.dropLast(4))
|
||||
if(target2?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
targetNt = target2!!.type
|
||||
} else {
|
||||
// if no target found, assume that the identifier is a struct field
|
||||
targetNt = StNodeType.STATICVAR
|
||||
}
|
||||
} else {
|
||||
targetNt = target.type
|
||||
}
|
||||
|
||||
val prefixType = typePrefixChar(targetNt)
|
||||
val newName = prefixScopedName(name, prefixType)
|
||||
val node = PtIdentifier(newName, type, position)
|
||||
node.parent = parent
|
||||
return node
|
||||
}
|
||||
|
||||
private fun typePrefixChar(targetNt: StNodeType): Char {
|
||||
return when(targetNt) {
|
||||
StNodeType.BLOCK -> 'b'
|
||||
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
|
||||
StNodeType.LABEL -> 'l'
|
||||
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
|
||||
StNodeType.CONSTANT -> 'c'
|
||||
StNodeType.BUILTINFUNC -> 's'
|
||||
StNodeType.MEMORYSLAB -> 'v'
|
||||
StNodeType.STRUCT -> 't'
|
||||
StNodeType.STRUCTINSTANCE -> 'i'
|
||||
else -> '?'
|
||||
return prog8.code.prefixSymbols(program, options, st)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AsmGen6502Internal (
|
||||
val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter,
|
||||
private var generatedLabelSequenceNumber: Int
|
||||
private var generatedLabelSequenceNumber: Int,
|
||||
private val preassignedCallSiteIds: Map<PtAsmSub, UByte> = emptyMap()
|
||||
) {
|
||||
|
||||
internal val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
internal val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320,640)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
private val zeropage = options.compTarget.zeropage
|
||||
private val allocator = VariableAllocator(symbolTable, options, errors)
|
||||
private val allocator = VariableAllocator(symbolTable, options, errors, program)
|
||||
private val assembly = mutableListOf<String>()
|
||||
private val breakpointLabels = mutableListOf<String>()
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(this, zeropage)
|
||||
@@ -348,6 +61,7 @@ class AsmGen6502Internal (
|
||||
private val ifElseAsmgen = IfElseAsmGen(program, symbolTable, this, pointerGen, assignmentAsmGen, errors)
|
||||
private val ifExpressionAsmgen = IfExpressionAsmGen(this, pointerGen, assignmentAsmGen, errors)
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, assignmentAsmGen, this, pointerGen, allocator)
|
||||
internal val extsubCallSiteIds: MutableMap<PtAsmSub, UByte> = preassignedCallSiteIds.toMutableMap()
|
||||
|
||||
init {
|
||||
assignmentAsmGen.augmentableAsmGen = augmentableAsmGen
|
||||
@@ -366,7 +80,7 @@ class AsmGen6502Internal (
|
||||
|
||||
if(errors.noErrors()) {
|
||||
val output = options.outputDir.resolve("${program.name}.asm")
|
||||
val asmLines = assembly.flatMapTo(mutableListOf()) { it.split('\n') }
|
||||
val asmLines = assembly.toMutableList()
|
||||
if(options.compTarget.name==Cx16Target.NAME) {
|
||||
scanInvalid65816instructions(asmLines)
|
||||
if(!errors.noErrors()) {
|
||||
@@ -375,9 +89,7 @@ class AsmGen6502Internal (
|
||||
}
|
||||
}
|
||||
if(options.optimize) {
|
||||
while(optimizeAssembly(asmLines, options.compTarget, symbolTable)>0) {
|
||||
// optimize the assembly source code
|
||||
}
|
||||
optimizeAssembly(asmLines, options.compTarget, symbolTable)
|
||||
output.writeLines(asmLines)
|
||||
} else {
|
||||
// write the unmodified code
|
||||
@@ -471,14 +183,21 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
internal fun out(str: String, splitlines: Boolean = true) {
|
||||
val fragment = (if(splitlines && " | " in str) str.replace("|", "\n") else str).trim('\r', '\n')
|
||||
if (splitlines) {
|
||||
for (line in fragment.splitToSequence('\n')) {
|
||||
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line
|
||||
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
|
||||
assembly.add(trimmed)
|
||||
if ('\n' in str || '|' in str) {
|
||||
val fragment = if (" | " in str) str.replace("|", "\n").trim('\r', '\n') else str.trim('\r', '\n')
|
||||
val lines = fragment.split('\n')
|
||||
for (i in lines.indices) {
|
||||
val line = lines[i]
|
||||
assembly.add(if (line.startsWith(' ')) "\t${line.trim()}" else line)
|
||||
}
|
||||
} else {
|
||||
val trimmed = str.trim()
|
||||
assembly.add(if (str.startsWith(' ')) "\t$trimmed" else trimmed)
|
||||
}
|
||||
} else assembly.add(fragment)
|
||||
} else {
|
||||
assembly.add(str.trim('\r', '\n'))
|
||||
}
|
||||
}
|
||||
|
||||
fun asmSymbolName(regs: RegisterOrPair): String =
|
||||
@@ -1120,7 +839,43 @@ class AsmGen6502Internal (
|
||||
TargetStorageKind.POINTER -> TODO("assign typecasted long into pointer ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else throw AssemblyError("weird casted type")
|
||||
} else if(value.value.type.isFloat) {
|
||||
// first evaluate the float expression into FAC1
|
||||
assignExpressionToRegister(value.value, RegisterOrPair.FAC1)
|
||||
// then convert float in FAC1 to long and store to target
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta ${target.asmVarname}
|
||||
lda floats.FAC_ADDR+3
|
||||
sta ${target.asmVarname}+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta ${target.asmVarname}+3""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
require(target.register in CombinedLongRegisters)
|
||||
val startreg = target.register!!.startregname()
|
||||
out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta cx16.$startreg
|
||||
lda floats.FAC_ADDR+3
|
||||
sta cx16.$startreg+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta cx16.$startreg+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta cx16.$startreg+3""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("assign typecasted float to long array ${target.position}")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.POINTER -> TODO("assign typecasted float to long pointer ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
} else throw AssemblyError("weird casted type ${value.value.type} as ${value.type} at $value")
|
||||
} else {
|
||||
TODO("assign typecasted expression $value to a long target ${target.kind} at ${target.position} - please report this issue. Use simple expressions and temporary variables for now")
|
||||
}
|
||||
|
||||
@@ -13,39 +13,42 @@ import prog8.code.core.ICompilationTarget
|
||||
// TrimmedLine has .value (original), .trimmed (trimStart), and .instruction (instructionPart).
|
||||
|
||||
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable): Int {
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: ICompilationTarget, symbolTable: SymbolTable) {
|
||||
val pretrimmed = lines.map { it.trimStart() }.toMutableList()
|
||||
|
||||
/** Runs an optimization pass, applies modifications if any, and recomputes line windows. */
|
||||
fun runPass(
|
||||
mods: List<Modification>,
|
||||
windowSize: Int,
|
||||
currentLines: Sequence<List<TrimmedLine>>
|
||||
): Sequence<List<TrimmedLine>> {
|
||||
if (mods.isNotEmpty()) {
|
||||
apply(mods, lines, pretrimmed)
|
||||
numberOfOptimizations++
|
||||
return getLinesBy(pretrimmed, lines, windowSize)
|
||||
while(true) {
|
||||
var modified = false
|
||||
|
||||
/** Runs a single optimization pass, applies modifications if any, and recomputes line windows. */
|
||||
fun runPass(
|
||||
mods: List<Modification>,
|
||||
windowSize: Int,
|
||||
currentLines: Sequence<List<TrimmedLine>>
|
||||
): Sequence<List<TrimmedLine>> {
|
||||
if (mods.isNotEmpty()) {
|
||||
apply(mods, lines, pretrimmed)
|
||||
modified = true
|
||||
return getLinesBy(pretrimmed, lines, windowSize)
|
||||
}
|
||||
return currentLines
|
||||
}
|
||||
return currentLines
|
||||
|
||||
var linesByFour = getLinesBy(pretrimmed, lines, 4)
|
||||
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
|
||||
|
||||
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
|
||||
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
|
||||
|
||||
if(!modified)
|
||||
break
|
||||
}
|
||||
|
||||
var linesByFour = getLinesBy(pretrimmed, lines, 4)
|
||||
linesByFour = runPass(optimizeIncDec(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeStoreLoadSame(linesByFour, machine, symbolTable), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeJsrRtsAndOtherCombinations(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUselessPushPopStack(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeUnneededTempvarInAdd(linesByFour), 4, linesByFour)
|
||||
linesByFour = runPass(optimizeTSBtoRegularOr(linesByFour), 4, linesByFour)
|
||||
|
||||
var linesByFourteen = getLinesBy(pretrimmed, lines, 14)
|
||||
linesByFourteen = runPass(optimizeSameAssignments(linesByFourteen, machine, symbolTable), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeSamePointerIndexingAndUselessBeq(linesByFourteen), 14, linesByFourteen)
|
||||
linesByFourteen = runPass(optimizeAddWordToSameVariableOrExtraRegisterLoadInWordStore(linesByFourteen), 14, linesByFourteen)
|
||||
|
||||
return numberOfOptimizations
|
||||
}
|
||||
|
||||
internal fun String.isBranch() = this.startsWith("b")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtSubroutineParameter
|
||||
import prog8.code.core.CombinedLongRegisters
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub, argComplexity: List<Boolean>): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) combined cx16 virtual registers (longs),
|
||||
@@ -15,6 +17,8 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
// 4) single CPU registers (order Y,X,A),
|
||||
// 5) floating point registers (FAC1, FAC2),
|
||||
// 6) CPU Carry status flag
|
||||
// Within each group, complex expressions are evaluated first (they may clobber registers,
|
||||
// so running them before simple expressions avoids needing to save/restore already-loaded values).
|
||||
val args = sub.parameters.withIndex()
|
||||
val (combinedLongRegs, args1) = args.partition { it.value.first.registerOrPair in CombinedLongRegisters }
|
||||
val (cx16regs, args2) = args1.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
@@ -23,12 +27,17 @@ fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
val (singleRegsMixed, rest) = args3.partition { it.value.first.registerOrPair != null }
|
||||
val (singleCpuRegs, floatRegs) = singleRegsMixed.partition {it.value.first.registerOrPair != RegisterOrPair.FAC1 && it.value.first.registerOrPair != RegisterOrPair.FAC2 }
|
||||
|
||||
combinedLongRegs.forEach { order += it.index }
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
singleCpuRegs.sortedBy { it.value.first.registerOrPair!!.asCpuRegister() }.asReversed().forEach { order += it.index }
|
||||
combinedLongRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
cx16regs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
pairedRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
singleCpuRegs
|
||||
.sortedWith(
|
||||
compareByDescending<IndexedValue<Pair<RegisterOrStatusflag, PtSubroutineParameter>>> { argComplexity[it.index] }
|
||||
.thenByDescending { it.value.first.registerOrPair!!.asCpuRegister() }
|
||||
)
|
||||
.forEach { order += it.index }
|
||||
require(rest.all { it.value.first.registerOrPair==null && it.value.first.statusflag!=null})
|
||||
floatRegs.forEach { order += it.index }
|
||||
floatRegs.sortedByDescending { argComplexity[it.index] }.forEach { order += it.index }
|
||||
rest.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
|
||||
|
||||
@@ -402,14 +402,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
|
||||
private fun funcCallFar(fcall: PtFunctionCall): Array<RegisterOrPair> {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
if(targetName !in arrayOf("cx16", "c64", "c128"))
|
||||
throw AssemblyError("callfar only works on cx16, c64 and c128 targets at this time")
|
||||
|
||||
val jsrfar = when(targetName) {
|
||||
"cx16" -> "cx16.JSRFAR"
|
||||
"c64" -> "c64.x16jsrfar"
|
||||
"c128" -> "c128.x16jsrfar"
|
||||
else -> TODO("jsrfar routine")
|
||||
else -> "$targetName.x16jsrfar"
|
||||
}
|
||||
val constBank = fcall.args[0].asConstInteger()
|
||||
val constAddress = fcall.args[1].asConstInteger()
|
||||
@@ -763,7 +760,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror2 byte ptr indexing ${what.position}")
|
||||
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
@@ -789,7 +786,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror2 word ptr indexing ${what.position}")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
@@ -841,7 +838,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror byte ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" ror ${varname},x")
|
||||
}
|
||||
@@ -883,7 +880,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("ror word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" ror ${varname}_msb,x | ror ${varname}_lsb,x")
|
||||
@@ -950,7 +947,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
when (what) {
|
||||
is PtArrayIndexer -> {
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol2 byte ptr indexing ${what.position}")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
|
||||
@@ -976,7 +973,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
is PtArrayIndexer -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol2 word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" asl ${varname}_lsb,x | rol ${varname}_msb,x | bcc + | inc ${varname}_lsb,x |+")
|
||||
@@ -1024,7 +1021,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol byte ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
asmgen.out(" rol ${varname},x")
|
||||
}
|
||||
@@ -1066,7 +1063,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
if(!what.index.isSimple()) asmgen.out(" plp")
|
||||
if(what.variable==null)
|
||||
TODO("ptr indexing ${what.position}")
|
||||
TODO("rol word ptr indexing ${what.position}")
|
||||
val varname = asmgen.asmVariableName(what.variable!!)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" rol ${varname}_lsb,x | rol ${varname}_msb,x")
|
||||
|
||||
@@ -9,6 +9,16 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
|
||||
|
||||
/*
|
||||
* CRITICAL: `inline asmsub` subroutines MUST be inlined at the call site.
|
||||
* They are NOT called with jsr - their assembly body is copied directly into the caller.
|
||||
* This means: no .proc/.pend, no rts, no jsr. The return value is left in the appropriate
|
||||
* CPU register (A/X/Y) and the caller picks it up.
|
||||
*
|
||||
* This is handled at line 68: `if (sub.inline) { ... }`.
|
||||
* Any new codegen backend MUST replicate this behavior.
|
||||
*/
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||
@@ -33,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)!!
|
||||
val symbol = asmgen.symbolTable.lookup(call.name) ?: asmgen.symbolTable.lookupUnscoped(call.name) ?: throw IllegalStateException("Symbol not found: ${call.name}")
|
||||
if(symbol.type == StNodeType.LABEL) {
|
||||
require(call.void)
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedNameString)}")
|
||||
@@ -44,6 +54,26 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||
|
||||
if(sub is PtAsmSub) {
|
||||
val varBank = sub.address?.varbank
|
||||
var varbank: String? = null
|
||||
var bankFixLabel: String? = null
|
||||
if (varBank != null) {
|
||||
// the bank variable can also be the name of a subroutine that should be called to return the bank byte dynamically
|
||||
val bankSymbol = asmgen.symbolTable.lookup(varBank.name) ?: asmgen.symbolTable.lookupUnscoped(varBank.name)
|
||||
if (bankSymbol?.type == StNodeType.SUBROUTINE || bankSymbol?.type == StNodeType.EXTSUB) {
|
||||
val extsubId = asmgen.extsubCallSiteIds[sub]
|
||||
if (extsubId != null) {
|
||||
asmgen.out(" lda #${extsubId.toInt()}")
|
||||
}
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(varBank.name)}")
|
||||
// store bank byte directly into the JSRFAR instruction data (forward label reference)
|
||||
bankFixLabel = asmgen.makeLabel("bank")
|
||||
asmgen.out(" sta $bankFixLabel")
|
||||
} else {
|
||||
varbank = asmgen.asmVariableName(varBank)
|
||||
}
|
||||
}
|
||||
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline) {
|
||||
// inline the subroutine. (regardless of optimization settings!)
|
||||
@@ -56,54 +86,57 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
} else {
|
||||
val bank = sub.address?.constbank?.toString()
|
||||
if(bank==null) {
|
||||
val varbank = if(sub.address?.varbank==null) null else asmgen.asmVariableName(sub.address!!.varbank!!)
|
||||
if(varbank!=null) {
|
||||
if(varbank!=null || bankFixLabel!=null) {
|
||||
if(asmgen.options.romable)
|
||||
TODO("no codegen yet for non-const bank in subroutine call that's usable in ROM ${call.position}")
|
||||
|
||||
// variable bank case: save/restore registers around the self-modifying sta
|
||||
if(varbank!=null) {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp""")
|
||||
}
|
||||
val label = bankFixLabel ?: "+"
|
||||
|
||||
// self-modifying code: set jsrfar bank argument
|
||||
when(asmgen.options.compTarget.name) {
|
||||
"cx16" -> {
|
||||
// JSRFAR can jump to a banked RAM address as well!
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr cx16.JSRFAR
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c64.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out("""
|
||||
php
|
||||
pha
|
||||
lda $varbank
|
||||
sta +
|
||||
pla
|
||||
plp
|
||||
jsr c128.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
$label .byte 0 ; modified"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
@@ -133,7 +166,17 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
.byte $bank"""
|
||||
)
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
else -> {
|
||||
if(asmgen.options.compTarget.supportsBankedCalls) {
|
||||
val targetName = asmgen.options.compTarget.name
|
||||
asmgen.out("""
|
||||
jsr $targetName.x16jsrfar
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
.byte $bank"""
|
||||
)
|
||||
} else
|
||||
throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,7 +277,8 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub)
|
||||
val argComplexity = call.args.map { usesOtherRegistersWhileEvaluating(it) }
|
||||
val optimalEvalOrder = asmsub6502ArgsEvalOrder(sub, argComplexity)
|
||||
optimalEvalOrder.forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
@@ -343,7 +387,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
AsmAssignTarget.fromRegisters(register, parameter.value.type.isSigned, value.position, scope, asmgen)
|
||||
}
|
||||
val src = if(value.type.isPassByRef) {
|
||||
if(value is PtIdentifier) {
|
||||
if(value is PtIdentifier || value is PtPointerDeref) {
|
||||
val addr = PtAddressOf(value.type.typeForAddressOf(false), false, value.position)
|
||||
addr.add(value)
|
||||
addr.parent = scope as PtNode
|
||||
@@ -382,6 +426,9 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
if(argType.isUnsignedWord && paramType.isString)
|
||||
return true
|
||||
|
||||
if (argType.isArray && (paramType.isString || paramType.isPointerToByte))
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,13 +372,14 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun asmTypeString(dt: DataType): String {
|
||||
val effectiveDt = if(dt.isArray) dt.elementType() else dt
|
||||
return when {
|
||||
dt.isBool || dt.isUnsignedByte -> ".byte"
|
||||
dt.isSignedByte -> ".char"
|
||||
dt.isUnsignedWord || dt.isPointer -> ".word"
|
||||
dt.isSignedWord -> ".sint"
|
||||
dt.isLong -> ".dint"
|
||||
dt.isFloat -> ".byte"
|
||||
effectiveDt.isBool || effectiveDt.isUnsignedByte -> ".byte"
|
||||
effectiveDt.isSignedByte -> ".char"
|
||||
effectiveDt.isUnsignedWord || effectiveDt.isPointer -> ".word"
|
||||
effectiveDt.isSignedWord -> ".sint"
|
||||
effectiveDt.isLong -> ".dint"
|
||||
effectiveDt.isFloat -> ".byte"
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
@@ -387,42 +388,56 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
private fun structInstances2asm() {
|
||||
|
||||
fun initValues(instance: StStructInstance): List<String> {
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
return structtype.fields.zip(instance.initialValues).map { (field, value) ->
|
||||
if(field.first.isFloat) {
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
"["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
} else {
|
||||
when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(field.first.isPointer)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(field.first.isInteger)
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.value.toString()
|
||||
}
|
||||
is StArrayElement.AddressOf -> value.symbol
|
||||
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
|
||||
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
|
||||
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
|
||||
}
|
||||
fun formatValue(value: StArrayElement, dt: DataType): String {
|
||||
if(dt.isFloat) {
|
||||
val numValue = (value as? StArrayElement.Number)?.value
|
||||
?: throw AssemblyError("expected number for float field")
|
||||
return "["+compTarget.getFloatAsmBytes(numValue)+"]"
|
||||
}
|
||||
return when (value) {
|
||||
is StArrayElement.Number -> {
|
||||
if(dt.isPointer)
|
||||
"$"+value.value.toInt().toString(16)
|
||||
else if(dt.isInteger)
|
||||
value.value.toInt().toString()
|
||||
else
|
||||
value.value.toString()
|
||||
}
|
||||
is StArrayElement.AddressOf -> value.symbol
|
||||
is StArrayElement.MemorySlab -> "$StMemorySlabBlockName.${value.name}"
|
||||
is StArrayElement.BoolValue -> if(value.value) "1" else "0"
|
||||
is StArrayElement.StructInstance -> throw AssemblyError("struct instance in struct initializer")
|
||||
}
|
||||
}
|
||||
|
||||
fun flatInitValues(instance: StStructInstance, structtype: StStruct): List<String> {
|
||||
var valueIndex = 0
|
||||
return structtype.fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
(0 until count).map {
|
||||
val value = instance.initialValues[valueIndex++]
|
||||
formatValue(value, elemDt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
asmgen.out("; struct types")
|
||||
symboltable.allStructTypes().distinctBy { it.name }.forEach { structtype ->
|
||||
val structargs = structtype.fields.withIndex().joinToString(",") { field -> "f${field.index}" }
|
||||
val paramFields = structtype.fields.filter { !it.isArray }
|
||||
val structargs = paramFields.indices.joinToString(",") { "f$it" }
|
||||
asmgen.out("${structtype.scopedNameString} .struct $structargs\n")
|
||||
structtype.fields.withIndex().forEach { (index, field) ->
|
||||
val dt = field.first
|
||||
val varname = "f${index}"
|
||||
val type = asmTypeString(dt)
|
||||
asmgen.out("p8v_${field.second} $type \\$varname") // note: struct field symbol prefixing done here because that is a lot simpler than fixing up all expressions in the AST
|
||||
var paramIdx = 0
|
||||
for(field in structtype.fields) {
|
||||
val type = asmTypeString(field.type)
|
||||
if(field.isArray) {
|
||||
val anonymous = List(field.arraySize!!) { "?" }.joinToString(",")
|
||||
asmgen.out("p8v_${field.name} $type $anonymous")
|
||||
} else {
|
||||
asmgen.out("p8v_${field.name} $type \\f$paramIdx")
|
||||
paramIdx++
|
||||
}
|
||||
}
|
||||
asmgen.out(" .endstruct\n")
|
||||
}
|
||||
@@ -433,14 +448,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("${StStructInstanceBlockName}_bss .block\n")
|
||||
instancesNoInit.forEach {
|
||||
val structtype: StStruct = symboltable.lookup(it.structName) as StStruct
|
||||
val zerovalues = structtype.fields.map { field ->
|
||||
if(field.first.isFloat) {
|
||||
val floatbytes = List(compTarget.memorySize(BaseDataType.FLOAT)) { "?" }
|
||||
"[${floatbytes.joinToString(",")}]"
|
||||
}
|
||||
else "?"
|
||||
}
|
||||
asmgen.out("${it.name} .dstruct ${it.structName}, ${zerovalues.joinToString(",")}\n")
|
||||
asmgen.out("${it.name} .fill ${structtype.size}\n")
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send BSS\n")
|
||||
@@ -448,9 +456,28 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; struct instances with initialization values\n")
|
||||
asmgen.out(" .section STRUCTINSTANCES\n")
|
||||
asmgen.out("$StStructInstanceBlockName .block\n")
|
||||
instances.forEach {
|
||||
val instancename = it.name.substringAfter('.')
|
||||
asmgen.out("$instancename .dstruct ${it.structName}, ${initValues(it).joinToString(",")}\n")
|
||||
instances.forEach { instance ->
|
||||
val instancename = instance.name.substringAfter('.')
|
||||
val structtype: StStruct = symboltable.lookup(instance.structName) as StStruct
|
||||
val values = flatInitValues(instance, structtype)
|
||||
val hasArrays = structtype.fields.any { fld -> fld.isArray }
|
||||
if(hasArrays) {
|
||||
asmgen.out(instancename)
|
||||
var valueIndex = 0
|
||||
for(field in structtype.fields) {
|
||||
val arraySz = field.arraySize
|
||||
val count = arraySz ?: 1
|
||||
val elemDt = if(arraySz!=null) field.type.elementType() else field.type
|
||||
val type = asmTypeString(elemDt)
|
||||
val fieldValues = (0 until count).joinToString(",") {
|
||||
formatValue(instance.initialValues[valueIndex++], elemDt)
|
||||
}
|
||||
asmgen.out(" $type $fieldValues")
|
||||
}
|
||||
asmgen.out("\n")
|
||||
} else {
|
||||
asmgen.out("$instancename .dstruct ${instance.structName}, ${values.joinToString(",")}\n")
|
||||
}
|
||||
}
|
||||
asmgen.out(" .endblock\n")
|
||||
asmgen.out(" .send STRUCTINSTANCES\n")
|
||||
@@ -617,18 +644,26 @@ internal class ProgramAndVarsGen(
|
||||
arrayVarsWithInitInZp.forEach {
|
||||
val size = it.alloc.size
|
||||
val name = asmgen.asmVariableName(it.name)
|
||||
asmgen.out("""
|
||||
lda #<${name}_init_value
|
||||
ldy #>${name}_init_value
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
lda #<${name}
|
||||
ldy #>${name}
|
||||
sta cx16.r1
|
||||
sty cx16.r1+1
|
||||
lda #<$size
|
||||
ldy #>$size
|
||||
jsr sys.memcopy""")
|
||||
if(it.alloc.dt.isSplitWordArray) {
|
||||
val halfSize = size / 2
|
||||
if(halfSize>0) {
|
||||
asmgen.out("""
|
||||
ldx #${halfSize-1}
|
||||
- lda ${name}_init_value_lsb,x
|
||||
sta ${name}_lsb,x
|
||||
lda ${name}_init_value_msb,x
|
||||
sta ${name}_msb,x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldx #${size-1}
|
||||
- lda ${name}_init_value,x
|
||||
sta ${name},x
|
||||
dex
|
||||
bpl -""")
|
||||
}
|
||||
}
|
||||
asmgen.out(" jmp +")
|
||||
}
|
||||
@@ -874,14 +909,14 @@ internal class ProgramAndVarsGen(
|
||||
dt.isSplitWordArray -> {
|
||||
if(dt.elementType().isUnsignedWord || dt.elementType().isPointer) {
|
||||
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
} else {
|
||||
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
|
||||
asmgen.out("_array_$varname := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <_array_$varname")
|
||||
asmgen.out("${varname}_msb\t.byte >_array_$varname")
|
||||
asmgen.out("${varname}_words := ${data.joinToString()}")
|
||||
asmgen.out("${varname}_lsb\t.byte <${varname}_words")
|
||||
asmgen.out("${varname}_msb\t.byte >${varname}_words")
|
||||
}
|
||||
}
|
||||
dt.isUnsignedWordArray -> {
|
||||
|
||||
@@ -3,17 +3,21 @@ package prog8.codegen.cpu6502
|
||||
import com.github.michaelbull.result.fold
|
||||
import com.github.michaelbull.result.onOk
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
private val errors: IErrorReporter,
|
||||
private val program: PtProgram
|
||||
) {
|
||||
|
||||
private val zeropage = options.compTarget.zeropage
|
||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
|
||||
private val allStaticVariables by lazy { collectAllVariables(symboltable) }
|
||||
private val usageScores by lazy { computeUsageScores(allStaticVariables) }
|
||||
|
||||
init {
|
||||
allocateZeropageVariables()
|
||||
@@ -46,7 +50,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
if(options.zeropage== ZeropageType.DONTUSE)
|
||||
return
|
||||
|
||||
val allVariables = collectAllVariables(symboltable)
|
||||
val allVariables = allStaticVariables
|
||||
|
||||
val numberOfAllocatableVariables = allVariables.size
|
||||
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
|
||||
@@ -90,9 +94,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
}
|
||||
|
||||
// try to allocate the "don't care" interger variables into the zeropage until it is full.
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...? Simply sorting by size (bytes prioritized) has BAD results
|
||||
// Prioritize by usage frequency: sort by computed hotness score (access frequency × type weight × loop depth)
|
||||
if(errors.noErrors()) {
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { it.scopedNameString }
|
||||
val sortedList = varsDontCareWithoutAlignment.sortedByDescending { usageScores[it.scopedNameString] ?: 0 }
|
||||
for (variable in sortedList) {
|
||||
if(variable.dt.isIntegerOrBool || variable.dt.isPointer) {
|
||||
if(zeropage.free.isEmpty()) {
|
||||
@@ -129,4 +133,33 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
collect(st)
|
||||
return vars.sortedBy { it.dt.base }
|
||||
}
|
||||
|
||||
private fun computeUsageScores(vars: Collection<StStaticVariable>): Map<String, Int> {
|
||||
val scores = mutableMapOf<String, Int>()
|
||||
val varInfo = vars.associateBy { it.scopedNameString }
|
||||
var loopDepth = 0
|
||||
|
||||
fun walk(node: PtNode) {
|
||||
val isLoopEnter = node is PtForLoop || node is PtRepeatLoop
|
||||
if(isLoopEnter) loopDepth++
|
||||
|
||||
if(node is PtIdentifier) {
|
||||
val v = varInfo[node.name]
|
||||
if(v != null) {
|
||||
val weight = when {
|
||||
v.dt.isPointer || v.dt.isWord -> 2
|
||||
else -> 1
|
||||
}
|
||||
scores[node.name] = (scores[node.name] ?: 0) + weight * (loopDepth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
node.children.forEach { walk(it) }
|
||||
|
||||
if(isLoopEnter) loopDepth--
|
||||
}
|
||||
|
||||
walk(program)
|
||||
return scores
|
||||
}
|
||||
}
|
||||
@@ -244,13 +244,217 @@ internal class AssignmentAsmGen(
|
||||
val elementDt = assign.source.datatype
|
||||
val valueVar = value.variable
|
||||
if(valueVar==null) {
|
||||
TODO("translate assignment of indexed ${elementDt} pointer ${value.position}")
|
||||
val pointerDeref = value.pointerderef!!
|
||||
val constIndex = value.index.asConstInteger()
|
||||
val eltSize = program.memsizer.memorySize(elementDt, null)
|
||||
|
||||
if(constIndex != null) {
|
||||
val (zpPtrVar, structOffset) = pointergen.deref(pointerDeref, false)
|
||||
val totalOffset = eltSize * constIndex + structOffset.toInt()
|
||||
|
||||
when {
|
||||
elementDt.isByteOrBool -> {
|
||||
if(totalOffset > 255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out(" lda $zpPtrVar+1 | clc | adc #>($totalOffset) | sta $zpPtrVar+1 | ldy #<($totalOffset) | lda ($zpPtrVar),y")
|
||||
} else {
|
||||
asmgen.out(" lda $zpPtrVar | sta P8ZP_SCRATCH_PTR | lda $zpPtrVar+1 | clc | adc #>($totalOffset) | sta P8ZP_SCRATCH_PTR+1 | ldy #<($totalOffset) | lda (P8ZP_SCRATCH_PTR),y")
|
||||
}
|
||||
} else {
|
||||
asmgen.loadIndirectByte(zpPtrVar, totalOffset.toUByte())
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt.isSigned, false)
|
||||
}
|
||||
elementDt.isWord -> {
|
||||
if(totalOffset > 255) {
|
||||
val yOffset = totalOffset and 0xff
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta $zpPtrVar+1
|
||||
ldy #$yOffset
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #$yOffset
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
tax
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
} else {
|
||||
asmgen.loadIndirectWordAY(zpPtrVar, totalOffset.toUByte())
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
elementDt.isLong -> {
|
||||
if(totalOffset > 255) {
|
||||
val yOffset = totalOffset and 0xff
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta $zpPtrVar+1
|
||||
ldy #$yOffset
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+3""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>($totalOffset)
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #$yOffset
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1+2
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
sta P8ZP_SCRATCH_W1+3""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #$totalOffset
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+2
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
sta P8ZP_SCRATCH_W1+3""")
|
||||
}
|
||||
assignVariableLong(assign.target, "P8ZP_SCRATCH_W1", DataType.LONG)
|
||||
}
|
||||
elementDt.isFloat -> {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<($totalOffset)
|
||||
pha
|
||||
lda $zpPtrVar+1
|
||||
adc #>($totalOffset)
|
||||
tay
|
||||
pla""")
|
||||
assignFloatFromAY(assign.target)
|
||||
}
|
||||
else ->
|
||||
throw AssemblyError("weird array element type for pointer indexed read $elementDt")
|
||||
}
|
||||
} else {
|
||||
val (zpPtrVar, _) = pointergen.deref(pointerDeref, addOffsetToPointer = true)
|
||||
|
||||
when {
|
||||
elementDt.isByteOrBool -> {
|
||||
val idx = value.index
|
||||
if(idx is PtIdentifier) {
|
||||
if(eltSize > 1) {
|
||||
TODO("non-byte element size $eltSize for pointer array index read with variable index ${value.position}")
|
||||
}
|
||||
val indexVarName = asmgen.asmVariableName(idx)
|
||||
asmgen.out(" ldy $indexVarName | lda ($zpPtrVar),y")
|
||||
} else if(idx.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, idx)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out(" lda ($zpPtrVar),y")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, idx)
|
||||
asmgen.out(" pla | clc | adc $zpPtrVar+1 | sta $zpPtrVar+1")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out(" lda ($zpPtrVar),y")
|
||||
}
|
||||
assignRegisterByte(assign.target, CpuRegister.A, elementDt.isSigned, true)
|
||||
}
|
||||
elementDt.isWord -> {
|
||||
val idx = value.index
|
||||
if(idx is PtIdentifier) {
|
||||
if(eltSize > 1) {
|
||||
TODO("non-byte element size $eltSize for pointer array index read with variable index ${value.position}")
|
||||
}
|
||||
val indexVarName = asmgen.asmVariableName(idx)
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
} else if(idx.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, idx)
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, idx)
|
||||
asmgen.out(" pla | clc | adc $zpPtrVar+1 | sta $zpPtrVar+1")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda ($zpPtrVar),y
|
||||
tax
|
||||
iny
|
||||
lda ($zpPtrVar),y
|
||||
tay
|
||||
txa""")
|
||||
}
|
||||
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
}
|
||||
elementDt.isLong -> {
|
||||
TODO("variable index pointer array read of long ${value.position}")
|
||||
}
|
||||
elementDt.isFloat -> {
|
||||
TODO("variable index pointer array read of float ${value.position}")
|
||||
}
|
||||
else ->
|
||||
throw AssemblyError("weird array element type for pointer indexed read $elementDt")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
val arrayVarName = asmgen.asmVariableName(valueVar)
|
||||
|
||||
if(valueVar.type.isPointer) {
|
||||
pointergen.assignIndexedPointer(assign.target, arrayVarName, value, valueVar.type)
|
||||
pointergen.assignIndexedPointer(assign.target, arrayVarName, value)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2794,6 +2998,21 @@ $endLabel""")
|
||||
asmgen.out(" jsr prog8_lib.containment_linearwordarray")
|
||||
}
|
||||
}
|
||||
dt.isLongArray -> {
|
||||
asmgen.assignExpressionToRegister(containment.needle, RegisterOrPair.R14R15, true)
|
||||
asmgen.out("""
|
||||
lda cx16.r14
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda cx16.r14+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r15
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda cx16.r15+1
|
||||
sta P8ZP_SCRATCH_W2+1""")
|
||||
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, DataType.UWORD, containment.definingISub(), containment.position, "P8ZP_SCRATCH_PTR"), symbolName, false, null, null)
|
||||
asmgen.out(" ldy #$numElements")
|
||||
asmgen.out(" jsr prog8_lib.containment_longarray")
|
||||
}
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
}
|
||||
@@ -3229,7 +3448,19 @@ $endLabel""")
|
||||
BaseDataType.BYTE -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName")
|
||||
BaseDataType.UWORD -> asmgen.out(" jsr floats.cast_FAC1_as_uw_into_ya | sty $targetAsmVarName | sta $targetAsmVarName+1")
|
||||
BaseDataType.WORD -> asmgen.out(" jsr floats.cast_FAC1_as_w_into_ay | sta $targetAsmVarName | sty $targetAsmVarName+1")
|
||||
BaseDataType.LONG -> TODO("cast float to long")
|
||||
BaseDataType.LONG -> {
|
||||
asmgen.out("""
|
||||
jsr floats.QINT
|
||||
lda floats.FAC_ADDR+4
|
||||
sta $targetAsmVarName
|
||||
lda floats.FAC_ADDR+3
|
||||
sta $targetAsmVarName+1
|
||||
lda floats.FAC_ADDR+2
|
||||
sta $targetAsmVarName+2
|
||||
lda floats.FAC_ADDR+1
|
||||
sta $targetAsmVarName+3
|
||||
""".trimIndent())
|
||||
}
|
||||
else -> throw AssemblyError("weird type $targetDt")
|
||||
}
|
||||
}
|
||||
@@ -3971,7 +4202,7 @@ $endLabel""")
|
||||
lda $arrayVarName+3,y
|
||||
sta ${target.asmVarname}+3,x""")
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignIndexedPointer(target, arrayVarName, index, DataType.arrayFor(BaseDataType.LONG))
|
||||
TargetStorageKind.POINTER -> pointergen.assignIndexedPointer(target, arrayVarName, index)
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is bytes not long ${target.position}")
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
|
||||
@@ -490,15 +490,275 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
|
||||
internal fun assignWord(target: IndexedPtrTarget, word: Int) {
|
||||
TODO("array ptr assign const word ${target.position}")
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda #<$word
|
||||
sta ($zpPtrVar),y
|
||||
lda #>$word
|
||||
iny
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignLong(target: IndexedPtrTarget, long: Int) {
|
||||
TODO("array ptr assign const long ${target.position}")
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
val hex = long.toLongHex()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
clc
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta (P8ZP_SCRATCH_PTR),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta (P8ZP_SCRATCH_PTR),y""")
|
||||
}
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy #<$offset
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
} else if(target.index is PtIdentifier) {
|
||||
val (zpPtrVar, _) = deref(target.pointer, addOffsetToPointer=true)
|
||||
val indexVarName = asmgen.asmVariableName(target.index)
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
if(target.index.type.isWord) {
|
||||
asmgen.out("""
|
||||
clc
|
||||
lda $zpPtrVar+1
|
||||
adc $indexVarName+1
|
||||
sta $zpPtrVar+1""")
|
||||
}
|
||||
val hex = long.toLongHex()
|
||||
asmgen.out("""
|
||||
ldy $indexVarName
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
if(eltSize!=1) {
|
||||
TODO("multiply index by element size $eltSize ${target.position}")
|
||||
}
|
||||
val hex = long.toLongHex()
|
||||
if(target.index.type.isByte) {
|
||||
asmgen.pushCpuStack(BaseDataType.UBYTE, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(BaseDataType.UWORD, target.index)
|
||||
val (zpPtrVar, offset) = deref(target.pointer, addOffsetToPointer = true)
|
||||
require(offset==0.toUByte())
|
||||
asmgen.out("""
|
||||
pla
|
||||
clc
|
||||
adc $zpPtrVar+1
|
||||
sta $zpPtrVar+1""")
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65C02)) asmgen.out(" ply") else asmgen.out(" pla | tay")
|
||||
asmgen.out("""
|
||||
lda #$${hex.substring(6,8)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(4,6)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.substring(2,4)}
|
||||
sta ($zpPtrVar),y
|
||||
iny
|
||||
lda #$${hex.take(2)}
|
||||
sta ($zpPtrVar),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFloat(target: IndexedPtrTarget, float: Double) {
|
||||
TODO("array ptr assign const float ${target.position}")
|
||||
// For now, only the constant-index case is supported
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val eltSize = asmgen.program.memsizer.memorySize(target.elementDt, null)
|
||||
val (zpPtrVar, offset2) = deref(target.pointer)
|
||||
val offset = eltSize*constIndex + offset2.toInt()
|
||||
if(offset>255) {
|
||||
if(zpPtrVar.startsWith("P8ZP_SCRATCH_")) {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<$offset
|
||||
sta $zpPtrVar
|
||||
lda $zpPtrVar+1
|
||||
adc #>$offset
|
||||
sta $zpPtrVar+1""")
|
||||
asmgen.storeIndirectFloat(float, zpPtrVar, 0.toUByte())
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $zpPtrVar
|
||||
clc
|
||||
adc #<$offset
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
lda $zpPtrVar+1
|
||||
adc #>$offset
|
||||
sta P8ZP_SCRATCH_PTR+1""")
|
||||
asmgen.storeIndirectFloat(float, "P8ZP_SCRATCH_PTR", 0.toUByte())
|
||||
}
|
||||
} else {
|
||||
asmgen.storeIndirectFloat(float, zpPtrVar, offset.toUByte())
|
||||
}
|
||||
} else {
|
||||
TODO("array ptr assign const float with variable index not yet supported ${target.position}")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFAC1(target: IndexedPtrTarget) {
|
||||
@@ -2560,7 +2820,7 @@ internal class PointerAssignmentsGen(private val asmgen: AsmGen6502Internal, pri
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer, arrayDt: DataType) {
|
||||
internal fun assignIndexedPointer(target: AsmAssignTarget, arrayVarName: String, arrayIndexer: PtArrayIndexer) {
|
||||
// Load value from array[index] and store to pointer target
|
||||
if(target.datatype.isLong) {
|
||||
// Get address of array[index] into AY, then 4-byte copy loop to target pointer
|
||||
|
||||
@@ -30,45 +30,45 @@ class TestAsmOptimizer: FunSpec({
|
||||
symbolTable.add(StMemVar("A1", DataType.UWORD, 0x0310u, null, null))
|
||||
symbolTable.add(StMemVar("A2", DataType.UWORD, 0x0312u, null, null))
|
||||
|
||||
fun optimize(lines: MutableList<String>): Int {
|
||||
return optimizeAssembly(lines, machine, symbolTable)
|
||||
fun optimize(lines: MutableList<String>) {
|
||||
optimizeAssembly(lines, machine, symbolTable)
|
||||
}
|
||||
|
||||
// --- optimizeIncDec ---
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence") {
|
||||
val lines = mutableListOf(" iny", " dey", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes iny+dey sequence with comments") {
|
||||
val lines = mutableListOf(" iny ; increment Y", " dey ; decrement Y", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes inx+dex sequence") {
|
||||
val lines = mutableListOf(" inx", " dex", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dey+iny sequence") {
|
||||
val lines = mutableListOf(" dey", " iny", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: removes dex+inx sequence") {
|
||||
val lines = mutableListOf(" dex", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop")
|
||||
}
|
||||
|
||||
test("optimizeIncDec: preserves non-canceling sequences") {
|
||||
val lines = mutableListOf(" iny", " inx", " rts", " nop")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" iny", " inx", " rts", " nop")
|
||||
}
|
||||
|
||||
@@ -76,42 +76,37 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X") {
|
||||
val lines = mutableListOf(" lda #42", " sta myvar", " lda myvar", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda #42", " sta myvar", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes sta X + lda X with comments") {
|
||||
// Note: comments don't affect the optimization, but the optimizer checks lines[1] and lines[2]
|
||||
// and the comment is part of the line, so "sta myvar ; store it" doesn't match "sta myvar"
|
||||
// The optimizer uses substring(4) which includes the comment
|
||||
val lines = mutableListOf(" nop", " sta myvar", " lda myvar", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " sta myvar", " rts ; return")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes pha + pla") {
|
||||
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
|
||||
val lines = mutableListOf(" nop", " pha", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phx + plx") {
|
||||
val lines = mutableListOf(" nop", " phx", " plx", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes phy + ply") {
|
||||
val lines = mutableListOf(" nop", " phy", " ply", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " rts")
|
||||
}
|
||||
|
||||
test("optimizeStoreLoadSame: removes lda X + sta X") {
|
||||
// Note: optimizer checks lines[1] and lines[2] of the 4-line window
|
||||
val lines = mutableListOf(" nop", " lda myvar", " sta myvar", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" nop", " lda myvar", " rts")
|
||||
}
|
||||
|
||||
@@ -119,50 +114,49 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp") {
|
||||
val lines = mutableListOf(" jsr mysub", " rts", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jmp mysub", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: converts jsr+rts to jmp with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" jsr mysub ; call subroutine", " rts ; return", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jmp mysub ; call subroutine", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+jmp") {
|
||||
val lines = mutableListOf(" rts", " jmp somewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes rts+bra") {
|
||||
val lines = mutableListOf(" rts", " bra somewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0") {
|
||||
val lines = mutableListOf(" lda myvar", " cmp #0", " beq skip", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda myvar", " beq skip", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes lda+cmp #0 with comments") {
|
||||
val lines = mutableListOf(" lda myvar ; load variable", " cmp #0 ; compare to zero", " beq skip ; branch if equal", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda myvar ; load variable", " beq skip ; branch if equal", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: removes bra+jmp") {
|
||||
val lines = mutableListOf(" bra somewhere", " jmp elsewhere", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" bra somewhere", " nop", " nop")
|
||||
}
|
||||
|
||||
test("optimizeJsrRts: preserves jsr+rts for floats.pushFAC") {
|
||||
val lines = mutableListOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" jsr floats.pushFAC", " rts", " nop", " nop")
|
||||
}
|
||||
|
||||
@@ -170,26 +164,26 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeUselessPushPop: removes phy+ldy+pla") {
|
||||
val lines = mutableListOf(" phy", " ldy #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" tya", " ldy #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes phx+ldx+pla") {
|
||||
val lines = mutableListOf(" phx", " ldx #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" txa", " ldx #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+lda+pla") {
|
||||
// Note: optimizer removes all three: pha, lda, and pla
|
||||
val lines = mutableListOf(" pha", " lda #5", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("optimizeUselessPushPop: removes pha+tya+tay+pla") {
|
||||
val lines = mutableListOf(" pha", " tya", " tay", " pla", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
@@ -197,14 +191,14 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add") {
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts")
|
||||
}
|
||||
|
||||
test("optimizeUnneededTempvarInAdd: removes scratch variable in add with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" sta P8ZP_SCRATCH_W1", " lda #5", " clc", " adc P8ZP_SCRATCH_W1", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" clc", " adc #5", " rts ; return")
|
||||
}
|
||||
|
||||
@@ -212,14 +206,14 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta") {
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts")
|
||||
}
|
||||
|
||||
test("optimizeTSBtoRegularOr: converts lda/tsb/lda to lda/ora/sta with comments") {
|
||||
// Note: comments don't affect the optimization
|
||||
val lines = mutableListOf(" lda var2", " tsb var1", " lda var1", " rts ; return")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda var1", " ora var2", " sta var1", " rts ; return")
|
||||
}
|
||||
|
||||
@@ -231,7 +225,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" lda #1", " ldy #0", " sta var2", " sty var2+1",
|
||||
" rts", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" lda #1", " ldy #0", " sta var1", " sty var1+1",
|
||||
" sta var2", " sty var2+1",
|
||||
@@ -246,7 +240,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" ldy #0", " lda (ptr),y", " ora #3", " ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora #3", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
@@ -260,7 +254,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
" sta P8ZP_SCRATCH_PTR", " sty P8ZP_SCRATCH_PTR+1", " rts",
|
||||
" nop", " nop", " nop", " nop"
|
||||
)
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" clc", " adc P8ZP_SCRATCH_PTR", " sta P8ZP_SCRATCH_PTR",
|
||||
" tya", " adc P8ZP_SCRATCH_PTR+1", " sta P8ZP_SCRATCH_PTR+1", " rts",
|
||||
@@ -271,7 +265,7 @@ class TestAsmOptimizer: FunSpec({
|
||||
test("optimizeExtraRegisterLoadInWordStore: removes ldx+sta+txa+ldy+sta") {
|
||||
val lines = mutableListOf(" ldx #42", " sta myvar", " txa", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" sta myvar", " lda #42", " ldy #1", " sta myvar+1", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
@@ -280,19 +274,19 @@ class TestAsmOptimizer: FunSpec({
|
||||
|
||||
test("handles empty input") {
|
||||
val lines = mutableListOf<String>()
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe emptyList()
|
||||
}
|
||||
|
||||
test("handles single line input") {
|
||||
val lines = mutableListOf(" rts")
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" rts")
|
||||
}
|
||||
|
||||
test("multiple optimizations in one pass") {
|
||||
val lines = mutableListOf(" iny", " dey", " lda #1", " sta var", " lda var", " rts")
|
||||
optimize(lines) shouldBe 2
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" lda #1", " sta var", " rts")
|
||||
}
|
||||
|
||||
@@ -547,7 +541,9 @@ class TestAsmOptimizer: FunSpec({
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should be removed since "ora" doesn't modify Y
|
||||
optimize(lines) shouldBe 1
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(" ldy #0", " lda (ptr),y", " ora myvar", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("actual Y-modifying instructions should still be detected") {
|
||||
@@ -557,7 +553,11 @@ class TestAsmOptimizer: FunSpec({
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop"
|
||||
)
|
||||
// The second ldy should NOT be removed since "tay" modifies Y
|
||||
optimize(lines) shouldBe 0
|
||||
optimize(lines)
|
||||
lines shouldBe listOf(
|
||||
" ldy #0", " lda (ptr),y", " tay",
|
||||
" ldy #0", " sta (ptr),y", " rts",
|
||||
" nop", " nop", " nop", " nop", " nop", " nop", " nop", " nop")
|
||||
}
|
||||
|
||||
test("modifiesYRegister: correctly identifies Y-modifying instructions") {
|
||||
@@ -584,8 +584,13 @@ class TestAsmOptimizer: FunSpec({
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
val count = optimize(lines)
|
||||
count shouldBe 0
|
||||
optimize(lines)
|
||||
lines.size shouldBe 8 // all instructions preserved
|
||||
lines shouldBe listOf(
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" lda p8b_main.p8v_io_data", " sta p8b_main.p8v_result",
|
||||
" rts", " nop"
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -170,7 +170,7 @@ class TestCodegen: FunSpec({
|
||||
val st = SymbolTable(program)
|
||||
st.add(StMemVar("zpmemvar", DataType.WORD, 0x20u, null, null))
|
||||
st.add(StMemVar("normalmemvar", DataType.WORD, 0x9000u, null, null))
|
||||
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests())
|
||||
val allocator = VariableAllocator(st, getTestOptions(), ErrorReporterForTests(), program)
|
||||
allocator.isZpVar("zpmemvar") shouldBe true
|
||||
allocator.isZpVar("normalmemvar") shouldBe false
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":intermediate"))
|
||||
implementation(project(":codeGenIntermediate"))
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.3.1")
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,46 +0,0 @@
|
||||
package prog8.codegen.experimental
|
||||
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
|
||||
class ExperiCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram {
|
||||
|
||||
// you could write a code generator directly on the PtProgram AST,
|
||||
// but you can also use the Intermediate Representation to build a codegen on:
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
|
||||
val irProgram = irCodeGen.generate()
|
||||
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
|
||||
|
||||
// this stub only writes the IR program to disk but doesn't generate anything else.
|
||||
IRFileWriter(irProgram, null).write()
|
||||
|
||||
if(!options.quiet)
|
||||
println("** experimental codegen stub: no assembly generated **")
|
||||
return EmptyProgram
|
||||
}
|
||||
}
|
||||
|
||||
private object EmptyProgram : IAssemblyProgram {
|
||||
override val name = "<Empty Program>"
|
||||
override val irInstructionCount: Int = 0
|
||||
override val irChunkCount: Int = 0
|
||||
override val irRegisterCount: Int = 0
|
||||
override fun assemble(options: CompilationOptions, errors: IErrorReporter): Boolean {
|
||||
if(!options.quiet)
|
||||
println("** nothing assembled **")
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,5 +11,4 @@ dependencies {
|
||||
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -55,7 +55,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(!target.void) {
|
||||
val reg = it.second.first
|
||||
val regnum = codeGen.registers.next(irType(it.second.second))
|
||||
val p = StExtSubParameter(reg, it.second.second)
|
||||
val p = StExtSubParameter("", it.second.second, reg)
|
||||
result += assignCpuRegister(p, regnum, target)
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(!target.void) {
|
||||
val reg = it.second.first
|
||||
val regnum = codeGen.registers.next(irType(it.second.second))
|
||||
val p = StExtSubParameter(reg, it.second.second)
|
||||
val p = StExtSubParameter("", it.second.second, reg)
|
||||
result += assignCpuRegister(p, regnum, target)
|
||||
}
|
||||
}
|
||||
@@ -91,12 +91,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
private fun assignCpuRegister(returns: StExtSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
when(returns.register.registerOrPair) {
|
||||
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum), null)
|
||||
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum), null)
|
||||
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum), null)
|
||||
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum), null)
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum), null)
|
||||
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=0), null)
|
||||
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=1), null)
|
||||
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=regNum, immediate=2), null)
|
||||
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=3), null)
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=4), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=regNum, immediate=5), null)
|
||||
in Cx16VirtualRegisters -> addInstr(result, IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}"), null)
|
||||
in CombinedLongRegisters -> {
|
||||
require(returns.type.isLong)
|
||||
@@ -224,6 +224,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
}
|
||||
"or=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
@@ -232,6 +234,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
}
|
||||
"and=" -> {
|
||||
val shortcutLabel = codeGen.createLabelName()
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.CMPI, targetDt, reg1 = oldvalueReg, immediate = 0), null)
|
||||
addInstr(inplaceInstrs, IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel), null)
|
||||
val valueTr = exprGen.translateExpression(value)
|
||||
inplaceInstrs += valueTr.chunks
|
||||
@@ -385,7 +389,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
val arrayVariableName = array.variable!!.name
|
||||
val skipCarryLabel = codeGen.createLabelName()
|
||||
if(constIndex!=null) {
|
||||
val negLsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = negLsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex), null)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = negLsbReg, immediate = 0), null)
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipCarryLabel), null)
|
||||
addInstr(result, IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), null)
|
||||
addInstr(result, IRInstruction(Opcode.NEGM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex), skipCarryLabel)
|
||||
@@ -1029,6 +1037,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = shortcutLabel)
|
||||
}
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -1289,6 +1299,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1=inplaceReg, labelSymbol = symbol)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, vmDt, reg1=inplaceReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = shortcutLabel)
|
||||
}
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
@@ -1477,11 +1489,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(constValue==1) {
|
||||
addInstr(result, IRInstruction(Opcode.DECM, eltDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
} else {
|
||||
val valueReg=codeGen.registers.next(eltDt)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, eltDt, reg1=valueReg, immediate = constValue)
|
||||
it += IRInstruction(Opcode.SUBM, eltDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.SUBIM, eltDt, immediate = constValue, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1604,8 +1612,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.DECM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediateFp = constValue, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediateFp = constValue, labelSymbol = symbol), null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, if(constAddress!=null)
|
||||
@@ -1619,8 +1631,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.DECM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.DECM, vmDt, labelSymbol = symbol), null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if(constAddress!=null)
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediate = constValue.toInt(), address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBIM, vmDt, immediate = constValue.toInt(), labelSymbol = symbol), null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, if(constAddress!=null)
|
||||
@@ -1712,6 +1728,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
|
||||
it += IRInstruction(Opcode.DECM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
|
||||
}
|
||||
@@ -1793,11 +1811,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
if(constValue==1) {
|
||||
addInstr(result, IRInstruction(Opcode.INCM, elementDt, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
} else {
|
||||
val valueReg=codeGen.registers.next(elementDt)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOAD, elementDt, reg1=valueReg, immediate = constValue)
|
||||
it += IRInstruction(Opcode.ADDM, elementDt, reg1=valueReg, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.ADDIM, elementDt, immediate = constValue, labelSymbol = arrayVariableName, symbolOffset = constIndex*eltSize), null)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1923,8 +1937,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) , null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if (constAddress != null)
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediateFp = constValue, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediateFp = constValue, labelSymbol = symbol) , null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, -1, tr.resultFpReg)
|
||||
addInstr(result, if (constAddress != null)
|
||||
@@ -1938,8 +1956,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
IRInstruction(Opcode.INCM, vmDt, address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.INCM, vmDt, labelSymbol = symbol) , null)
|
||||
}
|
||||
else {
|
||||
} else if(constValue!=null) {
|
||||
addInstr(result, if (constAddress != null)
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediate = constValue.toInt(), address = constAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDIM, vmDt, immediate = constValue.toInt(), labelSymbol = symbol) , null)
|
||||
} else {
|
||||
val tr = exprGen.translateExpression(operand)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
if (constAddress != null)
|
||||
@@ -1960,8 +1982,12 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
|
||||
if(constValue==1) {
|
||||
val skip = codeGen.createLabelName()
|
||||
val lsbReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
it += IRInstruction(Opcode.LOADM, IRDataType.BYTE, reg1 = lsbReg, labelSymbol = arrayVariableName+"_lsb", symbolOffset = constIndex)
|
||||
if(!codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1 = lsbReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = skip)
|
||||
it += IRInstruction(Opcode.INCM, IRDataType.BYTE, labelSymbol = arrayVariableName+"_msb", symbolOffset = constIndex)
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
else {
|
||||
val resultvalueReg = codeGen.registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=resultvalueReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=resultvalueReg, immediate=4), null)
|
||||
ExpressionCodeResult(result, IRDataType.WORD, resultvalueReg, -1)
|
||||
}
|
||||
}
|
||||
@@ -256,12 +256,12 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
// return the 2 result values: division and remainder division in AY, remainder in R15
|
||||
if(type==IRDataType.BYTE) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=divisionReg)
|
||||
it += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=divisionReg, immediate=0)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=remainderReg, labelSymbol = "cx16.r15")
|
||||
}
|
||||
} else if(type==IRDataType.WORD) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=divisionReg)
|
||||
it += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=divisionReg, immediate=4)
|
||||
it += IRInstruction(Opcode.STOREM, IRDataType.WORD, reg1=remainderReg, labelSymbol = "cx16.r15")
|
||||
}
|
||||
} else throw AssemblyError("invalid type for DIVMOD")
|
||||
@@ -300,12 +300,15 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
val tr = exprGen.translateExpression(call.args[0])
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
val needsExplicitCmpi = !codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
when (sourceDt.base) {
|
||||
BaseDataType.BYTE -> {
|
||||
val notNegativeLabel = codeGen.createLabelName()
|
||||
val compareReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.BYTE, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -317,6 +320,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val compareReg = codeGen.registers.next(IRDataType.WORD)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.WORD, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.WORD, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -328,6 +333,8 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
val compareReg = codeGen.registers.next(IRDataType.LONG)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADR, IRDataType.LONG, reg1=compareReg, reg2=tr.resultReg)
|
||||
if(needsExplicitCmpi)
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.LONG, reg1=compareReg, immediate = 0)
|
||||
it += IRInstruction(Opcode.BSTPOS, labelSymbol = notNegativeLabel)
|
||||
it += IRInstruction(Opcode.NEG, IRDataType.LONG, reg1=tr.resultReg)
|
||||
}
|
||||
@@ -468,40 +475,30 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
|
||||
private fun funcClamp(call: PtFunctionCall): ExpressionCodeResult {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val type = irType(call.type)
|
||||
require(type != IRDataType.FLOAT)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val valueTr = exprGen.translateExpression(call.args[0])
|
||||
val minimumTr = exprGen.translateExpression(call.args[1])
|
||||
val maximumTr = exprGen.translateExpression(call.args[2])
|
||||
result += valueTr.chunks
|
||||
result += minimumTr.chunks
|
||||
result += maximumTr.chunks
|
||||
if(type==IRDataType.FLOAT) {
|
||||
result += codeGen.makeSyscall(
|
||||
IMSyscall.CLAMP_FLOAT, listOf(
|
||||
valueTr.dt to valueTr.resultFpReg,
|
||||
minimumTr.dt to minimumTr.resultFpReg,
|
||||
maximumTr.dt to maximumTr.resultFpReg,
|
||||
), type to valueTr.resultFpReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, -1, valueTr.resultFpReg)
|
||||
} else {
|
||||
val syscall = when(call.type.base) {
|
||||
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
|
||||
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
|
||||
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
|
||||
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
|
||||
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
result += codeGen.makeSyscall(syscall, listOf(
|
||||
valueTr.dt to valueTr.resultReg,
|
||||
minimumTr.dt to minimumTr.resultReg,
|
||||
maximumTr.dt to maximumTr.resultReg,
|
||||
), type to valueTr.resultReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
|
||||
val syscall = when(call.type.base) {
|
||||
BaseDataType.UBYTE -> IMSyscall.CLAMP_UBYTE
|
||||
BaseDataType.BYTE -> IMSyscall.CLAMP_BYTE
|
||||
BaseDataType.UWORD -> IMSyscall.CLAMP_UWORD
|
||||
BaseDataType.WORD -> IMSyscall.CLAMP_WORD
|
||||
BaseDataType.LONG -> IMSyscall.CLAMP_LONG
|
||||
else -> throw AssemblyError("invalid dt")
|
||||
}
|
||||
result += codeGen.makeSyscall(syscall, listOf(
|
||||
valueTr.dt to valueTr.resultReg,
|
||||
minimumTr.dt to minimumTr.resultReg,
|
||||
maximumTr.dt to maximumTr.resultReg,
|
||||
), type to valueTr.resultReg
|
||||
)
|
||||
return ExpressionCodeResult(result, type, valueTr.resultReg, -1)
|
||||
}
|
||||
|
||||
private fun funcMin(call: PtFunctionCall): ExpressionCodeResult {
|
||||
@@ -701,7 +698,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
addInstr(result, IRInstruction(Opcode.MIDB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1 = byteReg, labelSymbol = "cx16.r15"), null)
|
||||
addInstr(result, IRInstruction(Opcode.LSIGB, IRDataType.LONG, reg1 = byteReg, reg2 = tr.resultReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1 = byteReg), null)
|
||||
addInstr(result, IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1 = byteReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
|
||||
@@ -762,9 +759,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
val arr = (arg as? PtArrayIndexer)
|
||||
val index = arr?.index?.asConstInteger()
|
||||
if(arr!=null && index!=null) {
|
||||
if(arr.variable==null)
|
||||
TODO("support for ptr indexing ${arr.position}")
|
||||
if(arr!=null && index!=null && arr.variable!=null) {
|
||||
val variable = arr.variable!!.name
|
||||
if(arr.splitWords) {
|
||||
result += IRCodeChunk(null, null).also {
|
||||
@@ -828,11 +823,34 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
}
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
if(target.splitWords) {
|
||||
if(target.variable==null) {
|
||||
// pointer-based array indexing: compute memory address and store byte
|
||||
val eltSize = codeGen.program.memsizer.memorySize(target.type, null)
|
||||
val pointerTr = exprGen.translateExpression(target.pointerderef!!)
|
||||
addToResult(result, pointerTr, pointerTr.resultReg, -1)
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(constIndex != null) {
|
||||
val offset = eltSize * constIndex + if(msb) eltSize - 1 else 0
|
||||
if(offset > 0)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = offset), null)
|
||||
} else {
|
||||
val (code, indexWordReg) = codeGen.loadIndexReg(target.index, eltSize, true, false)
|
||||
result += code
|
||||
addInstr(result, IRInstruction(Opcode.ADDR, IRDataType.WORD, reg1 = pointerTr.resultReg, reg2 = indexWordReg), null)
|
||||
if(msb)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerTr.resultReg, immediate = eltSize - 1), null)
|
||||
}
|
||||
if(isConstZeroValue) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREZI, IRDataType.BYTE, reg1 = pointerTr.resultReg, immediate = 0), null)
|
||||
} else {
|
||||
val valueTr = exprGen.translateExpression(call.args[1])
|
||||
addToResult(result, valueTr, valueTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREI, IRDataType.BYTE, reg1 = valueTr.resultReg, reg2 = pointerTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
}
|
||||
else if(target.splitWords) {
|
||||
// lsb/msb in split arrays, element index 'size' is always 1
|
||||
val constIndex = target.index.asConstInteger()
|
||||
if(target.variable==null)
|
||||
TODO("support for ptr indexing ${target.position}")
|
||||
val varName = target.variable!!.name + if(msb) "_msb" else "_lsb"
|
||||
if(isConstZeroValue) {
|
||||
if(constIndex!=null) {
|
||||
|
||||
@@ -96,7 +96,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
is PtBinaryExpression -> translate(expr)
|
||||
is PtIfExpression -> translate(expr)
|
||||
is PtBranchCondExpression -> translate(expr)
|
||||
is PtFunctionCall -> translate(expr)
|
||||
is PtFunctionCall -> translate(expr, true)
|
||||
is PtContainmentCheck -> translate(expr)
|
||||
is PtPointerDeref -> translate(expr)
|
||||
is PtConstant -> {
|
||||
@@ -211,6 +211,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val (instructions, offset) = traverseRestOfDerefChainToCalculateFinalAddress(deref, pointerReg)
|
||||
result += instructions
|
||||
|
||||
// For inline array fields (not pointer fields), return the address (ptr+offset) instead of loading from it
|
||||
if(deref.type.isArray && !deref.derefLast) {
|
||||
if(offset > 0u)
|
||||
addInstr(result, IRInstruction(Opcode.ADD, IRDataType.WORD, reg1 = pointerReg, immediate = offset.toInt()), null)
|
||||
return ExpressionCodeResult(result, IRDataType.WORD, pointerReg, -1)
|
||||
}
|
||||
|
||||
return if(deref.type.isFloat) {
|
||||
val resultReg = codeGen.registers.next(IRDataType.FLOAT)
|
||||
addInstr(result, IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1 = RegisterNum(resultReg), reg1 = pointerReg, immediate = offset.toInt()), null)
|
||||
@@ -238,24 +245,16 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(ifExpr.condition is PtBinaryExpression) {
|
||||
val useBIT = checkIfConditionCanUseBIT(ifExpr.condition as PtBinaryExpression)
|
||||
if(useBIT!=null) {
|
||||
// use a BIT instruction to test for bit 7 or 6 set/clear
|
||||
val (testBitSet, variable, bitmask) = useBIT
|
||||
val bitBranchOpcode = when(testBitSet) {
|
||||
true -> when(bitmask) {
|
||||
64 -> Opcode.BSTVC
|
||||
128 -> Opcode.BSTPOS
|
||||
else -> throw AssemblyError("need bit 6 or 7")
|
||||
}
|
||||
false -> when(bitmask) {
|
||||
64 -> Opcode.BSTVS
|
||||
128 -> Opcode.BSTNEG
|
||||
else -> throw AssemblyError("need bit 6 or 7")
|
||||
}
|
||||
}
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.BIT, IRDataType.BYTE, labelSymbol = variable.name)
|
||||
it += IRInstruction(bitBranchOpcode, labelSymbol = falseLabel)
|
||||
}
|
||||
// use a BITTST instruction to test any single bit
|
||||
val (testBitSet, expr, bitmask) = useBIT
|
||||
val bitPos = Integer.numberOfTrailingZeros(bitmask)
|
||||
val tr = translateExpression(expr)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.BITTST, tr.dt, reg1 = tr.resultReg, immediate = bitPos), null)
|
||||
if(testBitSet)
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = falseLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(Opcode.BSTNE, labelSymbol = falseLabel), null)
|
||||
addToResult(result, trueTr, trueTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = endLabel), null)
|
||||
result += IRCodeChunk(falseLabel, null)
|
||||
@@ -269,9 +268,14 @@ 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)
|
||||
// Check if the last instruction already sets status flags - if so, skip the CMPI #0
|
||||
// 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 = lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
val skipCmpi = codeGen.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
&& lastInstr != null
|
||||
&& lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=condTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
@@ -537,7 +541,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
result += codeGen.makeSyscall(IMSyscall.WORDARRAY_CONTAINS, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
|
||||
val syscall = if(haystackVar.type.isSplitWordArray) IMSyscall.SPLIT_WORDARRAY_CONTAINS else IMSyscall.WORDARRAY_CONTAINS
|
||||
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
haystackVar.type.isLongArray -> {
|
||||
val elementTr = translateExpression(check.needle)
|
||||
addToResult(result, elementTr, elementTr.resultReg, -1)
|
||||
val iterableTr = translateExpression(haystackVar)
|
||||
addToResult(result, iterableTr, iterableTr.resultReg, -1)
|
||||
val lengthReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val resultReg = codeGen.registers.next(IRDataType.BYTE)
|
||||
val iterableLength = codeGen.symbolTable.getLength(haystackVar.name)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, immediate = iterableLength!!), null)
|
||||
result += codeGen.makeSyscall(IMSyscall.LONGARRAY_CONTAINS, listOf(IRDataType.LONG to elementTr.resultReg, IRDataType.WORD to iterableTr.resultReg, IRDataType.BYTE to lengthReg), IRDataType.BYTE to resultReg)
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, IRDataType.BYTE, reg1=resultReg, immediate = 0), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, resultReg, -1)
|
||||
}
|
||||
@@ -923,7 +941,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
|
||||
internal fun translate(fcall: PtFunctionCall, resultUsedAsExpression: Boolean = false): ExpressionCodeResult {
|
||||
if(fcall.builtin)
|
||||
return codeGen.translateBuiltinFunc(fcall)
|
||||
|
||||
@@ -961,11 +979,14 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val paramDt = irType(parameter.type)
|
||||
if(parameter.register==null) {
|
||||
val tr = translateExpression(arg)
|
||||
result += tr.chunks
|
||||
// Note: explicit setCpuRegister is not needed here because the CALL instruction
|
||||
// carries the same slot information in its argument specs, and the code generator
|
||||
// backend uses those to load the CPU registers directly.
|
||||
result += tr.chunks
|
||||
if(paramDt==IRDataType.FLOAT)
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), null, null)))
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), null, null)))
|
||||
} else {
|
||||
require(parameter.register in Cx16VirtualRegisters || parameter.register in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = parameter.register!!.asScopedNameVirtualReg(parameter.type).joinToString(".")
|
||||
@@ -984,7 +1005,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else {
|
||||
callTarget.returns.map {
|
||||
val returnIrType = irType(it)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), null)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), null, null)
|
||||
}
|
||||
}
|
||||
// create the call
|
||||
@@ -1011,12 +1032,22 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
val paramDt = irType(parameter.type)
|
||||
val tr = translateExpression(arg)
|
||||
val (slot, flag) = registerOrStatusflagToSlotAndFlag(parameter.register)
|
||||
val argName = if(slot==null && flag==null) {
|
||||
val reg = parameter.register.registerOrPair
|
||||
if(reg != null && reg in Cx16VirtualRegisters) {
|
||||
// CX16 virtual register parameter - use the cx16 symbol name
|
||||
// so the codegen can resolve it to the correct address
|
||||
"cx16.${reg.toString().lowercase()}"
|
||||
} else {
|
||||
parameter.name
|
||||
}
|
||||
} else ""
|
||||
if(paramDt==IRDataType.FLOAT)
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), parameter.register)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(argName, null, FunctionCallArgs.RegSpec(IRDataType.FLOAT, RegisterNum(tr.resultFpReg), slot, flag)))
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), parameter.register)))
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec(argName, null, FunctionCallArgs.RegSpec(paramDt, RegisterNum(tr.resultReg), slot, flag)))
|
||||
result += tr.chunks
|
||||
result += codeGen.setCpuRegister(parameter.register, paramDt, tr.resultReg, tr.resultFpReg)
|
||||
}
|
||||
|
||||
if(callTarget.returns.size>1)
|
||||
@@ -1029,44 +1060,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else {
|
||||
val returns = callTarget.returns[0]
|
||||
val returnIrType = irType(returns.type)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), returns.register)
|
||||
val (retSlot, retFlag) = registerOrStatusflagToSlotAndFlag(returns.register)
|
||||
FunctionCallArgs.RegSpec(returnIrType, RegisterNum(codeGen.registers.next(returnIrType)), retSlot, retFlag)
|
||||
}
|
||||
}
|
||||
// create the call
|
||||
val returnRegs = if(returnRegSpec==null) emptyList() else listOf(returnRegSpec)
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
else {
|
||||
val address = callTarget.address!!
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
|
||||
} else if(address.constbank!=null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt()
|
||||
)
|
||||
} else {
|
||||
val tr = translateExpression(address.varbank!!)
|
||||
require(tr.dt==IRDataType.BYTE)
|
||||
result += tr.chunks
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toAddress(),
|
||||
reg1 = tr.resultReg
|
||||
)
|
||||
}
|
||||
}
|
||||
val call = emitExtSubCall(callTarget, fcall, FunctionCallArgs(argRegisters, returnRegs), result)
|
||||
addInstr(result, call, null)
|
||||
var finalReturnRegister = returnRegSpec?.registerNum?.value ?: -1
|
||||
|
||||
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
|
||||
if(resultUsedAsExpression) {
|
||||
// look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned)
|
||||
val statusFlagResult = returnRegSpec?.cpuRegister?.statusflag
|
||||
val statusFlagResult = returnRegSpec?.statusflag
|
||||
if(statusFlagResult!=null) {
|
||||
// assign status flag bit to the return value register
|
||||
finalReturnRegister = returnRegSpec.registerNum.value
|
||||
@@ -1127,24 +1133,19 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun checkIfConditionCanUseBIT(condition: PtBinaryExpression): Triple<Boolean, PtIdentifier, Int>? {
|
||||
// test for occurrence of: x & 64 != 0 (or 128) , this can be performed with a BIT instruction
|
||||
data class BitTestResult(val testBitSet: Boolean, val expression: PtExpression, val bitmask: Int)
|
||||
|
||||
internal fun checkIfConditionCanUseBIT(condition: PtBinaryExpression): BitTestResult? {
|
||||
// test for occurrence of: x & power_of_2 != 0 (or == 0) , this can be performed with a BITTST instruction
|
||||
if(condition.operator == "==" || condition.operator == "!=") {
|
||||
if (condition.right.asConstInteger() == 0) {
|
||||
val and = condition.left as? PtBinaryExpression
|
||||
if (and != null && and.operator == "&" && and.type.isUnsignedByte) {
|
||||
val bitmask = and.right.asConstInteger()
|
||||
if(bitmask==128 || bitmask==64) {
|
||||
val variable = and.left as? PtIdentifier
|
||||
if (variable != null && variable.type.isByte) {
|
||||
return Triple(condition.operator=="!=", variable, bitmask)
|
||||
}
|
||||
val typecast = and.left as? PtTypeCast
|
||||
if (typecast != null && typecast.type.isUnsignedByte) {
|
||||
val castedVariable = typecast.value as? PtIdentifier
|
||||
if(castedVariable!=null && castedVariable.type.isByte)
|
||||
return Triple(condition.operator=="!=", castedVariable, bitmask)
|
||||
}
|
||||
if (and != null && and.operator == "&") {
|
||||
val bitmask = and.right.asConstInteger() ?: return null
|
||||
if(bitmask > 0 && bitmask and (bitmask - 1) == 0) { // power of two
|
||||
val expr = and.left
|
||||
if(expr is PtIdentifier || expr is PtTypeCast || expr is PtArrayIndexer)
|
||||
return BitTestResult(condition.operator=="!=", expr, bitmask)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1161,43 +1162,67 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
// return multiple values
|
||||
val returnRegisters = callTarget.returns.map {
|
||||
val regnum = codeGen.registers.next(irType(it.type))
|
||||
FunctionCallArgs.RegSpec(irType(it.type), RegisterNum(regnum), it.register)
|
||||
val (slot, flag) = registerOrStatusflagToSlotAndFlag(it.register)
|
||||
FunctionCallArgs.RegSpec(irType(it.type), RegisterNum(regnum), slot, flag)
|
||||
}
|
||||
// create the call
|
||||
val call =
|
||||
if(callTarget.address==null)
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
|
||||
else {
|
||||
val address = callTarget.address!!
|
||||
if(address.constbank==null && address.varbank==null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
|
||||
)
|
||||
} else if(address.constbank!=null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt()
|
||||
)
|
||||
} else {
|
||||
val tr = translateExpression(address.varbank!!)
|
||||
require(tr.dt==IRDataType.BYTE)
|
||||
result += tr.chunks
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toAddress(),
|
||||
reg1 = tr.resultReg
|
||||
)
|
||||
}
|
||||
}
|
||||
val call = emitExtSubCall(callTarget, fcall, FunctionCallArgs(argRegisters, returnRegisters), result)
|
||||
addInstr(result, call, null)
|
||||
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum.value}
|
||||
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum.value}
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1, resultRegs, resultFpRegs)
|
||||
}
|
||||
|
||||
private fun emitExtSubCall(
|
||||
callTarget: StExtSub,
|
||||
fcall: PtFunctionCall,
|
||||
fcallArgs: FunctionCallArgs,
|
||||
result: MutableList<IRCodeChunkBase>
|
||||
): IRInstruction {
|
||||
val address = callTarget.address
|
||||
return if (address == null) {
|
||||
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = fcallArgs)
|
||||
} else if (address.constbank == null && address.varbank == null) {
|
||||
IRInstruction(
|
||||
Opcode.CALL,
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = fcallArgs
|
||||
)
|
||||
} else if (address.constbank != null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt(),
|
||||
fcallArgs = fcallArgs
|
||||
)
|
||||
} else {
|
||||
val varBank = address.varbank!!
|
||||
val target = codeGen.symbolTable.lookup(varBank.name) ?: codeGen.symbolTable.lookupUnscoped(varBank.name)
|
||||
val bankReg = if (target?.type == StNodeType.SUBROUTINE || target?.type == StNodeType.EXTSUB) {
|
||||
// generate a call to the bank subroutine with the call ID as argument
|
||||
val bankFcall = PtFunctionCall(varBank.name, false, false, arrayOf(DataType.UBYTE), varBank.position)
|
||||
val callId = codeGen.extsubCallSiteIds[fcall.name]
|
||||
if (callId != null) {
|
||||
bankFcall.add(PtNumber(BaseDataType.UBYTE, callId.toDouble(), varBank.position))
|
||||
}
|
||||
val tr = translate(bankFcall, true)
|
||||
result += tr.chunks
|
||||
tr.resultReg
|
||||
} else {
|
||||
val tr = translateExpression(varBank)
|
||||
require(tr.dt == IRDataType.BYTE)
|
||||
result += tr.chunks
|
||||
tr.resultReg
|
||||
}
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toAddress(),
|
||||
reg1 = bankReg,
|
||||
fcallArgs = fcallArgs
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun operatorGreaterThan(
|
||||
binExpr: PtBinaryExpression,
|
||||
vmDt: IRDataType,
|
||||
@@ -1291,7 +1316,14 @@ 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()
|
||||
val skipCmpi = rightConst.toInt() == 0 && lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
// 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 OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, leftTr.dt, reg1 = leftTr.resultReg, immediate = rightConst.toInt()), null)
|
||||
}
|
||||
@@ -1833,6 +1865,23 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1 = pointerReg, reg2 = pointerReg, immediate = fieldinfo.second.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerOrStatusflagToSlotAndFlag(reg: RegisterOrStatusflag): Pair<CallingConventionSlot?, Statusflag?> {
|
||||
if (reg.statusflag != null) return null to reg.statusflag
|
||||
if (reg.registerOrPair == null) return null to null
|
||||
if (reg.registerOrPair in Cx16VirtualRegisters || reg.registerOrPair in CombinedLongRegisters) return null to null
|
||||
return when (reg.registerOrPair) {
|
||||
RegisterOrPair.A -> CallingConventionSlot(0) to null
|
||||
RegisterOrPair.X -> CallingConventionSlot(1) to null
|
||||
RegisterOrPair.Y -> CallingConventionSlot(2) to null
|
||||
RegisterOrPair.AX -> CallingConventionSlot(3) to null
|
||||
RegisterOrPair.AY -> CallingConventionSlot(4) to null
|
||||
RegisterOrPair.XY -> CallingConventionSlot(5) to null
|
||||
RegisterOrPair.FAC1 -> CallingConventionSlot(6) to null
|
||||
RegisterOrPair.FAC2 -> CallingConventionSlot(7) to null
|
||||
else -> throw AssemblyError("unsupported register $reg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,24 +3,38 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.intermediate.*
|
||||
import kotlin.io.path.readBytes
|
||||
|
||||
// NOTE: All symbol names in the generated IR are fully scoped (entire dotted path)
|
||||
// and carry type prefixes: p8b_ (block), p8s_ (sub), p8v_ (variable), p8c_ (const),
|
||||
// p8l_ (label), p8t_ (struct), p8i_ (instance). Any backend consuming the IR
|
||||
// can rely on these already being present and does not need to add them itself.
|
||||
|
||||
|
||||
class IRCodeGen(
|
||||
internal val program: PtProgram,
|
||||
internal val symbolTable: SymbolTable,
|
||||
internal var symbolTable: SymbolTable,
|
||||
internal val options: CompilationOptions,
|
||||
internal val errors: IErrorReporter,
|
||||
internal val retainSSA: Boolean
|
||||
internal val retainSSA: Boolean,
|
||||
preassignedCallSiteIds: Map<String, UByte> = emptyMap()
|
||||
) {
|
||||
|
||||
private val expressionEval = ExpressionGen(this)
|
||||
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
|
||||
private val assignmentGen = AssignmentGen(this, expressionEval)
|
||||
internal val registers = RegisterPool()
|
||||
internal val extsubCallSiteIds: MutableMap<String, UByte> = preassignedCallSiteIds.toMutableMap()
|
||||
|
||||
fun generate(): IRProgram {
|
||||
// The pure "virtual" (VM) target doesn't need symbol prefixing because the VM
|
||||
// doesn't emit assembly and has no risk of symbol clashes. Any other target
|
||||
// (that goes through assembly code generation) MUST prefix symbols to avoid
|
||||
// clashes with 64tass reserved names, BASIC tokens, and other toolchain symbols.
|
||||
if(options.compTarget.name!=VMTarget.NAME)
|
||||
symbolTable = prefixSymbols(program, options, symbolTable)
|
||||
makeAllNodenamesScoped(program)
|
||||
moveAllNestedSubroutinesToBlockScope(program)
|
||||
verifyNameScoping(program, symbolTable)
|
||||
@@ -45,6 +59,11 @@ class IRCodeGen(
|
||||
generateRomableInits(irProg)
|
||||
}
|
||||
|
||||
// generate global init code for string/array-initialized variables
|
||||
// (the existing mechanisms only handle numeric inits; string/array data stays
|
||||
// in the INIT section declarations but no runtime copy instructions are emitted)
|
||||
generateStringArrayInits(irProg)
|
||||
|
||||
irProg.addAsmSymbols(options.symbolDefs)
|
||||
|
||||
for (block in program.allBlocks())
|
||||
@@ -139,6 +158,120 @@ class IRCodeGen(
|
||||
replacements.forEach { irProg.st.add(it) }
|
||||
}
|
||||
|
||||
private fun generateStringArrayInits(irProg: IRProgram) {
|
||||
// ZP variables with string/array init values need runtime copy code.
|
||||
// The VM's varsToMemory already initializes them from the INIT section,
|
||||
// but a 6502 backend needs explicit init instructions.
|
||||
//
|
||||
// Create a non-ZP shadow variable holding the init bytes, clear the
|
||||
// original variable's init value so it becomes uninitialized/BSS, then
|
||||
// emit a single MEMCOPY from the shadow to the ZP variable at startup.
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
for(variable in irProg.st.allVariables().toList()) {
|
||||
if(variable.initializationValue == null)
|
||||
continue
|
||||
if(variable.inBss)
|
||||
continue
|
||||
if(variable.zpwish == ZeropageWish.DONTCARE || variable.zpwish == ZeropageWish.NOT_IN_ZEROPAGE)
|
||||
continue
|
||||
if(variable.dt.isSplitWordArray)
|
||||
continue
|
||||
if(variable.initializationValue is IRVariableInitializer.Numeric)
|
||||
continue
|
||||
|
||||
val initBytes = when(val initVal = variable.initializationValue) {
|
||||
is IRVariableInitializer.Str -> {
|
||||
irProg.encoding.encodeString(initVal.text, initVal.encoding) + 0u
|
||||
}
|
||||
is IRVariableInitializer.Array -> {
|
||||
val elemDt = variable.dt.elementType()
|
||||
val elemByteSize = when {
|
||||
elemDt.isByte || elemDt.isBool -> 1
|
||||
elemDt.isWord || elemDt.isPointer -> 2
|
||||
else -> -1
|
||||
}
|
||||
if(elemByteSize < 0)
|
||||
continue
|
||||
val bytes = mutableListOf<UByte>()
|
||||
for(element in initVal.elements) {
|
||||
when(element) {
|
||||
is IRStSymbolicReference.Numeric -> {
|
||||
val value = element.value.toInt()
|
||||
when(elemByteSize) {
|
||||
1 -> bytes += value.toUByte()
|
||||
2 -> {
|
||||
bytes += (value and 0xFF).toUByte()
|
||||
bytes += ((value shr 8) and 0xFF).toUByte()
|
||||
}
|
||||
}
|
||||
}
|
||||
is IRStSymbolicReference.BoolValue -> {
|
||||
bytes += (if(element.value) 1u else 0u).toUByte()
|
||||
}
|
||||
is IRStSymbolicReference.Symbol -> {
|
||||
bytes.clear()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if(bytes.isEmpty())
|
||||
continue
|
||||
bytes
|
||||
}
|
||||
else -> continue
|
||||
}
|
||||
|
||||
if(initBytes.isEmpty())
|
||||
continue
|
||||
|
||||
val shadowName = variable.name + "_init_value"
|
||||
val shadowInit = IRVariableInitializer.Array(initBytes.map { IRStSymbolicReference.Numeric(it.toDouble()) })
|
||||
val shadowVar = IRStStaticVariable(
|
||||
shadowName,
|
||||
DataType.arrayFor(BaseDataType.UBYTE, false),
|
||||
shadowInit,
|
||||
initBytes.size.toUInt(),
|
||||
ZeropageWish.NOT_IN_ZEROPAGE,
|
||||
0u,
|
||||
false,
|
||||
inBss = false,
|
||||
readonly = false
|
||||
)
|
||||
irProg.st.add(shadowVar)
|
||||
|
||||
// Clear the original variable's init value so it becomes uninitialized (BSS/NOINIT).
|
||||
// The runtime copy from the shadow variable will provide the actual initial bytes.
|
||||
val clearedVar = IRStStaticVariable(
|
||||
variable.name,
|
||||
variable.dt,
|
||||
null,
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align,
|
||||
variable.dirty,
|
||||
variable.inBss,
|
||||
variable.readonly
|
||||
)
|
||||
irProg.st.add(clearedVar)
|
||||
|
||||
val sourceReg = registers.next(IRDataType.WORD)
|
||||
val destReg = registers.next(IRDataType.WORD)
|
||||
val countReg = registers.next(IRDataType.WORD)
|
||||
chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = sourceReg, labelSymbol = shadowName)
|
||||
chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = destReg, labelSymbol = variable.name)
|
||||
chunk += IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1 = countReg, immediate = initBytes.size)
|
||||
val args = listOf(
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(sourceReg), null, null)),
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(destReg), null, null)),
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(IRDataType.WORD, RegisterNum(countReg), null, null))
|
||||
)
|
||||
chunk += IRInstruction(Opcode.SYSCALL, immediate = IMSyscall.MEMCOPY.number, fcallArgs = FunctionCallArgs(args, emptyList()))
|
||||
}
|
||||
|
||||
if(chunk.isNotEmpty())
|
||||
irProg.addGlobalInits(chunk)
|
||||
}
|
||||
|
||||
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
|
||||
fun verifyPtNode(node: PtNode) {
|
||||
when (node) {
|
||||
@@ -160,9 +293,16 @@ class IRCodeGen(
|
||||
is PtPointerDeref -> require('.' in node.startpointer.name) { "node $node name is not scoped: ${node.startpointer.name}" }
|
||||
is PtIdentifier -> {
|
||||
if('.' !in node.name) {
|
||||
// there is 1 case where the identifier is not scoped: if it's the value field name after a pointer array indexing.
|
||||
// there are 2 cases where the identifier is not scoped:
|
||||
// 1) it is the value field name after a pointer array indexing.
|
||||
// 2) it is the operand of a PtAddressOf referring to a block name.
|
||||
val expr = node.parent as? PtBinaryExpression
|
||||
if (expr?.operator != "." || expr.right !== node || expr.left !is PtArrayIndexer || (!expr.left.type.isPointer && !expr.left.type.isStructInstance))
|
||||
val isFieldOfPointerArrayElement = expr?.operator == "." && expr.right === node
|
||||
&& expr.left is PtArrayIndexer
|
||||
&& (expr.left.type.isPointer || expr.left.type.isStructInstance)
|
||||
val isAddressOfBlock = node.parent is PtAddressOf
|
||||
&& symbolTable.lookup(node.name)?.type == StNodeType.BLOCK
|
||||
if(!isFieldOfPointerArrayElement && !isAddressOfBlock)
|
||||
require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
}
|
||||
}
|
||||
@@ -248,8 +388,9 @@ class IRCodeGen(
|
||||
val old = it.first.instructions[it.second]
|
||||
val formats = instructionFormats.getValue(old.opcode)
|
||||
val format = formats.getOrElse(old.type) { throw IllegalArgumentException("type ${old.type} invalid for ${old.opcode}") }
|
||||
val immediateValue = if(format.immediate) it.third.toInt() else null
|
||||
val addressValue: UInt? = if(format.immediate) null else it.third
|
||||
val addressUsed = format.address != OperandDirection.UNUSED
|
||||
val immediateValue = if(addressUsed) old.immediate else if(format.immediate) it.third.toInt() else null
|
||||
val addressValue: UInt? = if(addressUsed) it.third else null
|
||||
|
||||
it.first.instructions[it.second] = IRInstruction(
|
||||
old.opcode,
|
||||
@@ -260,6 +401,7 @@ class IRCodeGen(
|
||||
old.fpReg1,
|
||||
old.fpReg2,
|
||||
immediate = immediateValue,
|
||||
immediateFp = old.immediateFp,
|
||||
address = addressValue?.toAddress()
|
||||
)
|
||||
}
|
||||
@@ -274,7 +416,7 @@ class IRCodeGen(
|
||||
is PtAssignment -> assignmentGen.translate(node)
|
||||
is PtAugmentedAssign -> assignmentGen.translate(node)
|
||||
is PtNodeGroup -> translateGroup(node.children)
|
||||
is PtFunctionCall -> expressionEval.translate(node).chunks // it's not an expression so no result value
|
||||
is PtFunctionCall -> expressionEval.translate(node, false).chunks // it's not an expression so no result value
|
||||
is PtNop -> emptyList()
|
||||
is PtReturn -> translate(node)
|
||||
is PtJump -> translate(node)
|
||||
@@ -552,6 +694,12 @@ class IRCodeGen(
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = indexReg, immediate = 0), null)
|
||||
result += IRCodeChunk(loopLabel, null).also {
|
||||
it += IRInstruction(Opcode.LOADX, elementDt, reg1 = tmpReg, reg2 = indexReg, labelSymbol = iterable.name)
|
||||
// Emit explicit CMPI #0 before the BSTEQ branch on 8-bit targets
|
||||
// (where LOADX doesn't reliably set Z for multi-byte results).
|
||||
// See CpuType.statusBitsOnMultiByteOps.
|
||||
if(!options.compTarget.cpu.statusBitsOnMultiByteOps) {
|
||||
it += IRInstruction(Opcode.CMPI, elementDt, reg1 = tmpReg, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTEQ, labelSymbol = endLabel)
|
||||
it += IRInstruction(Opcode.STOREM, elementDt, reg1 = tmpReg, labelSymbol = loopvarSymbol)
|
||||
}
|
||||
@@ -651,6 +799,12 @@ class IRCodeGen(
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, -1)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = fromTr.resultReg, labelSymbol = loopvarSymbol)
|
||||
// Emit explicit CMPI #0 before the BSTNE branch on 8-bit targets where
|
||||
// LOADM doesn't reliably set Z for multi-byte results. Skip on targets
|
||||
// that honor the contract (e.g. M68000). See CpuType.statusBitsOnMultiByteOps.
|
||||
if(!options.compTarget.cpu.statusBitsOnMultiByteOps) {
|
||||
it += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = fromTr.resultReg, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
}
|
||||
}
|
||||
@@ -744,6 +898,14 @@ class IRCodeGen(
|
||||
}
|
||||
else if(iterable.step==-1 && iterable.last==1) {
|
||||
// downto 1 optimization (byte and word)
|
||||
// On 8-bit targets the preceding addConstMem emitted a DECM which doesn't
|
||||
// reliably set Z; we must explicitly test the loop var. Skip the explicit
|
||||
// CMPI on targets that honor the contract (e.g. M68000).
|
||||
// See CpuType.statusBitsOnMultiByteOps.
|
||||
chunk2 += IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol)
|
||||
if(!options.compTarget.cpu.statusBitsOnMultiByteOps) {
|
||||
chunk2 += IRInstruction(Opcode.CMPI, loopvarDtIr, reg1 = indexReg, immediate = 0)
|
||||
}
|
||||
chunk2 += IRInstruction(Opcode.BSTNE, labelSymbol = loopLabel)
|
||||
} else {
|
||||
// downto some other value
|
||||
@@ -819,20 +981,35 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val valueReg = registers.next(dt)
|
||||
if(value>0) {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = -value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
if(dt==IRDataType.FLOAT) {
|
||||
// float loop variables are not currently supported; keep the load+addm/subm form
|
||||
val valueReg = registers.next(dt)
|
||||
if(value>0) {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
}
|
||||
else {
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=valueReg, immediate = -value)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBM, dt, reg1=valueReg, labelSymbol = symbol)
|
||||
}
|
||||
} else {
|
||||
if(value>0) {
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.ADDIM, dt, immediate = value, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.ADDIM, dt, immediate = value, labelSymbol = symbol)
|
||||
} else {
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.SUBIM, dt, immediate = -value, address = knownAddress.toAddress())
|
||||
else
|
||||
IRInstruction(Opcode.SUBIM, dt, immediate = -value, labelSymbol = symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -911,8 +1088,8 @@ class IRCodeGen(
|
||||
}
|
||||
else if(pow2>=1) {
|
||||
// just shift multiple bits
|
||||
val pow2reg = registers.next(dt)
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
val pow2reg = registers.next(IRDataType.BYTE)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSLNM, dt, reg1=pow2reg, address = knownAddress.toAddress())
|
||||
else
|
||||
@@ -1029,8 +1206,8 @@ class IRCodeGen(
|
||||
IRInstruction(Opcode.ASRM, dt, labelSymbol = symbol)
|
||||
} else {
|
||||
// just shift multiple bits (signed)
|
||||
val pow2reg = registers.next(dt)
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1 = pow2reg, immediate = pow2)
|
||||
val pow2reg = registers.next(IRDataType.BYTE)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = pow2reg, immediate = pow2)
|
||||
code += if (knownAddress != null)
|
||||
IRInstruction(Opcode.ASRNM, dt, reg1 = pow2reg, address = knownAddress.toAddress())
|
||||
else
|
||||
@@ -1046,8 +1223,8 @@ class IRCodeGen(
|
||||
}
|
||||
else {
|
||||
// just shift multiple bits (unsigned)
|
||||
val pow2reg = registers.next(dt)
|
||||
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
|
||||
val pow2reg = registers.next(IRDataType.BYTE)
|
||||
code += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=pow2reg, immediate = pow2)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.LSRNM, dt, reg1 = pow2reg, address = knownAddress.toAddress())
|
||||
else
|
||||
@@ -1159,9 +1336,17 @@ class IRCodeGen(
|
||||
// Fallback: materialize expression and branch on result
|
||||
val tr = expressionEval.translateExpression(condition)
|
||||
result += tr.chunks
|
||||
// Check if the last instruction already sets status flags - if so, skip the CMPI #0
|
||||
// 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 skipCmpi = lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
val targetHonorsContract = options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
val skipCmpi = targetHonorsContract
|
||||
&& lastInstr != null
|
||||
&& lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
if (!skipCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, tr.dt, reg1 = tr.resultReg, immediate = 0), null)
|
||||
}
|
||||
@@ -1179,14 +1364,17 @@ class IRCodeGen(
|
||||
private fun translateIntegerComparison(condition: PtBinaryExpression, onTrueLabel: String?, onTrueAddress: MemoryAddress?, onFalseLabel: String?, onFalseAddress: MemoryAddress?, result: MutableList<IRCodeChunkBase>) {
|
||||
val useBIT = expressionEval.checkIfConditionCanUseBIT(condition)
|
||||
if (useBIT != null) {
|
||||
val (testBitSet, variable, bitmask) = useBIT
|
||||
addInstr(result, IRInstruction(Opcode.BIT, IRDataType.BYTE, labelSymbol = variable.name), null)
|
||||
val (testBitSet, expr, bitmask) = useBIT
|
||||
val bitPos = Integer.numberOfTrailingZeros(bitmask)
|
||||
val leftTr = expressionEval.translateExpression(expr)
|
||||
addToResult(result, leftTr, leftTr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.BITTST, leftTr.dt, reg1 = leftTr.resultReg, immediate = bitPos), null)
|
||||
if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) {
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
} else if ((onFalseLabel != null || onFalseAddress != null) && (onTrueLabel == null && onTrueAddress == null)) {
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, !testBitSet), labelSymbol = onFalseLabel, address = onFalseAddress), null)
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(!testBitSet), labelSymbol = onFalseLabel, address = onFalseAddress), null)
|
||||
} else if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel != null || onFalseAddress != null)) {
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(bitmask, testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
addInstr(result, IRInstruction(getBitBranchOpcode(testBitSet), labelSymbol = onTrueLabel, address = onTrueAddress), null)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = onFalseLabel, address = onFalseAddress), null)
|
||||
}
|
||||
return
|
||||
@@ -1201,7 +1389,15 @@ class IRCodeGen(
|
||||
if (number != null) {
|
||||
val isComparingWithZero = number == 0
|
||||
val lastInstr = leftTr.chunks.lastOrNull()?.instructions?.lastOrNull()
|
||||
val canSkipCmpi = isComparingWithZero && lastInstr != null && lastInstr.opcode in OpcodesThatSetStatusbits
|
||||
// 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 OpcodesThatSetStatusbits
|
||||
|
||||
if ((onTrueLabel != null || onTrueAddress != null) && (onFalseLabel == null && onFalseAddress == null)) {
|
||||
var (opcode, useCmpi) = getIntegerComparisonBranch(condition.operator, false, signed)
|
||||
@@ -1255,12 +1451,8 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBitBranchOpcode(bitmask: Int, wantSet: Boolean): Opcode {
|
||||
return if (wantSet) {
|
||||
if (bitmask == 64) Opcode.BSTVS else Opcode.BSTNEG
|
||||
} else {
|
||||
if (bitmask == 64) Opcode.BSTVC else Opcode.BSTPOS
|
||||
}
|
||||
private fun getBitBranchOpcode(wantSet: Boolean): Opcode {
|
||||
return if (wantSet) Opcode.BSTNE else Opcode.BSTEQ
|
||||
}
|
||||
|
||||
private fun invertOperator(operator: String) = when (operator) {
|
||||
@@ -1371,13 +1563,22 @@ class IRCodeGen(
|
||||
val skipRepeatLabel = createLabelName()
|
||||
val constRepeats = repeat.count.asConstInteger()
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
// The DEC instruction only sets Z correctly for BYTE (or for the full multi-byte
|
||||
// value if the target honors the multi-byte status-bits contract, e.g. M68000).
|
||||
// On 8-bit targets we must emit an explicit CMPI #0 before the BSTNE branch
|
||||
// because DEC only sets Z based on the last byte.
|
||||
// See CpuType.statusBitsOnMultiByteOps for the rationale.
|
||||
val needsExplicitCmpi = !options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
if(constRepeats==65536) {
|
||||
// make use of the word wrap around to count to 65536
|
||||
val resultRegister = registers.next(IRDataType.WORD)
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.WORD, reg1=resultRegister, immediate = 0), null)
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1 = resultRegister) // sets status bits
|
||||
it += IRInstruction(Opcode.DEC, IRDataType.WORD, reg1 = resultRegister)
|
||||
if (needsExplicitCmpi) {
|
||||
it += IRInstruction(Opcode.CMPI, IRDataType.WORD, reg1 = resultRegister, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
}
|
||||
} else {
|
||||
@@ -1385,12 +1586,20 @@ class IRCodeGen(
|
||||
val countTr = expressionEval.translateExpression(repeat.count)
|
||||
addToResult(result, countTr, countTr.resultReg, -1)
|
||||
if (repeat.count.asConstValue() == null) {
|
||||
// check if the counter is already zero
|
||||
// check if the counter is already zero. On 8-bit targets we need an
|
||||
// explicit CMPI #0 because the previous load (from expressionEval) does
|
||||
// not reliably set Z for multi-byte values.
|
||||
if (needsExplicitCmpi) {
|
||||
addInstr(result, IRInstruction(Opcode.CMPI, irDt, reg1 = countTr.resultReg, immediate = 0), null)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.BSTEQ, labelSymbol = skipRepeatLabel), null)
|
||||
}
|
||||
result += labelFirstChunk(translateNode(repeat.statements), repeatLabel)
|
||||
result += IRCodeChunk(null, null).also {
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg) // sets status bits
|
||||
it += IRInstruction(Opcode.DEC, irDt, reg1 = countTr.resultReg)
|
||||
if (needsExplicitCmpi) {
|
||||
it += IRInstruction(Opcode.CMPI, irDt, reg1 = countTr.resultReg, immediate = 0)
|
||||
}
|
||||
it += IRInstruction(Opcode.BSTNE, labelSymbol = repeatLabel)
|
||||
}
|
||||
}
|
||||
@@ -1453,7 +1662,7 @@ class IRCodeGen(
|
||||
?: throw AssemblyError("expected function call for multi-value return ${ret.position}")
|
||||
|
||||
// Translate the function call (this generates the CALL instruction)
|
||||
val callResult = expressionEval.translate(fcall)
|
||||
val callResult = expressionEval.translate(fcall, false)
|
||||
result += callResult.chunks
|
||||
|
||||
// Get the return register specs for the called function
|
||||
@@ -1564,7 +1773,11 @@ class IRCodeGen(
|
||||
is PtNop -> { /* nothing */ }
|
||||
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
|
||||
is PtVariable, is PtConstant, is PtMemMapped, is PtMemorySlabReservation -> { /* vars should be looked up via symbol table */ }
|
||||
is PtAlign -> TODO("ir support for inline %align ${child.position}")
|
||||
is PtAlign -> {
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
chunk += IRInstruction(Opcode.ALIGN, immediate = child.align.toInt())
|
||||
irBlock += chunk
|
||||
}
|
||||
is PtSub -> {
|
||||
val sub = IRSubroutine(child.name, translateParameters(child.signature.children), child.signature.returns, child.position)
|
||||
for (subchild in child.children) {
|
||||
@@ -1573,11 +1786,15 @@ class IRCodeGen(
|
||||
irBlock += sub
|
||||
}
|
||||
is PtAsmSub -> {
|
||||
if(child.address!=null) {
|
||||
// extmsub. No codegen needed: calls to this are jumping straight to the address.
|
||||
val addr = child.address
|
||||
if(addr!=null) {
|
||||
// extsub: emit as inline assembly equate so that other tools (vm, 6502 codegen) can reference the symbol as an address label
|
||||
require(child.children.isEmpty()) {
|
||||
"extsub should be empty at ${child.position}"
|
||||
}
|
||||
val bank = if(addr.constbank!=null) " ; @bank ${addr.constbank}" else ""
|
||||
val asm = "${child.name} = ${addr.address.toHex()}$bank"
|
||||
irBlock += IRInlineAsmChunk(null, asm, false, null)
|
||||
} else {
|
||||
// regular asmsub
|
||||
if(child.children.mapTo(mutableSetOf()) { (it as PtInlineAssembly).isIR }.size>1)
|
||||
@@ -1594,7 +1811,8 @@ class IRCodeGen(
|
||||
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
|
||||
child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
|
||||
asmChunk,
|
||||
child.position
|
||||
child.position,
|
||||
child.inline
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1679,9 +1897,10 @@ class IRCodeGen(
|
||||
} else {
|
||||
val reg = it.register
|
||||
require(reg in Cx16VirtualRegisters || reg in CombinedLongRegisters) { "can only use R0-R15 'registers' here" }
|
||||
val regname = it.register!!.asScopedNameVirtualReg(it.type).joinToString(".")
|
||||
val targetVar = symbolTable.lookup(regname) as StMemVar
|
||||
result += IRSubroutine.IRParam(regname, targetVar.dt)
|
||||
//val regname = it.register!!.asScopedNameVirtualReg(it.type).joinToString(".")
|
||||
require('.' in it.name) { "even parameter names should have been made fully scoped by now" }
|
||||
val targetVar = symbolTable.lookup(it.name) as StMemVar
|
||||
result += IRSubroutine.IRParam(it.name, targetVar.dt)
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -1704,10 +1923,10 @@ class IRCodeGen(
|
||||
internal fun makeSyscall(syscall: IMSyscall, params: List<Pair<IRDataType, Int>>, returns: Pair<IRDataType, Int>?, label: String?=null): IRCodeChunk {
|
||||
return IRCodeChunk(label, null).also {
|
||||
val args = params.map { (dt, reg)->
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, RegisterNum(reg), null))
|
||||
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, RegisterNum(reg), null, null))
|
||||
}
|
||||
// for now, syscalls have 0 or 1 return value
|
||||
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, RegisterNum(returns.second), null))
|
||||
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, RegisterNum(returns.second), null, null))
|
||||
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
|
||||
}
|
||||
}
|
||||
@@ -1715,12 +1934,12 @@ class IRCodeGen(
|
||||
internal fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
when(registerOrFlag.registerOrPair) {
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=0)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=1)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.BYTE, reg1=resultReg, immediate=2)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=3)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=4)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHR, IRDataType.WORD, reg1=resultReg, immediate=5)
|
||||
RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg))
|
||||
RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(resultFpReg))
|
||||
in Cx16VirtualRegisters -> {
|
||||
@@ -1745,12 +1964,12 @@ class IRCodeGen(
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
val irType = irType(fromType)
|
||||
when(registerOrFlag.registerOrPair) {
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=tempReg)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=tempReg)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=tempReg)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=tempReg)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=tempReg)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=tempReg)
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=0)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=1)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.BYTE, reg1=tempReg, immediate=2)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=3)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=4)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.LOADHR, IRDataType.WORD, reg1=tempReg, immediate=5)
|
||||
RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.LOADHFACZERO, IRDataType.FLOAT, fpReg1 = RegisterNum(tempReg))
|
||||
RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.LOADHFACONE, IRDataType.FLOAT, fpReg1 = RegisterNum(tempReg))
|
||||
in Cx16VirtualRegisters -> {
|
||||
@@ -1839,5 +2058,4 @@ class IRCodeGen(
|
||||
result += multiplyByConst(DataType.UBYTE, byteIndexTr.resultReg, itemsize)
|
||||
return Pair(result, byteIndexTr.resultReg)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -339,23 +339,28 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA:
|
||||
}
|
||||
|
||||
private fun removeNeedlessCompares(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// a CMPI with 0, after an instruction like LOAD that already sets the status bits, can be removed.
|
||||
// but only if the instruction after it is not using the Carry bit because that won't be set by a LOAD instruction etc.
|
||||
// A CMPI #0 after an instruction that already sets the status bits can be removed.
|
||||
// The optimization is ONLY safe if the target CPU honors the "multi-byte ops set
|
||||
// status bits based on the full value" contract (CpuType.statusBitsOnMultiByteOps).
|
||||
// On 8-bit targets like 6502/65C02 a 16/32-bit DEC/INC/AND/OR/XOR/LOAD only sets
|
||||
// Z from the last byte, so a following CMPI #0 is NOT redundant - it tests the
|
||||
// full multi-byte value. In that case we keep the explicit CMPI.
|
||||
// See CpuType.statusBitsOnMultiByteOps for the rationale.
|
||||
val targetHonorsContract = irprog.options.compTarget.cpu.statusBitsOnMultiByteOps
|
||||
if (!targetHonorsContract) return false
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx>0 && idx<(indexedInstructions.size-1) && ins.opcode==Opcode.CMPI && ins.immediate==0) {
|
||||
val previous = indexedInstructions[idx-1].value
|
||||
if(previous.reg1==ins.reg1) {
|
||||
if (previous.opcode in OpcodesThatSetStatusbitsIncludingCarry) {
|
||||
if (previous.opcode in OpcodesThatSetStatusbits) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
} else if (previous.opcode in OpcodesThatSetStatusbitsButNotCarry) {
|
||||
val next = indexedInstructions[idx + 1].value
|
||||
if (next.opcode !in OpcodesThatDependOnCarry) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// Note: the OpcodesThatSetStatusbitsButNotCarry set is empty under
|
||||
// the strict contract (see IRInstructions.kt) so we don't need a
|
||||
// second branch here. Only CMP/CMPI/SGN/BITTST are recognized as
|
||||
// setting flags.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,7 +452,7 @@ jump p8_label_gen_2
|
||||
}
|
||||
}
|
||||
Opcode.AND -> {
|
||||
when (ins.immediate) {
|
||||
when (val imm = ins.immediate) {
|
||||
0 -> {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
|
||||
changed = true
|
||||
@@ -465,23 +470,54 @@ jump p8_label_gen_2
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
// convert AND with all-ones-except-one-bit into BITCLR
|
||||
if(!changed) {
|
||||
val imm = ins.immediate ?: return@forEach
|
||||
val clearedBits = when(ins.type) {
|
||||
IRDataType.BYTE -> imm xor 0xFF
|
||||
IRDataType.WORD -> imm xor 0xFFFF
|
||||
IRDataType.LONG -> imm xor -1
|
||||
else -> 0
|
||||
}
|
||||
if(clearedBits > 0 && clearedBits and (clearedBits - 1) == 0) {
|
||||
val bitPos = Integer.numberOfTrailingZeros(clearedBits)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.BITCLR, ins.type, reg1 = ins.reg1, immediate = bitPos)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
Opcode.OR -> {
|
||||
if (ins.immediate == 0) {
|
||||
val imm = ins.immediate
|
||||
if (imm == null) return@forEach
|
||||
if (imm == 0) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
} else if ((ins.immediate == 255 && ins.type == IRDataType.BYTE) ||
|
||||
(ins.immediate == 65535 && ins.type == IRDataType.WORD) ||
|
||||
(ins.immediate == -1 && ins.type == IRDataType.LONG)) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = ins.immediate)
|
||||
} else if ((imm == 255 && ins.type == IRDataType.BYTE) ||
|
||||
(imm == 65535 && ins.type == IRDataType.WORD) ||
|
||||
(imm == -1 && ins.type == IRDataType.LONG)) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = imm)
|
||||
changed = true
|
||||
}
|
||||
// convert OR with power-of-2 into BITSET
|
||||
if(!changed && imm > 0 && imm and (imm - 1) == 0) {
|
||||
val bitPos = Integer.numberOfTrailingZeros(imm)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.BITSET, ins.type, reg1 = ins.reg1, immediate = bitPos)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.XOR -> {
|
||||
if (ins.immediate == 0) {
|
||||
val imm = ins.immediate
|
||||
if (imm == null) return@forEach
|
||||
if (imm == 0) {
|
||||
chunk.instructions.removeAt(idx)
|
||||
changed = true
|
||||
}
|
||||
// convert XOR with power-of-2 into BITTOG
|
||||
if(!changed && imm > 0 && imm and (imm - 1) == 0) {
|
||||
val bitPos = Integer.numberOfTrailingZeros(imm)
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.BITTOG, ins.type, reg1 = ins.reg1, immediate = bitPos)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
||||
@@ -76,17 +76,31 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
|
||||
// remove stray loads
|
||||
val readRegs = mutableSetOf<Int>()
|
||||
val readFpRegs = mutableSetOf<Int>()
|
||||
instructions.forEach { ins ->
|
||||
val readRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val readFpRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val writeRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val writeFpRegsCounts = mutableMapOf<RegisterNum, Int>()
|
||||
val regsTypes = mutableMapOf<RegisterNum, IRDataType>()
|
||||
ins.addUsedRegistersCounts(readRegsCounts, writeRegsCounts, readFpRegsCounts, writeFpRegsCounts, regsTypes, null)
|
||||
readRegs.addAll(readRegsCounts.keys.map { it.value })
|
||||
readFpRegs.addAll(readFpRegsCounts.keys.map { it.value })
|
||||
}
|
||||
instructions.toTypedArray().forEach { ins ->
|
||||
if(ins.opcode in arrayOf(Opcode.LOAD, Opcode.LOADR, Opcode.LOADM)) {
|
||||
if(ins.reg1!=0) {
|
||||
if(instructions.count { it.reg1==ins.reg1 || it.reg2==ins.reg1 } <2) {
|
||||
val reg1 = ins.reg1
|
||||
val fpReg1 = ins.fpReg1
|
||||
if(reg1!=null) {
|
||||
if(reg1 !in readRegs) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
}
|
||||
}
|
||||
else if(ins.fpReg1!=null) {
|
||||
if (instructions.count { it.fpReg1 == ins.fpReg1 || it.fpReg2 == ins.fpReg1 } < 2) {
|
||||
else if(fpReg1!=null) {
|
||||
if(fpReg1.value !in readFpRegs) {
|
||||
if(ins.labelSymbol!=null)
|
||||
code.st.removeIfExists(ins.labelSymbol!!)
|
||||
instructions.remove(ins)
|
||||
@@ -193,10 +207,15 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
return numRemoved
|
||||
}
|
||||
|
||||
|
||||
private val entrypointNames = setOf("p8b_main.p8s_start", "main.start")
|
||||
private val mainBlockNames = setOf("p8b_main", "main")
|
||||
|
||||
|
||||
private fun removeUnreachable(allLabeledChunks: MutableMap<String, IRCodeChunkBase>): Int {
|
||||
val entrypointSub = irprog.blocks.single { it.label=="main" }
|
||||
.children.single { it is IRSubroutine && it.label=="main.start" }
|
||||
val entrypointSub = irprog.blocks.single { it.label in mainBlockNames }
|
||||
.children.single { it is IRSubroutine && it.label in entrypointNames }
|
||||
val reachable = mutableSetOf((entrypointSub as IRSubroutine).chunks.first())
|
||||
reachable.add(irprog.globalInits)
|
||||
|
||||
@@ -269,7 +288,7 @@ class IRUnusedCodeRemover(
|
||||
linkedChunks += it.branchTarget!!
|
||||
}
|
||||
}
|
||||
if (chunk.label == "main.start")
|
||||
if (chunk.label in entrypointNames)
|
||||
linkedChunks += chunk
|
||||
}
|
||||
|
||||
@@ -290,6 +309,14 @@ class IRUnusedCodeRemover(
|
||||
private fun removeUnlinkedChunks(linkedChunks: Set<IRCodeChunkBase>): Int {
|
||||
var numRemoved = 0
|
||||
irprog.foreachSub { sub ->
|
||||
// don't remove stuff in library modules or with %forceoutput
|
||||
// TODO this is needed to keep the subroutine body of sqrt_long from being wiped !??! Sounds like the bad solution to the problem: why is the *body* being cleared , making an empty sqrt_long subrotuine end up in the p8ir output (and eventually in the .asm, which crashes the resulting program)
|
||||
// TODO this causes other stuff to end up in the output IR that is not used......
|
||||
// note: this wasn't a problem on the virtual target, but it is a problem when using the expericodegen target (because on the virtual target, the VM simply executes the SQRT instruction directly without needing a library routine)
|
||||
val block = irprog.blocks.first { b -> b.children.any { it === sub } }
|
||||
if (block.library || block.options.forceOutput)
|
||||
return@foreachSub
|
||||
|
||||
sub.chunks.withIndex().reversed().forEach { (index, chunk) ->
|
||||
if (chunk !in linkedChunks) {
|
||||
if (chunk === sub.chunks[0]) {
|
||||
@@ -315,4 +342,4 @@ class IRUnusedCodeRemover(
|
||||
}
|
||||
return numRemoved
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.*
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.ast.PtStructField
|
||||
import prog8.intermediate.*
|
||||
import kotlin.collections.flatMap
|
||||
|
||||
/**
|
||||
* Converter object for transforming SymbolTable nodes to IR symbol table nodes.
|
||||
@@ -10,7 +11,10 @@ import prog8.intermediate.*
|
||||
*/
|
||||
private class StToIrConverter(val romable: Boolean) {
|
||||
fun convert(struct: StStruct): IRStStructDef =
|
||||
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
|
||||
IRStStructDef(struct.scopedNameString, makeIrStructFieldsFrom(struct.fields), struct.size)
|
||||
|
||||
private fun makeIrStructFieldsFrom(fields: List<PtStructField>): List<IRStStructField> =
|
||||
fields.map { IRStStructField(it.type, it.name, it.arraySize) }
|
||||
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStSymbolicReference {
|
||||
return when(elt) {
|
||||
@@ -73,7 +77,7 @@ private class StToIrConverter(val romable: Boolean) {
|
||||
|
||||
val name = if('.' in variable.name) variable.name else variable.scopedNameString
|
||||
val hasInit = initValue != null
|
||||
val inBss = romable && hasInit
|
||||
val inBss = !hasInit || (romable && hasInit)
|
||||
val readonly = romable && hasInit
|
||||
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly)
|
||||
}
|
||||
@@ -127,10 +131,17 @@ private class StToIrConverter(val romable: Boolean) {
|
||||
IRStMemorySlab("$StMemorySlabBlockName.${mem.name}", mem.size, mem.align)
|
||||
}
|
||||
|
||||
fun convert(instance: StStructInstance, fields: Iterable<Pair<DataType, String>>): IRStStructInstance {
|
||||
val values = fields.zip(instance.initialValues).map { (field, value) ->
|
||||
fun convert(instance: StStructInstance, fields: Iterable<PtStructField>): IRStStructInstance {
|
||||
val expanded = fields.flatMap { field ->
|
||||
val arraySz = field.arraySize
|
||||
if(arraySz!=null)
|
||||
List(arraySz) { field.type.sub!! }
|
||||
else
|
||||
listOf(field.type.base)
|
||||
}
|
||||
val values = expanded.zip(instance.initialValues).map { (baseType, value) ->
|
||||
val elt = convertArrayElt(value)
|
||||
IRStructInitValue(field.first.base, elt)
|
||||
IRStructInitValue(baseType, elt)
|
||||
}
|
||||
return if('.' in instance.name)
|
||||
IRStStructInstance(instance.name, instance.structName, values, instance.size)
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
/**
|
||||
* Prefixes all symbol names in the given program's AST with type-specific prefixes
|
||||
* (p8b_ for blocks, p8s_ for subroutines, p8v_ for variables, p8c_ for constants,
|
||||
* p8l_ for labels, p8t_ for structs).
|
||||
*
|
||||
* The AST should have simple (unscoped) names for all named nodes at this point.
|
||||
* Identifiers and function calls may already have dotted scoped names.
|
||||
* After prefixing, call [makeAllNodenamesScoped] to add scoping to the prefix names.
|
||||
*
|
||||
* Blocks with `noSymbolPrefixing=true` keep their own names intact,
|
||||
* but their references to symbols in prefixed blocks still get prefixed.
|
||||
*
|
||||
* @return A new [SymbolTable] rebuilt from the now-prefixed AST.
|
||||
*/
|
||||
fun prefixSymbols(program: PtProgram, options: CompilationOptions, st: SymbolTable): SymbolTable {
|
||||
val nodesToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val functionCallsToPrefix = mutableListOf<Pair<PtNode, Int>>() // parent + index
|
||||
val expressionsToFixSubtype = mutableListOf<PtExpression>()
|
||||
val variablesToFixSubtype = mutableListOf<IPtVariable>()
|
||||
|
||||
fun prefixNamedNode(node: PtNamedNode) {
|
||||
when(node) {
|
||||
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
||||
is PtBlock -> node.name = "p8b_${node.name}"
|
||||
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}"
|
||||
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
||||
is PtStructDecl -> node.name = "p8t_${node.name}"
|
||||
}
|
||||
}
|
||||
|
||||
fun prefixSymbols(node: PtNode) {
|
||||
when(node) {
|
||||
is PtAsmSub -> {
|
||||
prefixNamedNode(node)
|
||||
node.parameters.forEach { (_, param) -> prefixNamedNode(param) }
|
||||
val address = node.address
|
||||
if (address?.varbank != null) {
|
||||
address.varbank = address.varbank!!.prefixIdentifierOrThis(node, st)
|
||||
}
|
||||
}
|
||||
is PtSub -> prefixNamedNode(node)
|
||||
is PtFunctionCall -> {
|
||||
if(node.builtin) {
|
||||
if(node.name=="prog8_lib_structalloc") {
|
||||
val struct = node.type.subType!!
|
||||
if(struct is StStruct) {
|
||||
node.type.subType = st.lookup(struct.scopedNameString) as StStruct
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if (stNode.astNode?.definingBlock()?.options?.noSymbolPrefixing != true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val pexpr = node.parent as? PtBinaryExpression
|
||||
if(pexpr?.operator==".") {
|
||||
if(pexpr.left is PtArrayIndexer) {
|
||||
val arrayvar = (pexpr.left as PtArrayIndexer).variable!!
|
||||
if(arrayvar.type.subType!=null) {
|
||||
return
|
||||
} else
|
||||
throw AssemblyError("can only use deref expression on struct type")
|
||||
} else {
|
||||
TODO("handle other left operand ${pexpr.left }in dereferencing of struct fields ${node.position}")
|
||||
}
|
||||
}
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?:
|
||||
throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
is PtBlock -> prefixNamedNode(node)
|
||||
is PtConstant -> node.name = "p8c_${node.name}"
|
||||
is PtLabel -> prefixNamedNode(node)
|
||||
is PtMemMapped -> prefixNamedNode(node)
|
||||
is PtSubroutineParameter -> prefixNamedNode(node)
|
||||
is PtVariable -> {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
is PtStructDecl -> prefixNamedNode(node)
|
||||
else -> { }
|
||||
}
|
||||
|
||||
if(node is IPtVariable) {
|
||||
if(node.type.subType!=null)
|
||||
variablesToFixSubtype.add(node)
|
||||
} else if(node is PtExpression) {
|
||||
if(node.type.subType!=null)
|
||||
expressionsToFixSubtype.add(node)
|
||||
}
|
||||
|
||||
node.children.forEach { prefixSymbols(it) }
|
||||
}
|
||||
|
||||
fun maybePrefixFunctionCallsAndIdentifierReferences(node: PtNode) {
|
||||
if(node is PtFunctionCall && !node.builtin) {
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
else if (node is PtIdentifier) {
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
node.children.forEach { maybePrefixFunctionCallsAndIdentifierReferences(it) }
|
||||
}
|
||||
|
||||
program.allBlocks().forEach { block ->
|
||||
if (!block.options.noSymbolPrefixing) {
|
||||
prefixSymbols(block)
|
||||
} else {
|
||||
maybePrefixFunctionCallsAndIdentifierReferences(block)
|
||||
}
|
||||
}
|
||||
|
||||
nodesToPrefix.forEach { (parent, index) ->
|
||||
when(val node = parent.children[index]) {
|
||||
is PtIdentifier -> parent.setChild(index, node.prefixIdentifierOrThis(parent, st))
|
||||
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
|
||||
is PtVariable -> parent.setChild(index, node.prefixVariableOrThis(parent, st))
|
||||
else -> throw AssemblyError("weird node to prefix $node")
|
||||
}
|
||||
}
|
||||
|
||||
// reversed so inner calls (such as arguments to a function call) get processed before the actual function call itself
|
||||
functionCallsToPrefix.reversed().forEach { (parent, index) ->
|
||||
val node = parent.children[index]
|
||||
if(node is PtFunctionCall) {
|
||||
val prefixedName = PtIdentifier(node.name, DataType.UNDEFINED, node.position).prefixIdentifierOrThis(parent, st)
|
||||
val prefixedNode = node.withNewName(prefixedName.name)
|
||||
parent.setChild(index, prefixedNode)
|
||||
} else {
|
||||
throw AssemblyError("expected PtFunctionCall")
|
||||
}
|
||||
}
|
||||
|
||||
val updatedSt = SymbolTableMaker(program, options).make()
|
||||
|
||||
fun findSubtypeReplacement(sub: ISubType): StStruct? {
|
||||
if(updatedSt.lookup(sub.scopedNameString)!=null)
|
||||
return null
|
||||
val old = st.lookup(sub.scopedNameString) ?: throw AssemblyError("old subtype not found: ${sub.scopedNameString}")
|
||||
|
||||
val prefixed = ArrayDeque<String>()
|
||||
var node: StNode = old
|
||||
while(node.type!= StNodeType.GLOBAL) {
|
||||
val typeChar = typePrefixChar(node.type)
|
||||
prefixed.addFirst("p8${typeChar}_${node.name}")
|
||||
node=node.parent
|
||||
}
|
||||
return updatedSt.lookup(prefixed.joinToString(".")) as StStruct
|
||||
}
|
||||
|
||||
expressionsToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
variablesToFixSubtype.forEach { node ->
|
||||
findSubtypeReplacement(node.type.subType!!)?.let {
|
||||
node.type.subType = it
|
||||
}
|
||||
}
|
||||
|
||||
return updatedSt
|
||||
}
|
||||
|
||||
|
||||
internal fun prefixScopedName(name: String, type: Char): String {
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(GENERATED_LABEL_PREFIX))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
return prefixed.joinToString(".")
|
||||
}
|
||||
|
||||
|
||||
internal fun typePrefixChar(targetNt: StNodeType): Char {
|
||||
return when(targetNt) {
|
||||
StNodeType.BLOCK -> 'b'
|
||||
StNodeType.SUBROUTINE, StNodeType.EXTSUB -> 's'
|
||||
StNodeType.LABEL -> 'l'
|
||||
StNodeType.STATICVAR, StNodeType.MEMVAR -> 'v'
|
||||
StNodeType.CONSTANT -> 'c'
|
||||
StNodeType.BUILTINFUNC -> 's'
|
||||
StNodeType.MEMORYSLAB -> 'v'
|
||||
StNodeType.STRUCT -> 't'
|
||||
StNodeType.STRUCTINSTANCE -> 'i'
|
||||
else -> '?'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun PtVariable.prefixVariableOrThis(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
name = prefixScopedName(name, 'v')
|
||||
if(value==null)
|
||||
return this
|
||||
|
||||
val arrayValue = value as? PtArray
|
||||
return if(arrayValue!=null && arrayValue.children.any { it !is PtNumber && it !is PtBool } ) {
|
||||
val newValue = PtArray(arrayValue.type, arrayValue.position)
|
||||
arrayValue.children.forEach { elt ->
|
||||
when(elt) {
|
||||
is PtBool -> newValue.add(elt)
|
||||
is PtNumber -> newValue.add(elt)
|
||||
is PtConstant -> newValue.add(elt)
|
||||
is PtAddressOf -> {
|
||||
if(elt.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
newValue.add(elt)
|
||||
else {
|
||||
val newAddr = PtAddressOf(elt.type, false, elt.position)
|
||||
newAddr.add(elt.identifier!!.prefixIdentifierOrThis(newAddr, st))
|
||||
if (elt.arrayIndexExpr != null)
|
||||
newAddr.add(elt.arrayIndexExpr!!)
|
||||
newAddr.parent = arrayValue
|
||||
newValue.add(newAddr)
|
||||
}
|
||||
}
|
||||
is PtFunctionCall if elt.builtin -> {
|
||||
if (elt.name != "prog8_lib_structalloc")
|
||||
throw AssemblyError("weird array value element $elt")
|
||||
else
|
||||
newValue.add(elt)
|
||||
}
|
||||
else -> throw AssemblyError("weird array value element $elt")
|
||||
}
|
||||
}
|
||||
val result = PtVariable(name, type, zeropage, align, dirty, newValue, arraySize, position)
|
||||
result.parent = parent
|
||||
result
|
||||
}
|
||||
else this
|
||||
}
|
||||
|
||||
|
||||
private fun PtIdentifier.prefixIdentifierOrThis(parent: PtNode, st: SymbolTable): PtIdentifier {
|
||||
val targetNt: StNodeType
|
||||
val target = st.lookup(name)
|
||||
if(target?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
|
||||
if(target==null) {
|
||||
if(name.endsWith("_lsb") || name.endsWith("_msb")) {
|
||||
val target2 = st.lookup(name.dropLast(4))
|
||||
if(target2?.astNode?.definingBlock()?.options?.noSymbolPrefixing==true)
|
||||
return this
|
||||
targetNt = target2!!.type
|
||||
} else {
|
||||
targetNt = StNodeType.STATICVAR
|
||||
}
|
||||
} else {
|
||||
targetNt = target.type
|
||||
}
|
||||
|
||||
val prefixType = typePrefixChar(targetNt)
|
||||
val newName = prefixScopedName(name, prefixType)
|
||||
val node = PtIdentifier(newName, type, position)
|
||||
node.parent = parent
|
||||
return node
|
||||
}
|
||||
|
||||
|
||||
private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
|
||||
val call = PtFunctionCall(name, builtin, hasNoSideEffects, returntypes, position)
|
||||
call.addAll(children)
|
||||
call.children.forEach { it.parent = call }
|
||||
call.parent = parent
|
||||
return call
|
||||
}
|
||||
@@ -10,14 +10,16 @@ import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
import prog8.intermediate.IRProgram
|
||||
|
||||
class VmCodeGen(val retainSSA: Boolean): ICodeGeneratorBackend {
|
||||
class VmCodeGen(val retainSSA: Boolean,
|
||||
private val preassignedCallSiteIds: Map<String, UByte> = emptyMap()
|
||||
): ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram {
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA)
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors, retainSSA, preassignedCallSiteIds)
|
||||
val irProgram = irCodeGen.generate()
|
||||
|
||||
irProgram.verifyRegisterTypes(irCodeGen.registerTypes())
|
||||
|
||||
@@ -11,9 +11,9 @@ import prog8tests.helpers.ErrorReporterForTests
|
||||
|
||||
class TestIRPeepholeOpt: FunSpec({
|
||||
fun makeIRProgram(chunks: List<IRCodeChunkBase>): IRProgram {
|
||||
require(chunks.first().label=="main.start")
|
||||
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val sub = IRSubroutine("main.start", emptyList(), emptyList(), Position.DUMMY)
|
||||
require(chunks.first().label=="p8b_main.p8s_start")
|
||||
val block = IRBlock("p8b_main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val sub = IRSubroutine("p8b_main.p8s_start", emptyList(), emptyList(), Position.DUMMY)
|
||||
chunks.forEach { sub += it }
|
||||
block += sub
|
||||
val target = VMTarget()
|
||||
@@ -31,7 +31,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
fun makeIRProgram(instructions: List<IRInstruction>): IRProgram {
|
||||
val chunk = IRCodeChunk("main.start", null)
|
||||
val chunk = IRCodeChunk("p8b_main.p8s_start", null)
|
||||
instructions.forEach { chunk += it }
|
||||
return makeIRProgram(listOf(chunk))
|
||||
}
|
||||
@@ -51,7 +51,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
}
|
||||
|
||||
test("remove jmp to label below but keep labels") {
|
||||
val c1 = IRCodeChunk("main.start", null)
|
||||
val c1 = IRCodeChunk("p8b_main.p8s_start", null)
|
||||
c1 += IRInstruction(Opcode.JUMP, labelSymbol = "label")
|
||||
val c2 = IRCodeChunk("label", null)
|
||||
c2 += IRInstruction(Opcode.JUMP, labelSymbol = "label2")
|
||||
@@ -68,7 +68,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val chunks = irProg.chunks()
|
||||
chunks.size shouldBe 4
|
||||
chunks[0].label shouldBe "main.start"
|
||||
chunks[0].label shouldBe "p8b_main.p8s_start"
|
||||
chunks[1].label shouldBe "label"
|
||||
chunks[2].label shouldBe "label2"
|
||||
chunks[3].label shouldBe "label3"
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
import io.kotest.assertions.throwables.shouldThrow
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.codegen.intermediate.prefixScopedName
|
||||
import prog8.codegen.intermediate.prefixSymbols
|
||||
import prog8.codegen.intermediate.typePrefixChar
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
|
||||
class TestSymbolPrefixer : FunSpec({
|
||||
|
||||
// --- prefixScopedName tests ---
|
||||
|
||||
test("prefixScopedName single name with type char") {
|
||||
prefixScopedName("main", 'b') shouldBe "p8b_main"
|
||||
prefixScopedName("start", 's') shouldBe "p8s_start"
|
||||
prefixScopedName("x", 'v') shouldBe "p8v_x"
|
||||
prefixScopedName("SIZE", 'c') shouldBe "p8c_SIZE"
|
||||
prefixScopedName("label", 'l') shouldBe "p8l_label"
|
||||
prefixScopedName("Point", 't') shouldBe "p8t_Point"
|
||||
}
|
||||
|
||||
test("prefixScopedName dotted scoped name") {
|
||||
prefixScopedName("main.start", 's') shouldBe "p8b_main.p8s_start"
|
||||
prefixScopedName("main.start.x", 'v') shouldBe "p8b_main.p8s_start.p8v_x"
|
||||
prefixScopedName("main.start.looplabel", 'l') shouldBe "p8b_main.p8s_start.p8l_looplabel"
|
||||
prefixScopedName("main.SIZE", 'c') shouldBe "p8b_main.p8c_SIZE"
|
||||
prefixScopedName("lib.module.func", 's') shouldBe "p8b_lib.p8s_module.p8s_func"
|
||||
}
|
||||
|
||||
test("prefixScopedName generated label is not prefixed") {
|
||||
val genLabel = "p8_label_gen_42"
|
||||
prefixScopedName(genLabel, 's') shouldBe genLabel
|
||||
prefixScopedName("main.start.$genLabel", 'v') shouldBe "p8b_main.p8s_start.$genLabel"
|
||||
}
|
||||
|
||||
// --- typePrefixChar tests ---
|
||||
|
||||
test("typePrefixChar maps all StNodeTypes correctly") {
|
||||
typePrefixChar(StNodeType.BLOCK) shouldBe 'b'
|
||||
typePrefixChar(StNodeType.SUBROUTINE) shouldBe 's'
|
||||
typePrefixChar(StNodeType.EXTSUB) shouldBe 's'
|
||||
typePrefixChar(StNodeType.LABEL) shouldBe 'l'
|
||||
typePrefixChar(StNodeType.STATICVAR) shouldBe 'v'
|
||||
typePrefixChar(StNodeType.MEMVAR) shouldBe 'v'
|
||||
typePrefixChar(StNodeType.CONSTANT) shouldBe 'c'
|
||||
typePrefixChar(StNodeType.BUILTINFUNC) shouldBe 's'
|
||||
typePrefixChar(StNodeType.MEMORYSLAB) shouldBe 'v'
|
||||
typePrefixChar(StNodeType.STRUCT) shouldBe 't'
|
||||
typePrefixChar(StNodeType.STRUCTINSTANCE) shouldBe 'i'
|
||||
}
|
||||
|
||||
// --- full program prefixing tests ---
|
||||
|
||||
test("prefixSymbols prefixes a simple program") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val varX = PtVariable("x", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY)
|
||||
sub.add(varX)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
|
||||
val updatedSt = prefixSymbols(program, options, st)
|
||||
|
||||
// AST names should be prefixed
|
||||
block.name shouldBe "p8b_main"
|
||||
sub.name shouldBe "p8s_start"
|
||||
varX.name shouldBe "p8v_x"
|
||||
|
||||
// symbol table should have prefixed entries
|
||||
updatedSt.lookup("p8b_main") shouldNotBe null
|
||||
updatedSt.lookup("p8b_main.p8s_start") shouldNotBe null
|
||||
updatedSt.lookup("p8b_main.p8s_start.p8v_x") shouldNotBe null
|
||||
}
|
||||
|
||||
test("prefixSymbols handles subroutine parameters") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val param = PtSubroutineParameter("arg", DataType.UBYTE, null, Position.DUMMY)
|
||||
sub.add(param)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
param.name shouldBe "p8v_arg"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles constants") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val constant = PtConstant("SIZE", DataType.UWORD, 100.0, null, Position.DUMMY)
|
||||
block.add(constant)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
constant.name shouldBe "p8c_SIZE"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles labels") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val label = PtLabel("mylabel", Position.DUMMY)
|
||||
sub.add(label)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
label.name shouldBe "p8l_mylabel"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles generated labels without prefixing them") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val genLabel = PtLabel("p8_label_gen_42", Position.DUMMY)
|
||||
sub.add(genLabel)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
genLabel.name shouldBe "p8_label_gen_42"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles struct declarations") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val struct = PtStructDecl("Point", listOf(
|
||||
PtStructField(DataType.UBYTE, "x", null),
|
||||
PtStructField(DataType.UBYTE, "y", null)
|
||||
), Position.DUMMY)
|
||||
block.add(struct)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
struct.name shouldBe "p8t_Point"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles asmsub") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val asmsub = PtAsmSub("routine", PtAsmSub.Address(null, null, 0x5000u), setOf(CpuRegister.Y), emptyList(), emptyList(), false, Position.DUMMY)
|
||||
block.add(asmsub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
asmsub.name shouldBe "p8s_routine"
|
||||
}
|
||||
|
||||
test("prefixSymbols handles memmapped variables") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val memvar = PtMemMapped("reg", DataType.UBYTE, 0xd020u, null, Position.DUMMY)
|
||||
block.add(memvar)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
memvar.name shouldBe "p8v_reg"
|
||||
}
|
||||
|
||||
// --- noSymbolPrefixing tests ---
|
||||
|
||||
test("noSymbolPrefixing blocks keep their names intact") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val libOptions = PtBlock.Options(noSymbolPrefixing = true)
|
||||
val libBlock = PtBlock("lib", false, SourceCode.Generated("test"), libOptions, Position.DUMMY)
|
||||
val libSub = PtSub("func", emptyList(), emptyList(), Position.DUMMY)
|
||||
libBlock.add(libSub)
|
||||
program.add(libBlock)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
libBlock.name shouldBe "lib" // no prefix
|
||||
libSub.name shouldBe "func" // no prefix
|
||||
}
|
||||
|
||||
test("references from user block to noSymbolPrefixing block are not prefixed") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
|
||||
// Library block with no symbol prefixing
|
||||
val libOptions = PtBlock.Options(noSymbolPrefixing = true)
|
||||
val libBlock = PtBlock("lib", false, SourceCode.Generated("test"), libOptions, Position.DUMMY)
|
||||
val libSub = PtSub("print", emptyList(), emptyList(), Position.DUMMY)
|
||||
libBlock.add(libSub)
|
||||
program.add(libBlock)
|
||||
|
||||
// User block (regular)
|
||||
val userBlock = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val userSub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
userBlock.add(userSub)
|
||||
program.add(userBlock)
|
||||
|
||||
val options = basicTestOptions()
|
||||
|
||||
// Now add a function call referencing the library sub - this must happen AFTER making the st
|
||||
// because the st is needed for lookup during prefixing
|
||||
val call = PtFunctionCall("lib.print", false, false, emptyArray(), Position.DUMMY)
|
||||
userSub.add(call)
|
||||
|
||||
// Rebuild st after adding the call
|
||||
val st2 = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st2)
|
||||
|
||||
// The call site should NOT be prefixed (lib has noSymbolPrefixing)
|
||||
call.name shouldBe "lib.print"
|
||||
}
|
||||
|
||||
test("references from noSymbolPrefixing block to regular block get prefixed") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
|
||||
// Regular user block
|
||||
val userBlock = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val userSub = PtSub("helper", emptyList(), emptyList(), Position.DUMMY)
|
||||
userBlock.add(userSub)
|
||||
program.add(userBlock)
|
||||
|
||||
// Library block with no symbol prefixing
|
||||
val libOptions = PtBlock.Options(noSymbolPrefixing = true)
|
||||
val libBlock = PtBlock("lib", false, SourceCode.Generated("test"), libOptions, Position.DUMMY)
|
||||
val libSub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val call = PtFunctionCall("main.helper", false, false, emptyArray(), Position.DUMMY)
|
||||
libSub.add(call)
|
||||
libBlock.add(libSub)
|
||||
program.add(libBlock)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
// The call site from lib to main should get prefixed (new node, get from parent)
|
||||
val newCall = libSub.children.find { it is PtFunctionCall } as PtFunctionCall
|
||||
newCall.name shouldBe "p8b_main.p8s_helper"
|
||||
}
|
||||
|
||||
test("identifier references are prefixed according to their target type") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val varX = PtVariable("x", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY)
|
||||
sub.add(varX)
|
||||
|
||||
// Add an identifier reference to the variable
|
||||
val idRef = PtIdentifier("main.start.x", DataType.UBYTE, Position.DUMMY)
|
||||
sub.add(idRef)
|
||||
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
prefixSymbols(program, options, st)
|
||||
|
||||
// The identifier reference should have been prefixed (new node, get from parent)
|
||||
val newIdRef = sub.children.find { it is PtIdentifier } as PtIdentifier
|
||||
newIdRef.name shouldBe "p8b_main.p8s_start.p8v_x"
|
||||
}
|
||||
|
||||
test("prefixSymbols exception for unknown identifier") {
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
val idRef = PtIdentifier("nonexistent", DataType.UBYTE, Position.DUMMY)
|
||||
sub.add(idRef)
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = basicTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
|
||||
shouldThrow<AssemblyError> {
|
||||
prefixSymbols(program, options, st)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
private fun basicTestOptions(): CompilationOptions {
|
||||
val target = prog8.code.target.VMTarget()
|
||||
return CompilationOptions.builder(target)
|
||||
.output(OutputType.RAW)
|
||||
.zeropage(ZeropageType.DONTUSE)
|
||||
.floats(true)
|
||||
.compilerVersion("99.99")
|
||||
.build()
|
||||
}
|
||||
@@ -628,11 +628,40 @@ class TestVmCodeGen: FunSpec({
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||
val irSub = result.irProgram.blocks.first().children.filterIsInstance<IRSubroutine>().single()
|
||||
val irChunks = irSub.chunks
|
||||
irChunks.size shouldBe 1
|
||||
irChunks[0].instructions.size shouldBe 1
|
||||
val callInstr = irChunks[0].instructions[0]
|
||||
callInstr.opcode shouldBe Opcode.CALL
|
||||
callInstr.address shouldBe 0x5000u.toAddress()
|
||||
}
|
||||
|
||||
test("ir codegen for target 'virtual' produces no prefixed names") {
|
||||
val codegen = VmCodeGen(false)
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", false, SourceCode.Generated("test"), PtBlock.Options(), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), emptyList(), Position.DUMMY)
|
||||
sub.add(PtVariable("x", DataType.UBYTE, ZeropageWish.DONTCARE, 0u, false, null, null, Position.DUMMY))
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
val options = getTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irProg = result.irProgram
|
||||
|
||||
// Block label should not be prefixed
|
||||
val irBlock = irProg.blocks.first()
|
||||
irBlock.label shouldBe "main"
|
||||
|
||||
// Subroutine label should be scoped but not prefixed
|
||||
val irSub = irBlock.children.single() as IRSubroutine
|
||||
irSub.label shouldBe "main.start"
|
||||
|
||||
// Variable in IR symbol table should not have prefixed scoped name
|
||||
val irVar = irProg.st.allVariables().single()
|
||||
irVar.name shouldBe "main.start.x"
|
||||
}
|
||||
})
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -66,21 +66,6 @@ class ConstExprEvaluator {
|
||||
return NumericLiteral.optimalNumeric(left.type, null, result.toDouble(), left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
if(leftDt.isByteOrBool)
|
||||
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
|
||||
else if(leftDt.isInteger) {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral.optimalNumeric((left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseOr(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
@@ -90,7 +75,7 @@ class ConstExprEvaluator {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() or right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral.optimalNumeric((left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left | $right", left.position)
|
||||
@@ -105,12 +90,27 @@ class ConstExprEvaluator {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() and right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral.optimalNumeric((left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
}
|
||||
|
||||
private fun bitwiseXor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
|
||||
if(right.type.isIntegerOrBool) {
|
||||
val leftDt = left.type
|
||||
if(leftDt.isByteOrBool)
|
||||
return NumericLiteral(BaseDataType.UBYTE, ((left.number.toInt() xor right.number.toInt()) and 255).toDouble(), left.position)
|
||||
else if(leftDt.isInteger) {
|
||||
if (leftDt == BaseDataType.UWORD || leftDt == BaseDataType.WORD)
|
||||
return NumericLiteral(BaseDataType.UWORD, ((left.number.toInt() xor right.number.toInt()) and 65535).toDouble(), left.position)
|
||||
else if (leftDt == BaseDataType.LONG)
|
||||
return NumericLiteral(BaseDataType.LONG, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left ^ $right", left.position)
|
||||
}
|
||||
|
||||
private fun logicalAnd(left: NumericLiteral, right: NumericLiteral): NumericLiteral =
|
||||
NumericLiteral.fromBoolean(left.asBooleanValue and right.asBooleanValue, left.position)
|
||||
|
||||
@@ -355,33 +355,10 @@ class ConstExprEvaluator {
|
||||
}
|
||||
|
||||
private fun evalString(func: FunctionCallExpression, args: List<NumericLiteral>): NumericLiteral? {
|
||||
return when(func.target.nameInSource[1]) {
|
||||
"isdigit" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 48..57, func.position)
|
||||
}
|
||||
"isupper" -> {
|
||||
// shifted petscii has 2 ranges that contain the upper case letters... 97-122 and 193-218
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 97..122 || char in 193..218, func.position)
|
||||
}
|
||||
"islower" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 65..90, func.position)
|
||||
}
|
||||
"isletter" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 65..90 || char in 97..122 || char in 193..218, func.position)
|
||||
}
|
||||
"isspace" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in setOf(32, 13, 9, 10, 141, 160), func.position)
|
||||
}
|
||||
"isprint" -> {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 32..127 || char>=160, func.position)
|
||||
}
|
||||
else -> null
|
||||
return if (func.target.nameInSource[1] == "isdigit") {
|
||||
val char = args[0].number.toInt()
|
||||
NumericLiteral.fromBoolean(char in 48..57, func.position)
|
||||
}
|
||||
else null
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,11 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
|
||||
}
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<AstModification> {
|
||||
// @( &thing ) --> thing (but only if thing is a byte type!)
|
||||
// @( &thing ) --> thing (but only if thing is unsigned byte or bool, this prevents sign extension errors)
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null) {
|
||||
if(addrOf.identifier?.inferType(program)?.isBytes==true)
|
||||
val it = addrOf.identifier?.inferType(program)
|
||||
if(it?.isUnsignedByte==true || it?.isBool==true)
|
||||
return listOf(AstReplaceNode(memread, addrOf.identifier!!, parent))
|
||||
}
|
||||
return noModifications
|
||||
|
||||
@@ -930,7 +930,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteral(idt.getOrUndef().base, 0.0, expr.position)
|
||||
} else if (cv in powersOfTwoFloat && leftVal?.type?.isUnsignedInteger == true) {
|
||||
} else if (cv in powersOfTwoFloat && expr.left.inferType(program).getOrUndef().isUnsignedInteger) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
return null
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.ParentSentinel
|
||||
import prog8.ast.Program
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
@@ -25,6 +26,7 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
|
||||
optimizer.visit(this)
|
||||
var tries=0
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0 && tries++ < 100000) {
|
||||
optimizer.linkAffectedParents(namespace)
|
||||
optimizer.visit(this)
|
||||
}
|
||||
require(tries<100000) { "endless loop in constantfold" }
|
||||
@@ -32,13 +34,14 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
|
||||
if (errors.noErrors()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
replacer.linkAffectedParents(namespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.noErrors())
|
||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||
namespace.linkParents(ParentSentinel) // re-link in final configuration
|
||||
}
|
||||
|
||||
|
||||
@@ -49,8 +52,7 @@ fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
val optimizer = StatementOptimizer(this, errors, functions, options)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
|
||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||
optimizer.linkAffectedParents(this.namespace)
|
||||
|
||||
return optimizationCount
|
||||
}
|
||||
@@ -61,11 +63,15 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
|
||||
|
||||
val inliner = Inliner(this, options)
|
||||
inliner.visit(this)
|
||||
return inliner.applyModifications()
|
||||
val mods = inliner.applyModifications()
|
||||
inliner.linkAffectedParents(namespace)
|
||||
return mods
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors, options)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
val mods = opti.applyModifications()
|
||||
opti.linkAffectedParents(namespace)
|
||||
return mods
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.*
|
||||
@@ -142,15 +139,16 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
stmt.target is NumericLiteral
|
||||
}
|
||||
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.isEmpty()) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is Subroutine || it is VarDecl }
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.size <= 1) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is Subroutine || (it is VarDecl && it.origin != VarDeclOrigin.SUBROUTINEPARAM) }
|
||||
if (!containsSubsOrVariables) {
|
||||
val hasOnlyBodyStatements = subroutine.statements.size == 1 ||
|
||||
(subroutine.statements.size == 2 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true)
|
||||
val functionalStatements = subroutine.statements.filter { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
val hasOnlyBodyStatements = functionalStatements.size == 1 ||
|
||||
(functionalStatements.size == 2 && functionalStatements.lastOrNull()?.let(::isEmptyReturn) == true)
|
||||
|
||||
if (hasOnlyBodyStatements) {
|
||||
if (subroutine !== program.entrypoint) {
|
||||
val bodyStmt = subroutine.statements.firstOrNull()
|
||||
val bodyStmt = functionalStatements.firstOrNull()
|
||||
val isAutoInlineable =
|
||||
when (val stmt = bodyStmt) {
|
||||
is Return -> isBodyInlineable(stmt)
|
||||
@@ -164,10 +162,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
}
|
||||
|
||||
if (subroutine.inline && subroutine.statements.size > 1) {
|
||||
if (subroutine.inline && functionalStatements.size > 1) {
|
||||
// Remove trailing return if it's empty and there's another body statement
|
||||
if (subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true) {
|
||||
subroutine.statements.removeLast()
|
||||
if (functionalStatements.lastOrNull()?.let(::isEmptyReturn) == true) {
|
||||
subroutine.statements.remove(functionalStatements.last())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,7 +174,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(identifier: IdentifierReference) {
|
||||
if (identifier.nameInSource.singleOrNull() in program.builtinFunctions.names) return
|
||||
identifier.targetStatement()?.let { target ->
|
||||
if (target is VarDecl && target.origin == VarDeclOrigin.SUBROUTINEPARAM) return@let
|
||||
val scoped = (target as INamedStatement).scopedName
|
||||
val scopedIdent = IdentifierReference(scoped, identifier.position)
|
||||
modifications += AstReplaceNode(identifier, scopedIdent, identifier.parent)
|
||||
@@ -246,8 +246,19 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
if (!sub.inline)
|
||||
return false to "subroutine not marked as inlineable"
|
||||
|
||||
if (sub.parameters.isNotEmpty()) {
|
||||
return false to "subroutines with parameters cannot be inlined in this compiler version"
|
||||
if (sub.isAsmSubroutine && sub.parameters.isNotEmpty()) {
|
||||
return false to "asmsub with parameters cannot be inlined"
|
||||
}
|
||||
|
||||
if (sub.parameters.size > 1) {
|
||||
return false to "subroutines with more than 1 parameter cannot be inlined in this compiler version"
|
||||
}
|
||||
|
||||
if (sub.parameters.size == 1) {
|
||||
val arg = fcall.args.first()
|
||||
if (!arg.isSimple) {
|
||||
return false to "argument is too complex to inline (might have side effects)"
|
||||
}
|
||||
}
|
||||
|
||||
if (options.compTarget.name != VMTarget.NAME) {
|
||||
@@ -293,7 +304,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
fun inlineFunctionBody(toInline: Return): Iterable<AstModification> {
|
||||
// call site is an expression, so we have to have a Return here in the inlined sub to provide the values
|
||||
return if (toInline.values.size == 1 && functionCallExpr !== toInline.values[0]) {
|
||||
// println(">>> INLINER: INLINED expression '${sub.name}' at ${functionCallExpr.position} (return value substituted)")
|
||||
sub.hasBeenInlined = true
|
||||
val substitutedReturn = substituteParameters(sub, functionCallExpr, toInline.values[0]) as Expression
|
||||
listOf(AstReplaceNode(functionCallExpr, substitutedReturn, parent))
|
||||
@@ -321,13 +331,11 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
|
||||
if (functionCalls.isEmpty()) {
|
||||
// No function calls in the return values - the void call has no side effects and can be removed.
|
||||
// println(">>> INLINER: INLINED void call to '${sub.name}' at ${functionCallStatement.position} (removed, no side effects)")
|
||||
sub.hasBeenInlined = true
|
||||
return listOf(AstRemove(origNode as Statement, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
// There are function calls in the return values - convert each to a void statement.
|
||||
// println(">>> INLINER: INLINED void call to '${sub.name}' at ${functionCallStatement.position} (converted ${functionCalls.size} inner call(s) to void)")
|
||||
sub.hasBeenInlined = true
|
||||
|
||||
val voidStatements: MutableList<Statement> = functionCalls.map { fcall ->
|
||||
@@ -347,7 +355,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
fun possiblyInlineFunctionBody(toInline: Statement): Iterable<AstModification> {
|
||||
val inlinedStatement = substituteParameters(sub, origNode as IFunctionCall, toInline) as Statement
|
||||
|
||||
// println(">>> INLINER: INLINED '${sub.name}' at ${functionCallStatement.position} (body inserted)")
|
||||
return if (origNode !== toInline) {
|
||||
sub.hasBeenInlined = true
|
||||
listOf(AstReplaceNode(origNode, inlinedStatement, parent))
|
||||
@@ -446,30 +453,56 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
private fun substituteParameters(sub: Subroutine, fcall: IFunctionCall, node: Node): Node {
|
||||
val paramVarDecls = sub.statements.filterIsInstance<VarDecl>().filter { it.origin == VarDeclOrigin.SUBROUTINEPARAM }
|
||||
val paramVarDecls = sub.parameters.map { param ->
|
||||
sub.statements.filterIsInstance<VarDecl>().find { it.origin == VarDeclOrigin.SUBROUTINEPARAM && it.name == param.name }
|
||||
?: throw FatalAstException("parameter ${param.name} not found in subroutine ${sub.name}")
|
||||
}
|
||||
val paramMap: Map<VarDecl, Expression> = paramVarDecls.zip(fcall.args).associate { it.first to it.second }
|
||||
|
||||
fun substitute(n: Node): Node {
|
||||
if (n is IdentifierReference) {
|
||||
val target = n.targetStatement(program.builtinFunctions)
|
||||
val arg = if (target is VarDecl) paramMap[target] else null
|
||||
if (arg != null) return arg.copy()
|
||||
if (target is VarDecl) {
|
||||
val arg = paramMap[target]
|
||||
if (arg != null) return arg.copy()
|
||||
}
|
||||
}
|
||||
|
||||
return when (n) {
|
||||
is BinaryExpression -> BinaryExpression(substitute(n.left) as Expression, n.operator, substitute(n.right) as Expression, n.position)
|
||||
is PrefixExpression -> PrefixExpression(n.operator, substitute(n.expression) as Expression, n.position)
|
||||
is DirectMemoryRead -> DirectMemoryRead(substitute(n.addressExpression) as Expression, n.position)
|
||||
is ArrayIndexedExpression -> ArrayIndexedExpression(
|
||||
n.plainarrayvar?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
subId
|
||||
},
|
||||
n.nestedArray?.let { substitute(it) as ArrayIndexedExpression },
|
||||
n.pointerderef?.let { substitute(it) as PtrDereference },
|
||||
substitute(n.indexer) as ArrayIndex,
|
||||
n.position
|
||||
)
|
||||
is ArrayIndex -> ArrayIndex(substitute(n.indexExpr) as Expression, n.position)
|
||||
is Return -> Return(n.values.map { substitute(it) as Expression }.toTypedArray(), n.position)
|
||||
is Assignment -> {
|
||||
val newTarget = n.target.copy()
|
||||
if (n.target.identifier != null) {
|
||||
val substituted = substitute(n.target.identifier!!)
|
||||
if (substituted is IdentifierReference) {
|
||||
newTarget.identifier = substituted
|
||||
}
|
||||
// if substituted is not an identifier, we leave the original (copied) identifier
|
||||
// in the target. This is not ideal but avoids the ClassCastException.
|
||||
newTarget.identifier = n.target.identifier?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
subId
|
||||
}
|
||||
newTarget.memoryAddress = n.target.memoryAddress?.let { DirectMemoryWrite(substitute(it.addressExpression) as Expression, it.position) }
|
||||
newTarget.arrayindexed = n.target.arrayindexed?.let { substitute(it) as ArrayIndexedExpression }
|
||||
newTarget.pointerDereference = n.target.pointerDereference?.let { substitute(it) as PtrDereference }
|
||||
newTarget.arrayIndexedDereference = n.target.arrayIndexedDereference?.let {
|
||||
ArrayIndexedPtrDereference(
|
||||
it.chain.map { (name, index) -> name to index?.let { substitute(it) as ArrayIndex } },
|
||||
it.derefLast,
|
||||
it.position
|
||||
)
|
||||
}
|
||||
|
||||
Assignment(
|
||||
newTarget,
|
||||
substitute(n.value) as Expression,
|
||||
@@ -478,12 +511,18 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
)
|
||||
}
|
||||
is FunctionCallExpression -> FunctionCallExpression(
|
||||
substitute(n.target) as IdentifierReference,
|
||||
substitute(n.target).let {
|
||||
if (it !is IdentifierReference) throw FatalAstException("parameter substituted by ${it::class.simpleName} but an identifier reference was expected")
|
||||
it
|
||||
},
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
n.position
|
||||
)
|
||||
is FunctionCallStatement -> FunctionCallStatement(
|
||||
substitute(n.target) as IdentifierReference,
|
||||
substitute(n.target).let {
|
||||
if (it !is IdentifierReference) throw FatalAstException("parameter substituted by ${it::class.simpleName} but an identifier reference was expected")
|
||||
it
|
||||
},
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
n.void,
|
||||
n.position
|
||||
@@ -493,6 +532,19 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
n.position
|
||||
)
|
||||
is Jump -> Jump(substitute(n.target) as Expression, n.position)
|
||||
is TypecastExpression -> TypecastExpression(substitute(n.expression) as Expression, n.type, n.implicit, n.position)
|
||||
is AddressOf -> AddressOf(
|
||||
n.identifier?.let {
|
||||
val subId = substitute(it)
|
||||
if (subId !is IdentifierReference) throw FatalAstException("parameter substituted by ${subId::class.simpleName} but an identifier reference was expected")
|
||||
subId
|
||||
},
|
||||
n.arrayIndex?.let { substitute(it) as ArrayIndex },
|
||||
n.dereference?.let { substitute(it) as PtrDereference },
|
||||
n.msb,
|
||||
n.typed,
|
||||
n.position
|
||||
)
|
||||
else -> n.copy()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,30 +313,21 @@ class StatementOptimizer(private val program: Program,
|
||||
|
||||
val rNum = (rExpr.right as? NumericLiteral)?.number
|
||||
if(rNum!=null) {
|
||||
if (op1 == "+" || op1 == "-") {
|
||||
if (op2 == "+") {
|
||||
// A = A +/- B + N ---> A = A +/- B ; A = A + N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val addConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
AstReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
AstInsert.after(assignment, addConstant, parent as IStatementContainer))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N ---> A = A +/- B ; A = A - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val subConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
AstReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
AstInsert.after(assignment, subConstant, parent as IStatementContainer))
|
||||
}
|
||||
if ((op1 == "+" || op1 == "-") && (op2 == "+" || op2 == "-")) {
|
||||
// A = A op1 (B op2 N) ---> A = A op1 B ; A = A secondOp N
|
||||
// only valid for additive operators (op2 must be + or -).
|
||||
// secondOp keeps the combined expression equivalent:
|
||||
// A+(B+N)=A+N, A+(B-N)=A-N, A-(B+N)=A-N, A-(B-N)=A+N
|
||||
val secondOp = if (op1 == "+") op2 else if (op2 == "+") "-" else "+"
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val opConstant = Assignment(
|
||||
assignment.target.copy(),
|
||||
BinaryExpression(binExpr.left.copy(), secondOp, rExpr.right, binExpr.position),
|
||||
AssignmentOrigin.OPTIMIZER, assignment.position
|
||||
)
|
||||
return listOf(
|
||||
AstReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
AstInsert.after(assignment, opConstant, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
plugins {
|
||||
id("application")
|
||||
kotlin("jvm")
|
||||
id("com.gradleup.shadow") version "9.3.2"
|
||||
id("com.peterabeles.gversion") version "1.10.3"
|
||||
id("com.gradleup.shadow") version "9.6.1"
|
||||
id("com.peterabeles.gversion") version "1.11.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -12,7 +12,6 @@ dependencies {
|
||||
implementation(project(":compilerAst"))
|
||||
implementation(project(":codeGenCpu6502"))
|
||||
implementation(project(":codeGenIntermediate"))
|
||||
implementation(project(":codeGenExperimental"))
|
||||
implementation(project(":intermediate"))
|
||||
implementation(project(":virtualmachine"))
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-cli:0.3.6")
|
||||
@@ -22,7 +21,6 @@ dependencies {
|
||||
testImplementation(testFixtures(project(":codeCore")))
|
||||
testImplementation(project(":intermediate"))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
testImplementation("com.github.irmen:ksim65:v2.1")
|
||||
}
|
||||
|
||||
@@ -43,6 +41,7 @@ application {
|
||||
tasks.shadowJar {
|
||||
archiveBaseName.set("prog8c")
|
||||
archiveVersion.set(version.toString())
|
||||
duplicatesStrategy = DuplicatesStrategy.WARN
|
||||
// minimize()
|
||||
}
|
||||
|
||||
@@ -52,7 +51,7 @@ gversion {
|
||||
className = "Version"
|
||||
language = "kotlin"
|
||||
debug = false
|
||||
annotate = true
|
||||
annotate = ""
|
||||
}
|
||||
|
||||
tasks.build {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="jetbrains.kotlinx.cli.jvm" level="project" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="io.kotest.framework.datatest" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="github.irmen.ksim65" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -534,6 +534,8 @@ sys {
|
||||
const word MAX_WORD = 32767
|
||||
const uword MIN_UWORD = 0
|
||||
const uword MAX_UWORD = 65535
|
||||
const long MIN_LONG = -2147483648
|
||||
const long MAX_LONG = 2147483647
|
||||
; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported
|
||||
|
||||
|
||||
|
||||
@@ -349,16 +349,28 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled.
|
||||
; Overdraws horizontal lines unfortunately.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -367,6 +379,10 @@ hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %1111
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*2+1)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*2+1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -446,4 +462,3 @@ _y_lookup_hi .byte >_plot_y_values
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -509,6 +509,8 @@ sys {
|
||||
const word MAX_WORD = 32767
|
||||
const uword MIN_UWORD = 0
|
||||
const uword MAX_UWORD = 65535
|
||||
const long MIN_LONG = -2147483648
|
||||
const long MAX_LONG = 2147483647
|
||||
; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported
|
||||
|
||||
|
||||
|
||||
@@ -439,12 +439,25 @@ gfx_hires {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -453,6 +466,10 @@ gfx_hires {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
@@ -462,12 +479,25 @@ gfx_hires {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -476,6 +506,10 @@ gfx_hires {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, ubyte @zp color) {
|
||||
|
||||
@@ -501,11 +501,24 @@ gfx_lores {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -514,6 +527,10 @@ gfx_lores {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, color)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
|
||||
@@ -523,6 +540,9 @@ gfx_lores {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
while radius>=yy {
|
||||
uword liney = ycenter+yy
|
||||
@@ -532,12 +552,22 @@ gfx_lores {
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-radius, lsb(liney), radius*$0002+1, color)
|
||||
|
||||
liney = ycenter+radius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
|
||||
liney = ycenter-radius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-yy, lsb(liney), yy*$0002+1, color)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
liney = ycenter+pendingRadius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(liney), pendingWidth*$0002+1, color)
|
||||
liney = ycenter-pendingRadius
|
||||
if msb(liney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(liney), pendingWidth*$0002+1, color)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
@@ -547,6 +577,14 @@ gfx_lores {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
uword flushLiney = ycenter+pendingRadius
|
||||
if msb(flushLiney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(flushLiney), pendingWidth*$0002+1, color)
|
||||
flushLiney = ycenter-pendingRadius
|
||||
if msb(flushLiney)==0
|
||||
safe_horizontal_line(xcenter-pendingWidth, lsb(flushLiney), pendingWidth*$0002+1, color)
|
||||
}
|
||||
}
|
||||
|
||||
asmsub plot(uword x @AX, ubyte y @Y, ubyte color @R0) {
|
||||
|
||||
@@ -604,17 +604,29 @@ drawmode: ora cx16.r15L
|
||||
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Warning: NO BOUNDS CHECKS. Make sure circle fits in the screen.
|
||||
; Midpoint algorithm, filled
|
||||
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -623,22 +635,38 @@ drawmode: ora cx16.r15L
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
; Does bounds checking and clipping.
|
||||
; Midpoint algorithm, filled
|
||||
; Note: has problems with INVERT draw mode because of horizontal span overdrawing. Horizontal lines may occur.
|
||||
if radius==0
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -647,6 +675,10 @@ drawmode: ora cx16.r15L
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
|
||||
@@ -11,7 +11,7 @@ prog8_lib {
|
||||
; NOTE: this is not in prog8_math because all the other sqrt routines are in prog8_funcs.asm here too
|
||||
|
||||
if num <= 0
|
||||
return 0
|
||||
sys.exit(99)
|
||||
|
||||
alias bit_pos = cx16.r0r1sl
|
||||
alias temp = cx16.r2r3sl
|
||||
|
||||
@@ -19,6 +19,13 @@ serial {
|
||||
const ubyte REG_DIVISOR_LATCH_LOW = 0 ;same as TXRX, but when bit-7 of line control high
|
||||
const ubyte REG_DIVISOR_LATCH_HI = 1 ;same as IRQE, but when bit-7 of line control high
|
||||
|
||||
; Note on 6502/65C02 indexed writes and UART registers:
|
||||
; 6502 indexed writes (STA (zp),Y) perform a dummy read before writing.
|
||||
; This can be problematic for hardware registers with read side effects.
|
||||
; However, Prog8 uses STA (zp) for offset 0 (TXRX buffer) which has NO dummy read.
|
||||
; For registers 1-7, the dummy reads hit harmless registers (LCR, MCR, IER, FCR, etc.)
|
||||
; that have no side effects on read, so indexed writes are safe here.
|
||||
|
||||
; Baud rate divisor values for TL16C2550 with 14.7456 MHz clock
|
||||
; Formula: divisor = 14745600 / (16 * baudrate)
|
||||
enum BAUD {
|
||||
@@ -39,6 +46,27 @@ serial {
|
||||
B300 = $0C00,
|
||||
}
|
||||
|
||||
sub get_baud_string(uword baud) -> str {
|
||||
when baud {
|
||||
BAUD::B921600 -> return "921600"
|
||||
BAUD::B460800 -> return "460800"
|
||||
BAUD::B230400 -> return "230400"
|
||||
BAUD::B115200 -> return "115200"
|
||||
BAUD::B57600 -> return "57600"
|
||||
BAUD::B38400 -> return "38400"
|
||||
BAUD::B28800 -> return "28800"
|
||||
BAUD::B19200 -> return "19200"
|
||||
BAUD::B14400 -> return "14400"
|
||||
BAUD::B9600 -> return "9600"
|
||||
BAUD::B4800 -> return "4800"
|
||||
BAUD::B2400 -> return "2400"
|
||||
BAUD::B1200 -> return "1200"
|
||||
BAUD::B600 -> return "600"
|
||||
BAUD::B300 -> return "300"
|
||||
else -> return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
; Bitfield masks for TL16C2550's Line Status Register
|
||||
enum LSR {
|
||||
DR = %00000001, ; Data Ready
|
||||
@@ -91,14 +119,14 @@ serial {
|
||||
}
|
||||
}
|
||||
|
||||
sub initialize_uart(uword uart_addr) {
|
||||
; 921600 baud, 8,N,1, AutoFlow Control, FIFOS, no interrupts
|
||||
sub initialize_uart(uword uart_addr, uword baud_divisor) {
|
||||
; 8,N,1, AutoFlow Control, FIFO, no interrupts, specified baudrate (based on BAUD enum)
|
||||
uart_addr[REG_LINE_CONTROL] = $80 ; Set DLAB
|
||||
uart_addr[REG_DIVISOR_LATCH_HI] = msb(BAUD::B921600)
|
||||
uart_addr[REG_DIVISOR_LATCH_LOW] = lsb(BAUD::B921600)
|
||||
uart_addr[REG_DIVISOR_LATCH_HI] = msb(baud_divisor)
|
||||
uart_addr[REG_DIVISOR_LATCH_LOW] = lsb(baud_divisor)
|
||||
uart_addr[REG_LINE_CONTROL] = $03 ; 8,N,1, DLAB off
|
||||
uart_addr[REG_FIFO_CONTROL] = $C3 ; FIFO enable & reset, trigger 14
|
||||
uart_addr[REG_MODEM_CONTROL] = $23 ; DTR/RTS + AutoFlow Control
|
||||
uart_addr[REG_FIFO_CONTROL] = $87 ; FIFO enable & reset both, RX trigger 8 bytes
|
||||
uart_addr[REG_MODEM_CONTROL] = $27 ; DTR/RTS + AutoFlow Control + OPA high (keep zimodem in stream mode)
|
||||
uart_addr[REG_INTERRUPT_ENABLE] = $00 ; No interrupts
|
||||
}
|
||||
|
||||
@@ -174,13 +202,13 @@ serial {
|
||||
|
||||
; zimodem routines:
|
||||
|
||||
sub zi_initialize(uword uart_addr) {
|
||||
; initializes the ZiModem on the given uart address
|
||||
sub zi_initialize(uword uart_addr, uword baud_divisor) {
|
||||
; initializes the ZiModem on the given uart address, with the specified baud rate (use the BAUD enum)
|
||||
zi_uart = uart_addr
|
||||
if zi_uart == 0
|
||||
return ; no uart present
|
||||
|
||||
initialize_uart(zi_uart)
|
||||
initialize_uart(zi_uart, baud_divisor)
|
||||
; ZiModem sends a version banner of the `ati` command when the ESP32 boots up.
|
||||
; Read it off if present. It is not always ready immediately so wait a tiny bit
|
||||
sys.wait(5)
|
||||
@@ -225,7 +253,6 @@ serial {
|
||||
}
|
||||
|
||||
sub zi_start_get_file(str filename) -> long {
|
||||
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
|
||||
; starts a file download from the given url, returns the size of the file to download.
|
||||
; you can download chunks of the file by repeatedly calling zi_get_file_chunk() until that returns 0
|
||||
write(zi_uart, iso:"at&g\"")
|
||||
@@ -289,7 +316,6 @@ serial {
|
||||
}
|
||||
|
||||
sub zi_get_file_chunk(^^ubyte @zp buffer, uword buffer_size, long remaining_file_size) -> uword {
|
||||
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
|
||||
; read the next chunk of data from the file, into buffer, up to buffer_size bytes.
|
||||
; returns the number of bytes read. 0 if no more data was available.
|
||||
|
||||
|
||||
@@ -1682,6 +1682,8 @@ sys {
|
||||
const word MAX_WORD = 32767
|
||||
const uword MIN_UWORD = 0
|
||||
const uword MAX_UWORD = 65535
|
||||
const long MIN_LONG = -2147483648
|
||||
const long MAX_LONG = 2147483647
|
||||
; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
; Play sounds from your PET speaker
|
||||
; Uses the VIA timer to be able to play them without blocking the program in a delay loop.
|
||||
; The VIA timer acts as an oscillator to generate tones without CPU involvement for the tone itself.
|
||||
; Notes lower than A3 cannot be played with this mechanism and need a custom CPU delay loop.
|
||||
;
|
||||
; This module provides both blocking and non-blocking sequenced playback.
|
||||
; Use play_note() or play_song() for simple blocking playback where the program waits for the duration.
|
||||
; Use song(), update() and is_playing() for non-blocking playback driven by a periodic timer (e.g. IRQ handler).
|
||||
;
|
||||
; inspiration: http://blog.tynemouthsoftware.co.uk/2022/05/pet-sounds.html
|
||||
;
|
||||
; Example:
|
||||
; Example of the simplest way to play some notes:
|
||||
; petsnd.on()
|
||||
; petsnd.note(petsnd.C_4)
|
||||
; sys.wait(30)
|
||||
@@ -24,40 +28,64 @@ petsnd {
|
||||
const ubyte OCTAVES_678 = 3
|
||||
|
||||
; notes:
|
||||
const ubyte REST = $00 ; silence (subsonic, timer wraps around)
|
||||
const ubyte A_SHARP_3 = $FF ; A#3 = 233.08 Hz
|
||||
const ubyte B_FLAT_3 = $FF ; A#3 = 233.08 Hz
|
||||
const ubyte B_3 = $FA ; B3 = 246.94 Hz
|
||||
const ubyte C_4 = $EE ; C4 = 261.63 Hz
|
||||
const ubyte C_SHARP_4 = $E0 ; C#4 = 277.18 Hz
|
||||
const ubyte D_FLAT_4 = $E0 ; C#4 = 277.18 Hz
|
||||
const ubyte D_4 = $D2 ; D4 = 293.66 Hz
|
||||
const ubyte D_SHARP_4 = $C7 ; D#4 = 311.13 Hz
|
||||
const ubyte E_FLAT_4 = $C7 ; D#4 = 311.13 Hz
|
||||
const ubyte E_4 = $BC ; E4 = 329.63 Hz
|
||||
const ubyte F_4 = $B1 ; F4 = 349.23 Hz
|
||||
const ubyte F_SHARP_4 = $A8 ; F#4 = 369.99 Hz
|
||||
const ubyte G_FLAT_4 = $A8 ; F#4 = 369.99 Hz
|
||||
const ubyte G_4 = $9E ; G4 = 392.00 Hz
|
||||
const ubyte G_SHARP_4 = $95 ; G#4 = 415.30 Hz
|
||||
const ubyte A_FLAT_4 = $95 ; G#4 = 415.30 Hz
|
||||
const ubyte A_4 = $8C ; A4 = 440.00 Hz
|
||||
const ubyte A_SHARP_4 = $85 ; A#4 = 466.16 Hz
|
||||
const ubyte B_FLAT_4 = $85 ; A#4 = 466.16 Hz
|
||||
const ubyte B_4 = $7D ; B4 = 493.78 Hz
|
||||
const ubyte C_5 = $76 ; C5 = 523.25 Hz
|
||||
const ubyte C_SHARP_5 = $6E ; C#5 = 554.37 Hz
|
||||
const ubyte D_FLAT_5 = $6E ; C#5 = 554.37 Hz
|
||||
const ubyte D_5 = $68 ; D5 = 587.33 Hz
|
||||
const ubyte D_SHARP_5 = $63 ; D#5 = 622.25 Hz
|
||||
const ubyte E_FLAT_5 = $63 ; D#5 = 622.25 Hz
|
||||
const ubyte E_5 = $5D ; E5 = 659.26 Hz
|
||||
const ubyte F_5 = $58 ; F5 = 698.46 Hz
|
||||
const ubyte F_SHARP_5 = $53 ; F#5 = 739.99 Hz
|
||||
const ubyte G_FLAT_5 = $53 ; F#5 = 739.99 Hz
|
||||
const ubyte G_5 = $4E ; G5 = 783.99 Hz
|
||||
const ubyte G_SHARP_5 = $4A ; G#5 = 830.61 Hz
|
||||
const ubyte A_FLAT_5 = $4A ; G#5 = 830.61 Hz
|
||||
const ubyte A_5 = $45 ; A5 = 880.00 Hz
|
||||
const ubyte A_SHARP_5 = $41 ; A#5 = 932.33 Hz
|
||||
const ubyte B_FLAT_5 = $41 ; A#5 = 932.33 Hz
|
||||
const ubyte B_5 = $3D ; B5 = 987.77 Hz
|
||||
const ubyte C_6 = $39 ; C6 = 1046.50 Hz
|
||||
|
||||
; sequencer state for non-blocking playback
|
||||
private ^^ubyte seq_notes
|
||||
private ^^ubyte seq_durations
|
||||
private ubyte seq_length
|
||||
private ubyte seq_position
|
||||
private ubyte seq_tick_counter
|
||||
private ubyte seq_playing
|
||||
private ubyte seq_gap = 1
|
||||
|
||||
|
||||
sub on() {
|
||||
; enable sound output via the VIA shift register
|
||||
pet.via1acr = $10 ; "shift out free running at T2 rate"
|
||||
octaves(petsnd.OCTAVES_456)
|
||||
}
|
||||
|
||||
sub off() {
|
||||
; disable sound output and clear any playing note
|
||||
pet.via1t2 = 0 ; clear note
|
||||
pet.via1acr = 0 ; stop sound.
|
||||
}
|
||||
@@ -74,6 +102,100 @@ petsnd {
|
||||
}
|
||||
|
||||
sub note(ubyte n) {
|
||||
; set the note frequency. use REST constant (0) for a silence rest.
|
||||
; higher values produce higher pitches.
|
||||
pet.via1t2 = n ; timer 2 rate (L)
|
||||
}
|
||||
|
||||
sub set_gap(ubyte ticks) {
|
||||
; set the number of silence ticks between notes in sequenced playback (default 1).
|
||||
; the note-on time is automatically adjusted: note = duration - gap, minimum 1 tick.
|
||||
; call this before song() to change the gap duration.
|
||||
seq_gap = ticks
|
||||
}
|
||||
|
||||
sub song(^^ubyte notes, ^^ubyte durations, ubyte length) {
|
||||
; prepare for non-blocking sequenced playback.
|
||||
; the durations array holds the total ticks per note slot (note-on + gap).
|
||||
; the gap is configurable via set_gap() (default 1 tick).
|
||||
; call on() before starting, and update() periodically to advance the sequencer.
|
||||
seq_notes = notes
|
||||
seq_durations = durations
|
||||
seq_length = length
|
||||
seq_position = 0
|
||||
seq_tick_counter = 1
|
||||
seq_playing = 2 ; start in "gap finished" phase so first note plays immediately
|
||||
}
|
||||
|
||||
sub update() -> bool {
|
||||
; Advance the sequencer by one tick.
|
||||
; always returns true to facilitate immediate chaining to the system IRQ handler.
|
||||
; the sequencer calls note() and off() internally as needed.
|
||||
; the durations array holds total ticks per note slot (note-on + gap).
|
||||
; the note-on time is adjusted automatically when a different gap is set via set_gap().
|
||||
|
||||
if seq_playing == 0
|
||||
return true
|
||||
|
||||
seq_tick_counter--
|
||||
if seq_tick_counter == 0 {
|
||||
if seq_playing == 2 {
|
||||
; gap finished, play the actual note
|
||||
advance()
|
||||
} else if seq_position >= seq_length {
|
||||
off()
|
||||
seq_playing = 0
|
||||
return true
|
||||
} else {
|
||||
; note duration expired
|
||||
if seq_gap > 0 {
|
||||
note(0)
|
||||
seq_tick_counter = seq_gap
|
||||
seq_playing = 2
|
||||
} else {
|
||||
; gap=0: seamless advance to next note, no silence
|
||||
if seq_position >= seq_length {
|
||||
off()
|
||||
seq_playing = 0
|
||||
return true
|
||||
}
|
||||
advance()
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
private sub advance() {
|
||||
note(seq_notes[seq_position])
|
||||
ubyte nt = seq_durations[seq_position] - seq_gap
|
||||
if nt == 0
|
||||
nt = 1
|
||||
seq_tick_counter = nt
|
||||
seq_position++
|
||||
seq_playing = 1
|
||||
}
|
||||
}
|
||||
|
||||
sub play_note(ubyte n, ubyte ticks) {
|
||||
; play a single note for a given duration in jiffy ticks (blocking).
|
||||
; requires on() to have been called before.
|
||||
note(n)
|
||||
sys.wait(ticks)
|
||||
}
|
||||
|
||||
sub play_song(^^ubyte notes, ^^ubyte durations, ubyte length) {
|
||||
; play a sequence of notes with given durations (blocking).
|
||||
; uses song()+update() internally with vsync timing.
|
||||
; requires on() to have been called before.
|
||||
song(notes, durations, length)
|
||||
do {
|
||||
sys.waitvsync()
|
||||
void update()
|
||||
} until not is_playing()
|
||||
}
|
||||
|
||||
sub is_playing() -> bool {
|
||||
; returns true if the non-blocking sequencer is currently playing a song
|
||||
return seq_playing != 0
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,8 @@ sys {
|
||||
const word MAX_WORD = 32767
|
||||
const uword MIN_UWORD = 0
|
||||
const uword MAX_UWORD = 65535
|
||||
const long MIN_LONG = -2147483648
|
||||
const long MAX_LONG = 2147483647
|
||||
; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported
|
||||
|
||||
|
||||
@@ -234,6 +236,77 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irq(uword handler @AY) clobbers(A) {
|
||||
; Install a custom IRQ handler to run on every system IRQ (60Hz).
|
||||
; The handler routine should return a boolean:
|
||||
; true (non-zero) to continue with the default system IRQ handler,
|
||||
; false (zero) to acknowledge the IRQ and return from interrupt immediately.
|
||||
; To restore the original IRQ handler, call restore_irq() afterwards.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
sta _vector
|
||||
sty _vector+1
|
||||
|
||||
lda cbm.CINV
|
||||
ldy cbm.CINV+1
|
||||
sta _old_irq
|
||||
sty _old_irq+1
|
||||
|
||||
lda #<_irq_handler
|
||||
sta cbm.CINV
|
||||
lda #>_irq_handler
|
||||
sta cbm.CINV+1
|
||||
plp
|
||||
rts
|
||||
|
||||
_irq_handler
|
||||
jsr sys.save_prog8_internals
|
||||
cld
|
||||
jsr _run_custom
|
||||
pha
|
||||
jsr sys.restore_prog8_internals
|
||||
pla
|
||||
beq _finish_irq
|
||||
jmp (_old_irq)
|
||||
|
||||
_finish_irq
|
||||
lda pet.via1t1l ; acknowledge VIA1 timer 1 interrupt
|
||||
pla
|
||||
tay
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
rti
|
||||
|
||||
_run_custom
|
||||
jmp (_vector)
|
||||
|
||||
.section BSS
|
||||
_vector .word ?
|
||||
_old_irq .word ?
|
||||
.send BSS
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
; Restore the original system IRQ handler that was saved by a prior set_irq() call.
|
||||
%asm {{
|
||||
php
|
||||
sei
|
||||
lda sys.set_irq._old_irq
|
||||
ora sys.set_irq._old_irq+1
|
||||
beq + ; check if it was actually set
|
||||
lda sys.set_irq._old_irq
|
||||
ldy sys.set_irq._old_irq+1
|
||||
sta cbm.CINV
|
||||
sty cbm.CINV+1
|
||||
+ plp
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A) {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; Note: on PET this simply waits until the next jiffy clock update, I don't know if a true vsync is possible there
|
||||
|
||||
@@ -388,6 +388,45 @@ _found lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
containment_longarray .proc
|
||||
; -- check if a value exists in a long array (4 bytes per element).
|
||||
; parameters: P8ZP_SCRATCH_W1: lower word of the value,
|
||||
; P8ZP_SCRATCH_W2: upper word of the value,
|
||||
; P8ZP_SCRATCH_PTR: address of the long array,
|
||||
; Y = number of elements in the array (>=1).
|
||||
; returns boolean 0/1 in A.
|
||||
sty P8ZP_SCRATCH_B1
|
||||
_loop lda P8ZP_SCRATCH_B1
|
||||
beq _notfound
|
||||
lda (P8ZP_SCRATCH_PTR)
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
bne _next
|
||||
ldy #1
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
cmp P8ZP_SCRATCH_W1+1
|
||||
bne _next
|
||||
ldy #2
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
cmp P8ZP_SCRATCH_W2
|
||||
bne _next
|
||||
ldy #3
|
||||
lda (P8ZP_SCRATCH_PTR),y
|
||||
cmp P8ZP_SCRATCH_W2+1
|
||||
bne _next
|
||||
lda #1
|
||||
rts
|
||||
_next clc
|
||||
lda P8ZP_SCRATCH_PTR
|
||||
adc #4
|
||||
sta P8ZP_SCRATCH_PTR
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_PTR+1
|
||||
+ dec P8ZP_SCRATCH_B1
|
||||
jmp _loop
|
||||
_notfound lda #0
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
memcopy_small .proc
|
||||
; copy up to a single page (256 bytes) of memory.
|
||||
|
||||
@@ -12,7 +12,7 @@ prog8_lib {
|
||||
; NOTE: this is not in prog8_math because all the other sqrt routines are in prog8_funcs.asm here too
|
||||
|
||||
if num <= 0
|
||||
return 0
|
||||
sys.exit(99)
|
||||
|
||||
alias bit_pos = cx16.r0r1sl
|
||||
alias temp = cx16.r2r3sl
|
||||
|
||||
@@ -64,56 +64,33 @@ asmsub tostr(float value @FAC1) clobbers(X) -> str @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
sub pow(float value, float power) -> float {
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr floats.CONUPK
|
||||
lda #<power
|
||||
ldy #>power
|
||||
jsr floats.FPWR
|
||||
ldx P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
inline asmsub pow(float base @FAC2, float power @FAC1) -> float @FAC1 {
|
||||
%asm{{
|
||||
jsr floats.FPWRT
|
||||
}}
|
||||
}
|
||||
|
||||
sub sin(float angle) -> float {
|
||||
inline asmsub sin(float angle @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
jmp SIN
|
||||
jsr floats.SIN
|
||||
}}
|
||||
}
|
||||
|
||||
sub cos(float angle) -> float {
|
||||
inline asmsub cos(float angle @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
jmp COS
|
||||
rts
|
||||
jsr floats.COS
|
||||
}}
|
||||
}
|
||||
|
||||
sub tan(float value) -> float {
|
||||
inline asmsub tan(float value @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
jmp TAN
|
||||
jsr floats.TAN
|
||||
}}
|
||||
}
|
||||
|
||||
sub atan(float value) -> float {
|
||||
inline asmsub atan(float value @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
jmp ATN
|
||||
jsr floats.ATN
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -140,20 +117,14 @@ sub secant(float value) -> float { return 1.0 / cos(value) }
|
||||
sub csc(float value) -> float { return 1.0 / sin(value) }
|
||||
sub cot(float value) -> float { return 1.0 / tan(value) }
|
||||
|
||||
sub ln(float value) -> float {
|
||||
inline asmsub ln(float value @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
jmp LOG
|
||||
jsr floats.LOG
|
||||
}}
|
||||
}
|
||||
|
||||
sub log2(float value) -> float {
|
||||
asmsub log2(float value @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<FL_LOG2_const
|
||||
@@ -166,58 +137,44 @@ FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
|
||||
}}
|
||||
}
|
||||
|
||||
sub rad(float angle) -> float {
|
||||
asmsub rad(float angle @FAC1) -> float @FAC1 {
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jmp FMULT
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
sub deg(float angle) -> float {
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
asmsub deg(float angle @FAC1) -> float @FAC1 {
|
||||
; -- convert radians to degrees (d * 180 / pi)
|
||||
%asm {{
|
||||
lda #<angle
|
||||
ldy #>angle
|
||||
jsr MOVFM
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jmp FMULT
|
||||
rts
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 180 / pi
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
sub round(float value) -> float {
|
||||
inline asmsub round(float value @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
jsr FADDH
|
||||
jmp INT
|
||||
jsr floats.FADDH
|
||||
jsr floats.INT
|
||||
}}
|
||||
}
|
||||
|
||||
sub floor(float value) -> float {
|
||||
inline asmsub floor(float value @FAC1) -> float @FAC1 {
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
jmp INT
|
||||
jsr floats.INT
|
||||
}}
|
||||
}
|
||||
|
||||
sub ceil(float value) -> float {
|
||||
asmsub ceil(float value @FAC1) -> float @FAC1 {
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
@@ -234,40 +191,108 @@ sub ceil(float value) -> float {
|
||||
}}
|
||||
}
|
||||
|
||||
sub rndseed(float seed) {
|
||||
if seed>0
|
||||
seed = -seed ; make sure fp seed is always negative
|
||||
|
||||
asmsub rndseed(float seed @FAC1) {
|
||||
; make sure fp seed is always negative (or zero)
|
||||
%asm {{
|
||||
lda #<seed
|
||||
ldy #>seed
|
||||
jsr MOVFM ; load float into fac1
|
||||
lda #-1
|
||||
jsr SIGN
|
||||
cmp #0
|
||||
beq +
|
||||
bmi +
|
||||
jsr NEGOP
|
||||
+ lda #-1
|
||||
jmp floats.RND
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub minf(float f1, float f2) -> float {
|
||||
if f1<f2
|
||||
return f1
|
||||
return f2
|
||||
%asm {{
|
||||
lda #<f1
|
||||
ldy #>f1
|
||||
jsr MOVFM
|
||||
lda #<f2
|
||||
ldy #>f2
|
||||
jsr FCOMP
|
||||
bmi +
|
||||
lda #<f2
|
||||
ldy #>f2
|
||||
jmp MOVFM
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub maxf(float f1, float f2) -> float {
|
||||
if f1>f2
|
||||
return f1
|
||||
return f2
|
||||
%asm {{
|
||||
lda #<f1
|
||||
ldy #>f1
|
||||
jsr MOVFM
|
||||
lda #<f2
|
||||
ldy #>f2
|
||||
jsr FCOMP
|
||||
bpl +
|
||||
lda #<f2
|
||||
ldy #>f2
|
||||
jmp MOVFM
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub clampf(float value, float minimum, float maximum) -> float {
|
||||
if value>maximum
|
||||
value=maximum
|
||||
if value>minimum
|
||||
return value
|
||||
return minimum
|
||||
%asm {{
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM
|
||||
lda #<maximum
|
||||
ldy #>maximum
|
||||
jsr FCOMP
|
||||
cmp #1
|
||||
beq _return_max
|
||||
lda #<minimum
|
||||
ldy #>minimum
|
||||
jsr FCOMP
|
||||
cmp #255
|
||||
beq _return_min
|
||||
rts
|
||||
_return_max
|
||||
lda #<maximum
|
||||
ldy #>maximum
|
||||
jmp MOVFM
|
||||
_return_min
|
||||
lda #<minimum
|
||||
ldy #>minimum
|
||||
jmp MOVFM
|
||||
}}
|
||||
}
|
||||
|
||||
sub mod(float value, float divisor) -> float {
|
||||
%asm {{
|
||||
; FAC = divisor
|
||||
lda #<divisor
|
||||
ldy #>divisor
|
||||
jsr MOVFM
|
||||
|
||||
; FDIV mem: FAC = mem / FAC = value / divisor
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr FDIV
|
||||
|
||||
; INT: FAC = floor(value / divisor)
|
||||
jsr INT
|
||||
|
||||
; FMULT mem: FAC *= divisor
|
||||
lda #<divisor
|
||||
ldy #>divisor
|
||||
jsr FMULT
|
||||
|
||||
; FSUB mem: FAC = mem - FAC = value - product
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr FSUB
|
||||
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub lerp(float v0, float v1, float t) -> float {
|
||||
|
||||
@@ -206,6 +206,10 @@ sub clampf(float value, float minimum, float maximum) -> float {
|
||||
return maximum
|
||||
}
|
||||
|
||||
sub mod(float value, float divisor) -> float {
|
||||
return value - floor(value / divisor) * divisor
|
||||
}
|
||||
|
||||
sub normalize(float value) -> float {
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -304,11 +304,24 @@ monogfx {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
while radius>=yy {
|
||||
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -317,6 +330,10 @@ monogfx {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub safe_disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, bool draw) {
|
||||
@@ -326,11 +343,24 @@ monogfx {
|
||||
return
|
||||
ubyte @zp yy = 0
|
||||
word @zp decisionOver2 = (1 as word)-radius
|
||||
ubyte pendingRadius
|
||||
ubyte pendingWidth
|
||||
bool hasPending = false
|
||||
while radius>=yy {
|
||||
safe_horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, draw)
|
||||
if hasPending and pendingRadius != radius {
|
||||
if pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
hasPending = false
|
||||
}
|
||||
if not hasPending {
|
||||
pendingRadius = radius
|
||||
hasPending = true
|
||||
}
|
||||
pendingWidth = yy
|
||||
yy++
|
||||
if decisionOver2>=0 {
|
||||
radius--
|
||||
@@ -339,6 +369,10 @@ monogfx {
|
||||
decisionOver2 += yy*$0002
|
||||
decisionOver2++
|
||||
}
|
||||
if hasPending and pendingRadius != pendingWidth {
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter+pendingRadius, pendingWidth*$0002+1, draw)
|
||||
safe_horizontal_line(xcenter-pendingWidth, ycenter-pendingRadius, pendingWidth*$0002+1, draw)
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword @zp xx, uword @zp yy, bool @zp draw) {
|
||||
|
||||
@@ -23,6 +23,8 @@ sys {
|
||||
const word MAX_WORD = 32767
|
||||
const uword MIN_UWORD = 0
|
||||
const uword MAX_UWORD = 65535
|
||||
const long MIN_LONG = -2147483648
|
||||
const long MAX_LONG = 2147483647
|
||||
; MIN_FLOAT and MAX_FLOAT are defined in the floats module if imported
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package prog8
|
||||
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.AstException
|
||||
import prog8.code.core.ErrorsReportedException
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.source.ImportFileSystem
|
||||
import prog8.code.source.ImportFileSystem.expandTilde
|
||||
@@ -58,7 +59,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val libDump by cli.option(ArgType.String, fullName = "libdump", description = "dump all the embedded library files into the specified output directory")
|
||||
val startEmulator1 by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
|
||||
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
|
||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||
val float2bytes by cli.option(ArgType.String, fullName = "float2bytes", description = "convert floating point number to a list of bytes for the target system. NOTE: you need to supply a target option too, and also still have to supply a dummy module file name as well!")
|
||||
val ignoreFootguns by cli.option(ArgType.Boolean, fullName = "ignorefootguns", description = "don't print warnings for 'footgun' issues: 'Yes I know I'm treading on mighty thin ice here'.")
|
||||
val profilingInstrumentation by cli.option(ArgType.Boolean, fullName = "profiling", description = "add subroutine profiling instrumentation (cx16 only).")
|
||||
@@ -73,14 +73,14 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val slabsGolden by cli.option(ArgType.Boolean, fullName = "slabsgolden", description = "put memory() slabs in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
|
||||
val slabsHighBank by cli.option(ArgType.Int, fullName = "slabshigh", description = "put memory() slabs in high memory area instead of at the end of the program. On the cx16 target the value specifies the HIRAM bank to use, on other systems this value is ignored.")
|
||||
val dontIncludeSourcelines by cli.option(ArgType.Boolean, fullName = "nosourcelines", description = "do not include original Prog8 source lines in generated asm code")
|
||||
val nostdlib by cli.option(ArgType.Boolean, fullName = "nostdlib", description = "disable loading of builtin standard library files from internal resources")
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
|
||||
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules. These are prepended to the module search path and have the highest priority.").multiple().delimiter(File.pathSeparator)
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of ${CompilationTargets.joinToString(",")} or a custom target properties file) (required)")
|
||||
val showTimings by cli.option(ArgType.Boolean, fullName = "timings", description = "show internal compiler timings (for performance analysis)")
|
||||
val varsGolden by cli.option(ArgType.Boolean, fullName = "varsgolden", description = "put uninitialized variables in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
|
||||
val varsHighBank by cli.option(ArgType.Int, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program. On the cx16 target the value specifies the HIRAM bank to use, on other systems this value is ignored.")
|
||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "run a .p8ir IR source file in the embedded VM")
|
||||
val vmTrace by cli.option(ArgType.Boolean, fullName = "vmtrace", description = "trace VM execution instruction by instruction (use with -vm or -emu)")
|
||||
val traceImports by cli.option(ArgType.Boolean, fullName = "traceimports", description = "trace all module imports and loads")
|
||||
val warnSymbolShadowing by cli.option(ArgType.Boolean, fullName = "warnshadow", description="show assembler warnings about symbol shadowing")
|
||||
val warnImplicitTypeCasts by cli.option(ArgType.Boolean, fullName = "warnimplicitcasts", description="show compiler warnings about implicit casts from a smaller to a larger type")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
||||
@@ -129,9 +129,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
val srcdirs = sourceDirs.map { expandTilde(it) }.toMutableList()
|
||||
if(sourceDirs.firstOrNull()!=".")
|
||||
srcdirs.add(0, ".")
|
||||
val srcdirs = sourceDirs.map { expandTilde(it) }
|
||||
|
||||
if(startVm==null) {
|
||||
if(compilationTarget==null) {
|
||||
@@ -214,7 +212,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
showTimings == true,
|
||||
asmListfile == true,
|
||||
dontIncludeSourcelines != true,
|
||||
experimentalCodegen == true,
|
||||
dumpVariables == true,
|
||||
dumpSymbols == true,
|
||||
varsHighBank,
|
||||
@@ -227,7 +224,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
printAst2 == true,
|
||||
ignoreFootguns == true,
|
||||
profilingInstrumentation == true,
|
||||
nostdlib == true,
|
||||
traceImports == true,
|
||||
processedSymbols,
|
||||
srcdirs,
|
||||
outputPath,
|
||||
@@ -300,7 +297,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
showTimings == true,
|
||||
asmListfile == true,
|
||||
dontIncludeSourcelines != true,
|
||||
experimentalCodegen == true,
|
||||
dumpVariables == true,
|
||||
dumpSymbols == true,
|
||||
varsHighBank,
|
||||
@@ -313,9 +309,9 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
printAst2 == true,
|
||||
ignoreFootguns == true,
|
||||
profilingInstrumentation == true,
|
||||
nostdlib == true,
|
||||
processedSymbols,
|
||||
absoluteSrcDirs,
|
||||
traceImports == true,
|
||||
processedSymbols,
|
||||
absoluteSrcDirs,
|
||||
outputPath,
|
||||
cwd = Path.of(System.getProperty("user.dir")),
|
||||
errors = ErrorReporter(txtcolors)
|
||||
@@ -366,7 +362,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
showTimings == true,
|
||||
asmListfile == true,
|
||||
dontIncludeSourcelines != true,
|
||||
experimentalCodegen == true,
|
||||
dumpVariables == true,
|
||||
dumpSymbols==true,
|
||||
varsHighBank,
|
||||
@@ -379,7 +374,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
printAst2 == true,
|
||||
ignoreFootguns == true,
|
||||
profilingInstrumentation == true,
|
||||
nostdlib == true,
|
||||
traceImports == true,
|
||||
processedSymbols,
|
||||
srcdirs,
|
||||
outputPath,
|
||||
@@ -800,7 +795,6 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
|
||||
showTimings = compilerArgs.showTimings,
|
||||
asmListfile = compilerArgs.asmListfile,
|
||||
includeSourcelines = compilerArgs.includeSourcelines,
|
||||
experimentalCodegen = compilerArgs.experimentalCodegen,
|
||||
dumpVariables = compilerArgs.dumpVariables,
|
||||
dumpSymbols = compilerArgs.dumpSymbols,
|
||||
varsHighBank = compilerArgs.varsHighBank,
|
||||
@@ -813,7 +807,7 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
|
||||
printAst2 = compilerArgs.printAst2,
|
||||
ignoreFootguns = compilerArgs.ignoreFootguns,
|
||||
profilingInstrumentation = compilerArgs.profilingInstrumentation,
|
||||
nostdlib = compilerArgs.nostdlib,
|
||||
traceImports = compilerArgs.traceImports,
|
||||
symbolDefs = compilerArgs.symbolDefs,
|
||||
sourceDirs = compilerArgs.sourceDirs,
|
||||
outputDir = compilerArgs.outputDir.toString(),
|
||||
@@ -843,6 +837,11 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
|
||||
return response
|
||||
}
|
||||
|
||||
System.out.print(response.stdout)
|
||||
System.out.flush()
|
||||
System.err.print(response.stderr)
|
||||
System.err.flush()
|
||||
|
||||
val txtcolors = if(plainText) ErrorReporter.PlainText else ErrorReporter.AnsiColors
|
||||
val reporter = ErrorReporter(txtcolors)
|
||||
for (error in response.errors) {
|
||||
@@ -853,13 +852,12 @@ private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: Compiler
|
||||
"INFO" -> reporter.info(error.message, pos)
|
||||
}
|
||||
}
|
||||
reporter.report()
|
||||
|
||||
System.out.print(response.stdout)
|
||||
System.out.flush()
|
||||
System.err.print(response.stderr)
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
try {
|
||||
reporter.report()
|
||||
} catch (x: ErrorsReportedException) {
|
||||
if(!x.message.isNullOrEmpty())
|
||||
reporter.printSingleError(x.message!!)
|
||||
}
|
||||
|
||||
return response
|
||||
} finally {
|
||||
|
||||
@@ -8,9 +8,7 @@ import prog8.ast.statements.Directive
|
||||
import prog8.buildversion.VERSION
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.ast.printAst
|
||||
import prog8.code.ast.verifyFinalAstBeforeAsmGen
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.optimize.optimizeSimplifiedAst
|
||||
import prog8.code.source.ImportFileSystem.expandTilde
|
||||
@@ -57,7 +55,6 @@ class CompilerArguments(val filepath: Path,
|
||||
val showTimings: Boolean,
|
||||
val asmListfile: Boolean,
|
||||
val includeSourcelines: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val dumpVariables: Boolean,
|
||||
val dumpSymbols: Boolean,
|
||||
val varsHighBank: Int?,
|
||||
@@ -70,7 +67,7 @@ class CompilerArguments(val filepath: Path,
|
||||
val printAst2: Boolean,
|
||||
val ignoreFootguns: Boolean,
|
||||
val profilingInstrumentation: Boolean,
|
||||
val nostdlib: Boolean,
|
||||
val traceImports: Boolean = false,
|
||||
val symbolDefs: Map<String, String>,
|
||||
val sourceDirs: List<String> = emptyList(),
|
||||
val outputDir: Path = Path(""),
|
||||
@@ -105,7 +102,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
var symbolTable: SymbolTable? = null
|
||||
|
||||
val totalTime = measureTime {
|
||||
val libraryDirs = if(compTarget.libraryPath!=null) listOf(compTarget.libraryPath.toString()) else emptyList()
|
||||
val libraryDirs = if(compTarget.libraryPath!=null) listOf(compTarget.libraryPath.toString()) else emptyList()
|
||||
val (parseresult, parseDuration) = measureTimedValue {
|
||||
parseMainModule(
|
||||
args.filepath,
|
||||
@@ -115,7 +112,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
libraryDirs,
|
||||
args.cwd,
|
||||
args.quietAll,
|
||||
args.nostdlib
|
||||
args.traceImports
|
||||
)
|
||||
}
|
||||
|
||||
@@ -131,7 +128,6 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
profilingInstrumentation = args.profilingInstrumentation
|
||||
asmListfile = args.asmListfile
|
||||
includeSourcelines = args.includeSourcelines
|
||||
experimentalCodegen = args.experimentalCodegen
|
||||
dumpVariables = args.dumpVariables
|
||||
dumpSymbols = args.dumpSymbols
|
||||
breakpointCpuInstruction = args.breakpointCpuInstruction
|
||||
@@ -253,10 +249,12 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
}
|
||||
simplifiedAstDuration = simplifiedAstDuration2
|
||||
|
||||
writeBankedCallsFile(intermediateAst, symbolTable!!, compilationOptions, args.errors)
|
||||
|
||||
createAssemblyDuration = measureTime {
|
||||
val result = createAssemblyAndAssemble(
|
||||
intermediateAst,
|
||||
symbolTable!!,
|
||||
symbolTable,
|
||||
args.errors,
|
||||
compilationOptions,
|
||||
program.generatedLabelSequenceNumber
|
||||
@@ -413,12 +411,12 @@ fun parseMainModule(filepath: Path,
|
||||
libraryDirs: List<String>,
|
||||
cwd: Path,
|
||||
quiet: Boolean,
|
||||
nostdlib: Boolean): Triple<Program, CompilationOptions, List<Path>> {
|
||||
traceImports: Boolean): Triple<Program, CompilationOptions, List<Path>> {
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
|
||||
bf.program = program
|
||||
|
||||
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs, libraryDirs, cwd, quiet, nostdlib)
|
||||
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs, libraryDirs, cwd, quiet, traceImports)
|
||||
val importedModuleResult = importer.importMainModule(filepath)
|
||||
importedModuleResult.onErr { throw it }
|
||||
errors.report()
|
||||
@@ -507,13 +505,13 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
|
||||
}
|
||||
}
|
||||
var launcherType = if (launcherTypeStr == null)
|
||||
CbmPrgLauncherType.BASIC
|
||||
compTarget.defaultLauncherType
|
||||
else {
|
||||
try {
|
||||
CbmPrgLauncherType.valueOf(launcherTypeStr)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// set default value; actual check and error handling of invalid option is handled in the AstChecker later
|
||||
CbmPrgLauncherType.BASIC
|
||||
compTarget.defaultLauncherType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,8 +569,14 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
fun removeUnusedCode(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
val remover = UnusedCodeRemover(program, errors, compilerOptions)
|
||||
remover.visit(program)
|
||||
while (errors.noErrors() && remover.applyModifications() > 0) {
|
||||
remover.visit(program)
|
||||
for(numCycles in 0..2000) {
|
||||
if (errors.noErrors() && remover.applyModifications() > 0)
|
||||
remover.visit(program)
|
||||
else
|
||||
break
|
||||
|
||||
if(numCycles==2000)
|
||||
throw InternalCompilerException("removeUnusedCode() is looping endlessly")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,12 +630,17 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
program.verifyFunctionArgTypes(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.moveMainBlockAsFirst(compilerOptions.compTarget)
|
||||
|
||||
val fixer = BeforeAsmAstChanger(program, compilerOptions, errors)
|
||||
fixer.visit(program)
|
||||
while (errors.noErrors() && fixer.applyModifications() > 0) {
|
||||
fixer.visit(program)
|
||||
for(numCycles in 0..2000) {
|
||||
if (errors.noErrors() && fixer.applyModifications() > 0)
|
||||
fixer.visit(program)
|
||||
else
|
||||
break
|
||||
|
||||
if(numCycles==2000)
|
||||
throw InternalCompilerException("BeforeAsmAstChanger() is looping endlessly")
|
||||
}
|
||||
|
||||
program.checkValid(errors, compilerOptions) // check if final tree is still valid
|
||||
@@ -639,8 +648,14 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||
|
||||
val cleaner = BeforeAsmTypecastCleaner(program, errors)
|
||||
cleaner.visit(program)
|
||||
while (errors.noErrors() && cleaner.applyModifications() > 0) {
|
||||
cleaner.visit(program)
|
||||
for(numCycles in 0..2000) {
|
||||
if (errors.noErrors() && cleaner.applyModifications() > 0)
|
||||
cleaner.visit(program)
|
||||
else
|
||||
break
|
||||
|
||||
if(numCycles==2000)
|
||||
throw InternalCompilerException("BeforeAsmTypecastCleaner() is looping endlessly")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,14 +668,25 @@ private fun createAssemblyAndAssemble(program: PtProgram,
|
||||
|
||||
val retainSSAforIR = true
|
||||
|
||||
val asmgen = if(compilerOptions.experimentalCodegen)
|
||||
prog8.codegen.experimental.ExperiCodeGen(retainSSAforIR)
|
||||
else if (compilerOptions.compTarget.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65C02))
|
||||
prog8.codegen.cpu6502.AsmGen6502(prefixSymbols = true, lastGeneratedLabelSequenceNr+1)
|
||||
else if (compilerOptions.compTarget.name == VMTarget.NAME)
|
||||
VmCodeGen(retainSSAforIR)
|
||||
else
|
||||
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}")
|
||||
// single pass to assign call site IDs for all backends
|
||||
val bankedExtsubs = findBankSelectorExtsubs(program, symbolTable)
|
||||
val asm6502CallIds = mutableMapOf<PtAsmSub, UByte>()
|
||||
val irCallIds = mutableMapOf<String, UByte>()
|
||||
bankedExtsubs.forEachIndexed { index, node ->
|
||||
if (index > 255) {
|
||||
errors.err("too many extsub banking call sites (max 255)", node.position)
|
||||
} else {
|
||||
asm6502CallIds[node] = index.toUByte()
|
||||
irCallIds[node.scopedName] = index.toUByte()
|
||||
}
|
||||
}
|
||||
errors.report()
|
||||
|
||||
val asmgen = when {
|
||||
compilerOptions.compTarget.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65C02) -> prog8.codegen.cpu6502.AsmGen6502(prefixSymbols = true, lastGeneratedLabelSequenceNr+1, asm6502CallIds)
|
||||
compilerOptions.compTarget.name == VMTarget.NAME -> VmCodeGen(retainSSAforIR, irCallIds)
|
||||
else -> throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.cpu}")
|
||||
}
|
||||
|
||||
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
|
||||
errors.report()
|
||||
|
||||
@@ -234,7 +234,6 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
showTimings = showTimings,
|
||||
asmListfile = asmListfile,
|
||||
includeSourcelines = includeSourcelines,
|
||||
experimentalCodegen = experimentalCodegen,
|
||||
dumpVariables = dumpVariables,
|
||||
dumpSymbols = dumpSymbols,
|
||||
varsHighBank = varsHighBank,
|
||||
@@ -247,7 +246,7 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
printAst2 = printAst2,
|
||||
ignoreFootguns = ignoreFootguns,
|
||||
profilingInstrumentation = profilingInstrumentation,
|
||||
nostdlib = nostdlib,
|
||||
traceImports = traceImports,
|
||||
symbolDefs = symbolDefs,
|
||||
sourceDirs = sourceDirs,
|
||||
outputDir = resolvedOutputDir,
|
||||
|
||||
@@ -13,7 +13,6 @@ internal data class DaemonRequest(
|
||||
val showTimings: Boolean,
|
||||
val asmListfile: Boolean,
|
||||
val includeSourcelines: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val dumpVariables: Boolean,
|
||||
val dumpSymbols: Boolean,
|
||||
val varsHighBank: Int?,
|
||||
@@ -26,7 +25,7 @@ internal data class DaemonRequest(
|
||||
val printAst2: Boolean,
|
||||
val ignoreFootguns: Boolean,
|
||||
val profilingInstrumentation: Boolean,
|
||||
val nostdlib: Boolean,
|
||||
val traceImports: Boolean,
|
||||
val symbolDefs: Map<String, String>,
|
||||
val sourceDirs: List<String>,
|
||||
val outputDir: String,
|
||||
@@ -70,7 +69,6 @@ internal object DaemonProtocol {
|
||||
append(prop("showTimings", req.showTimings))
|
||||
append(prop("asmListfile", req.asmListfile))
|
||||
append(prop("includeSourcelines", req.includeSourcelines))
|
||||
append(prop("experimentalCodegen", req.experimentalCodegen))
|
||||
append(prop("dumpVariables", req.dumpVariables))
|
||||
append(prop("dumpSymbols", req.dumpSymbols))
|
||||
append(prop("varsHighBank", req.varsHighBank))
|
||||
@@ -83,7 +81,7 @@ internal object DaemonProtocol {
|
||||
append(prop("printAst2", req.printAst2))
|
||||
append(prop("ignoreFootguns", req.ignoreFootguns))
|
||||
append(prop("profilingInstrumentation", req.profilingInstrumentation))
|
||||
append(prop("nostdlib", req.nostdlib))
|
||||
append(prop("traceImports", req.traceImports))
|
||||
append(prop("symbolDefs", req.symbolDefs))
|
||||
append(prop("sourceDirs", req.sourceDirs))
|
||||
append(prop("outputDir", req.outputDir))
|
||||
@@ -164,7 +162,6 @@ internal object DaemonProtocol {
|
||||
showTimings = map["showTimings"] as Boolean,
|
||||
asmListfile = map["asmListfile"] as Boolean,
|
||||
includeSourcelines = map["includeSourcelines"] as Boolean,
|
||||
experimentalCodegen = map["experimentalCodegen"] as Boolean,
|
||||
dumpVariables = map["dumpVariables"] as Boolean,
|
||||
dumpSymbols = map["dumpSymbols"] as Boolean,
|
||||
varsHighBank = map["varsHighBank"] as? Int,
|
||||
@@ -177,7 +174,7 @@ internal object DaemonProtocol {
|
||||
printAst2 = map["printAst2"] as Boolean,
|
||||
ignoreFootguns = map["ignoreFootguns"] as Boolean,
|
||||
profilingInstrumentation = map["profilingInstrumentation"] as Boolean,
|
||||
nostdlib = map["nostdlib"] as Boolean,
|
||||
traceImports = map["traceImports"] as Boolean,
|
||||
symbolDefs = (map["symbolDefs"] as? Map<*, *>)?.mapKeys { it.key as String }?.mapValues { it.value as String } ?: emptyMap(),
|
||||
sourceDirs = (map["sourceDirs"] as? List<*>)?.map { it as String } ?: emptyList(),
|
||||
outputDir = map["outputDir"] as String,
|
||||
|
||||
@@ -15,6 +15,7 @@ import prog8.parser.Prog8Parser
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
|
||||
|
||||
@@ -25,13 +26,19 @@ class ModuleImporter(private val program: Program,
|
||||
libraryDirs: List<String>,
|
||||
val cwd: Path,
|
||||
val quiet: Boolean,
|
||||
val nostdlib: Boolean = false) {
|
||||
val traceImports: Boolean = false) {
|
||||
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it).sanitize() }.toSortedSet().toList()
|
||||
private val libraryPaths: List<Path> = libraryDirs.map { Path(it).sanitize() }.toSortedSet().toList()
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it).sanitize() }.distinct()
|
||||
private val libraryPaths: List<Path> = libraryDirs.map { Path(it).sanitize() }.distinct()
|
||||
|
||||
private fun trace(message: String) {
|
||||
if (traceImports) {
|
||||
println("[import-trace] $message")
|
||||
}
|
||||
}
|
||||
|
||||
fun importMainModule(filePath: Path): Result<Module, NoSuchFileException> {
|
||||
val searchIn = sourcePaths.toSortedSet()
|
||||
val searchIn = (sourcePaths + listOf(Path("").absolute())).distinct()
|
||||
val normalizedFilePath = filePath.normalize()
|
||||
|
||||
if (normalizedFilePath.exists()) {
|
||||
@@ -43,6 +50,7 @@ class ModuleImporter(private val program: Program,
|
||||
val programPath = path.resolve(normalizedFilePath)
|
||||
if(programPath.exists()) {
|
||||
printCompileInfo(programPath)
|
||||
trace("Importing main module '$normalizedFilePath' from file: $programPath")
|
||||
val source = ImportFileSystem.getFile(programPath)
|
||||
return Ok(importModule(source))
|
||||
}
|
||||
@@ -109,31 +117,36 @@ class ModuleImporter(private val program: Program,
|
||||
return existing
|
||||
}
|
||||
|
||||
// try internal library first (unless --nostdlib is active)
|
||||
val importedModule =
|
||||
if(!nostdlib) {
|
||||
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
moduleResourceSrc.fold(
|
||||
success = { importModule(it) },
|
||||
failure = { getModuleFromFilesystem(moduleName, importingModule, import.position) }
|
||||
)
|
||||
} else {
|
||||
// skip internal libraries, go directly to filesystem
|
||||
getModuleFromFilesystem(moduleName, importingModule, import.position)
|
||||
// try filesystem first
|
||||
var importedModule = getModuleFromFilesystem(moduleName, importingModule, import.position, true)
|
||||
|
||||
// try internal library
|
||||
if(importedModule == null) {
|
||||
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName, importingModule?.name ?: "~implicit~")
|
||||
moduleResourceSrc.onOk {
|
||||
importedModule = importModule(it)
|
||||
}
|
||||
}
|
||||
|
||||
// if still not found, report error
|
||||
if (importedModule == null)
|
||||
getModuleFromFilesystem(moduleName, importingModule, import.position, false)
|
||||
|
||||
if(importedModule != null)
|
||||
removeDirectivesFromImportedModule(importedModule)
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun getModuleFromFilesystem(moduleName: String, importingModule: Module?, errorPosition: Position): Module? {
|
||||
val moduleSrc = getModuleFromFile(moduleName, importingModule)
|
||||
private fun getModuleFromFilesystem(moduleName: String, importingModule: Module?, errorPosition: Position, suppressError: Boolean): Module? {
|
||||
val (moduleSrc, searchedPaths) = getModuleFromFile(moduleName, importingModule)
|
||||
return moduleSrc.fold(
|
||||
success = { importModule(it) },
|
||||
failure = {
|
||||
val searchPaths = if(nostdlib) "$sourcePaths (internal libraries disabled)" else "$sourcePaths (and internal libraries)"
|
||||
errors.err("no module found with name $moduleName. Searched in: $searchPaths", errorPosition)
|
||||
if (!suppressError) {
|
||||
val requestedBy = importingModule?.name ?: "~implicit~"
|
||||
val searchPaths = "$searchedPaths (and internal libraries)"
|
||||
errors.err("no module found with name $moduleName (imported by '$requestedBy'). Searched in: $searchPaths", errorPosition)
|
||||
}
|
||||
null
|
||||
}
|
||||
)
|
||||
@@ -148,39 +161,66 @@ class ModuleImporter(private val program: Program,
|
||||
importedModule.statements.addAll(0, directives)
|
||||
}
|
||||
|
||||
private fun getModuleFromResource(name: String, compilationTargetName: String): Result<SourceCode, NoSuchFileException> {
|
||||
val result =
|
||||
runCatching { ImportFileSystem.getResource("/prog8lib/$compilationTargetName/$name") }
|
||||
.orElse { runCatching { ImportFileSystem.getResource("/prog8lib/$name") } }
|
||||
private fun getModuleFromResource(name: String, compilationTargetName: String, requestedBy: String): Result<SourceCode, NoSuchFileException> {
|
||||
val targetPath = "/prog8lib/$compilationTargetName/$name"
|
||||
val generalPath = "/prog8lib/$name"
|
||||
|
||||
val result = runCatching { ImportFileSystem.getResource(targetPath) }
|
||||
.onOk { trace("Importing module '${name.removeSuffix(".p8")}' from resource: $targetPath (target-specific library) - imported by '$requestedBy'") }
|
||||
.orElse {
|
||||
runCatching { ImportFileSystem.getResource(generalPath) }
|
||||
.onOk { trace("Importing module '${name.removeSuffix(".p8")}' from resource: $generalPath (general library) - imported by '$requestedBy'") }
|
||||
}
|
||||
|
||||
return result.mapError { NoSuchFileException(File(name)) }
|
||||
}
|
||||
|
||||
private fun getModuleFromFile(name: String, importingModule: Module?): Result<SourceCode, NoSuchFileException> {
|
||||
internal fun getModuleFromFile(name: String, importingModule: Module?): Pair<Result<SourceCode, NoSuchFileException>, List<Path>> {
|
||||
val fileName = "$name.p8"
|
||||
val requestedBy = importingModule?.name ?: "~implicit~"
|
||||
|
||||
val cwd = Path("").absolute()
|
||||
val normalLocations =
|
||||
if (importingModule == null) {
|
||||
sourcePaths
|
||||
} else {
|
||||
(sourcePaths + listOf(cwd)).distinct()
|
||||
} else if (SourceCode.isRegularFilesystemPath(importingModule.position.file)) {
|
||||
val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).sanitize()
|
||||
listOf(pathFromImportingModule) + sourcePaths
|
||||
(sourcePaths + listOf(pathFromImportingModule, cwd)).distinct()
|
||||
} else {
|
||||
// library/string resources have no neighboring directory on disk
|
||||
(sourcePaths + listOf(cwd)).distinct()
|
||||
}
|
||||
|
||||
val libraryPathsSet = libraryPaths.toSet()
|
||||
val searched = mutableListOf<Path>()
|
||||
normalLocations.forEach {
|
||||
searched.add(it)
|
||||
try {
|
||||
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"
|
||||
else -> "neighboring directory"
|
||||
}
|
||||
trace("Importing module '$name' from file: $file (found in $origin) - imported by '$requestedBy'")
|
||||
return Ok(source) to searched
|
||||
} catch (_: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
libraryPaths.forEach {
|
||||
searched.add(it)
|
||||
try {
|
||||
return Ok(ImportFileSystem.getFile(it.resolve(fileName), true))
|
||||
val file = it.resolve(fileName)
|
||||
val source = ImportFileSystem.getFile(file, true)
|
||||
trace("Importing module '$name' from file: $file (found in libdirs) - imported by '$requestedBy'")
|
||||
return Ok(source) to searched
|
||||
} catch (_: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
normalLocations.forEach {
|
||||
try {
|
||||
return Ok(ImportFileSystem.getFile(it.resolve(fileName)))
|
||||
} catch (_: NoSuchFileException) {
|
||||
}
|
||||
}
|
||||
|
||||
return Err(NoSuchFileException(File("name")))
|
||||
return Err(NoSuchFileException(File(name))) to searched
|
||||
}
|
||||
}
|
||||
Loaded 100 of 233 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user