Compare commits

...
19 Commits
Author SHA1 Message Date
Irmen de Jong 86f9b6aa69 fixed cmp() sign/flags errors 2026-06-20 19:49:45 +02:00
Irmen de Jong 33f30d925e Update documentation with proper example links 2026-06-20 15:45:01 +02:00
Irmen de Jong db227679a8 fix empty/full boundary checks on various datastructions in the buffers module 2026-06-20 15:05:37 +02:00
Irmen de Jong 0f3475128a fix divmod_uw_asm to not clobber R15 anymore 2026-06-19 23:23:53 +02:00
Irmen de Jong d451953e40 improve prog8 skill 2026-06-19 02:18:41 +02:00
Irmen de Jong d505c42d15 improve undefined symbol error handling
(cherry picked from commit 8c92663824)
2026-06-19 02:16:11 +02:00
Irmen de Jong 5a593b9ef3 Preserve cx16.r15 register in % for words. Update related code and documentation accordingly. 2026-06-17 19:23:19 +02:00
Irmen de Jong b411914624 Document virtual register clobbering details and update related comments. 2026-06-17 03:00:33 +02:00
Irmen de Jong 0f80b695e7 Fix invalid optimizer edge cases 2026-06-17 01:18:39 +02:00
Irmen de Jong 276627abcb remove broken expression optimization that combined a=x, a*=y into a=x*y 2026-06-16 23:19:42 +02:00
Irmen de Jong 34225d10aa add missing division and modulus code generation support for LONG variables 2026-06-16 23:18:07 +02:00
Irmen de Jong 282c3aa00b fix a return split word array codegen issue where it didn't use lsb suffix 2026-06-16 00:39:51 +02:00
Irmen de Jong 19f3b03ac3 remove problematic inlining for subroutines having 1 or more parameters, to avoid an endless loop 2026-06-16 00:02:05 +02:00
Irmen de Jong 6cb6f37871 exclude private symbols from completion suggestions in the LSP 2026-06-13 12:15:11 +02:00
Irmen de Jong d0edc0b484 prevent TCO with defer blocks 2026-06-12 03:05:26 +02:00
Irmen de Jong e9a3ed457e fix optimizer problem that removed var inits for pointer vars in certain cases. Fixes #225 2026-06-11 22:37:51 +02:00
Irmen de Jong 6001da30d8 add comments to float library routines about the Z flag wrapper routines
(cherry picked from commit c55f5e4b50)
2026-06-11 21:50:25 +02:00
Irmen de Jong e983c0c2b9 fixed the c64 float heisenbug
(cherry picked from commit 532bf13057)
2026-06-11 21:40:42 +02:00
Irmen de Jong 131134d24b fix daemon output path 2026-06-09 02:06:07 +02:00
61 changed files with 1686 additions and 524 deletions

No files matched your search

+112 -10
View File
@@ -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
```
+17 -4
View File
@@ -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()
}
+41 -29
View File
@@ -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
}
+14 -10
View File
@@ -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) {
+40
View File
@@ -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
+5 -5
View File
@@ -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
+13 -9
View File
@@ -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) {
+4 -7
View File
@@ -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
+6
View File
@@ -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
+5 -5
View File
@@ -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 {
+2 -2
View File
@@ -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
}
+10 -2
View File
@@ -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))
}
}
}
}
+40 -40
View File
@@ -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)
)
+126
View File
@@ -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
}
})
+191 -8
View File
@@ -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 "/"
}
})
+4 -10
View File
@@ -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") {
+85
View File
@@ -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 {
+31
View File
@@ -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
}
}
})
+28 -4
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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
+1 -1
View File
@@ -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
+11 -3
View File
@@ -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
+21
View File
@@ -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
^^^^^^^^^^^^^^^^^^^^
+1 -7
View File
@@ -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?
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+8 -2
View File
@@ -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)
+1 -1
View File
@@ -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
+21 -52
View File
@@ -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()
}
+26 -24
View File
@@ -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
}