mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86f9b6aa69 | ||
|
|
33f30d925e | ||
|
|
db227679a8 | ||
|
|
0f3475128a | ||
|
|
d451953e40 | ||
|
|
d505c42d15 | ||
|
|
5a593b9ef3 | ||
|
|
b411914624 | ||
|
|
0f80b695e7 | ||
|
|
276627abcb | ||
|
|
34225d10aa | ||
|
|
282c3aa00b | ||
|
|
19f3b03ac3 | ||
|
|
6cb6f37871 | ||
|
|
d0edc0b484 | ||
|
|
e9a3ed457e | ||
|
|
6001da30d8 | ||
|
|
e983c0c2b9 | ||
|
|
131134d24b |
No files matched your search
@@ -19,6 +19,7 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
- **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.
|
||||
- `-` refers to the MOST RECENT `-` label, `--` to the one before that, etc.
|
||||
@@ -36,23 +37,35 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
rts
|
||||
++ inc $d020 ; third forward label
|
||||
```
|
||||
- Data directives: `.byte`, `.word`, `.dword`
|
||||
- Data directives: `.byte`, `.word`, `.dword`, `.fill` (allocate space)
|
||||
- Equates: `label = value` (not `label .equ value` or `#define`)
|
||||
- **Number Literals**: Hex `$1234`, Binary `%10101010`, Decimal `123`.
|
||||
- Zero-page variables defined with `=`
|
||||
- `.text` for inline string data
|
||||
- **Conditional assembly**: `.if`, `.elsif`, `.else`, `.endif`
|
||||
- **Memory Sections**: `.section <name>`, `.send <name>` (common sections: `CODE`, `DATA`, `BSS`, `BSS_NOCLEAR`)
|
||||
|
||||
## Instructions
|
||||
- **Instructions like `rol`, `ror`, `asl`, `lsr`, `php`, `pla` require an explicit operand for accumulator**: write `rol a`, `ror a`, not just `rol`/`ror`
|
||||
- **Instructions like `rol`, `ror`, `asl`, `lsr` (and `inc`, `dec` on 65C02) require an explicit operand for accumulator**: write `rol a`, `inc a`, etc., not just `rol` or `inc`.
|
||||
- Standard 6502 addressing modes: implied, immediate (`#`), zero-page (`zp`), zero-page,X (`zp,x`), absolute (`abs`), absolute,X (`abs,x`), absolute,Y (`abs,y`), indirect (`(abs)`), indirect,X (`(zp,x)`), indirect,Y (`(zp),y`), relative (branches), accumulator
|
||||
- Branches: `bne`, `beq`, `bmi`, `bpl`, `bcs`, `bcc`, `bvs`, `bvc` (relative, max +127/-128 bytes)
|
||||
- Jumps: `jmp` (absolute or indirect), `jsr`/`rts` (subroutine call/return)
|
||||
- No `push`/`pop` mnemonics — use `pha`/`pla` (byte) and `txa`/`phx`/`plx`/`tay`/`phy`/`ply` for registers
|
||||
|
||||
### Instruction Side Effects (Flags)
|
||||
- **`Z` (Zero)**: Set if the result of an operation is 0.
|
||||
- **`N` (Negative)**: Set if bit 7 of the result is 1.
|
||||
- **`C` (Carry)**: Used for unsigned overflow and shifts. `cmp` sets `C` if `Register >= Operand`.
|
||||
- **`V` (Overflow)**: Set if a signed arithmetic operation overflowed.
|
||||
- **Commonly affected by**: `lda`, `ldx`, `ldy`, `inx`, `dex`, `tax`, `tay`, `txa`, `tya`, `and`, `ora`, `eor`, `asl`, `lsr`, `rol`, `ror`, `adc`, `sbc`, `cmp`, `cpx`, `cpy`, `bit`.
|
||||
- **Note**: `lda`, `ldx`, `ldy` do NOT affect the Carry flag. Only `adc`, `sbc`, `cmp`, and shift/rotate instructions affect Carry.
|
||||
|
||||
## 6502 vs 65C02
|
||||
- **CX16 target only**: can use 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `clr`, `ina`, `dea`, `cmp (zp)`, `dec`/`inc abs,x` etc.
|
||||
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra`
|
||||
- **CX16 target only**: can use WDC 65C02 instructions — `stz`, `phx`, `plx`, `phy`, `ply`, `bra`, `trb`, `tsb`, `stp`, `wai`, `inc a`, `dec a`, `bit #imm`, `bit zp,x`, `bit abs,x`, `jmp (abs,x)`, and `(zp)` indirect addressing mode (e.g., `lda (zp)`).
|
||||
- **C64, C128, PET32 targets**: original 6502 only — no `stz`, no `phx`/`plx`/`phy`/`ply`, no `bra` etc.
|
||||
- **Note**: The Rockwell/bit-manipulation instructions (`rmb`, `smb`, `bbr`, `bbs`) are **NOT** available.
|
||||
- Check the target before using 65C02-specific instructions
|
||||
- **6502 / 65C02 instruction reference**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=2
|
||||
- **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).
|
||||
|
||||
## Calling Convention / Register Conventions
|
||||
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
|
||||
@@ -74,8 +87,9 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
- Parameters passed via registers: `@A`, `@X`, `@Y`, `@AX` (A low, X high), `@AY` (A low, Y high), `@R0`-`@R15`, `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
|
||||
- Return value: `-> type @register` — also via `@Pz`/`@Pc` for flags
|
||||
- Clobbers: `clobbers (A, X, Y)` — MUST list all modified registers
|
||||
- **Parameter names are documentation only** — use the actual registers in assembly, NOT parameter names
|
||||
- **CRITICAL**: Parameter names in `asmsub` are **documentation only**. You MUST use the actual registers in your assembly code, NOT the parameter names (unless you create aliases yourself).
|
||||
- Create symbolic aliases at assembly top for clarity: `x1 = cx16.r0`, `y1 = cx16.r0L`
|
||||
- Accessing Prog8 parameters if they were NOT mapped to registers: use `p8v_paramname`. (Mapping to registers is preferred for speed).
|
||||
|
||||
Example:
|
||||
```prog8
|
||||
@@ -133,12 +147,31 @@ All Prog8 symbols are prefixed when accessed from assembly:
|
||||
|
||||
## Common 6502 Patterns
|
||||
|
||||
### Common Branch Logic (Comparisons)
|
||||
| Logic | Unsigned | Signed |
|
||||
|-------|----------|--------|
|
||||
| `A == imm` | `cmp #imm`, `beq label` | (Same) |
|
||||
| `A != imm` | `cmp #imm`, `bne label` | (Same) |
|
||||
| `A < imm` | `cmp #imm`, `bcc label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bmi label` |
|
||||
| `A >= imm` | `cmp #imm`, `bcs label` | `sec`, `sbc #imm`, `bvc *+4`, `eor #$80`, `bpl label` |
|
||||
| `A <= imm` | `beq label`, `bcc label` | (Use complex signed logic or reorder) |
|
||||
| `A > imm` | `beq +`, `bcs label`, `+` | (Use complex signed logic or reorder) |
|
||||
|
||||
### Looping (downto with BNE)
|
||||
```asm
|
||||
ldx #count
|
||||
loop ; do work here
|
||||
dex
|
||||
bne loop ; loop while X != 0
|
||||
bne loop ; loop while X != 0 (runs 'count' times)
|
||||
```
|
||||
|
||||
### Self-Modifying Code (SMC) Detection
|
||||
Look for `sta`, `stx`, or `sty` pointing into code labels:
|
||||
```asm
|
||||
lda #$42
|
||||
sta _target+1 ; Modifies the immediate operand of the LDA at _target
|
||||
...
|
||||
_target lda #$00 ; This #$00 will be replaced by #$42 at runtime
|
||||
```
|
||||
|
||||
### Indirect indexed read (table of data)
|
||||
@@ -169,7 +202,76 @@ loop ; do work here
|
||||
ldy p8v_myword+1 ; loads MSB (word variables are stored LSB-first)
|
||||
```
|
||||
|
||||
## CPU Quirks and Pitfalls
|
||||
|
||||
### JMP ($xxFF) Page Wrap Bug (NMOS 6502)
|
||||
- **Problem**: On original 6502 CPUs, `jmp ($caff)` will fetch the LSB from `$caff` but the MSB from `$ca00` (instead of `$cb00`).
|
||||
- **Target**: Affects C64, C128, PET32.
|
||||
- **Solution**: Avoid placing indirect jump vectors on a page boundary, or use the CX16 (65C02) which fixed this bug.
|
||||
|
||||
### BRK Instruction and the "Signature Byte"
|
||||
- **Behavior**: After a `BRK` instruction, the return address on the stack is incremented by **2**. This means the CPU skips the byte immediately following the `BRK` opcode.
|
||||
- **Usage**: This skipped byte is often used as a "signature" or parameter byte for the BRK handler.
|
||||
|
||||
### BIT Instruction Flags
|
||||
- **Absolute/Zero-page**: `bit $1234` copies bit 7 of the memory value to the **N** flag and bit 6 to the **V** flag. The **Z** flag is set based on `A AND memory`.
|
||||
- **Immediate (65C02 only)**: `bit #$01` only affects the **Z** flag; it does **NOT** modify N or V.
|
||||
- **NMOS 6502**: Does **not** support `bit #imm`.
|
||||
|
||||
### The "B" (Break) Flag
|
||||
- **Quirk**: The B flag (bit 4 of the status register) doesn't actually exist in the hardware status register. It only exists on the stack after a `PHP` or `BRK` instruction (set to 1) or a hardware IRQ/NMI (set to 0).
|
||||
- **Detection**: To tell if an interrupt was caused by `BRK` or a hardware IRQ, your handler must `pla`, `and #$10`, and check the result.
|
||||
|
||||
### Decimal Mode Flag (D) Persistence
|
||||
- **Pitfall**: On NMOS 6502, the `D` flag is **not** cleared on interrupt. Always use `cld` in IRQ handlers. On 65C02, it is cleared automatically, but `cld` is still good practice.
|
||||
- **ADC/SBC**: Be extremely careful with arithmetic if you haven't explicitly set or cleared the `D` flag, as its state might be unknown.
|
||||
|
||||
## Optimization Tips
|
||||
- **`stz` (65C02 only)**: Saves cycles and bytes compared to `lda #0`, `sta ...`.
|
||||
- **`bra` (65C02 only)**: Shorter and usually faster than `jmp`.
|
||||
- **Avoid `clc` before `bcc`**: `cmp` already sets the carry flag correctly for `bcc`/`bcs`.
|
||||
- **`inx` / `dex` vs `clc`+`adc #1`**: Incrementing/decrementing is faster and doesn't affect the carry flag.
|
||||
- **Zero-page usage**: Accessing variables in zeropage is 1 cycle faster and 1 byte shorter than absolute addressing.
|
||||
- **Cycle Counting**: Most instructions take 2-4 cycles. `jsr` takes 6, `rts` takes 6. Branches take 2 (no branch), 3 (branch taken), or 4 (branch taken across page boundary).
|
||||
|
||||
## 64tass Macros
|
||||
Define macros for common tasks:
|
||||
```asm
|
||||
pushax .macro
|
||||
pha
|
||||
txa
|
||||
pha
|
||||
.endm
|
||||
|
||||
popax .macro
|
||||
pla
|
||||
tax
|
||||
pla
|
||||
.endm
|
||||
```
|
||||
Call them with `#pushax` and `#popax`.
|
||||
|
||||
## IRQ Handler Best Practices
|
||||
- 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
|
||||
- **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:
|
||||
|
||||
- `--ascii`: **CRITICAL.** Prog8 generates character and string data in ASCII. Without this flag, `64tass` defaults to PETSCII, which will garble your strings.
|
||||
- `--case-sensitive`: Prog8 is case-sensitive and expects the assembler to be as well.
|
||||
- `--long-branch`: Enables automatic conversion of relative branches (`beq`, `bne`, etc.) to absolute jumps if the target is out of range. Prog8 relies on this.
|
||||
- `-Wno-implied-reg`: Suppresses warnings when the accumulator `a` is omitted from instructions like `rol`, `lsr`, etc. (though the skill recommends always using `rol a` for clarity).
|
||||
- `-Wall`: Enables all warnings.
|
||||
- `--cbm-prg` (or `--atari-xex` / `--nostart`): Sets the output format and adds the appropriate load address header.
|
||||
|
||||
### Optional but Recommended for Debugging:
|
||||
- `--vice-labels --labels=labels.txt`: Generates a label file that can be loaded into the VICE monitor (`load_labels "labels.txt"`) to see your Prog8 symbol names while debugging.
|
||||
- `--list=listing.txt`: Generates a full assembly listing file with addresses and opcodes.
|
||||
|
||||
### Example manual invocation:
|
||||
```bash
|
||||
64tass --ascii --case-sensitive --long-branch -Wall --cbm-prg -o myprogram.prg myprogram.asm
|
||||
```
|
||||
@@ -26,6 +26,12 @@ Follow ALL the rules below carefully.
|
||||
- **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
|
||||
- **`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 ...`
|
||||
|
||||
@@ -92,7 +98,6 @@ No call stack for variable storage — recursion overwrites locals. To handle it
|
||||
- Bitwise operators: `&`, `|`, `^`, `~`, `<<`, `>>`
|
||||
- Bit rotation: `rol()`/`ror()` (through carry), `rol2()`/`ror2()` (no carry)
|
||||
- CPU status flag branches: `if_cs`, `if_cc`, `if_z`, `if_nz` (compile to single 6502 branch instructions)
|
||||
- Use `when` with choice blocks instead of multiple `if`
|
||||
- If-expressions for simple value assignments based on a choice
|
||||
- **Optional braces in if/else**: when the `if` or `else` body is a single statement, the `{ }` can be omitted. Place the statement on the next line, indented. Example:
|
||||
```
|
||||
@@ -103,6 +108,14 @@ No call stack for variable storage — recursion overwrites locals. To handle it
|
||||
- `goto`, labels, jump lists allowed
|
||||
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
|
||||
|
||||
### The when Statement
|
||||
The `when` statement is a control flow construct that enables you to execute a specific action based on the value of an expression. It is generally more readable and often more efficient than a sequence of `if-else if` statements, as the compiler can optimize it into more efficient branching structures, such as a jump table.
|
||||
- **Expression**: Evaluates an expression and compares it against case values.
|
||||
- **Cases**: Defined by a value followed by the `->` operator.
|
||||
- **Blocks**: Use `{ }` to enclose multiple statements for a case.
|
||||
- **Else Clause**: Serves as a default handler; mandatory unless the expression type is fully covered.
|
||||
- **Efficiency**: Recommended for handling sets of fixed choices as it typically results in better assembly code.
|
||||
|
||||
## Loop Constructs
|
||||
Prog8 supports these loop types. All support `break` and `continue` (except `unroll`).
|
||||
|
||||
@@ -180,9 +193,9 @@ unroll 80 {
|
||||
```
|
||||
|
||||
## Subroutines & Return Values
|
||||
- Symbols public by default; use `private` to restrict to block
|
||||
- **Don't use `private` on subroutines and variables** (including nested ones) unless the user asks for it. Everything is public by default in Prog8 — follow that convention.
|
||||
- `inline` keyword for subroutines to suggest inlining
|
||||
- No function overloading (except builtins). Cannot use builtin names (msw, lsw, msb, lsb, mkword, mklong, peek, peekw, peekl, etc.) as variable/sub names
|
||||
- No **function overloading** (except builtins) and no **polymorphism** in general. This means you must call specific routines for different types (e.g., `txt.print_ub(val)` vs `txt.print_w(val)` instead of a generic `print(val)`). Cannot use builtin names (msw, lsw, msb, lsb, mkword, mklong, peek, peekw, peekl, etc.) as variable/sub names
|
||||
- Can return 0, 1, or multiple values: `a, b, c = routine()`. Use `void` to skip: `void routine()`, `a, void, c = routine()`
|
||||
- Nested subroutines access parent scope variables directly
|
||||
|
||||
@@ -242,7 +255,7 @@ Used to call routines at fixed memory addresses (like ROM KERNAL routines or thi
|
||||
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
|
||||
- Online location: https://prog8.readthedocs.io/en/latest/libraries.html#low-fi-variable-and-subroutine-definitions-in-all-available-library-modules they are linked there 1 for each compilation target
|
||||
- Structure: builtin functions, then module sections with variables/constants (`type name`) and subroutines (`name (params) -> returntype`)
|
||||
- Text output: `textio` module (`txt.print`, `txt.chrout`, `txt.print_b`/`_ub`/`_w`/`_uw`/`_l`/`_bool`, `txt.print_f` for floats, `txt.spc()`, `txt.nl()`)
|
||||
- Text output: `textio` module (`txt.print`, `txt.chrout`, `txt.print_b`/`_ub`/`_w`/`_uw`/`_l`/`_bool`, `txt.print_f` for floats, `txt.spc()`, `txt.nl()`). **Note: Prog8 has no function overloading**, so you cannot use `txt.print(number)` — you must call the specific routine for the type (e.g., `txt.print_ub(val)` for an unsigned byte).
|
||||
- Math: `math` module — integer trig (`sin8`, `cos8`) via fast LUTs; `math.rnd()` for random numbers
|
||||
- String conversion: `conv` module (`str_uword`, `str2word`, etc.) — for printing numbers use txt routines instead
|
||||
- Char operations: `strings` module (`isdigit`, `isxdigit`, `isupper`, `islower`, `isletter`, `isspace`, `isprint`) — use these instead of manual ASCII/PETSCII arithmetic
|
||||
|
||||
@@ -73,6 +73,9 @@ val BaseDataType.isWordOrByteOrBool get() = this in setOf(BaseDataType.UBYTE, Ba
|
||||
val BaseDataType.isNumeric get() = this == BaseDataType.FLOAT || this.isInteger
|
||||
val BaseDataType.isNumericOrBool get() = this == BaseDataType.BOOL || this.isNumeric
|
||||
val BaseDataType.isSigned get() = this in setOf(BaseDataType.BYTE, BaseDataType.WORD, BaseDataType.LONG, BaseDataType.FLOAT)
|
||||
val BaseDataType.isUnsigned get() = !this.isSigned
|
||||
val BaseDataType.isSignedInteger get() = this.isSigned && this.isInteger
|
||||
val BaseDataType.isUnsignedInteger get() = this.isUnsigned && this.isInteger
|
||||
val BaseDataType.isArray get() = this == BaseDataType.ARRAY || this == BaseDataType.ARRAY_SPLITW || this == BaseDataType.ARRAY_POINTER
|
||||
val BaseDataType.isPointer get() = this == BaseDataType.POINTER
|
||||
val BaseDataType.isStructInstance get() = this == BaseDataType.STRUCT_INSTANCE
|
||||
|
||||
@@ -275,8 +275,14 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
asmgen.assignWordOperandsToAYAndVar(fcall.args[1], fcall.args[0], "P8ZP_SCRATCH_W1")
|
||||
// math.divmod_uw_asm: -- divide two unsigned words (16 bit each) into 16 bit results
|
||||
// input: P8ZP_SCRATCH_W1 in ZP: 16-bit number, A/Y: 16 bit divisor
|
||||
// output: cx16.r15: 16-bit remainder, A/Y: 16 bit division result
|
||||
// output: P8ZP_SCRATCH_W2: 16-bit remainder, A/Y: 16 bit division result
|
||||
asmgen.out(" jsr prog8_math.divmod_uw_asm")
|
||||
// copy remainder from P8ZP_SCRATCH_W2 to R15 (needed as second return value)
|
||||
asmgen.out("""
|
||||
ldx P8ZP_SCRATCH_W2
|
||||
stx cx16.r15L
|
||||
ldx P8ZP_SCRATCH_W2+1
|
||||
stx cx16.r15H""".trimIndent())
|
||||
return arrayOf(RegisterOrPair.AY, RegisterOrPair.R15)
|
||||
}
|
||||
|
||||
@@ -494,83 +500,115 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
val arg2 = fcall.args[1]
|
||||
if(arg1.type.isByte) {
|
||||
if(arg2.type.isByte) {
|
||||
val op = if (arg1.type.isSigned || arg2.type.isSigned) "sec | sbc" else "cmp"
|
||||
when (arg2) {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
|
||||
asmgen.out(" $op ${asmgen.asmVariableName(arg2)}")
|
||||
}
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.number.toInt()}")
|
||||
asmgen.out(" $op #${arg2.number.toInt()}")
|
||||
}
|
||||
is PtBool -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.asInt()}")
|
||||
asmgen.out(" $op #${arg2.asInt()}")
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
if(arg2.address is PtNumber) {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${arg2.address.asConstInteger()!!.toHex()}")
|
||||
asmgen.out(" $op ${arg2.address.asConstInteger()!!.toHex()}")
|
||||
} else {
|
||||
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" $op P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignByteOperandsToAAndVar(arg1, arg2, "P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
asmgen.out(" $op P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
} else if(arg1.type.isWord) {
|
||||
if(arg2.type.isWord) {
|
||||
if(arg1.type.isSigned) {
|
||||
if(arg1.type.isSigned || arg2.type.isSigned) {
|
||||
when (arg2) {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val var2 = asmgen.asmVariableName(arg2)
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc ${asmgen.asmVariableName(arg2)}
|
||||
sbc $var2
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
sbc ${asmgen.asmVariableName(arg2)}+1""")
|
||||
sbc $var2+1
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
is PtBool -> TODO("word compare against bool ${arg2.position}")
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val num = arg2.number.toInt()
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc #<${arg2.number.toInt()}
|
||||
sbc #<($num)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
sbc #>${arg2.number.toInt()}""")
|
||||
sbc #>($num)
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(arg1, arg2, "P8ZP_SCRATCH_W1")
|
||||
asmgen.out("""
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
sbc P8ZP_SCRATCH_W1+1""")
|
||||
sbc P8ZP_SCRATCH_W1+1
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (arg2) {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val var2 = asmgen.asmVariableName(arg2)
|
||||
asmgen.out("""
|
||||
cpy ${asmgen.asmVariableName(arg2)}+1
|
||||
cpy $var2+1
|
||||
bne +
|
||||
cmp ${asmgen.asmVariableName(arg2)}
|
||||
+""")
|
||||
cmp $var2
|
||||
+""")
|
||||
}
|
||||
is PtBool -> TODO("word compare against bool ${arg2.position}")
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
val num = arg2.number.toInt()
|
||||
asmgen.out("""
|
||||
cpy #>${arg2.number.toInt()}
|
||||
cpy #>($num)
|
||||
bne +
|
||||
cmp #<${arg2.number.toInt()}
|
||||
+""")
|
||||
cmp #<($num)
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignWordOperandsToAYAndVar(arg1, arg2, "P8ZP_SCRATCH_W1")
|
||||
@@ -578,7 +616,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
+""")
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,12 +631,25 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
sec
|
||||
lda $var1
|
||||
sbc #$${hex.substring(6, 8)}
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+1
|
||||
sbc #$${hex.substring(4, 6)}
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+2
|
||||
sbc #$${hex.substring(2, 4)}
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+3
|
||||
sbc #$${hex.take(2)}""")
|
||||
sbc #$${hex.take(2)}
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
} else if(arg1 is PtIdentifier && arg2 is PtIdentifier) {
|
||||
val var1 = asmgen.asmVariableName(arg1)
|
||||
val var2 = asmgen.asmVariableName(arg2)
|
||||
@@ -606,12 +657,25 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
sec
|
||||
lda $var1
|
||||
sbc $var2
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+1
|
||||
sbc $var2+1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+2
|
||||
sbc $var2+2
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $var1+3
|
||||
sbc $var2+3""")
|
||||
sbc $var2+3
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
} else {
|
||||
assignAsmGen.assignExpressionToRegister(arg2, RegisterOrPair.R14R15, true)
|
||||
if(!assignAsmGen.isRightTrivial(arg1))
|
||||
@@ -623,12 +687,25 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
sec
|
||||
lda cx16.r12
|
||||
sbc cx16.r14
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r12+1
|
||||
sbc cx16.r14+1
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r12+2
|
||||
sbc cx16.r14+2
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r12+3
|
||||
sbc cx16.r14+3""")
|
||||
sbc cx16.r14+3
|
||||
php
|
||||
bne +
|
||||
lda P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
pla
|
||||
and #%11111101
|
||||
pha
|
||||
+ plp""")
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
@@ -1121,19 +1198,16 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(arg is PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(arg)
|
||||
when(val dt = arg.type.base) {
|
||||
BaseDataType.UBYTE -> asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
|
||||
BaseDataType.BYTE -> asmgen.out(" lda $varname | jsr prog8_lib.func_sign_b_into_A")
|
||||
BaseDataType.UWORD -> asmgen.out(" lda $varname+1 | jsr prog8_lib.func_sign_ub_into_A")
|
||||
BaseDataType.WORD -> asmgen.out(" lda $varname+1 | jsr prog8_lib.func_sign_b_into_A")
|
||||
BaseDataType.LONG -> asmgen.out(" lda $varname+3 | jsr prog8_lib.func_sign_b_into_A")
|
||||
BaseDataType.FLOAT -> {
|
||||
translateArguments(fcall, null, scope)
|
||||
asmgen.out(" jsr floats.func_sign_f_into_A")
|
||||
BaseDataType.UBYTE -> {
|
||||
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_ub_into_A")
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
BaseDataType.BYTE -> {
|
||||
asmgen.out(" lda $varname | jsr prog8_lib.func_sign_b_into_A")
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// result in A
|
||||
return arrayOf(RegisterOrPair.A)
|
||||
}
|
||||
|
||||
when (val dt = arg.type.base) {
|
||||
|
||||
@@ -125,10 +125,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
|
||||
private fun assignFloatBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
// C64/PET32 ROM T-variant entries skip CONUPK and need arisgn+Z flag set up beforehand.
|
||||
// CX16 ROM entries are already fixed and handle this internally.
|
||||
val tSuffix = if(asmgen.options.compTarget.name in listOf(C64Target.NAME, PETTarget.NAME)) "_NZ" else ""
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FADDT")
|
||||
asmgen.out(" jsr floats.FADDT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
@@ -140,13 +143,13 @@ internal class AnyExprAsmGen(
|
||||
}
|
||||
"*" -> {
|
||||
assignFloatOperandsToFACandARG(expr.left, expr.right)
|
||||
asmgen.out(" jsr floats.FMULTT")
|
||||
asmgen.out(" jsr floats.FMULTT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
"/" -> {
|
||||
assignFloatOperandsToFACandARG(expr.right, expr.left)
|
||||
asmgen.out(" jsr floats.FDIVT")
|
||||
asmgen.out(" jsr floats.FDIVT$tSuffix")
|
||||
asmgen.assignRegister(RegisterOrPair.FAC1, assign.target)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
targetDt.isByte -> assignVariableByte(assign.target, variable)
|
||||
targetDt.isSignedWord -> assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
targetDt.isUnsignedWord -> {
|
||||
targetDt.isUnsignedWord || targetDt.isPointer -> {
|
||||
if(assign.source.datatype.isPassByRef)
|
||||
assignAddressOf(assign.target, variable, false, assign.source.datatype, assign.source.array ?: PtNumber(BaseDataType.UBYTE, 0.0, assign.position))
|
||||
else
|
||||
@@ -236,7 +236,6 @@ internal class AssignmentAsmGen(
|
||||
targetDt.isLong -> assignVariableLong(assign.target, variable, assign.source.datatype)
|
||||
targetDt.isFloat -> assignVariableFloat(assign.target, variable)
|
||||
targetDt.isString -> assignVariableString(assign.target, variable)
|
||||
targetDt.isPointer -> assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype} ${assign.position}")
|
||||
}
|
||||
}
|
||||
@@ -493,13 +492,7 @@ internal class AssignmentAsmGen(
|
||||
val identifier = value.identifier!!
|
||||
val source = asmgen.symbolTable.lookup(identifier.name)
|
||||
require(source !is StConstant) { "addressOf of a constant should have been rewritten to a simple addition expression" }
|
||||
val sourceName =
|
||||
if (value.isMsbForSplitArray)
|
||||
asmgen.asmSymbolName(identifier) + "_msb"
|
||||
else if (identifier.type.isSplitWordArray)
|
||||
asmgen.asmSymbolName(identifier) + "_lsb" // the _lsb split array comes first in memory
|
||||
else
|
||||
asmgen.asmSymbolName(identifier)
|
||||
val sourceName = asmgen.asmSymbolName(identifier)
|
||||
assignAddressOf(assign.target, sourceName, value.isMsbForSplitArray, identifier.type, value.arrayIndexExpr)
|
||||
} else {
|
||||
val ptrderef = value.dereference!!
|
||||
@@ -3661,17 +3654,26 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
|
||||
var actualSourceName = sourceName
|
||||
var actualMsb = msb
|
||||
if (arrayDt?.isSplitWordArray == true) {
|
||||
if (!sourceName.endsWith("_lsb") && !sourceName.endsWith("_msb")) {
|
||||
actualSourceName = if (msb) sourceName + "_msb" else sourceName + "_lsb"
|
||||
}
|
||||
actualMsb = false
|
||||
}
|
||||
|
||||
if(arrayIndexExpr!=null) {
|
||||
if(arrayDt?.isPointer==true) {
|
||||
require(!msb)
|
||||
return pointergen.assignAddressOfIndexedPointer(target, sourceName, arrayDt, arrayIndexExpr)
|
||||
require(!actualMsb)
|
||||
return pointergen.assignAddressOfIndexedPointer(target, actualSourceName, arrayDt, arrayIndexExpr)
|
||||
}
|
||||
val constIndex = arrayIndexExpr.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
if (arrayDt!!.isUnsignedWord) {
|
||||
// using a UWORD pointer with array indexing, always bytes
|
||||
require(!msb)
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
require(!actualMsb)
|
||||
assignVariableToRegister(actualSourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
if(constIndex in 1..255)
|
||||
asmgen.out("""
|
||||
clc
|
||||
@@ -3693,9 +3695,9 @@ $endLabel""")
|
||||
else {
|
||||
if(constIndex>0) {
|
||||
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
|
||||
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
|
||||
asmgen.out(" lda #<($actualSourceName + $offset) | ldy #>($actualSourceName + $offset)")
|
||||
} else {
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
asmgen.out(" lda #<$actualSourceName | ldy #>$actualSourceName")
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
@@ -3703,8 +3705,8 @@ $endLabel""")
|
||||
} else {
|
||||
if (arrayDt!!.isUnsignedWord) {
|
||||
// using a UWORD pointer with array indexing, always bytes
|
||||
require(!msb)
|
||||
assignVariableToRegister(sourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
require(!actualMsb)
|
||||
assignVariableToRegister(actualSourceName, RegisterOrPair.AY, false, arrayIndexExpr.definingISub(), arrayIndexExpr.position)
|
||||
asmgen.saveRegisterStack(CpuRegister.A, false)
|
||||
asmgen.saveRegisterStack(CpuRegister.Y, false)
|
||||
if(arrayIndexExpr.type.isWord) {
|
||||
@@ -3757,9 +3759,9 @@ $endLabel""")
|
||||
)
|
||||
} else throw AssemblyError("weird type $subtype")
|
||||
asmgen.out("""
|
||||
ldy #>$sourceName
|
||||
ldy #>$actualSourceName
|
||||
clc
|
||||
adc #<$sourceName
|
||||
adc #<$actualSourceName
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
@@ -3770,12 +3772,12 @@ $endLabel""")
|
||||
}
|
||||
|
||||
// address of a normal variable
|
||||
require(!msb)
|
||||
require(!actualMsb)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
lda #<$actualSourceName
|
||||
ldy #>$actualSourceName
|
||||
sta ${target.asmVarname}
|
||||
sty ${target.asmVarname}+1""")
|
||||
}
|
||||
@@ -3783,25 +3785,25 @@ $endLabel""")
|
||||
throw AssemblyError("can't store word into memory byte")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
asmgen.out(" lda #<$actualSourceName | ldy #>$actualSourceName")
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out(" ldx #>$sourceName | lda #<$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
|
||||
RegisterOrPair.AX -> asmgen.out(" ldx #>$actualSourceName | lda #<$actualSourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" ldy #>$actualSourceName | lda #<$actualSourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldy #>$actualSourceName | ldx #<$actualSourceName")
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
lda #<$actualSourceName
|
||||
ldy #>$actualSourceName
|
||||
sta cx16.${target.register.toString().lowercase()}
|
||||
sty cx16.${target.register.toString().lowercase()}+1""")
|
||||
}
|
||||
else -> throw AssemblyError("can only load address into 16 bit register")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.POINTER -> pointergen.assignAddressOf(PtrTarget(target), sourceName)
|
||||
TargetStorageKind.POINTER -> pointergen.assignAddressOf(PtrTarget(target), actualSourceName)
|
||||
TargetStorageKind.VOID -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,6 +682,53 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy result from R14R15 back to target
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $targetVar,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
asmgen.out("""
|
||||
; Copy target to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $targetVar,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Copy source to R14R15 (divisor)
|
||||
ldy #3
|
||||
- lda $sourceVar,y
|
||||
sta cx16.r14,y
|
||||
dey
|
||||
bpl -
|
||||
jsr prog8_math.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to target
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $targetVar
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $targetVar+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $targetVar+2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $targetVar+3""")
|
||||
}
|
||||
else -> {
|
||||
TODO("in-place modify LONG with variable, operator=$operator")
|
||||
}
|
||||
@@ -1036,6 +1083,65 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"/" -> {
|
||||
// long /= constant - use division routine
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.div_longs
|
||||
; Copy result from R14R15 back to variable
|
||||
ldy #3
|
||||
- lda cx16.r14,y
|
||||
sta $variable,y
|
||||
dey
|
||||
bpl -""")
|
||||
}
|
||||
"%" -> {
|
||||
// long %= constant - use division routine, remainder is in P8ZP_SCRATCH_W1/W2
|
||||
val lo = value and 0xFFFF
|
||||
val hi = (value ushr 16) and 0xFFFF
|
||||
asmgen.out("""
|
||||
; Copy variable to R12R13 (dividend)
|
||||
ldy #3
|
||||
- lda $variable,y
|
||||
sta cx16.r12,y
|
||||
dey
|
||||
bpl -
|
||||
; Load constant into R14R15 (divisor)
|
||||
lda #<${lo}
|
||||
sta cx16.r14
|
||||
lda #>${lo}
|
||||
sta cx16.r14+1
|
||||
lda #<${hi}
|
||||
sta cx16.r15
|
||||
lda #>${hi}
|
||||
sta cx16.r15+1
|
||||
jsr prog8_math.div_longs
|
||||
; Copy remainder from P8ZP_SCRATCH_W1/W2 back to variable
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta $variable
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta $variable+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $variable+2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $variable+3""")
|
||||
}
|
||||
"<<" -> if (value > 0) inplaceLongShiftLeft()
|
||||
">>" -> if (value > 0) inplaceLongShiftRight()
|
||||
"|" -> {
|
||||
@@ -3164,6 +3270,7 @@ $shortcutLabel:""")
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
txa
|
||||
oweroff
|
||||
jsr prog8_math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
|
||||
@@ -12,7 +12,7 @@ import kotlin.math.floor
|
||||
import kotlin.math.log2
|
||||
|
||||
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter, private val options: CompilationOptions) : AstWalker() {
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<AstModification> {
|
||||
val mods = mutableListOf<AstModification>()
|
||||
|
||||
@@ -177,7 +177,8 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
// X <= Y-1 ---> X<Y , X >= Y+1 ---> X>Y
|
||||
if(leftDt.isInteger && rightDt.isInteger) {
|
||||
// Only for signed integers to avoid unsigned wraparound issues.
|
||||
if(leftDt.isSignedInteger && rightDt.isSignedInteger) {
|
||||
val rightExpr = expr.right as? BinaryExpression
|
||||
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
|
||||
if (expr.operator == "<=" && rightExpr.operator == "-") {
|
||||
@@ -679,23 +680,23 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return null
|
||||
// absorption laws: a or (a and b) --> a, a and (a or b) --> a
|
||||
if(expr.operator=="or" && rightB.operator=="and") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && rightB.operator=="or") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="or" && rightB.operator=="or") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// a or (a or b) -> a or b
|
||||
return expr.right
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && rightB.operator=="and") {
|
||||
if(expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) {
|
||||
if((expr.left isSameAs rightB.left || expr.left isSameAs rightB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// a and (a and b) -> a and b
|
||||
return expr.right
|
||||
}
|
||||
@@ -707,23 +708,23 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return null
|
||||
// absorption laws: (a and b) or a --> a, (a or b) and a --> a
|
||||
if(expr.operator=="or" && leftB.operator=="and") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return expr.right
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && leftB.operator=="or") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return expr.right
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="or" && leftB.operator=="or") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
// (a or b) or a -> a or b
|
||||
return expr.left
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="and" && leftB.operator=="and") {
|
||||
if(expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) {
|
||||
if((expr.right isSameAs leftB.left || expr.right isSameAs leftB.right) && !expr.right.hasSideEffects(options.compTarget)) {
|
||||
// (a and b) or a -> a and b
|
||||
return expr.left
|
||||
}
|
||||
@@ -844,7 +845,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
if(expr.left.isSameAs(expr.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// optimize X+X into X *2
|
||||
expr.operator = "*"
|
||||
expr.right = NumericLiteral.optimalInteger(2, expr.right.position)
|
||||
@@ -879,7 +880,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
|
||||
if(expr.left.isSameAs(expr.right)) {
|
||||
if(expr.left.isSameAs(expr.right) && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
// optimize X-X into 0
|
||||
return NumericLiteral.optimalInteger(0, expr.position)
|
||||
}
|
||||
@@ -924,12 +925,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
val cv = rightVal?.number?.toInt()?.toDouble()
|
||||
when (expr.operator) {
|
||||
"%" -> {
|
||||
if (cv == 1.0) {
|
||||
if (cv == 1.0 && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
val idt = expr.inferType(program)
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteral(idt.getOrUndef().base, 0.0, expr.position)
|
||||
} else if (cv in powersOfTwoFloat) {
|
||||
} else if (cv in powersOfTwoFloat && leftVal?.type?.isUnsignedInteger == true) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
return null
|
||||
@@ -969,7 +970,7 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
256.0 -> {
|
||||
when(leftDt.base) {
|
||||
BaseDataType.UBYTE -> return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)
|
||||
BaseDataType.UBYTE -> if (!expr.left.hasSideEffects(options.compTarget)) return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position) else return null
|
||||
BaseDataType.BYTE -> return null // is either 0 or -1 we cannot tell here
|
||||
BaseDataType.UWORD, BaseDataType.WORD -> {
|
||||
// just use: msb(value) as type
|
||||
@@ -992,11 +993,11 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
}
|
||||
|
||||
if (leftDt.isUnsignedByte) {
|
||||
if (abs(rightConst.number) >= 256.0) {
|
||||
if (abs(rightConst.number) >= 256.0 && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
} else if (leftDt.isUnsignedWord) {
|
||||
if (abs(rightConst.number) >= 65536.0) {
|
||||
if (abs(rightConst.number) >= 65536.0 && !expr.left.hasSideEffects(options.compTarget)) {
|
||||
return NumericLiteral(BaseDataType.UBYTE, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
@@ -1006,8 +1007,9 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
// left value is a constant, see if we can optimize
|
||||
when (leftVal.number) {
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteral(leftVal.type, 0.0, expr.position)
|
||||
// 0 (only if not float and no side effects)
|
||||
if (!expr.inferType(program).isFloat && !expr.right.hasSideEffects(options.compTarget))
|
||||
return NumericLiteral(leftVal.type, 0.0, expr.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1030,8 +1032,11 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
return PrefixExpression("-", leftValue, expr.position)
|
||||
}
|
||||
0.0 -> {
|
||||
// 0
|
||||
return NumericLiteral(rightConst.type, 0.0, expr.position)
|
||||
// 0 (only if not float and no side effects)
|
||||
if (!expr.inferType(program).isFloat && !leftValue.hasSideEffects(options.compTarget))
|
||||
return NumericLiteral(rightConst.type, 0.0, expr.position)
|
||||
else
|
||||
return null
|
||||
}
|
||||
1.0 -> {
|
||||
// left
|
||||
|
||||
@@ -64,8 +64,8 @@ fun Program.inlineSubroutines(options: CompilationOptions): Int {
|
||||
return inliner.applyModifications()
|
||||
}
|
||||
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors)
|
||||
fun Program.simplifyExpressions(errors: IErrorReporter, options: CompilationOptions) : Int {
|
||||
val opti = ExpressionSimplifier(this, errors, options)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
@@ -14,10 +14,26 @@ import prog8.code.target.VMTarget
|
||||
|
||||
private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.values.isEmpty()
|
||||
|
||||
/**
|
||||
* The Inliner performs subroutine inlining on the AST.
|
||||
*
|
||||
* It supports both manual inlining (subroutines marked with the 'inline' keyword)
|
||||
* and automatic inlining for very simple subroutines.
|
||||
*
|
||||
* Current restrictions and considerations:
|
||||
* - Automatic inlining is currently restricted to subroutines with ZERO parameters.
|
||||
* - Inlining subroutines with parameters is complicated because it requires careful
|
||||
* parameter substitution to avoid side effects and name collisions.
|
||||
* - Handling parameterized inlining can easily lead to infinite loops or recursion
|
||||
* in the optimizer if not done VERY CAREFULLY (e.g. self-referential subroutines).
|
||||
* - Subroutines must have a single functional statement in their body to be auto-inlined.
|
||||
* - Multi-return value subroutines are not yet supported for auto-inlining.
|
||||
*/
|
||||
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
|
||||
|
||||
inner class DetermineInlineSubs(val program: Program): IAstVisitor {
|
||||
private val modifications = mutableListOf<AstModification>()
|
||||
private val visitedSubroutines = java.util.Collections.newSetFromMap(java.util.IdentityHashMap<Subroutine, Boolean>())
|
||||
|
||||
init {
|
||||
visit(program)
|
||||
@@ -25,7 +41,24 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
modifications.clear()
|
||||
}
|
||||
|
||||
private fun isRecursive(sub: Subroutine): Boolean {
|
||||
var recursive = false
|
||||
val visitor = object : IAstVisitor {
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
if (!recursive && functionCallExpr.target.targetSubroutine() == sub) recursive = true
|
||||
if (!recursive) super.visit(functionCallExpr)
|
||||
}
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
if (!recursive && functionCallStatement.target.targetSubroutine() == sub) recursive = true
|
||||
if (!recursive) super.visit(functionCallStatement)
|
||||
}
|
||||
}
|
||||
sub.statements.forEach { if (!recursive) it.accept(visitor) }
|
||||
return recursive
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if (!visitedSubroutines.add(subroutine)) return
|
||||
|
||||
fun isBodyInlineable(stmt: Return): Boolean {
|
||||
if (stmt.values.isEmpty())
|
||||
@@ -109,30 +142,15 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
stmt.target is NumericLiteral
|
||||
}
|
||||
|
||||
val hasRegisterAliasedParams = subroutine.parameters.any { it.registerOrPair != null }
|
||||
if (!subroutine.isAsmSubroutine && (subroutine.parameters.isEmpty() || !hasRegisterAliasedParams)) {
|
||||
// NOTE: We allow subroutines with parameters to be considered for inlining,
|
||||
// unless they are assembly subroutines or have register-aliased parameters (which cannot be inlined as they depend on specific CPU registers to be set).
|
||||
// For void calls where the body doesn't use the parameters, inlining is trivial -
|
||||
// we just remove the call entirely (no parameter substitution needed).
|
||||
// For calls that return values or use parameters in the body, we need simple arguments
|
||||
// (NumericLiteral or IdentifierReference) for parameter substitution to work.
|
||||
// See canInlineAtCallSite() for the argument simplicity check.
|
||||
|
||||
val containsSubsOrVariables = subroutine.statements.any {
|
||||
it is Subroutine || (it is VarDecl && it.origin != VarDeclOrigin.SUBROUTINEPARAM)
|
||||
}
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.isEmpty()) {
|
||||
val containsSubsOrVariables = subroutine.statements.any { it is Subroutine || it is VarDecl }
|
||||
if (!containsSubsOrVariables) {
|
||||
// For subroutines with parameters, there will be a VarDecl(SUBROUTINEPARAM) per parameter
|
||||
// plus the body statement(s)
|
||||
val expectedMaxStmts = subroutine.parameters.size + 1 // param VarDecls + 1 body statement
|
||||
val hasOnlyBodyStatements = subroutine.statements.size == expectedMaxStmts ||
|
||||
(subroutine.statements.size == expectedMaxStmts + 1 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true)
|
||||
val hasOnlyBodyStatements = subroutine.statements.size == 1 ||
|
||||
(subroutine.statements.size == 2 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true)
|
||||
|
||||
if (hasOnlyBodyStatements) {
|
||||
if (subroutine !== program.entrypoint) {
|
||||
// Find the first non-parameter statement (the actual body)
|
||||
val bodyStmt = subroutine.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
val bodyStmt = subroutine.statements.firstOrNull()
|
||||
val isAutoInlineable =
|
||||
when (val stmt = bodyStmt) {
|
||||
is Return -> isBodyInlineable(stmt)
|
||||
@@ -141,15 +159,14 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
is Jump -> isBodyInlineable(stmt)
|
||||
else -> false
|
||||
}
|
||||
if (isAutoInlineable)
|
||||
if (isAutoInlineable && !isRecursive(subroutine))
|
||||
subroutine.inline = true
|
||||
}
|
||||
}
|
||||
|
||||
if (subroutine.inline && subroutine.statements.size > 1) {
|
||||
// Remove trailing return if it's empty and there's another body statement
|
||||
val bodyStmts = subroutine.statements.filter { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM }
|
||||
if (bodyStmts.size > 1 && subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true) {
|
||||
if (subroutine.statements.lastOrNull()?.let(::isEmptyReturn) == true) {
|
||||
subroutine.statements.removeLast()
|
||||
}
|
||||
}
|
||||
@@ -230,12 +247,7 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
return false to "subroutine not marked as inlineable"
|
||||
|
||||
if (sub.parameters.isNotEmpty()) {
|
||||
if (sub.isAsmSubroutine) {
|
||||
return false to "parameterized asmsub cannot be inlined"
|
||||
}
|
||||
if (sub.parameters.any { it.registerOrPair != null }) {
|
||||
return false to "parameterized subroutine with register aliased parameters cannot be inlined"
|
||||
}
|
||||
return false to "subroutines with parameters cannot be inlined in this compiler version"
|
||||
}
|
||||
|
||||
if (options.compTarget.name != VMTarget.NAME) {
|
||||
|
||||
@@ -366,7 +366,7 @@ class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<AstModification> {
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
if(assignment.target isSameAs assignment.value && !assignment.target.hasSideEffects(options.compTarget) && !assignment.value.hasSideEffects(options.compTarget)) {
|
||||
// remove assignment to self
|
||||
return listOf(AstRemove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
@@ -380,7 +380,7 @@ class StatementOptimizer(private val program: Program,
|
||||
if(bexpr!=null) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number
|
||||
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left && !assignment.target.hasSideEffects(options.compTarget) && !bexpr.left.hasSideEffects(options.compTarget)) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
|
||||
@@ -255,7 +255,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<AstModification> {
|
||||
if(assignment.target isSameAs assignment.value)
|
||||
if(assignment.target isSameAs assignment.value && !assignment.target.hasSideEffects(options.compTarget) && !assignment.value.hasSideEffects(options.compTarget))
|
||||
return listOf(AstRemove(assignment, parent as IStatementContainer))
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -25,15 +25,16 @@ smallringbuffer {
|
||||
}
|
||||
|
||||
sub free() -> ubyte {
|
||||
return 255-fill
|
||||
return 255 - fill
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=254
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 254
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill<=1
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
@@ -92,11 +93,12 @@ smallstack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp <= 1
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==255
|
||||
return sp == 255
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -150,11 +152,12 @@ stack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp == 0 or sp > 8191
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==8191
|
||||
return sp == 8191
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -207,15 +210,16 @@ ringbuffer {
|
||||
}
|
||||
|
||||
sub free() -> uword {
|
||||
return 8191-fill
|
||||
return 8192-fill
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill==0
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=8191
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 8191
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
|
||||
@@ -539,3 +539,43 @@ swap_floats .proc
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
; Wrappers for the ROM's bare T-variant math routines (FADDT, FMULTT, FDIVT, FPWRT).
|
||||
; These ROM entries skip CONUPK which normally sets the Z flag and arisgn byte.
|
||||
; Callers that have not just come through CONUPK must set these up themselves
|
||||
; or the result can be wrong (heisenbug with some values).
|
||||
; The corresponding entries on the X16 (via its KERNAL jump table) do this correctly,
|
||||
; so these wrappers are only needed on C64 and PET32.
|
||||
FADDT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FADDT
|
||||
.pend
|
||||
|
||||
FMULTT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FMULTT
|
||||
.pend
|
||||
|
||||
FDIVT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FDIVT
|
||||
.pend
|
||||
|
||||
FPWRT_NZ .proc
|
||||
lda FAC_ADDR+5 ; facsgn
|
||||
eor FAC_ADDR+$0D ; argsgn
|
||||
sta FAC_ADDR+$0E ; arisgn
|
||||
lda FAC_ADDR+0 ; facexp to set Z flag
|
||||
jmp FPWRT
|
||||
.pend
|
||||
|
||||
@@ -51,15 +51,15 @@ extsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
extsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
extsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
|
||||
extsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
extsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FADDT_NZ wrapper
|
||||
extsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
extsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
extsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands ; (FSUBT does NOT need the Z flag hack, it already does the setup internally)
|
||||
extsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
extsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
extsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FMULTT_NZ wrapper
|
||||
extsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
extsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
extsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FDIVT_NZ wrapper
|
||||
extsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
extsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
extsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FPWRT_NZ wrapper
|
||||
extsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
extsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
|
||||
@@ -27,11 +27,12 @@ smallringbuffer {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=254
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 254
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill<=1
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
@@ -90,11 +91,12 @@ smallstack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp==0
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp <= 1
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==255
|
||||
return sp == 255
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -150,11 +152,12 @@ stack {
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return sp<=$a001
|
||||
; returns true if less than 2 bytes of space remaining (to allow push_w)
|
||||
return sp <= $a000
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return sp==$bfff
|
||||
return sp == $bfff
|
||||
}
|
||||
|
||||
sub push_b(ubyte value) {
|
||||
@@ -227,15 +230,16 @@ ringbuffer {
|
||||
}
|
||||
|
||||
sub free() -> uword {
|
||||
return $1fff-fill
|
||||
return 8192-fill
|
||||
}
|
||||
|
||||
sub isempty() -> bool {
|
||||
return fill<=1
|
||||
return fill == 0
|
||||
}
|
||||
|
||||
sub isfull() -> bool {
|
||||
return fill>=8191
|
||||
; returns true if less than 2 bytes of space remaining (to allow putw)
|
||||
return fill >= 8191 or head == $bfff
|
||||
}
|
||||
|
||||
sub put(ubyte value) {
|
||||
|
||||
@@ -434,8 +434,9 @@ divmod_w_asm .proc
|
||||
divmod_uw_asm .proc
|
||||
; -- divide two unsigned words (16 bit each) into 16 bit results
|
||||
; input: P8ZP_SCRATCH_W1 in ZP: 16 bit number, A/Y: 16 bit divisor
|
||||
; output: cx16.r15: 16 bit remainder, A/Y: 16 bit division result
|
||||
; output: P8ZP_SCRATCH_W2: 16 bit remainder, A/Y: 16 bit division result
|
||||
; division by zero will result in quotient = 65535 and remainder = 0
|
||||
; NOTE: does not clobber any cx16.rX registers.
|
||||
|
||||
dividend = P8ZP_SCRATCH_W1
|
||||
remainder = P8ZP_SCRATCH_W2
|
||||
@@ -446,8 +447,8 @@ result = dividend ;save memory by reusing dividend to store the result
|
||||
ora #0
|
||||
bne _nonzero
|
||||
lda #0 ; remainder = 0
|
||||
sta cx16.r15L
|
||||
sta cx16.r15H
|
||||
sta remainder
|
||||
sta remainder+1
|
||||
lda #$ff ; quotient = 65535 (A/Y)
|
||||
tay
|
||||
rts
|
||||
@@ -478,10 +479,6 @@ _nonzero
|
||||
+ dex
|
||||
bne -
|
||||
|
||||
lda P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
sta cx16.r15L
|
||||
sty cx16.r15H
|
||||
lda result
|
||||
ldy result+1
|
||||
rts
|
||||
|
||||
@@ -105,6 +105,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
; -- return random number uniformly distributed from 0 to n-1
|
||||
; NOTE: does not work for code in ROM, use randrange_rom instead for that
|
||||
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
|
||||
; Uses cx16.r0 as temporary
|
||||
cx16.r0 = math.rnd() * (n as uword)
|
||||
return cx16.r0H
|
||||
}
|
||||
@@ -113,6 +114,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
; -- return random number uniformly distributed from 0 to n-1
|
||||
; NOTE: works for code in ROM, make sure you have initialized the seed using rndseed_rom
|
||||
; why this works: https://www.youtube.com/watch?v=3DvlLUWTNMY&t=347s
|
||||
; Uses cx16.r0 as temporary
|
||||
cx16.r0 = math.rnd_rom() * (n as uword)
|
||||
return cx16.r0H
|
||||
}
|
||||
@@ -177,6 +179,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
}
|
||||
|
||||
asmsub log2w(uword value @AY) -> ubyte @Y {
|
||||
; Clobbers cx16.r0
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
@@ -267,6 +270,7 @@ asmsub direction_qd(ubyte quadrant @A, ubyte xdelta @X, ubyte ydelta @Y) -> ubyt
|
||||
; .reg:x @in x_delta Delta for x direction.
|
||||
; .reg:y @in y_delta Delta for y direction.
|
||||
; Returns A as the direction (0-23).
|
||||
; Clobbers cx16.r0 through cx16.r5 (used as temporary variables).
|
||||
|
||||
%asm {{
|
||||
x_delta = cx16.r0L
|
||||
@@ -388,6 +392,7 @@ asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the cartesian plane!
|
||||
;; http://codebase64.net/doku.php?id=base:8bit_atan2_8-bit_angle
|
||||
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
|
||||
;; Clobbers cx16.r0 through cx16.r4 (r0-r3 are input params, r4 is temporary).
|
||||
|
||||
%asm {{
|
||||
|
||||
@@ -531,6 +536,7 @@ log2_tab
|
||||
|
||||
asmsub diffw(uword w1 @R0, uword w2 @AY) -> uword @AY {
|
||||
; -- returns the (absolute) difference, or distance, between the two words
|
||||
; clobbers cx16.r0 (used as input and temporary)
|
||||
%asm {{
|
||||
sec
|
||||
sbc cx16.r0L
|
||||
|
||||
@@ -54,19 +54,19 @@ extsub $cd42 = MOVAF() clobbers(A,X) ; copy fac1 to fac2
|
||||
extsub $cd45 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
|
||||
extsub $cb20 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
extsub $cb61 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
extsub $cb61 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FMULTT_NZ wrapper
|
||||
extsub $cb5e = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
extsub $cc48 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
|
||||
extsub $cc48 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FDIVT_NZ wrapper
|
||||
extsub $cc45 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
|
||||
extsub $cc18 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
extsub $cc34 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
|
||||
extsub $c9a0 = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
extsub $c9a0 = FADDT() clobbers(A,X,Y) ; fac1 += fac2 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FADDT_NZ wrapper
|
||||
extsub $c99d = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
extsub $c97f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
|
||||
extsub $c989 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
extsub $c989 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands ; (FSUBT does NOT need the Z flag hack, it already does the setup internally)
|
||||
extsub $c986 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
extsub $cd91 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
extsub $d112 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
extsub $d112 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1 ; WARNING: bare entry, needs Z flag=facexp + arisgn; use FPWRT_NZ wrapper
|
||||
|
||||
|
||||
extsub $ca0d = NORMAL() clobbers(A) ; normalize FAC1
|
||||
|
||||
@@ -124,14 +124,13 @@ strings {
|
||||
|
||||
sub startswith(str st, str prefix) -> bool {
|
||||
ubyte prefix_len = length(prefix)
|
||||
ubyte str_len = length(st)
|
||||
if prefix_len > str_len
|
||||
if prefix_len > length(st)
|
||||
return false
|
||||
cx16.r9L = st[prefix_len]
|
||||
push(st[prefix_len])
|
||||
st[prefix_len] = 0
|
||||
cx16.r9H = compare(st, prefix) as ubyte
|
||||
st[prefix_len] = cx16.r9L
|
||||
return cx16.r9H==0
|
||||
bool result = compare(st, prefix) == 0
|
||||
st[prefix_len] = pop()
|
||||
return result
|
||||
}
|
||||
|
||||
sub endswith(str st, str suffix) -> bool {
|
||||
|
||||
@@ -581,7 +581,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
|
||||
for(numCycles in 0..10000) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = program.simplifyExpressions(errors)
|
||||
val optsDone1 = program.simplifyExpressions(errors, compilerOptions)
|
||||
val optsDone2 = program.optimizeStatements(errors, functions, compilerOptions)
|
||||
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
val optsDone3 = program.inlineSubroutines(compilerOptions) // inlining can expose new calls to inline
|
||||
@@ -601,7 +601,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
|
||||
removeUnusedCode(program, errors, compilerOptions)
|
||||
if(errors.noErrors()) {
|
||||
// last round of optimizations because inlining may have enabled more...
|
||||
program.simplifyExpressions(errors)
|
||||
program.simplifyExpressions(errors, compilerOptions)
|
||||
program.optimizeStatements(errors, functions, compilerOptions)
|
||||
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
}
|
||||
|
||||
@@ -215,6 +215,14 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
}
|
||||
|
||||
private fun DaemonRequest.toCompilerArguments(daemonErr: DaemonErrorReporter): CompilerArguments {
|
||||
// Resolve the output directory path: if relative, make it absolute by resolving against
|
||||
// the client's working directory (cwd) to avoid writing files in the daemon's CWD.
|
||||
val clientCwd = Path.of(cwd)
|
||||
val resolvedOutputDir = if (Path.of(outputDir).isAbsolute) {
|
||||
Path.of(outputDir)
|
||||
} else {
|
||||
clientCwd.resolve(outputDir)
|
||||
}
|
||||
return CompilerArguments(
|
||||
filepath = Path.of(filepath),
|
||||
optimize = optimize,
|
||||
@@ -242,8 +250,8 @@ internal class CompilerDaemon(private val socketPath: Path) {
|
||||
nostdlib = nostdlib,
|
||||
symbolDefs = symbolDefs,
|
||||
sourceDirs = sourceDirs,
|
||||
outputDir = Path.of(outputDir),
|
||||
cwd = Path.of(cwd),
|
||||
outputDir = resolvedOutputDir,
|
||||
cwd = clientCwd,
|
||||
errors = daemonErr
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1232,33 +1232,6 @@ _after:
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// consolidates simple assignment followed by augmented assignment into a single combined assignment.
|
||||
// a = simplevalue | a += expression --> a = simplevalue + expression (for all augmented operators)
|
||||
// NOTE: this is NOT SAFE to do when dealing with pointer arithmetic because the semantics of the combined expression is different!
|
||||
// NOTE: this is also NOT SAFE when the augmented assignment's RHS references the target variable (e.g., a = b; a *= a)
|
||||
if(!assignment.isAugmentable && assignment.value.isSimple) {
|
||||
val next = assignment.nextSibling() as? Assignment
|
||||
if(next?.isAugmentable==true && next.value is BinaryExpression && next.target.isSameAs(assignment.target, program)) {
|
||||
val combined = next.value as BinaryExpression
|
||||
if(combined.left isSameAs assignment.target) {
|
||||
// Check if RHS also references the target variable - if so, can't optimize
|
||||
if(!(combined.right isSameAs assignment.target)) {
|
||||
if(!assignment.target.inferType(program).isPointer && (assignment.value.inferType(program).isPointer || combined.right.inferType(program).isPointer)) {
|
||||
// avoid pointer arithmetic: a = (simplevalue as uword) + expression
|
||||
combined.left = TypecastExpression(assignment.value, DataType.UWORD, true, combined.left.position)
|
||||
} else {
|
||||
// no pointer arithmetic done, just transform it into a = simplevalue + expression
|
||||
combined.left = assignment.value
|
||||
}
|
||||
assignment.value = combined
|
||||
combined.linkParents(assignment)
|
||||
return listOf(AstRemove(next, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.target.pointerDereference!=null && assignment.value is PtrDereference) {
|
||||
val targetPtr = assignment.target.pointerDereference!!
|
||||
val sourcePtr = assignment.value as PtrDereference
|
||||
|
||||
@@ -142,9 +142,15 @@ internal object DeferProcessor {
|
||||
}
|
||||
|
||||
// complex return value, need to store it before calling the defer block
|
||||
val pushAndPopCalls = ret.children.map { makePushPopFunctionCalls(it as PtExpression) }
|
||||
val pushAndPopCalls = ret.children.map {
|
||||
val expr = it as PtExpression
|
||||
if (expr.type == DataType.UNDEFINED)
|
||||
Pair(expr, null)
|
||||
else
|
||||
makePushPopFunctionCalls(expr)
|
||||
}
|
||||
val pushCalls = pushAndPopCalls.map { it.first }.reversed() // push in reverse order
|
||||
val popCalls = pushAndPopCalls.map { it.second }
|
||||
val popCalls = pushAndPopCalls.mapNotNull { it.second }
|
||||
val newRet = PtReturn(ret.position)
|
||||
val group = PtNodeGroup()
|
||||
pushCalls.forEach { group.add(it) }
|
||||
|
||||
@@ -454,9 +454,13 @@ internal class StatementReorderer(
|
||||
val targetSub = funcValue.target.targetSubroutine()
|
||||
if(targetSub!=null && !targetSub.isAsmSubroutine) {
|
||||
if(targetSub.parameters.isEmpty()) {
|
||||
// replace return func() --> goto func
|
||||
val goto = Jump(funcValue.target, returnStmt.position)
|
||||
return listOf(AstReplaceNode(returnStmt, goto, parent))
|
||||
val currentSub = returnStmt.definingSubroutine
|
||||
if (currentSub == null || !currentSub.hasDeferStatement()) {
|
||||
// replace return func() --> goto func
|
||||
// but only if current sub has no defers that must be executed!
|
||||
val goto = Jump(funcValue.target, returnStmt.position)
|
||||
return listOf(AstReplaceNode(returnStmt, goto, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,23 +150,23 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
val onlyC64 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("balloonflight", c64SizeOptimized=1674, c64SizeUnoptimized=1745),
|
||||
ExampleSizes("balloonflight", c64SizeOptimized=1674, c64SizeUnoptimized=1737),
|
||||
ExampleSizes("banking", c64SizeOptimized=680, c64SizeUnoptimized=1451),
|
||||
ExampleSizes("bdmusic-irq", c64SizeOptimized=1001, c64SizeUnoptimized=1019),
|
||||
ExampleSizes("bdmusic", c64SizeOptimized=938, c64SizeUnoptimized=1011),
|
||||
ExampleSizes("charset", c64SizeOptimized=835, c64SizeUnoptimized=860),
|
||||
ExampleSizes("cube3d-sprites", c64SizeOptimized=2712, c64SizeUnoptimized=2764),
|
||||
ExampleSizes("bdmusic", c64SizeOptimized=944, c64SizeUnoptimized=1011),
|
||||
ExampleSizes("charset", c64SizeOptimized=841, c64SizeUnoptimized=860),
|
||||
ExampleSizes("cube3d-sprites", c64SizeOptimized=2704, c64SizeUnoptimized=2756),
|
||||
ExampleSizes("fileselector", c64SizeOptimized=3568, c64SizeUnoptimized=4758),
|
||||
ExampleSizes("library/main", c64SizeOptimized=321, c64SizeUnoptimized=1237),
|
||||
ExampleSizes("multiplexer", c64SizeOptimized=1356, c64SizeUnoptimized=1507),
|
||||
ExampleSizes("petswirl", c64SizeOptimized=959, c64SizeUnoptimized=1029),
|
||||
ExampleSizes("plasma", c64SizeOptimized=1690, c64SizeUnoptimized=1731),
|
||||
ExampleSizes("petswirl", c64SizeOptimized=953, c64SizeUnoptimized=1021),
|
||||
ExampleSizes("plasma", c64SizeOptimized=1688, c64SizeUnoptimized=1723),
|
||||
ExampleSizes("rasterbars", c64SizeOptimized=845, c64SizeUnoptimized=892),
|
||||
ExampleSizes("simplemultiplexer", c64SizeOptimized=843, c64SizeUnoptimized=914),
|
||||
ExampleSizes("sprites", c64SizeOptimized=963, c64SizeUnoptimized=1052),
|
||||
ExampleSizes("simplemultiplexer", c64SizeOptimized=843, c64SizeUnoptimized=902),
|
||||
ExampleSizes("sprites", c64SizeOptimized=963, c64SizeUnoptimized=1040),
|
||||
ExampleSizes("starfield", c64SizeOptimized=743, c64SizeUnoptimized=758),
|
||||
ExampleSizes("turtle-gfx", c64SizeOptimized=2745, c64SizeUnoptimized=2861),
|
||||
ExampleSizes("wizzine", c64SizeOptimized=1124, c64SizeUnoptimized=1236),
|
||||
ExampleSizes("turtle-gfx", c64SizeOptimized=2758, c64SizeUnoptimized=2860),
|
||||
ExampleSizes("wizzine", c64SizeOptimized=1124, c64SizeUnoptimized=1224),
|
||||
// boingball, cube3d, tehtriz are in BothC64andCx16
|
||||
),
|
||||
listOf(false, true)
|
||||
@@ -197,13 +197,13 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("vtui/testvtui", cx16SizeOptimized=3041, cx16SizeUnoptimized=3041),
|
||||
ExampleSizes("pcmaudio/play-adpcm", cx16SizeOptimized=36147, cx16SizeUnoptimized=36645),
|
||||
ExampleSizes("pcmaudio/play-adpcm", cx16SizeOptimized=36147, cx16SizeUnoptimized=36637),
|
||||
ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5636, cx16SizeUnoptimized=7878),
|
||||
ExampleSizes("pcmaudio/stream-simple-aflow", cx16SizeOptimized=1850, cx16SizeUnoptimized=3753),
|
||||
ExampleSizes("pcmaudio/stream-simple-poll", cx16SizeOptimized=1564, cx16SizeUnoptimized=3462),
|
||||
ExampleSizes("pcmaudio/stream-simple-aflow", cx16SizeOptimized=1842, cx16SizeUnoptimized=3745),
|
||||
ExampleSizes("pcmaudio/stream-simple-poll", cx16SizeOptimized=1556, cx16SizeUnoptimized=3454),
|
||||
ExampleSizes("pcmaudio/vumeter", cx16SizeOptimized=3888, cx16SizeUnoptimized=6381),
|
||||
ExampleSizes("sprites/dragon", cx16SizeOptimized=1858, cx16SizeUnoptimized=4374),
|
||||
ExampleSizes("sprites/dragons", cx16SizeOptimized=1511, cx16SizeUnoptimized=3686),
|
||||
ExampleSizes("sprites/dragons", cx16SizeOptimized=1503, cx16SizeUnoptimized=3678),
|
||||
ExampleSizes("zsmkit_v1/demo1", cx16SizeOptimized=7546, cx16SizeUnoptimized=7984),
|
||||
ExampleSizes("zsmkit_v1/demo2", cx16SizeOptimized=708, cx16SizeUnoptimized=2579),
|
||||
// zsmkit_v1/zsmkit_high and zsmkit_v1/zsmkit_low don't compile (empty source files)
|
||||
@@ -219,36 +219,36 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
ExampleSizes("automatons", cx16SizeOptimized=1037, cx16SizeUnoptimized=1342),
|
||||
ExampleSizes("balloonflight", cx16SizeOptimized=3187, cx16SizeUnoptimized=4163),
|
||||
ExampleSizes("bdmusic", cx16SizeOptimized=2427, cx16SizeUnoptimized=2792),
|
||||
ExampleSizes("bobs", cx16SizeOptimized=2800, cx16SizeUnoptimized=3414),
|
||||
ExampleSizes("bobs", cx16SizeOptimized=2800, cx16SizeUnoptimized=3406),
|
||||
ExampleSizes("bubbleuniverse", cx16SizeOptimized=1450, cx16SizeUnoptimized=1958),
|
||||
ExampleSizes("charfade", cx16SizeOptimized=1054, cx16SizeUnoptimized=1362),
|
||||
ExampleSizes("charsets", cx16SizeOptimized=1125, cx16SizeUnoptimized=1417),
|
||||
ExampleSizes("circles", cx16SizeOptimized=2385, cx16SizeUnoptimized=4547),
|
||||
ExampleSizes("cobramk3-gfx", cx16SizeOptimized=7705, cx16SizeUnoptimized=10612),
|
||||
ExampleSizes("circles", cx16SizeOptimized=2384, cx16SizeUnoptimized=4539),
|
||||
ExampleSizes("cobramk3-gfx", cx16SizeOptimized=7697, cx16SizeUnoptimized=10604),
|
||||
ExampleSizes("colorbars", cx16SizeOptimized=1606, cx16SizeUnoptimized=1904),
|
||||
ExampleSizes("cxlogo", cx16SizeOptimized=599, cx16SizeUnoptimized=895),
|
||||
ExampleSizes("diskspeed", cx16SizeOptimized=6280, cx16SizeUnoptimized=8283),
|
||||
ExampleSizes("fileseek", cx16SizeOptimized=2468, cx16SizeUnoptimized=4496),
|
||||
ExampleSizes("floatparse", cx16SizeOptimized=8303, cx16SizeUnoptimized=9190),
|
||||
ExampleSizes("interpolation", cx16SizeOptimized=3791, cx16SizeUnoptimized=4768),
|
||||
ExampleSizes("kefrenbars", cx16SizeOptimized=2386, cx16SizeUnoptimized=4654),
|
||||
ExampleSizes("interpolation", cx16SizeOptimized=3783, cx16SizeUnoptimized=4760),
|
||||
ExampleSizes("kefrenbars", cx16SizeOptimized=2393, cx16SizeUnoptimized=4654),
|
||||
ExampleSizes("keyboardhandler", cx16SizeOptimized=412, cx16SizeUnoptimized=717),
|
||||
ExampleSizes("landscape", cx16SizeOptimized=1673, cx16SizeUnoptimized=1997),
|
||||
ExampleSizes("landscape", cx16SizeOptimized=1665, cx16SizeUnoptimized=1989),
|
||||
ExampleSizes("life", cx16SizeOptimized=1320, cx16SizeUnoptimized=1751),
|
||||
ExampleSizes("mandelbrot", cx16SizeOptimized=1120, cx16SizeUnoptimized=1434),
|
||||
ExampleSizes("multi-irq-new", cx16SizeOptimized=1073, cx16SizeUnoptimized=1519),
|
||||
ExampleSizes("multi-irq-old", cx16SizeOptimized=962, cx16SizeUnoptimized=1410),
|
||||
ExampleSizes("plasma", cx16SizeOptimized=1739, cx16SizeUnoptimized=2066),
|
||||
ExampleSizes("plasma", cx16SizeOptimized=1731, cx16SizeUnoptimized=2058),
|
||||
ExampleSizes("rasterbars", cx16SizeOptimized=1268, cx16SizeUnoptimized=1685),
|
||||
ExampleSizes("serialdownload", cx16SizeOptimized=2516, cx16SizeUnoptimized=3945),
|
||||
ExampleSizes("showbmx", cx16SizeOptimized=2745, cx16SizeUnoptimized=5269),
|
||||
ExampleSizes("snow", cx16SizeOptimized=3120, cx16SizeUnoptimized=5295),
|
||||
ExampleSizes("showbmx", cx16SizeOptimized=2745, cx16SizeUnoptimized=5261),
|
||||
ExampleSizes("snow", cx16SizeOptimized=3119, cx16SizeUnoptimized=5287),
|
||||
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=3847),
|
||||
ExampleSizes("spotlight", cx16SizeOptimized=908, cx16SizeUnoptimized=1364),
|
||||
ExampleSizes("starszoom", cx16SizeOptimized=2399, cx16SizeUnoptimized=4863),
|
||||
ExampleSizes("starszoom", cx16SizeOptimized=2406, cx16SizeUnoptimized=4855),
|
||||
ExampleSizes("test_gfx_hires", cx16SizeOptimized=4835, cx16SizeUnoptimized=7514),
|
||||
ExampleSizes("test_gfx_lores", cx16SizeOptimized=5841, cx16SizeUnoptimized=7188),
|
||||
ExampleSizes("testmonogfx", cx16SizeOptimized=10463, cx16SizeUnoptimized=11982),
|
||||
ExampleSizes("test_gfx_lores", cx16SizeOptimized=5849, cx16SizeUnoptimized=7188),
|
||||
ExampleSizes("testmonogfx", cx16SizeOptimized=10480, cx16SizeUnoptimized=11974),
|
||||
ExampleSizes("textspotlight", cx16SizeOptimized=3431, cx16SizeUnoptimized=3788),
|
||||
),
|
||||
listOf(false, true)
|
||||
@@ -279,29 +279,29 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("balls", c64SizeOptimized=885, c64SizeUnoptimized=919, cx16SizeOptimized=874, cx16SizeUnoptimized=1188),
|
||||
ExampleSizes("boingball", c64SizeOptimized=1478, c64SizeUnoptimized=1544, cx16SizeOptimized=1486, cx16SizeUnoptimized=1957),
|
||||
ExampleSizes("cube3d", c64SizeOptimized=2444, c64SizeUnoptimized=2509, cx16SizeOptimized=2381, cx16SizeUnoptimized=2712),
|
||||
ExampleSizes("cube3d-float", c64SizeOptimized=2619, c64SizeUnoptimized=2668, cx16SizeOptimized=2511, cx16SizeUnoptimized=2835),
|
||||
ExampleSizes("cube3d-gfx", c64SizeOptimized=3536, c64SizeUnoptimized=3589, cx16SizeOptimized=2206, cx16SizeUnoptimized=2522),
|
||||
ExampleSizes("boingball", c64SizeOptimized=1513, c64SizeUnoptimized=1579, cx16SizeOptimized=1486, cx16SizeUnoptimized=1957),
|
||||
ExampleSizes("cube3d", c64SizeOptimized=2436, c64SizeUnoptimized=2501, cx16SizeOptimized=2373, cx16SizeUnoptimized=2704),
|
||||
ExampleSizes("cube3d-float", c64SizeOptimized=2654, c64SizeUnoptimized=2703, cx16SizeOptimized=2511, cx16SizeUnoptimized=2835),
|
||||
ExampleSizes("cube3d-gfx", c64SizeOptimized=3528, c64SizeUnoptimized=3581, cx16SizeOptimized=2198, cx16SizeUnoptimized=2514),
|
||||
ExampleSizes("dirlist", c64SizeOptimized=1543, c64SizeUnoptimized=2405, cx16SizeOptimized=1525, cx16SizeUnoptimized=3372),
|
||||
ExampleSizes("fibonacci", c64SizeOptimized=570, c64SizeUnoptimized=585, cx16SizeOptimized=626, cx16SizeUnoptimized=925),
|
||||
ExampleSizes("fractal-tree", c64SizeOptimized=2808, c64SizeUnoptimized=2845, cx16SizeOptimized=1220, cx16SizeUnoptimized=1511),
|
||||
ExampleSizes("fractal-tree", c64SizeOptimized=2832, c64SizeUnoptimized=2869, cx16SizeOptimized=1220, cx16SizeUnoptimized=1511),
|
||||
ExampleSizes("maze", c64SizeOptimized=3088, c64SizeUnoptimized=3189, cx16SizeOptimized=2873, cx16SizeUnoptimized=3251),
|
||||
ExampleSizes("mandelbrot", c64SizeOptimized=1138, c64SizeUnoptimized=1172, cx16SizeOptimized=1120, cx16SizeUnoptimized=1434),
|
||||
ExampleSizes("mandelbrot-gfx", c64SizeOptimized=1457, c64SizeUnoptimized=1479, cx16SizeOptimized=957, cx16SizeUnoptimized=1250),
|
||||
ExampleSizes("mandelbrot", c64SizeOptimized=1173, c64SizeUnoptimized=1207, cx16SizeOptimized=1120, cx16SizeUnoptimized=1434),
|
||||
ExampleSizes("mandelbrot-gfx", c64SizeOptimized=1492, c64SizeUnoptimized=1514, cx16SizeOptimized=957, cx16SizeUnoptimized=1250),
|
||||
ExampleSizes("multitasking", c64SizeOptimized=1956, c64SizeUnoptimized=2029, cx16SizeOptimized=1826, cx16SizeUnoptimized=2159),
|
||||
ExampleSizes("numbergame", c64SizeOptimized=1034, c64SizeUnoptimized=1050, cx16SizeOptimized=1079, cx16SizeUnoptimized=1382),
|
||||
ExampleSizes("primes", c64SizeOptimized=520, c64SizeUnoptimized=541, cx16SizeOptimized=565, cx16SizeUnoptimized=870),
|
||||
ExampleSizes("queens", c64SizeOptimized=1061, c64SizeUnoptimized=1093, cx16SizeOptimized=1111, cx16SizeUnoptimized=1427),
|
||||
ExampleSizes("queens", c64SizeOptimized=1053, c64SizeUnoptimized=1085, cx16SizeOptimized=1103, cx16SizeUnoptimized=1419),
|
||||
ExampleSizes("screencodes", c64SizeOptimized=667, c64SizeUnoptimized=687, cx16SizeOptimized=655, cx16SizeUnoptimized=959),
|
||||
ExampleSizes("swirl", c64SizeOptimized=828, c64SizeUnoptimized=831, cx16SizeOptimized=923, cx16SizeUnoptimized=1208),
|
||||
ExampleSizes("swirl-float", c64SizeOptimized=708, c64SizeUnoptimized=735, cx16SizeOptimized=595, cx16SizeUnoptimized=906),
|
||||
ExampleSizes("tehtriz", c64SizeOptimized=4333, c64SizeUnoptimized=4504, cx16SizeOptimized=6252, cx16SizeUnoptimized=6773),
|
||||
ExampleSizes("textelite", c64SizeOptimized=11440, c64SizeUnoptimized=12423, cx16SizeOptimized=10778, cx16SizeUnoptimized=13046),
|
||||
ExampleSizes("swirl-float", c64SizeOptimized=732, c64SizeUnoptimized=759, cx16SizeOptimized=595, cx16SizeUnoptimized=906),
|
||||
ExampleSizes("tehtriz", c64SizeOptimized=4331, c64SizeUnoptimized=4496, cx16SizeOptimized=6244, cx16SizeUnoptimized=6765),
|
||||
ExampleSizes("textelite", c64SizeOptimized=11428, c64SizeUnoptimized=12411, cx16SizeOptimized=10770, cx16SizeUnoptimized=13038),
|
||||
ExampleSizes("pointers/animalgame", c64SizeOptimized=1570, c64SizeUnoptimized=2126, cx16SizeOptimized=1605, cx16SizeUnoptimized=2412),
|
||||
ExampleSizes("pointers/binarytree", c64SizeOptimized=2562, c64SizeUnoptimized=2640, cx16SizeOptimized=2085, cx16SizeUnoptimized=2389),
|
||||
ExampleSizes("pointers/hashtable", c64SizeOptimized=3294, c64SizeUnoptimized=4140, cx16SizeOptimized=2832, cx16SizeUnoptimized=3786),
|
||||
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1274, c64SizeUnoptimized=1319, cx16SizeOptimized=1223, cx16SizeUnoptimized=1532),
|
||||
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1262, c64SizeUnoptimized=1307, cx16SizeOptimized=1215, cx16SizeUnoptimized=1524),
|
||||
ExampleSizes("pointers/sorting", c64SizeOptimized=1776, c64SizeUnoptimized=2676, cx16SizeOptimized=1788, cx16SizeUnoptimized=2659),
|
||||
),
|
||||
listOf(false, true),
|
||||
@@ -376,8 +376,8 @@ class TestCompilerOnExamplesPET32: FunSpec({
|
||||
|
||||
val onlyPET32 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("boingball", pet32SizeOptimized=1497, pet32SizeUnoptimized=1586),
|
||||
ExampleSizes("petswirl", pet32SizeOptimized=915, pet32SizeUnoptimized=985),
|
||||
ExampleSizes("boingball", pet32SizeOptimized=1532, pet32SizeUnoptimized=1621),
|
||||
ExampleSizes("petswirl", pet32SizeOptimized=909, pet32SizeUnoptimized=977),
|
||||
),
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package prog8tests.compiler
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestDefers : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("defer syntactic sugaring") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
void test()
|
||||
}
|
||||
|
||||
sub test() -> uword {
|
||||
defer {
|
||||
cx16.r0++
|
||||
cx16.r1++
|
||||
}
|
||||
|
||||
if cx16.r0==0 {
|
||||
defer cx16.r1++
|
||||
}
|
||||
|
||||
if cx16.r0==0
|
||||
return cx16.r0+cx16.r1
|
||||
defer cx16.r2++
|
||||
return 999
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), optimize = true, src, outputDir, writeAssembly = true)!!
|
||||
val main = result.codegenAst!!.allBlocks().single { it.name == "p8b_main" }
|
||||
val sub = main.children[1] as PtSub
|
||||
sub.scopedName shouldBe "p8b_main.p8s_test"
|
||||
|
||||
// check the desugaring of the defer statements
|
||||
sub.children[0] shouldBe instanceOf<PtSubSignature>()
|
||||
(sub.children[1] as PtVariable).name shouldBe "p8v_prog8_defers_mask"
|
||||
|
||||
val firstDefer = sub.children[3] as PtAugmentedAssign
|
||||
firstDefer.operator shouldBe "|="
|
||||
firstDefer.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask"
|
||||
firstDefer.value.asConstInteger() shouldBe 4
|
||||
|
||||
val firstIf = sub.children[4] as PtIfElse
|
||||
val deferInIf = firstIf.ifScope.children[0] as PtAugmentedAssign
|
||||
deferInIf.operator shouldBe "|="
|
||||
deferInIf.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask"
|
||||
deferInIf.value.asConstInteger() shouldBe 2
|
||||
|
||||
val lastDefer = sub.children[6] as PtAugmentedAssign
|
||||
lastDefer.operator shouldBe "|="
|
||||
lastDefer.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask"
|
||||
lastDefer.value.asConstInteger() shouldBe 1
|
||||
|
||||
val ifelse = sub.children[5] as PtIfElse
|
||||
val ifscope = ifelse.ifScope.children[0] as PtNodeGroup
|
||||
val ifscope_push = ifscope.children[0] as PtFunctionCall
|
||||
val ifscope_defer = ifscope.children[1] as PtFunctionCall
|
||||
val ifscope_return = ifscope.children[2] as PtReturn
|
||||
ifscope_defer.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
ifscope_push.name shouldBe "pushw"
|
||||
(ifscope_return.children.single() as PtFunctionCall).name shouldBe "popw"
|
||||
|
||||
val ending = sub.children[7] as PtFunctionCall
|
||||
ending.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
sub.children[8] shouldBe instanceOf<PtReturn>()
|
||||
val handler = sub.children[9] as PtSub
|
||||
handler.name shouldBe "p8s_prog8_invoke_defers"
|
||||
}
|
||||
|
||||
test("defer blocks prevent tail call optimization") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
void test()
|
||||
}
|
||||
|
||||
sub test() {
|
||||
defer {
|
||||
cx16.r0 = 1
|
||||
}
|
||||
return other()
|
||||
}
|
||||
|
||||
sub other() {
|
||||
cx16.r1 = 2
|
||||
}
|
||||
}"""
|
||||
// We compile for Cx16Target because tail call optimization (TCO) is a 6502 backend optimization
|
||||
// (actually it's in StatementReorderer which runs before codegen, but it targets JMP vs JSR)
|
||||
val result = compileText(Cx16Target(), optimize = true, src, outputDir, writeAssembly = true)!!
|
||||
val main = result.codegenAst!!.allBlocks().single { it.name == "p8b_main" }
|
||||
val testSub = main.children.filterIsInstance<PtSub>().find { it.scopedName == "p8b_main.p8s_test" }!!
|
||||
|
||||
// If TCO happened, we would see a PtJump to other instead of a PtFunctionCall to other.
|
||||
// But with defer, it should remain a PtFunctionCall (or rather, it's inside a node group that calls invoke_defers)
|
||||
|
||||
// Let's inspect the children of testSub.
|
||||
// It should have: signature, mask var, initialize mask, set mask bit, the call to other, call to invoke_defers, return.
|
||||
|
||||
fun collectCalls(node: PtNode): List<PtFunctionCall> {
|
||||
val calls = mutableListOf<PtFunctionCall>()
|
||||
if (node is PtFunctionCall) calls.add(node)
|
||||
node.children.forEach { calls.addAll(collectCalls(it)) }
|
||||
return calls
|
||||
}
|
||||
|
||||
val callToOther = collectCalls(testSub).find { it.name == "p8b_main.p8s_other" }
|
||||
callToOther shouldNotBe null
|
||||
|
||||
// Verify it is NOT a PtJump (which would be used for tail call optimization)
|
||||
// Actually, TCO in StatementReorderer converts return other() into a Goto.
|
||||
// In PtSub, a Goto becomes a PtJump.
|
||||
|
||||
testSub.children.any { it is PtJump && (it.target as? PtIdentifier)?.name == "p8b_main.p8s_other" } shouldBe false
|
||||
}
|
||||
})
|
||||
@@ -277,9 +277,9 @@ other {
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), optimize=true, src, outputDir, writeAssembly=false)!!
|
||||
result.compilerAst.entrypoint.statements.size shouldBe 6
|
||||
result.compilerAst.entrypoint.statements.size shouldBe 9
|
||||
val alldecls = result.compilerAst.entrypoint.allDefinedSymbols.toList()
|
||||
alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar")
|
||||
alldecls.map { it.first } shouldBe listOf("unused_but_shared", "usedvar_only_written", "usedvar")
|
||||
}
|
||||
|
||||
test("unused variable removal from subscope") {
|
||||
@@ -1675,7 +1675,7 @@ main {
|
||||
idAssigns.size shouldBe 3
|
||||
}
|
||||
|
||||
test("inline void call with one parameter") {
|
||||
xtest("inline void call with one parameter") {
|
||||
// Tests that void calls with one parameter are inlined when args are simple
|
||||
val src = """
|
||||
main {
|
||||
@@ -1699,7 +1699,7 @@ main {
|
||||
hasVoidCall shouldBe false
|
||||
}
|
||||
|
||||
test("inline void call with two parameters") {
|
||||
xtest("inline void call with two parameters") {
|
||||
// Tests that void calls with two parameters are inlined when args are simple
|
||||
val src = """
|
||||
main {
|
||||
@@ -1723,7 +1723,7 @@ main {
|
||||
hasVoidCall shouldBe false
|
||||
}
|
||||
|
||||
test("inline call with one return value and one parameter") {
|
||||
xtest("inline call with one return value and one parameter") {
|
||||
// Tests that function calls returning one value with one parameter are inlined
|
||||
// and that the parameter is correctly substituted with the argument
|
||||
val src = """
|
||||
@@ -1753,7 +1753,7 @@ main {
|
||||
binExpr.right shouldBe instanceOf<NumericLiteral>()
|
||||
}
|
||||
|
||||
test("inline call with two return values and two parameters") {
|
||||
xtest("inline call with two return values and two parameters") {
|
||||
// Tests that function calls returning two values with two parameters are inlined
|
||||
// and that both parameters are correctly substituted
|
||||
val src = """
|
||||
@@ -1826,7 +1826,7 @@ main {
|
||||
callNodes.size shouldBe 1
|
||||
}
|
||||
|
||||
test("inline void call with six parameters") {
|
||||
xtest("inline void call with six parameters") {
|
||||
// Tests that void calls with six parameters are inlined when args are simple
|
||||
val src = """
|
||||
main {
|
||||
@@ -1843,13 +1843,55 @@ main {
|
||||
|
||||
// Void call should be removed entirely
|
||||
val hasVoidCall = startSub.statements.any { stmt ->
|
||||
stmt is FunctionCallStatement &&
|
||||
stmt is FunctionCallStatement &&
|
||||
stmt.target.nameInSource.last() == "take_six" &&
|
||||
stmt.void
|
||||
}
|
||||
hasVoidCall shouldBe false
|
||||
}
|
||||
|
||||
test("parameterized subroutine with 'inline' is NOT inlined") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
foo(1)
|
||||
}
|
||||
inline sub foo(ubyte x) {
|
||||
cx16.r0 = x
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
|
||||
val startSub = result.compilerAst.entrypoint
|
||||
|
||||
// Should still contain a function call to foo
|
||||
val hasFooCall = startSub.statements.any { stmt ->
|
||||
stmt is FunctionCallStatement &&
|
||||
stmt.target.nameInSource.last() == "foo"
|
||||
}
|
||||
hasFooCall shouldBe true
|
||||
}
|
||||
|
||||
test("parameterized subroutine WITHOUT 'inline' is NOT auto-inlined") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
foo(1)
|
||||
}
|
||||
sub foo(ubyte x) {
|
||||
cx16.r0 = x
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false)!!
|
||||
val startSub = result.compilerAst.entrypoint
|
||||
|
||||
// Should still contain a function call to foo
|
||||
val hasFooCall = startSub.statements.any { stmt ->
|
||||
stmt is FunctionCallStatement &&
|
||||
stmt.target.nameInSource.last() == "foo"
|
||||
}
|
||||
hasFooCall shouldBe true
|
||||
}
|
||||
|
||||
|
||||
test("memory-mapped IO reads should not be eliminated (c64)") {
|
||||
val src = $$"""
|
||||
@@ -2033,6 +2075,28 @@ main {
|
||||
(y3.right as PtNumber).number shouldBe 3.0
|
||||
}
|
||||
|
||||
test("self-referential subroutine is not inlined (would cause infinite loop)") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
void self_ref()
|
||||
cx16.r0++
|
||||
}
|
||||
sub self_ref() {
|
||||
self_ref()
|
||||
}
|
||||
}
|
||||
"""
|
||||
val result = compileText(VMTarget(), true, src, outputDir)!!
|
||||
val startSub = result.compilerAst.entrypoint
|
||||
|
||||
// The self-referential call should NOT be inlined - verify the function call still exists
|
||||
val hasSelfRefCall = startSub.statements.any { stmt ->
|
||||
stmt is FunctionCallStatement && stmt.target.nameInSource.lastOrNull() == "self_ref"
|
||||
}
|
||||
hasSelfRefCall shouldBe true
|
||||
}
|
||||
|
||||
test("string literals in struct instances are not deduplicated") {
|
||||
val src = """
|
||||
main {
|
||||
@@ -2085,4 +2149,123 @@ main {
|
||||
// They should point to different interned strings
|
||||
name1.nameInSource shouldNotBe name2.nameInSource
|
||||
}
|
||||
|
||||
test("signed modulo strength reduction") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
byte @shared x = -5
|
||||
byte @shared res = x % 8
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(VMTarget(), true, src, outputDir)!!
|
||||
val sub = result.codegenAst!!.entrypoint()!!
|
||||
val assignments = sub.children.filterIsInstance<IPtAssignment>()
|
||||
val resAssign = assignments.find { it.target.identifier?.name?.contains("res") == true }
|
||||
?: fail("Could not find assignment to 'res'")
|
||||
val resMod = resAssign.value
|
||||
resMod shouldBe instanceOf<PtBinaryExpression>()
|
||||
(resMod as PtBinaryExpression).operator shouldBe "%"
|
||||
}
|
||||
|
||||
test("unsigned comparison wraparound") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
ubyte @shared x = 10
|
||||
ubyte @shared y = 0
|
||||
bool @shared b1 = x <= (y - 1)
|
||||
ubyte @shared y2 = 255
|
||||
bool @shared b2 = x >= (y2 + 1)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(VMTarget(), true, src, outputDir)!!
|
||||
val sub = result.codegenAst!!.entrypoint()!!
|
||||
val assignments = sub.children.filterIsInstance<IPtAssignment>()
|
||||
|
||||
val b1Assign = assignments.find { it.target.identifier?.name?.contains("b1") == true }
|
||||
?: fail("Could not find assignment to 'b1'")
|
||||
val b1 = b1Assign.value as PtBinaryExpression
|
||||
// Might be swapped to (y-1) >= x
|
||||
if (b1.operator == "<=") {
|
||||
(b1.left as PtIdentifier).name shouldContain "x"
|
||||
} else {
|
||||
b1.operator shouldBe ">="
|
||||
(b1.right as PtIdentifier).name shouldContain "x"
|
||||
}
|
||||
|
||||
val b2Assign = assignments.find { it.target.identifier?.name?.contains("b2") == true }
|
||||
?: fail("Could not find assignment to 'b2'")
|
||||
val b2 = b2Assign.value as PtBinaryExpression
|
||||
// Might be swapped to (y2+1) <= x
|
||||
if (b2.operator == ">=") {
|
||||
(b2.left as PtIdentifier).name shouldContain "x"
|
||||
} else {
|
||||
b2.operator shouldBe "<="
|
||||
(b2.right as PtIdentifier).name shouldContain "x"
|
||||
}
|
||||
}
|
||||
|
||||
test("identity folding with side effects (volatility)") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
bool @shared b = peek(${'$'}d012) == peek(${'$'}d012)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(C64Target(), true, src, outputDir, errors = errors)
|
||||
if (result == null) {
|
||||
println(errors.errors)
|
||||
fail("Compilation failed: " + errors.errors)
|
||||
}
|
||||
val sub = result.codegenAst!!.entrypoint()!!
|
||||
val assignments = sub.children.filterIsInstance<IPtAssignment>()
|
||||
val bAssign = assignments.find { it.target.identifier?.name?.contains("b") == true }
|
||||
?: fail("Could not find assignment to 'b'")
|
||||
val b = bAssign.value
|
||||
|
||||
// Should not be folded to 'true'
|
||||
b shouldBe instanceOf<PtBinaryExpression>()
|
||||
(b as PtBinaryExpression).operator shouldBe "=="
|
||||
b.left shouldBe instanceOf<PtMemoryByte>()
|
||||
b.right shouldBe instanceOf<PtMemoryByte>()
|
||||
}
|
||||
|
||||
test("floating-point algebraic identities") {
|
||||
val src = """
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
float @shared f = 0.0
|
||||
float @shared res1 = f * 0.0
|
||||
float @shared res2 = 0.0 * f
|
||||
float @shared res3 = 0.0 / f
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(VMTarget(), true, src, outputDir, errors = errors)
|
||||
if (result == null) {
|
||||
println(errors.errors)
|
||||
fail("Compilation failed: " + errors.errors)
|
||||
}
|
||||
val sub = result.codegenAst!!.entrypoint()!!
|
||||
val assignments = sub.children.filterIsInstance<IPtAssignment>()
|
||||
|
||||
val res1Assign = assignments.find { it.target.identifier?.name?.contains("res1") == true }!!
|
||||
res1Assign.value shouldBe instanceOf<PtBinaryExpression>()
|
||||
(res1Assign.value as PtBinaryExpression).operator shouldBe "*"
|
||||
|
||||
val res2Assign = assignments.find { it.target.identifier?.name?.contains("res2") == true }!!
|
||||
res2Assign.value shouldBe instanceOf<PtBinaryExpression>()
|
||||
(res2Assign.value as PtBinaryExpression).operator shouldBe "*"
|
||||
|
||||
val res3Assign = assignments.find { it.target.identifier?.name?.contains("res3") == true }!!
|
||||
res3Assign.value shouldBe instanceOf<PtBinaryExpression>()
|
||||
(res3Assign.value as PtBinaryExpression).operator shouldBe "/"
|
||||
}
|
||||
})
|
||||
@@ -2693,19 +2693,13 @@ main {
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)
|
||||
val st = result!!.compilerAst.entrypoint.statements
|
||||
st.size shouldBe 7
|
||||
st.size shouldBe 9
|
||||
val a1 = st[3] as Assignment
|
||||
a1.target.identifier!!.nameInSource shouldBe listOf("temp")
|
||||
val v1 = a1.value as BinaryExpression
|
||||
v1.operator shouldBe "+"
|
||||
(v1.left as TypecastExpression).type shouldBe DataType.UWORD
|
||||
v1.right shouldBe instanceOf<NumericLiteral>()
|
||||
val a2 = st[5] as Assignment
|
||||
a1.value shouldBe instanceOf<IdentifierReference>()
|
||||
val a2 = st[6] as Assignment
|
||||
a2.target.identifier!!.nameInSource shouldBe listOf("temp")
|
||||
val v2 = a2.value as BinaryExpression
|
||||
v2.operator shouldBe "+"
|
||||
v2.left shouldBe instanceOf<IdentifierReference>()
|
||||
v2.right shouldBe instanceOf<NumericLiteral>()
|
||||
a2.value shouldBe instanceOf<IdentifierReference>()
|
||||
}
|
||||
|
||||
test("indexed pointer derefs") {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
%encoding iso
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.iso()
|
||||
|
||||
; test divmod with uword values (hits funcDivmodW -> divmod_uw_asm, remainder via R15)
|
||||
check_divmod(40000, 500, 80, 0)
|
||||
check_divmod(43211, 2, 21605, 1)
|
||||
check_divmod(65535, 1000, 65, 535)
|
||||
check_divmod(12345, 777, 15, 690)
|
||||
|
||||
; test plain division with uword (hits optimizedDivideExpr -> divmod_uw_asm directly, quotient only)
|
||||
check_div(40000, 500, 80)
|
||||
check_div(43211, 2, 21605)
|
||||
check_div(65535, 1000, 65)
|
||||
check_div(12345, 777, 15)
|
||||
|
||||
; test remainder with uword (hits optimizedRemainderExpr -> divmod_uw_preserve_r15)
|
||||
check_mod(40000, 500, 0)
|
||||
check_mod(43211, 2, 1)
|
||||
check_mod(65535, 1000, 535)
|
||||
check_mod(12345, 777, 690)
|
||||
|
||||
txt.print("ALL TESTS DONE\n")
|
||||
sys.poweroff_system()
|
||||
}
|
||||
|
||||
sub check_divmod(uword a1, uword a2, uword expected_q, uword expected_r) {
|
||||
uword q, r = divmod(a1, a2)
|
||||
if q==expected_q and r==expected_r
|
||||
txt.print(" ok: ")
|
||||
else
|
||||
txt.print("FAIL: ")
|
||||
txt.print("divmod(")
|
||||
txt.print_uw(a1)
|
||||
txt.print(", ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(") = (")
|
||||
txt.print_uw(q)
|
||||
txt.print(", ")
|
||||
txt.print_uw(r)
|
||||
txt.print(") expected (")
|
||||
txt.print_uw(expected_q)
|
||||
txt.print(", ")
|
||||
txt.print_uw(expected_r)
|
||||
txt.print(")\n")
|
||||
}
|
||||
|
||||
sub check_div(uword a1, uword a2, uword expected) {
|
||||
uword r = a1 / a2
|
||||
if r==expected
|
||||
txt.print(" ok: ")
|
||||
else
|
||||
txt.print("FAIL: ")
|
||||
txt.print("uword ")
|
||||
txt.print_uw(a1)
|
||||
txt.print(" / ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_uw(r)
|
||||
txt.print(" expected ")
|
||||
txt.print_uw(expected)
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub check_mod(uword a1, uword a2, uword expected) {
|
||||
uword r = a1 % a2
|
||||
if r==expected
|
||||
txt.print(" ok: ")
|
||||
else
|
||||
txt.print("FAIL: ")
|
||||
txt.print("uword ")
|
||||
txt.print_uw(a1)
|
||||
txt.print(" % ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_uw(r)
|
||||
txt.print(" expected ")
|
||||
txt.print_uw(expected)
|
||||
txt.print("\n")
|
||||
}
|
||||
}
|
||||
@@ -1123,69 +1123,6 @@ main {
|
||||
array2.type shouldBe DataType.arrayFor(BaseDataType.UWORD, true)
|
||||
}
|
||||
|
||||
test("defer syntactic sugaring") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
void test()
|
||||
}
|
||||
|
||||
sub test() -> uword {
|
||||
defer {
|
||||
cx16.r0++
|
||||
cx16.r1++
|
||||
}
|
||||
|
||||
if cx16.r0==0 {
|
||||
defer cx16.r1++
|
||||
}
|
||||
|
||||
if cx16.r0==0
|
||||
return cx16.r0+cx16.r1
|
||||
defer cx16.r2++
|
||||
return 999
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), optimize=true, src, outputDir, writeAssembly=true)!!
|
||||
val main = result.codegenAst!!.allBlocks().single {it.name=="p8b_main"}
|
||||
val sub = main.children[1] as PtSub
|
||||
sub.scopedName shouldBe "p8b_main.p8s_test"
|
||||
|
||||
// check the desugaring of the defer statements
|
||||
sub.children[0] shouldBe instanceOf<PtSubSignature>()
|
||||
(sub.children[1] as PtVariable).name shouldBe "p8v_prog8_defers_mask"
|
||||
|
||||
val firstDefer = sub.children[3] as PtAugmentedAssign
|
||||
firstDefer.operator shouldBe "|="
|
||||
firstDefer.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask"
|
||||
firstDefer.value.asConstInteger() shouldBe 4
|
||||
|
||||
val firstIf = sub.children[4] as PtIfElse
|
||||
val deferInIf = firstIf.ifScope.children[0] as PtAugmentedAssign
|
||||
deferInIf.operator shouldBe "|="
|
||||
deferInIf.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask"
|
||||
deferInIf.value.asConstInteger() shouldBe 2
|
||||
|
||||
val lastDefer = sub.children[6] as PtAugmentedAssign
|
||||
lastDefer.operator shouldBe "|="
|
||||
lastDefer.target.identifier?.name shouldBe "p8b_main.p8s_test.p8v_prog8_defers_mask"
|
||||
lastDefer.value.asConstInteger() shouldBe 1
|
||||
|
||||
val ifelse = sub.children[5] as PtIfElse
|
||||
val ifscope = ifelse.ifScope.children[0] as PtNodeGroup
|
||||
val ifscope_push = ifscope.children[0] as PtFunctionCall
|
||||
val ifscope_defer = ifscope.children[1] as PtFunctionCall
|
||||
val ifscope_return = ifscope.children[2] as PtReturn
|
||||
ifscope_defer.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
ifscope_push.name shouldBe "pushw"
|
||||
(ifscope_return.children.single() as PtFunctionCall).name shouldBe "popw"
|
||||
|
||||
val ending = sub.children[7] as PtFunctionCall
|
||||
ending.name shouldBe "p8b_main.p8s_test.p8s_prog8_invoke_defers"
|
||||
sub.children[8] shouldBe instanceOf<PtReturn>()
|
||||
val handler = sub.children[9] as PtSub
|
||||
handler.name shouldBe "p8s_prog8_invoke_defers"
|
||||
}
|
||||
|
||||
test("unknown variable in for loop gives proper errors") {
|
||||
val src="""
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldNotContain
|
||||
import io.kotest.matchers.types.instanceOf
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.target.C64Target
|
||||
@@ -12,6 +13,7 @@ import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.simulate
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class TestArrayThings: FunSpec({
|
||||
@@ -149,6 +151,72 @@ main {
|
||||
assembly shouldContain "thearray2"
|
||||
}
|
||||
|
||||
test("split word array return and address-of uses lsb symbol") {
|
||||
val src = """
|
||||
main {
|
||||
uword[3] myarray = [1000, 2000, 3000]
|
||||
|
||||
sub start() {
|
||||
uword addr1 = get_addr()
|
||||
uword addr2 = &myarray
|
||||
}
|
||||
|
||||
sub get_addr() -> uword {
|
||||
return myarray
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
val result = compileText(Cx16Target(), false, src, outputDir, writeAssembly = true, errors = errors)
|
||||
errors.report()
|
||||
result shouldNotBe null
|
||||
val assemblyFile = result!!.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val assembly = assemblyFile.readText()
|
||||
|
||||
// it should use _lsb suffix for both the return and the direct assignment
|
||||
// because both are treating the array as a pointer/address
|
||||
|
||||
assembly shouldContain "_lsb"
|
||||
// Ensure it DOES NOT contain p8v_myarray followed by space, tab or newline (which would mean no suffix)
|
||||
// or used in lda #< / ldy #> without the suffix
|
||||
assembly shouldNotContain Regex("""#<.*p8v_myarray(?![_a-zA-Z0-9])""")
|
||||
assembly shouldNotContain Regex("""#>.*p8v_myarray(?![_a-zA-Z0-9])""")
|
||||
}
|
||||
|
||||
test("functional test: split word array return address") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
&ubyte poweroff = $f203
|
||||
&ubyte result = $02
|
||||
uword[2] myarray = [1234, 5678]
|
||||
|
||||
sub start() {
|
||||
uword addr = get_addr()
|
||||
; addr points to the LSB array.
|
||||
; result should get the LSB of the first element (1234 & 0xff = 210? No, 1234 = 4 * 256 + 210? 1234 / 256 = 4, 1234 % 256 = 210. 1234 = 0x04D2, so LSB is 0xD2 = 210)
|
||||
^^ubyte ptr = addr
|
||||
result = ptr[0]
|
||||
poweroff = 1
|
||||
}
|
||||
|
||||
sub get_addr() -> uword {
|
||||
return myarray
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir, errors = errors)
|
||||
errors.report()
|
||||
compileResult shouldNotBe null
|
||||
val machine = compileResult!!.simulate()
|
||||
machine.assertMemory(0x02, 1234 and 0xff)
|
||||
}
|
||||
|
||||
test("indexing str or pointervar with expression") {
|
||||
val text = """
|
||||
main {
|
||||
|
||||
@@ -1105,4 +1105,35 @@ main {
|
||||
result shouldNotBe null
|
||||
}
|
||||
|
||||
test("typed pointer var init not removed by optimizer when immediately dereferenced") {
|
||||
val src = """
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
main {
|
||||
uword data = $1234
|
||||
uword @shared result
|
||||
|
||||
sub start() {
|
||||
result = read_data()
|
||||
}
|
||||
|
||||
sub read_data() -> uword {
|
||||
^^uword ptr = &main.data
|
||||
ptr = ptr^^
|
||||
return ptr
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = true)!!
|
||||
val virtfile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".p8ir")
|
||||
val irContent = virtfile.readText()
|
||||
|
||||
val irProgram = IRFileReader().read(irContent)
|
||||
val allocations = VmVariableAllocator(irProgram.st, irProgram.encoding, irProgram.options.compTarget).allocations
|
||||
|
||||
VmRunner().runAndTestProgram(irContent) { vm ->
|
||||
vm.memory.getUW(allocations["main.result"]!!) shouldBe 0x1234u
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -102,6 +102,11 @@ interface IStatementContainer {
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is Defer -> {
|
||||
val found = stmt.scope.searchSymbol(name)
|
||||
if(found!=null)
|
||||
return found
|
||||
}
|
||||
is IfElse -> {
|
||||
val found = stmt.truepart.searchSymbol(name) ?: stmt.elsepart.searchSymbol(name)
|
||||
if(found!=null)
|
||||
@@ -154,6 +159,7 @@ interface IStatementContainer {
|
||||
is When -> stmt.choices.any { it.statements.hasReturnStatement() }
|
||||
is ConditionalBranch -> stmt.truepart.hasReturnStatement() || stmt.elsepart.hasReturnStatement()
|
||||
is UnrollLoop -> stmt.body.hasReturnStatement()
|
||||
is Defer -> stmt.scope.hasReturnStatement()
|
||||
is Return -> true
|
||||
else -> false
|
||||
}
|
||||
@@ -161,6 +167,24 @@ interface IStatementContainer {
|
||||
return statements.any { hasReturnStatement(it) }
|
||||
}
|
||||
|
||||
fun hasDeferStatement(): Boolean {
|
||||
fun hasDeferStatement(stmt: Statement): Boolean = when(stmt) {
|
||||
is AnonymousScope -> stmt.statements.any { hasDeferStatement(it) }
|
||||
is ForLoop -> stmt.body.hasDeferStatement()
|
||||
is IfElse -> stmt.truepart.hasDeferStatement() || stmt.elsepart.hasDeferStatement()
|
||||
is WhileLoop -> stmt.body.hasDeferStatement()
|
||||
is RepeatLoop -> stmt.body.hasDeferStatement()
|
||||
is UntilLoop -> stmt.body.hasDeferStatement()
|
||||
is When -> stmt.choices.any { it.statements.hasDeferStatement() }
|
||||
is ConditionalBranch -> stmt.truepart.hasDeferStatement() || stmt.elsepart.hasDeferStatement()
|
||||
is UnrollLoop -> stmt.body.hasDeferStatement()
|
||||
is Defer -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
return statements.any { hasDeferStatement(it) }
|
||||
}
|
||||
|
||||
val allDefinedSymbols: Sequence<Pair<String, Statement>>
|
||||
get() {
|
||||
return statements.asSequence().filterIsInstance<INamedStatement>().map { Pair(it.name, it as Statement) }
|
||||
@@ -234,7 +258,7 @@ interface INameScope: IStatementContainer, INamedStatement {
|
||||
if(struct!=null) {
|
||||
for ((idx, field) in scopedName.withIndex()) {
|
||||
val fieldDt = struct!!.getFieldType(field) ?:
|
||||
return null
|
||||
return null
|
||||
if (idx == scopedName.size - 1) {
|
||||
// was last path element
|
||||
val pointer = IdentifierReference(scopedName, localSymbol.position)
|
||||
@@ -362,9 +386,9 @@ open class Module(final override val statements: MutableList<Statement>,
|
||||
lateinit var program: Program
|
||||
|
||||
override val name = source.origin
|
||||
.substringBeforeLast(".")
|
||||
.substringAfterLast("/")
|
||||
.substringAfterLast("\\")
|
||||
.substringBeforeLast(".")
|
||||
.substringAfterLast("/")
|
||||
.substringAfterLast("\\")
|
||||
|
||||
val loadAddress: Pair<UInt, Position>? by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)
|
||||
|
||||
@@ -27,6 +27,8 @@ sealed class Expression: Node {
|
||||
abstract fun isIORead(target: ICompilationTarget): Boolean
|
||||
abstract val isSimple: Boolean
|
||||
|
||||
open fun hasSideEffects(target: ICompilationTarget): Boolean = isIORead(target)
|
||||
|
||||
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||
|
||||
infix fun isSameAs(other: Expression): Boolean {
|
||||
@@ -104,6 +106,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
}
|
||||
|
||||
override fun copy() = PrefixExpression(operator, expression.copy(), position)
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = expression.hasSideEffects(target)
|
||||
override fun constValue(program: Program): NumericLiteral? {
|
||||
val constval = expression.constValue(program) ?: return null
|
||||
val converted = when(operator) {
|
||||
@@ -186,6 +189,7 @@ class BinaryExpression(
|
||||
}
|
||||
|
||||
override fun copy() = BinaryExpression(left.copy(), operator, right.copy(), position)
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = left.hasSideEffects(target) || right.hasSideEffects(target)
|
||||
override fun toString() = "[$left $operator $right]"
|
||||
|
||||
override val isSimple = false
|
||||
@@ -539,6 +543,8 @@ class ArrayIndexedExpression(var plainarrayvar: IdentifierReference?,
|
||||
return ArrayIndexedExpression(plainarrayvar?.copy(), copyNested, pointerderef?.copy(), indexer.copy(), position)
|
||||
}
|
||||
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = true
|
||||
|
||||
fun isSameArrayIndexedAs(other: Expression): Boolean {
|
||||
if(other !is ArrayIndexedExpression || !(other.indexer.indexExpr isSameAs indexer.indexExpr))
|
||||
return false
|
||||
@@ -574,6 +580,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
}
|
||||
|
||||
override fun copy() = TypecastExpression(expression.copy(), type, implicit, position)
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = expression.hasSideEffects(target)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@@ -719,13 +726,19 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
}
|
||||
|
||||
override fun copy() = DirectMemoryRead(addressExpression.copy(), position)
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = isIORead(target)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = addressExpression.referencesIdentifier(nameInSource)
|
||||
override fun inferType(program: Program) = InferredTypes.knownFor(BaseDataType.UBYTE)
|
||||
override fun constValue(program: Program): NumericLiteral? = null
|
||||
override fun isIORead(target: ICompilationTarget) = addressExpression.isIORead(target)
|
||||
override fun isIORead(target: ICompilationTarget): Boolean {
|
||||
if (addressExpression is NumericLiteral) {
|
||||
return target.isIOAddress((addressExpression as NumericLiteral).number.toUInt())
|
||||
}
|
||||
return true // assume worst case if address is not constant
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "DirectMemoryRead($addressExpression)"
|
||||
}
|
||||
@@ -1207,6 +1220,7 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
|
||||
}
|
||||
|
||||
override fun copy(): ArrayLiteral = ArrayLiteral(type, value.map { it.copy() }.toTypedArray(), position)
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = value.any { it.hasSideEffects(target) }
|
||||
override val isSimple = true
|
||||
override fun isIORead(target: ICompilationTarget) = false
|
||||
|
||||
@@ -1222,6 +1236,7 @@ class ArrayLiteral(val type: InferredTypes.InferredType, // inferred because
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@Suppress("KotlinArrayToString")
|
||||
override fun toString(): String = "$value"
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
|
||||
|
||||
@@ -1472,28 +1487,34 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
}
|
||||
else
|
||||
definingScope.lookup(nameInSource)
|
||||
fun targetVarDecl(): VarDecl? {
|
||||
fun targetVarDecl(visitedAliases: MutableSet<Alias> = mutableSetOf()): VarDecl? {
|
||||
// follows aliases
|
||||
val t = targetStatement()
|
||||
return if(t is Alias)
|
||||
t.target.targetVarDecl()
|
||||
else
|
||||
return if (t is Alias) {
|
||||
if (t in visitedAliases) return null
|
||||
visitedAliases.add(t)
|
||||
t.target.targetVarDecl(visitedAliases)
|
||||
} else
|
||||
t as? VarDecl
|
||||
}
|
||||
fun targetSubroutine(): Subroutine? {
|
||||
fun targetSubroutine(visitedAliases: MutableSet<Alias> = mutableSetOf()): Subroutine? {
|
||||
// follows aliases
|
||||
val t = targetStatement()
|
||||
return if(t is Alias)
|
||||
t.target.targetSubroutine()
|
||||
else
|
||||
return if (t is Alias) {
|
||||
if (t in visitedAliases) return null
|
||||
visitedAliases.add(t)
|
||||
t.target.targetSubroutine(visitedAliases)
|
||||
} else
|
||||
t as? Subroutine
|
||||
}
|
||||
fun targetStructDecl(): StructDecl? {
|
||||
fun targetStructDecl(visitedAliases: MutableSet<Alias> = mutableSetOf()): StructDecl? {
|
||||
// follows aliases
|
||||
val t = targetStatement()
|
||||
return if(t is Alias)
|
||||
t.target.targetStructDecl()
|
||||
else
|
||||
return if (t is Alias) {
|
||||
if (t in visitedAliases) return null
|
||||
visitedAliases.add(t)
|
||||
t.target.targetStructDecl(visitedAliases)
|
||||
} else
|
||||
t as? StructDecl
|
||||
}
|
||||
|
||||
@@ -1659,6 +1680,7 @@ class FunctionCallExpression(override var target: IdentifierReference,
|
||||
}
|
||||
|
||||
override fun copy() = FunctionCallExpression(target.copy(), args.map { it.copy() }.toMutableList(), position)
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = true
|
||||
override val isSimple = if (target.nameInSource.singleOrNull() in SimpleBuiltinFunctions) this.args.all { it.isSimple } else false
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===target)
|
||||
@@ -1711,6 +1733,8 @@ class FunctionCallExpression(override var target: IdentifierReference,
|
||||
val constVal = constValue(program ,false)
|
||||
if(constVal!=null)
|
||||
return InferredTypes.knownFor(constVal.type)
|
||||
if (args.any { it.inferType(program).isUnknown })
|
||||
return InferredTypes.unknown()
|
||||
val stmt = target.targetStatement(program.builtinFunctions) ?: return InferredTypes.unknown()
|
||||
when (stmt) {
|
||||
is BuiltinFunctionPlaceholder -> {
|
||||
@@ -1847,6 +1871,8 @@ class IfExpression(var condition: Expression, var truevalue: Expression, var fal
|
||||
|
||||
override fun copy(): Expression = IfExpression(condition.copy(), truevalue.copy(), falsevalue.copy(), position)
|
||||
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = condition.hasSideEffects(target) || truevalue.hasSideEffects(target) || falsevalue.hasSideEffects(target)
|
||||
|
||||
override fun constValue(program: Program): NumericLiteral? {
|
||||
val cond = condition.constValue(program)
|
||||
if(cond!=null) {
|
||||
@@ -1955,6 +1981,7 @@ class PtrDereference(
|
||||
}
|
||||
|
||||
override val isSimple = false
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = true
|
||||
override fun isIORead(target: ICompilationTarget) = false
|
||||
override fun copy(): PtrDereference = PtrDereference(chain.toList(), derefLast, position)
|
||||
override fun constValue(program: Program): NumericLiteral? {
|
||||
@@ -2070,6 +2097,7 @@ class ArrayIndexedPtrDereference(
|
||||
}
|
||||
|
||||
override val isSimple = false
|
||||
override fun hasSideEffects(target: ICompilationTarget): Boolean = true
|
||||
override fun isIORead(target: ICompilationTarget) = false
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = chain.size==1 && chain==nameInSource
|
||||
|
||||
@@ -840,6 +840,19 @@ data class AssignTarget(
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
fun hasSideEffects(target: ICompilationTarget): Boolean {
|
||||
if (void) return false
|
||||
if (identifier != null) {
|
||||
val symbol = definingScope.lookup(identifier!!.nameInSource)
|
||||
if (symbol is VarDecl && symbol.type == VarDeclType.MEMORY && symbol.value is NumericLiteral) {
|
||||
return target.isIOAddress((symbol.value as NumericLiteral).number.toUInt())
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true // memoryAddress, arrayindexed, pointerDereference, arrayIndexedDereference
|
||||
}
|
||||
|
||||
override fun copy() = AssignTarget(
|
||||
identifier?.copy(),
|
||||
arrayindexed?.copy(),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.2.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c128.p8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.2.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c64.p8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.2.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-cx16.p8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.2.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-pet32.p8
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v12.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v12.2.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-virtual.p8
|
||||
|
||||
@@ -515,7 +515,7 @@ Examples
|
||||
|
||||
.. index:: single: Examples; Examples in source tree
|
||||
|
||||
A bunch of example programs can be found in the 'examples' directory of the source tree on Github.
|
||||
A bunch of example programs can be found in the :source:`examples` directory of the source tree on Github.
|
||||
(Or download the source archive from there to get all the files at once).
|
||||
|
||||
There are cross-platform examples that can be compiled for various systems unaltered,
|
||||
|
||||
+21
-3
@@ -39,11 +39,29 @@ version = read_properties("gradle.properties")["version"].strip()
|
||||
release = version
|
||||
|
||||
if release.endswith("SNAPSHOT"):
|
||||
commit_id = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"], capture_output=True, text=True, check=True
|
||||
).stdout.strip()
|
||||
try:
|
||||
commit_id = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"], capture_output=True, text=True, check=True
|
||||
).stdout.strip()
|
||||
except Exception:
|
||||
commit_id = "master"
|
||||
else:
|
||||
commit_id = f"v{release}"
|
||||
try:
|
||||
# If a tag v{version} exists, we use that.
|
||||
# Otherwise, we try to use the current branch name.
|
||||
subprocess.run(["git", "rev-parse", "--verify", commit_id], capture_output=True, check=True)
|
||||
except Exception:
|
||||
# Tag doesn't exist, try to get the branch name
|
||||
try:
|
||||
branch = subprocess.run(
|
||||
["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True, check=True
|
||||
).stdout.strip()
|
||||
if branch != "HEAD":
|
||||
commit_id = branch
|
||||
except Exception:
|
||||
# Git failed or detached HEAD, stay with v{release}
|
||||
pass
|
||||
|
||||
|
||||
# -- extensions
|
||||
|
||||
@@ -20,7 +20,7 @@ You can compile programs for various machines that are built into the compiler:
|
||||
* Commodore 128 (limited support)
|
||||
* Commodore PET (limited support)
|
||||
* any other 65(C)02 target machine or setup can be configured to a great extent in a user written configuration file.
|
||||
There are some examples included for the Atari 800 XL, NEO6502, Foenix F256, and such.
|
||||
There are some for the Atari 800 XL, NEO6502, Foenix F256, and such.
|
||||
* some users have been experimenting with a NES and a C64 OS target as well.
|
||||
|
||||
Some language features are mentioned below, and you can also read :ref:`comparingprog8` if you
|
||||
|
||||
@@ -67,10 +67,13 @@ Math
|
||||
Returns quotient and remainder of the division as two ubyte or uword values.
|
||||
Performs the division only once. Using '/' and '%' separately
|
||||
would perform the division twice, so using divmod is much more efficient for this.
|
||||
**Note:** Clobbers ``cx16.r15`` (the remainder is stored there for the word variant).
|
||||
The ``%`` operator on its own used to also clobber ``cx16.r15`` but now preserves it.
|
||||
|
||||
:index:`gcd` (a, b)
|
||||
Returns the GCD (greatest common divisor) of uwords a and b
|
||||
The routine is efficient and uses bit shifts instead of divisions.
|
||||
**Clobbers:** ``cx16.r0`` and ``cx16.r1``.
|
||||
|
||||
:index:`max` (x, y)
|
||||
Returns the largest of x and y. Supported for integer types only, for floats use ``floats.maxf()`` instead.
|
||||
@@ -938,6 +941,7 @@ miscellaneous
|
||||
``direction_qd (ubyte quadrant, ubyte xdelta, ubyte ydelta)``
|
||||
If you already know the quadrant and x/y deltas, calculate discrete direction between 0 and 23.
|
||||
This is a heavily optimized routine (small and fast).
|
||||
**Clobbers:** ``cx16.r0`` through ``cx16.r5`` (used as temporary variables).
|
||||
|
||||
``diff (ubyte b1, ubyte b2) -> ubyte``
|
||||
Returns the absolute difference, or distance, between the two byte values.
|
||||
@@ -946,6 +950,7 @@ miscellaneous
|
||||
``diffw (uword w1, uword w2) -> uword``
|
||||
Returns the absolute difference, or distance, between the two word values.
|
||||
(This routine is more efficient than doing a compare and a subtract separately, or using abs)
|
||||
**Clobbers:** ``cx16.r0``
|
||||
|
||||
|
||||
random numbers
|
||||
@@ -998,6 +1003,7 @@ random numbers
|
||||
|
||||
``log2w (uword v)``
|
||||
Returns the 2-Log of the word value v.
|
||||
**Clobbers:** ``cx16.r0``
|
||||
|
||||
trigonometry
|
||||
''''''''''''
|
||||
@@ -1014,6 +1020,7 @@ trigonometry
|
||||
``atan2 (ubyte x1, ubyte y1, ubyte x2, ubyte y2)``
|
||||
Fast arctan routine that uses more memory because of large lookup tables.
|
||||
Calculate the angle, in a 256-degree circle, between two points in the positive quadrant.
|
||||
**Clobbers:** ``cx16.r0`` through ``cx16.r4`` (used as temporary variables).
|
||||
|
||||
``sin8u (x)``
|
||||
Fast 8-bit ubyte sine using a lookup table.
|
||||
@@ -1199,7 +1206,7 @@ Available routines:
|
||||
Waveform constants: ``PULSE``, ``SAWTOOTH``, ``TRIANGLE``, ``NOISE``
|
||||
Channel constants: ``LEFT``, ``RIGHT``, ``BOTH``, ``DISABLED``
|
||||
|
||||
See the examples/cx16/bdmusic.p8 program for ideas how to use it.
|
||||
See the :source:`examples/cx16/bdmusic.p8` program for ideas how to use it.
|
||||
|
||||
Read the :source:`psg2 source code <compiler/res/prog8lib/cx16/psg2.p8>` to see everything that's in there.
|
||||
|
||||
@@ -1283,7 +1290,7 @@ Various sorting routines (gnome sort and shell sort variants) for byte, word and
|
||||
**NOTE:** all word and str arrays have to be @nosplit! Words and pointers need to be consecutive in memory for now.
|
||||
**NOTE:** sorting is done in ascending order.
|
||||
Read the :source:`sorting source code <compiler/res/prog8lib/sorting.p8>`
|
||||
to see what's in there. Also check out the `sortingbench` example.
|
||||
to see what's in there. Also check out the :source:`sortingbench <examples/cx16/sortingbench.p8>` example.
|
||||
|
||||
|
||||
sprites (cx16 only)
|
||||
@@ -1294,7 +1301,7 @@ Available for the Cx16 target. Simple routines to manipulate sprites.
|
||||
They're not written for high performance, but for simplicity.
|
||||
That's why they control one sprite at a time. The exception is the ``pos_batch`` routine,
|
||||
which is quite efficient to update sprite positions of multiple sprites in one go.
|
||||
See the examples/cx16/sprites/dragon.p8 and dragons.p8 programs for ideas how to use it.
|
||||
See the :source:`examples/cx16/sprites/dragon.p8` and :source:`dragons.p8 <examples/cx16/sprites/dragons.p8>` programs for ideas how to use it.
|
||||
|
||||
Read the :source:`sprites source code <compiler/res/prog8lib/cx16/sprites.p8>`
|
||||
to see what's in there.
|
||||
@@ -1587,6 +1594,7 @@ miscellaneous
|
||||
- 8 = Atari 8 bits
|
||||
- 16 = Commander X16
|
||||
- 25 = Foenix F256 family
|
||||
- 32 = Commodore PET
|
||||
- 64 = Commodore 64
|
||||
- 128 = Commodore 128
|
||||
- 255 = Virtual machine
|
||||
|
||||
@@ -1352,6 +1352,27 @@ flag such as Carry (Pc).
|
||||
For example, you can use R0+R1, R2+R3, R4+R5 and so on to take a long value instead.
|
||||
The syntax to use as a 'register' name for those pairs is ``R0R1``, ``R2R3``, ``R4R5`` and so on.
|
||||
|
||||
.. caution::
|
||||
**Virtual register clobbering by compiler operations:**
|
||||
|
||||
Various compiler operations and builtin routines use the virtual registers R0-R15 as
|
||||
temporary storage or for placing return values. If you are using the virtual registers
|
||||
directly in your program (or via ``@R0..@R15`` parameter annotations), be aware of the
|
||||
fact that they will not always preserve their value!
|
||||
|
||||
========================== ================================================
|
||||
Register(s) When clobbered
|
||||
========================== ================================================
|
||||
``R0-R3`` Many library routines use these as scratch
|
||||
``R12-R15`` Several long value operations and arithmetic expressions
|
||||
``R14-R15`` Several long value operations / word '%', '/', divmod
|
||||
``R0..R15`` if specified: multi-value subroutine return values, parameter reuse.
|
||||
several library routines may clobber one or more of these registers too.
|
||||
========================== ================================================
|
||||
|
||||
Additionally, on all targets, the virtual registers R0-R15 are **not preserved** across IRQ
|
||||
handler calls. Use ``cx16.save_virtual_registers()`` / ``cx16.restore_virtual_registers()``
|
||||
if your IRQ handler needs to use them.
|
||||
|
||||
External subroutines
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -10,13 +10,6 @@ Dead Code Elimination bug in 64tass, for nested subroutines
|
||||
that only used other gfx_lores functions (like ``circle()``). Fixed by moving it to a separate ``internal_line_plot()``.
|
||||
|
||||
|
||||
Weird Heisenbug
|
||||
^^^^^^^^^^^^^^^
|
||||
- BUG: examples/cube3d-float crashes with div by zero error on C64 (works on cx16. Already broken in v11, v10 still worked)
|
||||
caused by the RTS after JMP removal in optimizeJsrRtsAndOtherCombinations (replacing it with a NOP makes the problem disappear !??!?).
|
||||
Also observed in the boingball example for the C64 when some code was removed from the start and end.
|
||||
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- make enums strongly typed instead of just syntactic sugar for ints (see ideas/enum-strong-type.md for the plan)
|
||||
@@ -92,6 +85,7 @@ Libraries
|
||||
Optimizations
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
- Inline subroutines with 1 (or even more) simple argument, BE VERY CAREFUL though because this is a complicated inlining action that could cause endless loops. Currently inlining is limited to subroutines with 0 params.
|
||||
- Port more benchmarks from https://thred.github.io/c-bench-64/ to prog8 and see how it stacks up. (see benchmark-c/ directory)
|
||||
- Compilation speed: try to join multiple modifications in 1 result in the AST processors instead of returning it straight away every time
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
|
||||
@@ -327,7 +327,7 @@ to be done on word values, and don't want to explicitly have to cast everything
|
||||
**longs and cx16.R12,R13,R14,R15**:
|
||||
**Some operations on long values require the use of the R12-R15 virtual register as temporary storage**
|
||||
So if you are working with long values, you should assume that the contents of R12-R15 could be destroyed.
|
||||
()Many operations preserve the values, but not all because of reasons)
|
||||
(Many operations preserve the values, but not all, because of reasons)
|
||||
**Using R12,R13,R14,R15 in expressions that work with longs, will sometimes give a corrupted result, without
|
||||
a warning of the compiler!** It is strongly advised to *not* use R12-R15 at all when dealing with longs.
|
||||
|
||||
|
||||
+262
-3
@@ -1,10 +1,269 @@
|
||||
%encoding iso
|
||||
%option no_sysinit
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
main {
|
||||
; test values
|
||||
ubyte @shared ub0 = 0
|
||||
ubyte @shared ub1 = 1
|
||||
ubyte @shared ub127 = 127
|
||||
ubyte @shared ub128 = 128
|
||||
ubyte @shared ub255 = 255
|
||||
|
||||
byte @shared bmin = -128
|
||||
byte @shared b_1 = -1
|
||||
byte @shared b0 = 0
|
||||
byte @shared b1 = 1
|
||||
byte @shared bmax = 127
|
||||
|
||||
uword @shared uw0 = 0
|
||||
uword @shared uw1 = 1
|
||||
uword @shared uw32767 = 32767
|
||||
uword @shared uw32768 = 32768
|
||||
uword @shared uwmax = 65535
|
||||
|
||||
word @shared wmin = -32768
|
||||
word @shared w_1 = -1
|
||||
word @shared w0 = 0
|
||||
word @shared w1 = 1
|
||||
word @shared wmax = 32767
|
||||
|
||||
long @shared lmin = -2147483647
|
||||
long @shared l_1 = -1
|
||||
long @shared l0 = 0
|
||||
long @shared l1 = 1
|
||||
long @shared lmax = 2147483647
|
||||
uword @shared failures = 0
|
||||
|
||||
sub start() {
|
||||
const ^^ubyte name = "irmen"
|
||||
txt.print(name)
|
||||
txt.iso()
|
||||
txt.print("--- sgn tests ---\n")
|
||||
test_sgn()
|
||||
txt.print("--- cmp ubyte ---\n")
|
||||
test_ubyte()
|
||||
txt.print("--- cmp byte ---\n")
|
||||
test_byte()
|
||||
txt.print("--- cmp uword ---\n")
|
||||
test_uword()
|
||||
txt.print("--- cmp word ---\n")
|
||||
test_word()
|
||||
txt.print("--- cmp long ---\n")
|
||||
test_long()
|
||||
txt.print("\n")
|
||||
txt.print_uw(failures)
|
||||
txt.print(" failures!\n")
|
||||
sys.poweroff_system()
|
||||
}
|
||||
|
||||
sub print_flags(ubyte f) {
|
||||
if ((f & 128) != 0) txt.print("n") else txt.print(".")
|
||||
if ((f & 64) != 0) txt.print("v") else txt.print(".")
|
||||
if ((f & 2) != 0) txt.print("z") else txt.print(".")
|
||||
if ((f & 1) != 0) txt.print("c") else txt.print(".")
|
||||
}
|
||||
|
||||
sub test_sgn() {
|
||||
check_sgn_b(b_1, "-1")
|
||||
check_sgn_b(b0, "0")
|
||||
check_sgn_b(b1, "1")
|
||||
check_sgn_l(l_1, "-1")
|
||||
check_sgn_l(l0, "0")
|
||||
check_sgn_l(l1, "1")
|
||||
}
|
||||
|
||||
sub check_sgn_b(byte v, uword s) {
|
||||
txt.print("sgn(")
|
||||
txt.print(s)
|
||||
txt.print(") -> ")
|
||||
byte res = sgn(v)
|
||||
ubyte flags = sys.read_flags() & $c3
|
||||
txt.print_b(res)
|
||||
txt.print(" flags: ")
|
||||
print_flags(flags)
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub check_sgn_l(long v, uword s) {
|
||||
txt.print("sgn(")
|
||||
txt.print(s)
|
||||
txt.print(") -> ")
|
||||
byte res = sgn(v)
|
||||
ubyte flags = sys.read_flags() & $c3
|
||||
txt.print_b(res)
|
||||
txt.print(" flags: ")
|
||||
print_flags(flags)
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub check_ub(ubyte a, ubyte b, uword sa, uword sb) {
|
||||
txt.print("cmp(")
|
||||
txt.print(sa)
|
||||
txt.print(",")
|
||||
txt.print(sb)
|
||||
txt.print(") -> ")
|
||||
cmp(a, b)
|
||||
ubyte actual = sys.read_flags() & $83
|
||||
print_flags(actual)
|
||||
ubyte res = a - b
|
||||
ubyte expected = 0
|
||||
if res == 0 expected |= 2
|
||||
if (res & 128) != 0 expected |= 128
|
||||
if a >= b expected |= 1
|
||||
if actual != expected {
|
||||
txt.print(" fail!")
|
||||
failures += 1
|
||||
}
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub test_ubyte() {
|
||||
do_ub(ub0, ub0, "0", "0")
|
||||
do_ub(ub0, ub1, "0", "1")
|
||||
do_ub(ub127, ub128, "127", "128")
|
||||
do_ub(ub0, ub255, "0", "255")
|
||||
do_ub(ub255, ub255, "255", "255")
|
||||
}
|
||||
sub do_ub(ubyte a, ubyte b, uword sa, uword sb) {
|
||||
check_ub(a, b, sa, sb)
|
||||
if a != b check_ub(b, a, sb, sa)
|
||||
}
|
||||
|
||||
sub check_b(byte a, byte b, uword sa, uword sb) {
|
||||
txt.print("cmp(")
|
||||
txt.print(sa)
|
||||
txt.print(",")
|
||||
txt.print(sb)
|
||||
txt.print(") -> ")
|
||||
cmp(a, b)
|
||||
ubyte actual = sys.read_flags() & $c3
|
||||
print_flags(actual)
|
||||
byte res = a - b
|
||||
ubyte expected = 0
|
||||
if res == 0 expected |= 2
|
||||
if (res & 128) != 0 expected |= 128
|
||||
if ((a as ubyte) >= (b as ubyte)) { expected |= 1 }
|
||||
if (((a ^ b) & (a ^ res) & 128) != 0) expected |= 64
|
||||
if actual != expected {
|
||||
txt.print(" fail!")
|
||||
failures += 1
|
||||
}
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub test_byte() {
|
||||
do_b(b0, b0, "0", "0")
|
||||
do_b(b0, b1, "0", "1")
|
||||
do_b(b_1, b0, "-1", "0")
|
||||
do_b(bmin, bmax, "-128", "127")
|
||||
do_b(b_1, b1, "-1", "1")
|
||||
do_b(bmin, bmin, "-128", "-128")
|
||||
}
|
||||
sub do_b(byte a, byte b, uword sa, uword sb) {
|
||||
check_b(a, b, sa, sb)
|
||||
if a != b check_b(b, a, sb, sa)
|
||||
}
|
||||
|
||||
sub check_uw(uword a, uword b, uword sa, uword sb) {
|
||||
txt.print("cmp(")
|
||||
txt.print(sa)
|
||||
txt.print(",")
|
||||
txt.print(sb)
|
||||
txt.print(") -> ")
|
||||
cmp(a, b)
|
||||
ubyte actual = sys.read_flags() & $83
|
||||
print_flags(actual)
|
||||
uword res = a - b
|
||||
ubyte expected = 0
|
||||
if res == 0 expected |= 2
|
||||
if (res & $8000) != 0 expected |= 128
|
||||
if a >= b expected |= 1
|
||||
if actual != expected {
|
||||
txt.print(" fail!")
|
||||
failures += 1
|
||||
}
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub test_uword() {
|
||||
do_uw(uw0, uw0, "0", "0")
|
||||
do_uw(uw0, uw1, "0", "1")
|
||||
do_uw(uw32767, uw32768, "32767", "32768")
|
||||
do_uw(uw0, uwmax, "0", "65535")
|
||||
do_uw(uwmax, uwmax, "65535", "65535")
|
||||
}
|
||||
sub do_uw(uword a, uword b, uword sa, uword sb) {
|
||||
check_uw(a, b, sa, sb)
|
||||
if a != b check_uw(b, a, sb, sa)
|
||||
}
|
||||
|
||||
sub check_w(word a, word b, uword sa, uword sb) {
|
||||
txt.print("cmp(")
|
||||
txt.print(sa)
|
||||
txt.print(",")
|
||||
txt.print(sb)
|
||||
txt.print(") -> ")
|
||||
cmp(a, b)
|
||||
ubyte actual = sys.read_flags() & $c3
|
||||
print_flags(actual)
|
||||
word res = a - b
|
||||
ubyte expected = 0
|
||||
if res == 0 expected |= 2
|
||||
if (res & $8000) != 0 expected |= 128
|
||||
if ((a as uword) >= (b as uword)) { expected |= 1 }
|
||||
if (((a ^ b) & (a ^ res) & $8000) != 0) expected |= 64
|
||||
if actual != expected {
|
||||
txt.print(" fail!")
|
||||
failures += 1
|
||||
}
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub test_word() {
|
||||
do_w(w0, w0, "0", "0")
|
||||
do_w(w0, w1, "0", "1")
|
||||
do_w(w_1, w0, "-1", "0")
|
||||
do_w(wmin, wmax, "-32768", "32767")
|
||||
do_w(w_1, w1, "-1", "1")
|
||||
do_w(wmin, wmin, "-32768", "-32768")
|
||||
}
|
||||
sub do_w(word a, word b, uword sa, uword sb) {
|
||||
check_w(a, b, sa, sb)
|
||||
if a != b check_w(b, a, sb, sa)
|
||||
}
|
||||
|
||||
sub check_l(long a, long b, uword sa, uword sb) {
|
||||
txt.print("cmp(")
|
||||
txt.print(sa)
|
||||
txt.print(",")
|
||||
txt.print(sb)
|
||||
txt.print(") -> ")
|
||||
cmp(a, b)
|
||||
ubyte actual = sys.read_flags() & $c3
|
||||
print_flags(actual)
|
||||
long res = a - b
|
||||
ubyte expected = 0
|
||||
if res == 0 expected |= 2
|
||||
if (res & $80000000) != 0 expected |= 128
|
||||
if (a ^ $80000000) >= (b ^ $80000000) expected |= 1
|
||||
if (((a ^ b) & (a ^ res) & $80000000) != 0) expected |= 64
|
||||
if actual != expected {
|
||||
txt.print(" fail!")
|
||||
failures += 1
|
||||
}
|
||||
txt.print("\n")
|
||||
}
|
||||
|
||||
sub test_long() {
|
||||
do_l(l0, l0, "0", "0")
|
||||
do_l(l0, l1, "0", "1")
|
||||
do_l(l_1, l0, "-1", "0")
|
||||
do_l(lmin, lmax, "lmin", "lmax")
|
||||
do_l(l_1, l1, "-1", "1")
|
||||
do_l(lmin, lmin, "lmin", "lmin")
|
||||
}
|
||||
sub do_l(long a, long b, uword sa, uword sb) {
|
||||
check_l(a, b, sa, sb)
|
||||
if a != b check_l(b, a, sb, sa)
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,4 +4,4 @@ org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
org.gradle.configuration-cache=false
|
||||
kotlin.code.style=official
|
||||
version=12.2
|
||||
version=12.2.1
|
||||
@@ -1,11 +1,11 @@
|
||||
package prog8lsp
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.Position
|
||||
|
||||
@@ -479,6 +479,8 @@ object SymbolLookup {
|
||||
}
|
||||
}
|
||||
is Subroutine -> {
|
||||
if(stmt.isPrivate)
|
||||
return
|
||||
val params = stmt.parameters.joinToString(", ") { "${it.name}: ${it.type}" }
|
||||
symbols.add(CompletionSymbol(
|
||||
name = stmt.name,
|
||||
@@ -488,6 +490,8 @@ object SymbolLookup {
|
||||
))
|
||||
}
|
||||
is VarDecl -> {
|
||||
if(stmt.isPrivate)
|
||||
return
|
||||
val kind = if (stmt.type == VarDeclType.CONST) {
|
||||
org.eclipse.lsp4j.CompletionItemKind.Constant
|
||||
} else {
|
||||
@@ -501,6 +505,8 @@ object SymbolLookup {
|
||||
))
|
||||
}
|
||||
is StructDecl -> {
|
||||
if(stmt.isPrivate)
|
||||
return
|
||||
symbols.add(CompletionSymbol(
|
||||
name = stmt.name,
|
||||
kind = org.eclipse.lsp4j.CompletionItemKind.Struct,
|
||||
|
||||
@@ -112,6 +112,37 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
|
||||
fun asConstValue(): Double? = (this as? PtNumber)?.number ?: (this as? PtBool)?.asInt()?.toDouble() ?: (this as? PtTypeCast)?.value?.asConstValue()
|
||||
|
||||
fun hasSideEffects(target: ICompilationTarget? = null): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> arrayIndexExpr?.hasSideEffects(target) == true
|
||||
is PtArray -> children.any { (it as PtExpression).hasSideEffects(target) }
|
||||
is PtArrayIndexer -> true
|
||||
is PtBinaryExpression -> left.hasSideEffects(target) || right.hasSideEffects(target)
|
||||
is PtContainmentCheck -> children.any { it is PtExpression && it.hasSideEffects(target) }
|
||||
is PtFunctionCall -> !hasNoSideEffects || args.any { it.hasSideEffects(target) }
|
||||
is PtIdentifier -> false
|
||||
is PtIrRegister -> false
|
||||
is PtMemoryByte -> {
|
||||
val addr = address.asConstInteger()
|
||||
if (addr != null && target != null) {
|
||||
target.isIOAddress(addr.toUInt())
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
is PtBool -> false
|
||||
is PtNumber -> false
|
||||
is PtPrefix -> value.hasSideEffects(target)
|
||||
is PtRange -> from.hasSideEffects(target) || to.hasSideEffects(target)
|
||||
is PtString -> false
|
||||
is PtPointerDeref -> true
|
||||
is PtTypeCast -> value.hasSideEffects(target)
|
||||
is PtIfExpression -> condition.hasSideEffects(target) || truevalue.hasSideEffects(target) || falsevalue.hasSideEffects(target)
|
||||
is PtBranchCondExpression -> truevalue.hasSideEffects(target) || falsevalue.hasSideEffects(target)
|
||||
is PtConstant -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun isSimple(): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> this.arrayIndexExpr==null || this.arrayIndexExpr?.isSimple()==true
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package prog8.code.optimize
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.ComparisonOperators
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.invertedComparisonOperator
|
||||
import prog8.code.core.*
|
||||
|
||||
/**
|
||||
* Boolean and bitwise expression optimizations.
|
||||
@@ -15,7 +12,7 @@ internal object BooleanOptimizers {
|
||||
/**
|
||||
* Optimizes boolean expression patterns (and/or/xor with constants, double negation, etc.).
|
||||
*/
|
||||
fun optimizeBooleanExpressions(program: PtProgram): Int {
|
||||
fun optimizeBooleanExpressions(program: PtProgram, options: CompilationOptions): Int {
|
||||
var changes = 0
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
// Handle PtPrefix (not, -, ~)
|
||||
@@ -67,28 +64,28 @@ internal object BooleanOptimizers {
|
||||
node.parent.setChild(index, right)
|
||||
changes++
|
||||
}
|
||||
// x and false -> false
|
||||
else if (right is PtBool && !right.value) {
|
||||
// x and false -> false (without side effects)
|
||||
else if (right is PtBool && !right.value && !left.hasSideEffects(options.compTarget)) {
|
||||
val replacement = PtBool(false, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, replacement)
|
||||
changes++
|
||||
}
|
||||
// false and x -> false
|
||||
else if (left is PtBool && !left.value) {
|
||||
// false and x -> false (without side effects)
|
||||
else if (left is PtBool && !left.value && !right.hasSideEffects(options.compTarget)) {
|
||||
val replacement = PtBool(false, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, replacement)
|
||||
changes++
|
||||
}
|
||||
// x and x -> x (idempotent)
|
||||
else if (left isSameAs right) {
|
||||
// x and x -> x (idempotent, without side effects)
|
||||
else if (left isSameAs right && !left.hasSideEffects(options.compTarget)) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, left)
|
||||
changes++
|
||||
}
|
||||
// x and not(x) -> false (complement)
|
||||
else if (Helpers.isNegationOf(left, right) || Helpers.isNegationOf(right, left)) {
|
||||
// x and not(x) -> false (complement, without side effects)
|
||||
else if ((Helpers.isNegationOf(left, right) || Helpers.isNegationOf(right, left)) && !left.hasSideEffects(options.compTarget) && !right.hasSideEffects(options.compTarget)) {
|
||||
val replacement = PtBool(false, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, replacement)
|
||||
@@ -96,15 +93,15 @@ internal object BooleanOptimizers {
|
||||
}
|
||||
}
|
||||
"or" -> {
|
||||
// x or true -> true
|
||||
if (right is PtBool && right.value) {
|
||||
// x or true -> true (without side effects)
|
||||
if (right is PtBool && right.value && !left.hasSideEffects(options.compTarget)) {
|
||||
val replacement = PtBool(true, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, replacement)
|
||||
changes++
|
||||
}
|
||||
// true or x -> true
|
||||
else if (left is PtBool && left.value) {
|
||||
// true or x -> true (without side effects)
|
||||
else if (left is PtBool && left.value && !right.hasSideEffects(options.compTarget)) {
|
||||
val replacement = PtBool(true, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, replacement)
|
||||
@@ -122,14 +119,14 @@ internal object BooleanOptimizers {
|
||||
node.parent.setChild(index, right)
|
||||
changes++
|
||||
}
|
||||
// x or x -> x (idempotent)
|
||||
else if (left isSameAs right) {
|
||||
// x or x -> x (idempotent, without side effects)
|
||||
else if (left isSameAs right && !left.hasSideEffects(options.compTarget)) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, left)
|
||||
changes++
|
||||
}
|
||||
// x or not(x) -> true (complement)
|
||||
else if (Helpers.isNegationOf(left, right) || Helpers.isNegationOf(right, left)) {
|
||||
// x or not(x) -> true (complement, without side effects)
|
||||
else if ((Helpers.isNegationOf(left, right) || Helpers.isNegationOf(right, left)) && !left.hasSideEffects(options.compTarget) && !right.hasSideEffects(options.compTarget)) {
|
||||
val replacement = PtBool(true, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, replacement)
|
||||
@@ -167,8 +164,8 @@ internal object BooleanOptimizers {
|
||||
node.parent.setChild(index, right)
|
||||
changes++
|
||||
}
|
||||
// x xor x -> false (idempotent)
|
||||
else if (left isSameAs right) {
|
||||
// x xor x -> false (idempotent, without side effects)
|
||||
else if (left isSameAs right && !left.hasSideEffects(options.compTarget)) {
|
||||
val replacement = PtBool(false, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, replacement)
|
||||
@@ -206,7 +203,7 @@ internal object BooleanOptimizers {
|
||||
/**
|
||||
* Optimizes bitwise complement patterns: x & ~x -> 0, x | ~x -> -1
|
||||
*/
|
||||
fun optimizeBitwiseComplementBinary(program: PtProgram): Int {
|
||||
fun optimizeBitwiseComplementBinary(program: PtProgram, options: CompilationOptions): Int {
|
||||
var changes = 0
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
// Handle x & ~x -> 0 and x | ~x -> -1
|
||||
@@ -215,16 +212,16 @@ internal object BooleanOptimizers {
|
||||
val right = node.right
|
||||
|
||||
if (node.operator == "&") {
|
||||
// x & ~x -> 0
|
||||
if (Helpers.isBitwiseNegationOf(left, right) || Helpers.isBitwiseNegationOf(right, left)) {
|
||||
// x & ~x -> 0 (without side effects)
|
||||
if ((Helpers.isBitwiseNegationOf(left, right) || Helpers.isBitwiseNegationOf(right, left)) && !left.hasSideEffects(options.compTarget) && !right.hasSideEffects(options.compTarget)) {
|
||||
val zero = PtNumber(node.type.base, 0.0, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, zero)
|
||||
changes++
|
||||
}
|
||||
} else if (node.operator == "|") {
|
||||
// x | ~x -> -1 (all bits set)
|
||||
if (Helpers.isBitwiseNegationOf(left, right) || Helpers.isBitwiseNegationOf(right, left)) {
|
||||
// x | ~x -> -1 (all bits set, without side effects)
|
||||
if ((Helpers.isBitwiseNegationOf(left, right) || Helpers.isBitwiseNegationOf(right, left)) && !left.hasSideEffects(options.compTarget) && !right.hasSideEffects(options.compTarget)) {
|
||||
val allOnesValue = when (node.type.base) {
|
||||
BaseDataType.BYTE -> -1.0
|
||||
BaseDataType.UBYTE -> 255.0
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
package prog8.code.optimize
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.BaseDataType
|
||||
import prog8.code.core.ComparisonOperators
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.*
|
||||
|
||||
/**
|
||||
* Comparison pattern optimizations.
|
||||
@@ -104,7 +101,8 @@ internal object ComparisonOptimizers {
|
||||
}
|
||||
|
||||
// Comparison with arithmetic: x <= y-1 -> x < y, x >= y+1 -> x > y
|
||||
if (leftType.isInteger && rightType.isInteger) {
|
||||
// Only for signed integers to avoid unsigned wraparound issues.
|
||||
if (leftType.isSignedInteger && rightType.isSignedInteger) {
|
||||
val rightExpr = right as? PtBinaryExpression
|
||||
if (rightExpr != null) {
|
||||
val rightRightConst = rightExpr.right.asConstValue()
|
||||
@@ -136,7 +134,7 @@ internal object ComparisonOptimizers {
|
||||
/**
|
||||
* Optimizes comparison identities (x==x, x<x, etc.).
|
||||
*/
|
||||
fun optimizeComparisonIdentities(program: PtProgram): Int {
|
||||
fun optimizeComparisonIdentities(program: PtProgram, options: CompilationOptions): Int {
|
||||
var changes = 0
|
||||
walkAst(program) { node: PtNode, depth: Int ->
|
||||
if (node is PtBinaryExpression && node.operator in ComparisonOperators) {
|
||||
@@ -147,7 +145,8 @@ internal object ComparisonOptimizers {
|
||||
// (constant-vs-constant is already folded in compilerAST phase)
|
||||
// NOTE: do NOT apply to floats - NaN comparisons don't follow these rules
|
||||
// (e.g. NaN == NaN is false, NaN <= NaN is false)
|
||||
if (left isSameAs right && !left.type.isFloat) {
|
||||
// NOTE: do NOT apply if expressions have side effects (e.g. peek(1000) == peek(1000))
|
||||
if (left isSameAs right && !left.type.isFloat && !left.hasSideEffects(options.compTarget)) {
|
||||
val result = when (node.operator) {
|
||||
"==" -> true
|
||||
"!=" -> false
|
||||
|
||||
@@ -54,7 +54,7 @@ internal object ExpressionOptimizers {
|
||||
/**
|
||||
* Optimizes algebraic identity patterns (x+0, x*1, x*0, etc.).
|
||||
*/
|
||||
fun optimizeAlgebraicIdentities(program: PtProgram): Int {
|
||||
fun optimizeAlgebraicIdentities(program: PtProgram, options: CompilationOptions): Int {
|
||||
var changes = 0
|
||||
walkAst(program) { node: PtNode, _: Int ->
|
||||
if (node is PtBinaryExpression) {
|
||||
@@ -96,15 +96,15 @@ internal object ExpressionOptimizers {
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
// x * 0 -> 0
|
||||
if (rightConst == 0.0) {
|
||||
// x * 0 -> 0 (for non-floats and without side effects)
|
||||
if (rightConst == 0.0 && !node.type.isFloat && !node.left.hasSideEffects(options.compTarget)) {
|
||||
val zero = PtNumber(node.type.base, 0.0, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, zero)
|
||||
changes++
|
||||
}
|
||||
// 0 * x -> 0
|
||||
else if (leftConst == 0.0) {
|
||||
// 0 * x -> 0 (for non-floats and without side effects)
|
||||
else if (leftConst == 0.0 && !node.type.isFloat && !node.right.hasSideEffects(options.compTarget)) {
|
||||
val zero = PtNumber(node.type.base, 0.0, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, zero)
|
||||
@@ -164,8 +164,8 @@ internal object ExpressionOptimizers {
|
||||
}
|
||||
}
|
||||
"xor" -> {
|
||||
// x xor x -> 0
|
||||
if (left isSameAs right) {
|
||||
// x xor x -> 0 (without side effects)
|
||||
if (left isSameAs right && !left.hasSideEffects(options.compTarget)) {
|
||||
val zero = PtNumber(node.type.base, 0.0, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, zero)
|
||||
@@ -199,8 +199,8 @@ internal object ExpressionOptimizers {
|
||||
node.parent.setChild(index, right)
|
||||
changes++
|
||||
}
|
||||
// x & x -> x (idempotent)
|
||||
else if (left isSameAs right) {
|
||||
// x & x -> x (idempotent, without side effects)
|
||||
else if (left isSameAs right && !left.hasSideEffects(options.compTarget)) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, left)
|
||||
changes++
|
||||
@@ -233,8 +233,8 @@ internal object ExpressionOptimizers {
|
||||
node.parent.setChild(index, allOnes)
|
||||
changes++
|
||||
}
|
||||
// x | x -> x (idempotent)
|
||||
else if (left isSameAs right) {
|
||||
// x | x -> x (idempotent, without side effects)
|
||||
else if (left isSameAs right && !left.hasSideEffects(options.compTarget)) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, left)
|
||||
changes++
|
||||
@@ -271,8 +271,8 @@ internal object ExpressionOptimizers {
|
||||
negation.parent = node.parent
|
||||
changes++
|
||||
}
|
||||
// x ^ x -> 0 (idempotent)
|
||||
else if (left isSameAs right) {
|
||||
// x ^ x -> 0 (idempotent, without side effects)
|
||||
else if (left isSameAs right && !left.hasSideEffects(options.compTarget)) {
|
||||
val zero = PtNumber(node.type.base, 0.0, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, zero)
|
||||
@@ -376,7 +376,7 @@ internal object ExpressionOptimizers {
|
||||
* Strength reduction: replaces expensive operations with cheaper equivalents.
|
||||
* Division/modulo by powers of two become shifts/masks.
|
||||
*/
|
||||
fun optimizeStrengthReduction(program: PtProgram): Int {
|
||||
fun optimizeStrengthReduction(program: PtProgram, options: CompilationOptions): Int {
|
||||
var changes = 0
|
||||
walkAst(program) { node: PtNode, _: Int ->
|
||||
if (node is PtBinaryExpression && node.type.isInteger) {
|
||||
@@ -396,8 +396,8 @@ internal object ExpressionOptimizers {
|
||||
}
|
||||
}
|
||||
|
||||
// Modulo by power of two: x % 2^n -> x & (2^n - 1) (for all integers)
|
||||
if (node.operator == "%" && rightConst != null) {
|
||||
// Modulo by power of two: x % 2^n -> x & (2^n - 1) (for unsigned integers only)
|
||||
if (node.operator == "%" && rightConst != null && node.type.isUnsignedInteger) {
|
||||
if (rightConst in powersOfTwoFloat) {
|
||||
val mask = rightConst - 1.0
|
||||
val andExpr = PtBinaryExpression("&", node.type, node.position)
|
||||
@@ -408,8 +408,8 @@ internal object ExpressionOptimizers {
|
||||
andExpr.parent = node.parent
|
||||
changes++
|
||||
}
|
||||
// x % 1 -> 0
|
||||
else if (rightConst == 1.0) {
|
||||
// x % 1 -> 0 (without side effects)
|
||||
else if (rightConst == 1.0 && !node.left.hasSideEffects(options.compTarget)) {
|
||||
val zero = PtNumber(node.type.base, 0.0, node.position)
|
||||
val index = node.parent.children.indexOf(node)
|
||||
node.parent.setChild(index, zero)
|
||||
|
||||
@@ -120,7 +120,7 @@ fun referencesIdentifier(node: PtNode, identifier: PtIdentifier): Boolean {
|
||||
is PtIfExpression -> expr.children.any { referencesIdentifier(it, identifier) }
|
||||
is PtFunctionCall -> true
|
||||
is PtMemoryByte -> expr.children.any { referencesIdentifier(it, identifier) }
|
||||
is PtPointerDeref -> false
|
||||
is PtPointerDeref -> expr.children.any { referencesIdentifier(it, identifier) }
|
||||
is PtPrefix -> expr.children.any { referencesIdentifier(it, identifier) }
|
||||
is PtRange -> expr.children.any { referencesIdentifier(it, identifier) }
|
||||
is PtTypeCast -> expr.children.any { referencesIdentifier(it, identifier) }
|
||||
|
||||
@@ -88,7 +88,7 @@ fun optimizeSimplifiedAst(program: PtProgram, options: CompilationOptions, st: S
|
||||
runFixpointOptimizations(program, ctx)
|
||||
|
||||
// Run single-pass optimizations (only need to run once)
|
||||
runSinglePassOptimizations(program)
|
||||
runSinglePassOptimizations(program, options)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +105,7 @@ private fun runFixpointOptimizations(program: PtProgram, ctx: OptimizerContext)
|
||||
|
||||
while (ctx.errors.noErrors() &&
|
||||
VariableOptimizers.optimizeAssignTargets(program, ctx.st)
|
||||
+ ExpressionOptimizers.optimizeAlgebraicIdentities(program)
|
||||
+ ExpressionOptimizers.optimizeAlgebraicIdentities(program, ctx.options)
|
||||
+ ExpressionOptimizers.optimizeExpressionRearrangement(program)
|
||||
+ BooleanOptimizers.optimizeBitwisePrefix(program)
|
||||
+ MemoryOptimizers.optimizeAddressOfDereference(program)
|
||||
@@ -113,9 +113,9 @@ private fun runFixpointOptimizations(program: PtProgram, ctx: OptimizerContext)
|
||||
+ ComparisonOptimizers.optimizeFloatComparesToZero(program)
|
||||
+ ComparisonOptimizers.optimizeSgnComparisons(program, ctx.errors)
|
||||
+ ComparisonOptimizers.optimizeComparisonSimplifications(program)
|
||||
+ ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
+ BooleanOptimizers.optimizeBooleanExpressions(program)
|
||||
+ BooleanOptimizers.optimizeBitwiseComplementBinary(program)
|
||||
+ ComparisonOptimizers.optimizeComparisonIdentities(program, ctx.options)
|
||||
+ BooleanOptimizers.optimizeBooleanExpressions(program, ctx.options)
|
||||
+ BooleanOptimizers.optimizeBitwiseComplementBinary(program, ctx.options)
|
||||
+ ExpressionOptimizers.optimizeBinaryExpressions(program, ctx.options)
|
||||
+ ExpressionOptimizers.optimizeOperandOrder(program)
|
||||
+ ControlFlowOptimizers.optimizeSingleWhens(program, ctx.errors)
|
||||
@@ -133,10 +133,10 @@ private fun runFixpointOptimizations(program: PtProgram, ctx: OptimizerContext)
|
||||
* Runs optimizations that only need to execute once.
|
||||
* These don't create opportunities for other optimizations, so no fixpoint loop needed.
|
||||
*/
|
||||
private fun runSinglePassOptimizations(program: PtProgram) {
|
||||
private fun runSinglePassOptimizations(program: PtProgram, options: CompilationOptions) {
|
||||
// Variable optimizations
|
||||
VariableOptimizers.optimizeRedundantVarInits(program)
|
||||
|
||||
// Strength reduction (x/2^n→x>>n, x%2^n→x&(2^n-1)) doesn't create opportunities for other opts
|
||||
ExpressionOptimizers.optimizeStrengthReduction(program)
|
||||
ExpressionOptimizers.optimizeStrengthReduction(program, options)
|
||||
}
|
||||
@@ -88,8 +88,12 @@ internal object VariableOptimizers {
|
||||
if (assignIndex>initializerIndex) {
|
||||
val inbetween = parent.children.subList(initializerIndex+1, assignIndex)
|
||||
if(!inbetween.any { stmt -> referencesIdentifier(stmt, identifier) }) {
|
||||
// var initializer is redundant, it will be overwritten by an assignment later. remove the initializer
|
||||
removeInitializations.add(parent to parent.children[initializerIndex] as PtAssignment)
|
||||
// also check that the overwriting assignment's value doesn't reference the variable
|
||||
val assignment = parent.children[assignIndex] as PtAssignment
|
||||
if(!referencesIdentifier(assignment.value, identifier)) {
|
||||
// var initializer is redundant, it will be overwritten by an assignment later. remove the initializer
|
||||
removeInitializations.add(parent to parent.children[initializerIndex] as PtAssignment)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ package prog8tests.code
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.optimize.ComparisonOptimizers
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
import prog8tests.helpers.DummyStringEncoder
|
||||
|
||||
@@ -15,6 +17,7 @@ import prog8tests.helpers.DummyStringEncoder
|
||||
* (because NaN comparisons don't follow the same rules).
|
||||
*/
|
||||
class TestComparisonOptimizers: FunSpec({
|
||||
val options = CompilationOptions.builder(VMTarget()).build()
|
||||
|
||||
// Helper to create a simple program with a binary expression in a sub
|
||||
fun createProgramWithComparison(operator: String, leftType: DataType): PtProgram {
|
||||
@@ -37,7 +40,7 @@ class TestComparisonOptimizers: FunSpec({
|
||||
|
||||
test("optimizeComparisonIdentities: integer x==x should be optimized to true") {
|
||||
val program = createProgramWithComparison("==", DataType.UWORD)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program, options)
|
||||
|
||||
changes shouldBe 1
|
||||
// The comparison should be replaced by PtBool(true)
|
||||
@@ -49,7 +52,7 @@ class TestComparisonOptimizers: FunSpec({
|
||||
|
||||
test("optimizeComparisonIdentities: byte x<x should be optimized to false") {
|
||||
val program = createProgramWithComparison("<", DataType.UBYTE)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program, options)
|
||||
|
||||
changes shouldBe 1
|
||||
val sub = program.children.filterIsInstance<PtBlock>().first()
|
||||
@@ -59,7 +62,7 @@ class TestComparisonOptimizers: FunSpec({
|
||||
|
||||
test("optimizeComparisonIdentities: word x<=x should be optimized to true") {
|
||||
val program = createProgramWithComparison("<=", DataType.WORD)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program, options)
|
||||
|
||||
changes shouldBe 1
|
||||
val sub = program.children.filterIsInstance<PtBlock>().first()
|
||||
@@ -69,7 +72,7 @@ class TestComparisonOptimizers: FunSpec({
|
||||
|
||||
test("optimizeComparisonIdentities: float x==x should NOT be optimized (NaN)") {
|
||||
val program = createProgramWithComparison("==", DataType.FLOAT)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program, options)
|
||||
|
||||
// NaN == NaN is false, so x==x should NOT be optimized to true for floats
|
||||
changes shouldBe 0
|
||||
@@ -81,7 +84,7 @@ class TestComparisonOptimizers: FunSpec({
|
||||
|
||||
test("optimizeComparisonIdentities: float x<=x should NOT be optimized (NaN)") {
|
||||
val program = createProgramWithComparison("<=", DataType.FLOAT)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program, options)
|
||||
|
||||
// NaN <= NaN is false, so x<=x should NOT be optimized to true for floats
|
||||
changes shouldBe 0
|
||||
@@ -92,7 +95,7 @@ class TestComparisonOptimizers: FunSpec({
|
||||
|
||||
test("optimizeComparisonIdentities: float x>=x should NOT be optimized (NaN)") {
|
||||
val program = createProgramWithComparison(">=", DataType.FLOAT)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program, options)
|
||||
|
||||
// NaN >= NaN is false, so x>=x should NOT be optimized to true for floats
|
||||
changes shouldBe 0
|
||||
@@ -103,7 +106,7 @@ class TestComparisonOptimizers: FunSpec({
|
||||
|
||||
test("optimizeComparisonIdentities: float x!=x should NOT be optimized (NaN)") {
|
||||
val program = createProgramWithComparison("!=", DataType.FLOAT)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program)
|
||||
val changes = ComparisonOptimizers.optimizeComparisonIdentities(program, options)
|
||||
|
||||
// NaN != NaN is true, but we should not optimize this as it's NaN-specific behavior
|
||||
changes shouldBe 0
|
||||
|
||||
@@ -1411,26 +1411,14 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
}
|
||||
|
||||
private fun InsSGN(i: IRInstruction) {
|
||||
var sign: Int
|
||||
when(i.type!!) {
|
||||
IRDataType.BYTE -> {
|
||||
sign = registers.getSB(i.reg2!!).toInt().sign
|
||||
registers.setSB(i.reg1!!, sign.toByte())
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
sign = registers.getSW(i.reg2!!).toInt().sign
|
||||
registers.setSB(i.reg1!!, sign.toByte())
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
sign = registers.getSL(i.reg2!!).sign
|
||||
registers.setSB(i.reg1!!, sign.toByte())
|
||||
}
|
||||
IRDataType.FLOAT -> {
|
||||
sign = registers.getFloat(i.fpReg1!!).sign.toInt()
|
||||
registers.setSB(i.reg1!!, sign.toByte())
|
||||
}
|
||||
val sign: Int = when (i.type!!) {
|
||||
IRDataType.BYTE -> registers.getSB(i.reg2!!).toInt().sign
|
||||
IRDataType.WORD -> registers.getSW(i.reg2!!).toInt().sign
|
||||
IRDataType.LONG -> registers.getSL(i.reg2!!).sign
|
||||
IRDataType.FLOAT -> registers.getFloat(i.fpReg1!!).sign.toInt()
|
||||
}
|
||||
statusbitsComparison(sign, i.type!!)
|
||||
registers.setSB(i.reg1!!, sign.toByte())
|
||||
statusbitsComparisonWithOverflow(sign, 0, IRDataType.BYTE)
|
||||
nextPc()
|
||||
}
|
||||
|
||||
@@ -1472,57 +1460,38 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
}
|
||||
|
||||
private fun InsCMP(i: IRInstruction) {
|
||||
val leftOrig = when(i.type!!) {
|
||||
val type = i.type!!
|
||||
val left = when(type) {
|
||||
IRDataType.BYTE -> registers.getUB(i.reg1!!).toInt()
|
||||
IRDataType.WORD -> registers.getUW(i.reg1!!).toInt()
|
||||
IRDataType.LONG -> registers.getSL(i.reg1!!)
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
val comparison = when(i.type!!) {
|
||||
IRDataType.BYTE -> {
|
||||
val reg1 = registers.getUB(i.reg1!!)
|
||||
val reg2 = registers.getUB(i.reg2!!)
|
||||
reg1.toInt() - reg2.toInt()
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
val reg1 = registers.getUW(i.reg1!!)
|
||||
val reg2 = registers.getUW(i.reg2!!)
|
||||
reg1.toInt() - reg2.toInt()
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
val reg1 = registers.getSL(i.reg1!!)
|
||||
val reg2 = registers.getSL(i.reg2!!)
|
||||
reg1 - reg2
|
||||
}
|
||||
val right = when(type) {
|
||||
IRDataType.BYTE -> registers.getUB(i.reg2!!).toInt()
|
||||
IRDataType.WORD -> registers.getUW(i.reg2!!).toInt()
|
||||
IRDataType.LONG -> registers.getSL(i.reg2!!)
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
statusbitsComparisonWithOverflow(comparison, leftOrig, i.type!!)
|
||||
statusbitsComparisonWithOverflow(left, right, type)
|
||||
nextPc()
|
||||
}
|
||||
|
||||
private fun InsCMPI(i: IRInstruction) {
|
||||
val leftOrig = when(i.type!!) {
|
||||
val type = i.type!!
|
||||
val left = when(type) {
|
||||
IRDataType.BYTE -> registers.getUB(i.reg1!!).toInt()
|
||||
IRDataType.WORD -> registers.getUW(i.reg1!!).toInt()
|
||||
IRDataType.LONG -> registers.getSL(i.reg1!!)
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
val comparison = when(i.type!!) {
|
||||
IRDataType.BYTE -> {
|
||||
val reg1 = registers.getUB(i.reg1!!)
|
||||
reg1.toInt() - (i.immediate!! and 255)
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
val reg1 = registers.getUW(i.reg1!!)
|
||||
reg1.toInt() - (i.immediate!! and 65535)
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
val reg1 = registers.getSL(i.reg1!!)
|
||||
reg1 - i.immediate!!
|
||||
}
|
||||
val right = when(type) {
|
||||
IRDataType.BYTE -> i.immediate!! and 0xff
|
||||
IRDataType.WORD -> i.immediate!! and 0xffff
|
||||
IRDataType.LONG -> i.immediate!!
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
statusbitsComparisonWithOverflow(comparison, leftOrig, i.type!!)
|
||||
statusbitsComparisonWithOverflow(left, right, type)
|
||||
nextPc()
|
||||
}
|
||||
|
||||
|
||||
@@ -13,43 +13,45 @@ internal fun VirtualMachine.statusbitsNZ(value: Int, type: IRDataType) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.statusbitsComparison(comparison: Int, type: IRDataType) {
|
||||
if(comparison==0) {
|
||||
statusZero = true
|
||||
statusCarry = true
|
||||
} else if(comparison>0) {
|
||||
statusZero = false
|
||||
statusCarry = true
|
||||
} else {
|
||||
statusZero = false
|
||||
statusCarry = false
|
||||
}
|
||||
when(type) {
|
||||
IRDataType.BYTE -> statusNegative = (comparison and 0x80)!=0
|
||||
IRDataType.WORD -> statusNegative = (comparison and 0x8000)!=0
|
||||
IRDataType.LONG -> statusNegative = comparison<0
|
||||
internal fun VirtualMachine.statusbitsComparison(left: Int, right: Int, type: IRDataType) {
|
||||
val comparison = left - right
|
||||
statusZero = comparison == 0
|
||||
when (type) {
|
||||
IRDataType.BYTE -> {
|
||||
statusNegative = (comparison and 0x80) != 0
|
||||
statusCarry = (left and 0xff) >= (right and 0xff)
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
statusNegative = (comparison and 0x8000) != 0
|
||||
statusCarry = (left and 0xffff) >= (right and 0xffff)
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
statusNegative = comparison < 0
|
||||
statusCarry = Integer.compareUnsigned(left, right) >= 0
|
||||
}
|
||||
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.statusbitsComparisonWithOverflow(comparison: Int, leftOrig: Int, type: IRDataType) {
|
||||
statusbitsComparison(comparison, type)
|
||||
when(type) {
|
||||
internal fun VirtualMachine.statusbitsComparisonWithOverflow(left: Int, right: Int, type: IRDataType) {
|
||||
statusbitsComparison(left, right, type)
|
||||
val comparison = left - right
|
||||
when (type) {
|
||||
IRDataType.BYTE -> {
|
||||
val signBit = 0x80
|
||||
val leftSign = leftOrig and signBit
|
||||
val rightSign = (leftOrig - comparison) and signBit
|
||||
val leftSign = left and signBit
|
||||
val rightSign = right and signBit
|
||||
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
val signBit = 0x8000
|
||||
val leftSign = leftOrig and signBit
|
||||
val rightSign = (leftOrig - comparison) and signBit
|
||||
val leftSign = left and signBit
|
||||
val rightSign = right and signBit
|
||||
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
val leftSign = leftOrig < 0
|
||||
val rightSign = (leftOrig - comparison) < 0
|
||||
val leftSign = left < 0
|
||||
val rightSign = right < 0
|
||||
val resultSign = comparison < 0
|
||||
statusOverflow = leftSign != rightSign && leftSign != resultSign
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user