mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
47
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fad90af6e | ||
|
|
3076318d7e | ||
|
|
9c99110738 | ||
|
|
1457d36997 | ||
|
|
43fb9296dd | ||
|
|
8bf744cc4a | ||
|
|
f1f5f070cf | ||
|
|
191a240d0d | ||
|
|
cafb2e355b | ||
|
|
c99c5751f8 | ||
|
|
435b7e56fa | ||
|
|
14f13fac53 | ||
|
|
f8b5037924 | ||
|
|
d2870cb02e | ||
|
|
8e43fc426c | ||
|
|
ba1b6e127a | ||
|
|
4f36a6958c | ||
|
|
df8f08b4ab | ||
|
|
fd5746d3fc | ||
|
|
6ab74fd51d | ||
|
|
245082ca1d | ||
|
|
845e58ea10 | ||
|
|
2a5dbc1ef7 | ||
|
|
02a1deb1e0 | ||
|
|
3ba8e5de10 | ||
|
|
819d1b4af7 | ||
|
|
cdd3d37376 | ||
|
|
d0bc01d618 | ||
|
|
ffab73334a | ||
|
|
83aeb8846c | ||
|
|
3a2087310b | ||
|
|
61b616701e | ||
|
|
bddf872906 | ||
|
|
48214a6fdb | ||
|
|
b7340856df | ||
|
|
2685854113 | ||
|
|
ad92f39469 | ||
|
|
d3fe041e01 | ||
|
|
f32bac5004 | ||
|
|
9a8e4ac105 | ||
|
|
78857e5c22 | ||
|
|
1bb828875d | ||
|
|
82aa67ffda | ||
|
|
7c44835772 | ||
|
|
8b9c63fe90 | ||
|
|
e77fcc8c16 | ||
|
|
08c12131d4 |
No files matched your search
@@ -49,6 +49,7 @@ You are writing **6502/65C02 assembly** using **64tass syntax**, in separate `*.
|
||||
- **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`
|
||||
- Check the target before using 65C02-specific instructions
|
||||
- **6502 / 65C02 instruction reference**: https://www.pagetable.com/c64ref/6502/?cpu=65c02&tab=2
|
||||
|
||||
## Calling Convention / Register Conventions
|
||||
- **Accumulator (A)**: 8-bit, used for most arithmetic, data movement, return values
|
||||
|
||||
@@ -76,6 +76,11 @@ No call stack for variable storage — recursion overwrites locals. To handle it
|
||||
- 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:
|
||||
```
|
||||
if x < 5
|
||||
txt.print("small")
|
||||
```
|
||||
- `defer` defers statement execution until scope exit
|
||||
- `goto`, labels, jump lists allowed
|
||||
- **Common mistake**: `and`/`or` for bitmasking — use `&`/`|` instead!
|
||||
@@ -164,30 +169,57 @@ unroll 80 {
|
||||
- Nested subroutines access parent scope variables directly
|
||||
|
||||
## Assembly Subroutines (asmsub)
|
||||
For kernel (ROM) routines or low-level assembly that gets arguments via registers.
|
||||
- Parameter passing via registers: `@A` (accu), `@X`, `@Y`, `@AX`/`@AY` (16-bit), `@R0`-`@R15` (virtual regs), `@FAC1`/`@FAC2` (float), `@Pc` (carry), `@Pz` (zero)
|
||||
- Return value: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use)
|
||||
- Clobbers: `clobbers (A, X, Y)` — list all modified registers
|
||||
- Parameter names are documentation only — use registers in assembly, not parameter names
|
||||
- Create symbolic aliases at assembly start: `x1 = cx16.r0`, `x1_lo = cx16.r0L`
|
||||
- Example:
|
||||
For low-level assembly that gets arguments via registers and returns values in registers.
|
||||
- **Syntax**: `[private] [inline] asmsub subname(params) [clobbers(regs)] [-> returns] { %asm {{ ... }} }`
|
||||
- **Body Restriction**: The body of an `asmsub` **must only contain** a single `%asm {{ ... }}` node. Regular Prog8 statements or nested blocks are NOT allowed.
|
||||
- **Parameters**: `type name @register` (e.g., `ubyte val @A`, `uword addr @AX`, `float f @FAC1`).
|
||||
- **Return Values**: `-> type @register` (e.g., `-> ubyte @A`, `-> bool @Pz` for immediate branch use).
|
||||
- **Registers**:
|
||||
- **8-bit**: `@A`, `@X`, `@Y`
|
||||
- **16-bit**: `@AX`, `@AY`, `@XY` (register pairs)
|
||||
- **Float**: `@FAC1`, `@FAC2` (Floating Point Accumulators)
|
||||
- **Virtual**: `@R0`–`@R15` (16-bit), `@R0R1`–`@R14R15` (32-bit combined)
|
||||
- **Status Flags (for returns)**: `@Pc` (Carry), `@Pz` (Zero), `@Pv` (Overflow), `@Pn` (Negative)
|
||||
- **Clobbers**: `clobbers (A, X, Y)` — list all hardware registers modified by the routine.
|
||||
- **Parameter names** are for documentation and type checking only. Use the registers in your assembly code.
|
||||
- **Inlining**: `inline asmsub` will paste the assembly code directly at the call site, avoiding `jsr`/`rts` overhead.
|
||||
- **Symbol prefixes** in assembly: `p8v_` (vars), `p8s_` (subs), `p8b_` (blocks), etc.
|
||||
|
||||
## External Subroutines (extsub)
|
||||
Used to call routines at fixed memory addresses (like ROM KERNAL routines or third-party drivers).
|
||||
- **Syntax**: `[private] extsub [@bank <value>] address = subname(params) [clobbers(regs)] [-> returns]`
|
||||
- **Address**: Can be a hex literal (`$C000`) or a constant expression.
|
||||
- **Bank (optional)**: `@bank <integer>` (constant bank) or `@bank <identifier>` (variable bank).
|
||||
- **No Body**: Unlike `asmsub`, `extsub` has no `{ }` body; it just maps a signature to an address.
|
||||
- **Example**:
|
||||
```prog8
|
||||
asmsub line(uword x1 @R0, ubyte y1 @A, uword x2 @R1, ubyte y2 @Y) clobbers (A, X, Y) {
|
||||
; CX16 KERNAL CHROUT
|
||||
extsub $FFD2 = chrout(ubyte char @A) clobbers(A, X, Y)
|
||||
|
||||
; Routine in a specific RAM bank
|
||||
extsub @bank 10 $C09F = audio_init() clobbers(A, X, Y) -> bool @Pc
|
||||
```
|
||||
|
||||
## Assembly Programming Details
|
||||
- **Symbol prefixes**: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
|
||||
- **Fully qualified names**: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
|
||||
- **Within a `.proc`**, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
|
||||
- **Split word arrays**: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
|
||||
- **CX16 target**: use 65C02 instructions (STZ, PHX, etc). Others: 6502 only
|
||||
- **Assembly syntax**: 64tass. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
|
||||
- **Instructions** like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`
|
||||
- **Anonymous labels**: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
|
||||
- **Register Aliases**: It's common to define aliases at the start of an `asmsub`:
|
||||
```prog8
|
||||
asmsub my_routine(uword ptr @AX) {
|
||||
%asm {{
|
||||
x1 = cx16.r0
|
||||
x2 = cx16.r1
|
||||
lda x1
|
||||
ptr_lo = p8zp_scratch_w1
|
||||
ptr_hi = p8zp_scratch_w1+1
|
||||
sta ptr_lo
|
||||
stx ptr_hi
|
||||
}}
|
||||
}
|
||||
```
|
||||
- Symbol prefixes in assembly: `p8v_` (variables), `p8s_` (subroutines), `p8b_` (blocks), `p8c_` (constants), `p8l_` (labels), `p8t_` (structs), `p8_` (other)
|
||||
- Fully qualified: `p8b_blockname.p8v_varname`, `p8b_blockname.p8s_subname.p8v_localvar`
|
||||
- Within a `.proc`, short names often work. `%option no_symbol_prefixing` disables prefixes (used by `cbm`, `cx16`, `txt`)
|
||||
- Split word arrays: append `_lsb` / `_msb` to variable name (e.g., `p8v_myarray_lsb`)
|
||||
- CX16 target only: use 65C02 instructions (STZ etc). Others: 6502 only
|
||||
- Assembly syntax: 64tass assembler. `.proc`/`.pend`, `_label` for locals, `.byte`/`.word`/`.dword` for data, `=` for equates
|
||||
- Instructions like `rol`, `ror`, `asl`, `lsr` require explicit operand: `rol a`, not just `rol`
|
||||
- Anonymous labels: `+` (forward), `-` (backward), branch with `+`, `++`, `+++` or `-`, `--`, `---`
|
||||
|
||||
## Standard Library
|
||||
- Find routines, functions, variables, modules and signatures in the symbol dump file for the given compilation target.
|
||||
@@ -212,7 +244,7 @@ For kernel (ROM) routines or low-level assembly that gets arguments via register
|
||||
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
|
||||
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
|
||||
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. They are syntactic sugar for a list of `const` declarations, not a type. Use enums for related values, `const` for standalone
|
||||
- Avoid `globals.XXXX` — move constants closer to where they're used
|
||||
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
|
||||
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
|
||||
|
||||
@@ -10,29 +10,30 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: build and install recent 64tass
|
||||
- name: install 64tass
|
||||
run: |
|
||||
sudo apt-get install -y make build-essential
|
||||
git clone --depth=1 https://github.com/irmen/64tass
|
||||
cd 64tass
|
||||
make -j4
|
||||
sudo make install
|
||||
sudo apt update
|
||||
sudo apt install 64tass
|
||||
64tass --version
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Build and test with Gradle
|
||||
run: |
|
||||
./gradlew build shadowJar --no-daemon
|
||||
./gradlew build shadowJar --no-daemon --console=plain
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: prog8-compiler-jar-zipped
|
||||
path: |
|
||||
|
||||
Generated
+1
-1
@@ -14,6 +14,6 @@
|
||||
<option name="additionalArguments" value="-jvm-default=no-compatibility" />
|
||||
</component>
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.3.20" />
|
||||
<option name="version" value="2.3.21" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+10
-10
@@ -1,23 +1,23 @@
|
||||
<component name="libraryTable">
|
||||
<library name="KotlinJavaRuntime" type="repository">
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.20" />
|
||||
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.3.21" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-javadoc.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-javadoc.jar!/" />
|
||||
</JAVADOC>
|
||||
<SOURCES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.21/kotlin-stdlib-jdk8-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.21/kotlin-stdlib-2.3.21-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20-sources.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.21/kotlin-stdlib-jdk7-2.3.21-sources.jar!/" />
|
||||
</SOURCES>
|
||||
</library>
|
||||
</component>
|
||||
Generated
+4
-4
@@ -1,14 +1,14 @@
|
||||
<component name="libraryTable">
|
||||
<library name="github.irmen.ksim65" type="repository">
|
||||
<properties maven-id="com.github.irmen:ksim65:v2.0" />
|
||||
<properties maven-id="com.github.irmen:ksim65:v2.1" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.0/ksim65-v2.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/github/irmen/ksim65/v2.1/ksim65-v2.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.3.20/kotlin-stdlib-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.3.20/kotlin-stdlib-jdk8-2.3.20.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.3.20/kotlin-stdlib-jdk7-2.3.20.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
</library>
|
||||
</component>
|
||||
</component>
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -63,6 +63,7 @@ What does Prog8 provide?
|
||||
- programs run very fast because it's compiled to native machine code
|
||||
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
|
||||
- various data types other than just bytes (16-bit words, long integers, floats, strings)
|
||||
- Structs and typed pointers
|
||||
- 2D arrays (`matrix[row][col]` syntax)
|
||||
|
||||
@@ -61,8 +61,7 @@ life {
|
||||
math.rndseed(12345,9999)
|
||||
uword offset = STRIDE+1
|
||||
ubyte x
|
||||
ubyte y
|
||||
for y in 0 to HEIGHT-1 {
|
||||
repeat HEIGHT {
|
||||
for x in 0 to WIDTH-1 {
|
||||
active_world[offset+x] = math.rnd() & 1
|
||||
}
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.3.20"
|
||||
kotlin("jvm") version "2.3.21"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -53,7 +53,7 @@ subprojects {
|
||||
// This manages all Kotest module versions centrally
|
||||
dependencies {
|
||||
testImplementation(platform("io.kotest:kotest-bom:5.9.1"))
|
||||
// implementation("com.github.irmen:ksim65:v2.0")
|
||||
// implementation("com.github.irmen:ksim65:v2.1")
|
||||
}
|
||||
|
||||
// Common sourceSets configuration
|
||||
|
||||
@@ -173,6 +173,7 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
"rsave" to FSignature(false, emptyArray()),
|
||||
"rrestore" to FSignature(false, emptyArray()),
|
||||
"memory" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR), FParam("size", BaseDataType.UWORD), FParam("alignment", BaseDataType.UWORD)),
|
||||
"memory__ref" to FSignature(true, arrayOf(BaseDataType.UWORD), FParam("name", BaseDataType.STR)),
|
||||
"callfar" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("arg", BaseDataType.UWORD)),
|
||||
"callfar2" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("bank", BaseDataType.UBYTE), FParam("address", BaseDataType.UWORD), FParam("argA", BaseDataType.UBYTE), FParam("argX", BaseDataType.UBYTE), FParam("argY", BaseDataType.UBYTE), FParam("argC", BaseDataType.BOOL)),
|
||||
"call" to FSignature(false, arrayOf(BaseDataType.UWORD), FParam("address", BaseDataType.UWORD)),
|
||||
|
||||
@@ -35,6 +35,11 @@ object ImportFileSystem {
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCaches() {
|
||||
cache.clear()
|
||||
lineSpanCache.clear()
|
||||
}
|
||||
|
||||
fun retrieveSourceLine(position: Position): String {
|
||||
if(SourceCode.isLibraryResource(position.file)) {
|
||||
val key = SourceCode.withoutPrefix(position.file)
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.io.CharConversionException
|
||||
object PetsciiEncoding {
|
||||
|
||||
// decoding: from Petscii/Screencodes (0-255) to Unicode
|
||||
// character tables used from https://github.com/irmen/cbmcodecs2
|
||||
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
|
||||
|
||||
private val decodingPetsciiLowercase = charArrayOf(
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
|
||||
@@ -1024,7 +1024,7 @@ class AsmGen6502Internal (
|
||||
assignExpressionToRegister(value, target.register!!)
|
||||
} else {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AX)
|
||||
pointerGen.assignWordReg(PtrTarget(target), RegisterOrPair.AX)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AX)
|
||||
}
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPassByRef -> {
|
||||
@@ -1175,6 +1175,7 @@ class AsmGen6502Internal (
|
||||
when {
|
||||
iterations == 0 -> {}
|
||||
iterations == 1 -> translate(stmt.statements)
|
||||
iterations > 65536 -> repeatLongCount(iterations.toLong(), stmt)
|
||||
iterations !in 0..65536 -> throw AssemblyError("invalid number of iterations")
|
||||
iterations <= 256 -> repeatByteCount(iterations, stmt)
|
||||
else -> repeatWordCount(iterations, stmt)
|
||||
@@ -1186,26 +1187,36 @@ class AsmGen6502Internal (
|
||||
val name = asmVariableName(stmt.count as PtIdentifier)
|
||||
when {
|
||||
vardecl.type.isByte -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position)
|
||||
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
|
||||
repeatCountInY(stmt, endLabel)
|
||||
}
|
||||
vardecl.type.isWord -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position)
|
||||
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
|
||||
repeatWordCountInAY(endLabel, stmt)
|
||||
}
|
||||
vardecl.type.isLong -> {
|
||||
val reg = CombinedLongRegisters.last()
|
||||
assignVariableToRegister(name, reg, stmt.definingISub(), (stmt.count as PtIdentifier).position, vardecl.type.isSigned)
|
||||
repeatLongCountInReg(reg, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop variable datatype ${vardecl.type}")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when {
|
||||
stmt.count.type.isByte -> {
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.Y)
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.Y, stmt.count.type.isSigned)
|
||||
repeatCountInY(stmt, endLabel)
|
||||
}
|
||||
stmt.count.type.isWord -> {
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.AY)
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.AY, stmt.count.type.isSigned)
|
||||
repeatWordCountInAY(endLabel, stmt)
|
||||
}
|
||||
stmt.count.type.isLong -> {
|
||||
val reg = CombinedLongRegisters.last()
|
||||
assignExpressionToRegister(stmt.count, reg, stmt.count.type.isSigned)
|
||||
repeatLongCountInReg(reg, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop expression datatype ${stmt.count.type}")
|
||||
}
|
||||
}
|
||||
@@ -1213,6 +1224,75 @@ class AsmGen6502Internal (
|
||||
|
||||
loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun repeatLongCount(iterations: Long, stmt: PtRepeatLoop) {
|
||||
require(iterations > 65536) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val counterVar = createTempVarReused(BaseDataType.LONG, true, stmt)
|
||||
|
||||
val bytes = mutableListOf<Int>()
|
||||
var currentN = iterations
|
||||
while (currentN > 0) {
|
||||
val b = (currentN % 256).toInt()
|
||||
bytes.add(b)
|
||||
currentN = if (b == 0) currentN / 256 else currentN / 256 + 1
|
||||
if (currentN == 1L) break
|
||||
}
|
||||
|
||||
for (i in bytes.indices) {
|
||||
val v = bytes[i]
|
||||
val suffix = if (i > 0) "+$i" else ""
|
||||
if (v == 0 && isTargetCpu(CpuType.CPU65C02)) {
|
||||
out(" stz $counterVar$suffix")
|
||||
} else {
|
||||
out(" lda #$v")
|
||||
out(" sta $counterVar$suffix")
|
||||
}
|
||||
}
|
||||
|
||||
out(repeatLabel)
|
||||
translate(stmt.statements)
|
||||
|
||||
for (i in bytes.indices) {
|
||||
val suffix = if (i > 0) "+$i" else ""
|
||||
out(" dec $counterVar$suffix")
|
||||
out(" bne $repeatLabel")
|
||||
}
|
||||
}
|
||||
|
||||
private fun repeatLongCountInReg(reg: RegisterOrPair, endLabel: String, stmt: PtRepeatLoop) {
|
||||
require(reg in CombinedLongRegisters)
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val regName = "cx16." + reg.startregname()
|
||||
val d1 = makeLabel("skip_dec1")
|
||||
val d2 = makeLabel("skip_dec2")
|
||||
val d3 = makeLabel("skip_dec3")
|
||||
out("""
|
||||
lda $regName
|
||||
ora $regName+1
|
||||
ora $regName+2
|
||||
ora $regName+3
|
||||
beq $endLabel
|
||||
$repeatLabel""")
|
||||
translate(stmt.statements)
|
||||
out("""
|
||||
lda $regName
|
||||
bne $d3
|
||||
lda $regName+1
|
||||
bne $d2
|
||||
lda $regName+2
|
||||
bne $d1
|
||||
dec $regName+3
|
||||
$d1 dec $regName+2
|
||||
$d2 dec $regName+1
|
||||
$d3 dec $regName
|
||||
lda $regName
|
||||
ora $regName+1
|
||||
ora $regName+2
|
||||
ora $regName+3
|
||||
bne $repeatLabel""")
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
|
||||
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
|
||||
|
||||
@@ -613,9 +613,12 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
lda $var1+3
|
||||
sbc $var2+3""")
|
||||
} else {
|
||||
// cmp() doesn't return a value and as such can't be used in an expression, so no need to save the temp registers' original values
|
||||
assignAsmGen.assignExpressionToRegister(arg2, RegisterOrPair.R14R15, true)
|
||||
if(!assignAsmGen.isRightTrivial(arg1))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignAsmGen.assignExpressionToRegister(arg1, RegisterOrPair.R12R13, true)
|
||||
if(!assignAsmGen.isRightTrivial(arg1))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda cx16.r12
|
||||
@@ -1846,7 +1849,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(tryOptimizedLoopCopyWithY("", "$address"))
|
||||
return emptyArray()
|
||||
|
||||
asmgen.assignVariableToRegister(address.toString(), RegisterOrPair.R14R15, null, fcall.position)
|
||||
asmgen.assignVariableToRegister(address.toString(), RegisterOrPair.R14R15, null, fcall.position, true)
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
}
|
||||
|
||||
@@ -1875,7 +1878,7 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
if(tryOptimizedLoopCopyWithY("lda #<$varname+$offset | sta P8ZP_SCRATCH_W1 | lda #>$varname+$offset | sta P8ZP_SCRATCH_W1+1", "(P8ZP_SCRATCH_W1)"))
|
||||
return emptyArray()
|
||||
|
||||
asmgen.assignVariableToRegister("$varname+$offset", RegisterOrPair.R14R15, null, fcall.position)
|
||||
asmgen.assignVariableToRegister("$varname+$offset", RegisterOrPair.R14R15, null, fcall.position, true)
|
||||
return arrayOf(RegisterOrPair.R14R15)
|
||||
} else if(result.second is PtIdentifier) {
|
||||
val offsetname = asmgen.asmVariableName(result.second as PtIdentifier)
|
||||
@@ -1996,7 +1999,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R12R13, true) // left
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R14R15, true) // right
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out("""
|
||||
lda cx16.r12
|
||||
cmp cx16.r14
|
||||
@@ -2095,7 +2102,11 @@ import prog8.codegen.cpu6502.assignment.*
|
||||
}
|
||||
fcall.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.R12R13, true) // left
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.R14R15, true) // right
|
||||
if(!assignAsmGen.isRightTrivial(fcall.args[1]))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out("""
|
||||
lda cx16.r12
|
||||
cmp cx16.r14
|
||||
|
||||
@@ -42,12 +42,129 @@ internal class ForLoopsAsmGen(
|
||||
when {
|
||||
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte, word or long")
|
||||
}
|
||||
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
|
||||
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1 || stepsize == -1)
|
||||
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
else
|
||||
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
|
||||
// Use temporary variable to store the limit
|
||||
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1) {
|
||||
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
|
||||
asmgen.out("""
|
||||
inc $toValueVar
|
||||
bne +
|
||||
inc $toValueVar+1
|
||||
bne +
|
||||
inc $toValueVar+2
|
||||
bne +
|
||||
inc $toValueVar+3
|
||||
+
|
||||
""")
|
||||
} else {
|
||||
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
|
||||
val label1 = asmgen.makeLabel("dec_1")
|
||||
val label2 = asmgen.makeLabel("dec_2")
|
||||
val label3 = asmgen.makeLabel("dec_3")
|
||||
asmgen.out("""
|
||||
lda $toValueVar
|
||||
bne $label1
|
||||
lda $toValueVar+1
|
||||
bne $label2
|
||||
lda $toValueVar+2
|
||||
bne $label3
|
||||
dec $toValueVar+3
|
||||
$label3: dec $toValueVar+2
|
||||
$label2: dec $toValueVar+1
|
||||
$label1: dec $toValueVar
|
||||
""")
|
||||
}
|
||||
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
|
||||
if (stepsize == 1) {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
bne +
|
||||
inc $varname+1
|
||||
bne +
|
||||
inc $varname+2
|
||||
bne +
|
||||
inc $varname+3
|
||||
+ lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne +++
|
||||
lda $varname+1
|
||||
bne ++
|
||||
lda $varname+2
|
||||
bne +
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
// TODO: implement 32-bit loop
|
||||
asmgen.romableError("for loops over long ranges with step != 1 not yet fully implemented", forloop.position)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
|
||||
@@ -1580,7 +1580,13 @@ internal class IfElseAsmGen(private val program: PtProgram,
|
||||
lda $opL+3
|
||||
sbc $varR+3""")
|
||||
} else {
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $opL
|
||||
|
||||
@@ -83,7 +83,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
expr.type.isWord || expr.type.isString -> {
|
||||
expr.type.isWord || expr.type.isString || expr.type.isPointer -> {
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(trueLabel)
|
||||
@@ -656,7 +656,13 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
lda $opL+3
|
||||
sbc $varR+3""")
|
||||
} else {
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.assignExpressionToRegister(r, RegisterOrPair.R14R15, true)
|
||||
if (varL == null && !assignmentAsmGen.isRightTrivial(r)) {
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
}
|
||||
asmgen.out("""
|
||||
sec
|
||||
lda $opL
|
||||
|
||||
@@ -18,6 +18,9 @@ internal class AssignmentAsmGen(
|
||||
) {
|
||||
lateinit var augmentableAsmGen: AugmentableAssignmentAsmGen
|
||||
|
||||
internal fun isRightTrivial(expr: PtExpression): Boolean =
|
||||
expr is PtNumber || expr is PtBool || expr is PtIdentifier
|
||||
|
||||
fun translate(assignment: PtAssignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment.target, assignment.definingISub(), asmgen)
|
||||
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
|
||||
@@ -867,12 +870,8 @@ internal class AssignmentAsmGen(
|
||||
|
||||
return if(translatedOk)
|
||||
true
|
||||
else {
|
||||
if(expr.type.isLong && expr.operator in "/%")
|
||||
TODO("long division - use floats for now ${expr.position} ")
|
||||
|
||||
else
|
||||
anyExprGen.assignAnyExpressionUsingStack(expr, assign)
|
||||
}
|
||||
}
|
||||
|
||||
private fun optimizedComparison(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
|
||||
@@ -1161,6 +1160,11 @@ internal class AssignmentAsmGen(
|
||||
// assignVariableWord(target, "cx16.r15", DataType.WORD)
|
||||
// return true
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
asmgen.errors.err("remainder can only be used on unsigned integer operands on 6502 target for now", expr.right.position)
|
||||
return true
|
||||
// TODO implement the signed long remainder asm routine
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
@@ -1198,6 +1202,17 @@ internal class AssignmentAsmGen(
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
return true
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out(" jsr prog8_math.div_longs")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
@@ -1242,7 +1257,11 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
expr.type.isLong -> {
|
||||
asmgen.assignExpressionToRegister(expr.left, RegisterOrPair.R12R13, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.assignExpressionToRegister(expr.right, RegisterOrPair.R14R15, true)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
asmgen.out(" jsr prog8_math.multiply_longs")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
return true
|
||||
@@ -2179,13 +2198,17 @@ internal class AssignmentAsmGen(
|
||||
else
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R12R13, 2)
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.R14R15, expr.left.type.isSigned)
|
||||
// save left operand because right eval may clobber R14R15
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13, expr.right.type.isSigned)
|
||||
// restore left operand
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "cx16.r12")
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
if(targetreg==RegisterOrPair.R14R15)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 1)
|
||||
else
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 2)
|
||||
asmgen.popLongRegisters(RegisterOrPair.R12R13, 2)
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -2363,8 +2386,12 @@ internal class AssignmentAsmGen(
|
||||
else if(varname!=null)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, varname)
|
||||
else {
|
||||
// TODO: preserve R14:R15 on stack here? (the function does't do it for us)
|
||||
augmentableAsmGen.inplacemodificationLongWithExpression("cx16.r14", expr.operator, expr.right)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.pushLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
assignExpressionToRegister(expr.right, RegisterOrPair.R12R13, expr.right.type.isSigned)
|
||||
if(!isRightTrivial(expr.right))
|
||||
asmgen.popLongRegisters(RegisterOrPair.R14R15, 1)
|
||||
augmentableAsmGen.inplacemodificationLongWithVariable("cx16.r14", expr.operator, "cx16.r12")
|
||||
}
|
||||
assignRegisterLong(target, RegisterOrPair.R14R15)
|
||||
if(targetreg!=RegisterOrPair.R14R15) {
|
||||
|
||||
+29
-14
@@ -318,14 +318,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
SourceStorageKind.EXPRESSION ->
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
TODO("inplace long modification ${target.position}")
|
||||
} else {
|
||||
TODO("inplace long modification ${target.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,24 +576,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
clc
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
adc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
adc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
adc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
adc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
adc $sourceVar+3
|
||||
sta $targetVar+3
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
sec
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
sbc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
sbc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
sbc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
sbc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
sbc $sourceVar+3
|
||||
sta $targetVar+3
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
|
||||
@@ -33,10 +33,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
extsub.returns.zip(assignmentTargets).forEach { pair ->
|
||||
val returns = pair.first
|
||||
val target = pair.second as PtAssignTarget
|
||||
// consume the register even for void targets to keep indices aligned
|
||||
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
|
||||
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
|
||||
if (!target.void) {
|
||||
// Determine if this return uses an FP register or CPU register
|
||||
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
|
||||
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
|
||||
result += assignCpuRegister(returns, regNumber, target)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
is PtContainmentCheck -> translate(expr)
|
||||
is PtPointerDeref -> translate(expr)
|
||||
is PtConstant -> {
|
||||
val name = expr.name
|
||||
val slab = expr.memorySlab
|
||||
// Check if the constant has a numeric value or is a memory slab reference
|
||||
if(slab != null) {
|
||||
@@ -1176,8 +1175,22 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
|
||||
)
|
||||
} else if(address.constbank!=null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt()
|
||||
)
|
||||
} else {
|
||||
val tr = translateExpression(address.varbank!!)
|
||||
require(tr.dt==IRDataType.BYTE)
|
||||
result += tr.chunks
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toAddress(),
|
||||
reg1 = tr.resultReg
|
||||
)
|
||||
}
|
||||
else TODO("extsub with banked address got called ${callTarget.name} ${fcall.position}")
|
||||
}
|
||||
addInstr(result, call, null)
|
||||
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum.value}
|
||||
|
||||
@@ -26,7 +26,7 @@ class IRCodeGen(
|
||||
verifyNameScoping(program, symbolTable)
|
||||
changeGlobalVarInits(symbolTable)
|
||||
|
||||
val irSymbolTable = convertStToIRSt(symbolTable)
|
||||
val irSymbolTable = convertStToIRSt(symbolTable, options.romable)
|
||||
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
|
||||
|
||||
// collect global variables initializers
|
||||
@@ -39,6 +39,12 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
// in romable mode, generate global init code for declaration-initialized variables
|
||||
// and clear their init value so they go into the BSS section
|
||||
if(options.romable) {
|
||||
generateRomableInits(irProg)
|
||||
}
|
||||
|
||||
irProg.addAsmSymbols(options.symbolDefs)
|
||||
|
||||
for (block in program.allBlocks())
|
||||
@@ -63,6 +69,11 @@ class IRCodeGen(
|
||||
fun registerTypes(): Map<RegisterNum, IRDataType> = registers.getTypes()
|
||||
|
||||
private fun changeGlobalVarInits(symbolTable: SymbolTable) {
|
||||
// In romable mode, don't pull initializers into the symbol table;
|
||||
// they stay as assignments in the AST and become global init code naturally.
|
||||
if(options.romable)
|
||||
return
|
||||
|
||||
// Normally, block level (global) variables that have a numeric initialization value
|
||||
// are initialized via an assignment statement.
|
||||
val initsToRemove = mutableListOf<Pair<PtBlock, PtAssignment>>()
|
||||
@@ -95,6 +106,39 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateRomableInits(irProg: IRProgram) {
|
||||
// For romable mode, declaration-initialized variables marked inBss need
|
||||
// runtime init code in the globalInits chunk, and their initializationValue
|
||||
// must be cleared so they appear in the NOINIT (BSS) section of the IR file.
|
||||
val replacements = mutableListOf<IRStStaticVariable>()
|
||||
for(variable in irProg.st.allVariables()) {
|
||||
if(variable.inBss && variable.initializationValue != null) {
|
||||
when(val initValue = variable.initializationValue) {
|
||||
is IRVariableInitializer.Numeric -> {
|
||||
val dt = irType(variable.dt)
|
||||
val tempReg = registers.next(dt)
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
chunk += IRInstruction(Opcode.LOAD, dt, reg1 = tempReg, immediate = initValue.value.toInt())
|
||||
chunk += IRInstruction(Opcode.STOREM, dt, reg1 = tempReg, labelSymbol = variable.name)
|
||||
irProg.addGlobalInits(chunk)
|
||||
// replace with uninitialized version so it goes to NOINIT section
|
||||
replacements += IRStStaticVariable(
|
||||
variable.name, variable.dt, null, variable.length,
|
||||
variable.zpwish, variable.align, variable.dirty,
|
||||
variable.inBss, variable.readonly
|
||||
)
|
||||
}
|
||||
is IRVariableInitializer.Str, is IRVariableInitializer.Array -> {
|
||||
// Strings and arrays: keep inline data but now marked readonly.
|
||||
// They can serve as ROM-based read-only data.
|
||||
}
|
||||
null -> {} // uninitialized, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
replacements.forEach { irProg.st.add(it) }
|
||||
}
|
||||
|
||||
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
|
||||
fun verifyPtNode(node: PtNode) {
|
||||
when (node) {
|
||||
|
||||
@@ -3,14 +3,6 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.intermediate.*
|
||||
|
||||
/**
|
||||
* Represents what value a register currently holds for LOADR forwarding optimization.
|
||||
*/
|
||||
private sealed class RegisterContent {
|
||||
data class Constant(val value: Any) : RegisterContent() // Int or Double
|
||||
data class Register(val originalReg: Int) : RegisterContent()
|
||||
}
|
||||
|
||||
class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA: Boolean) {
|
||||
fun optimize(optimizationsEnabled: Boolean, errors: IErrorReporter) {
|
||||
if(!optimizationsEnabled)
|
||||
@@ -65,6 +57,9 @@ class IRPeepholeOptimizer(private val irprog: IRProgram, private val retainSSA:
|
||||
|| removeNeedlessLoads(chunk1, indexedInstructions)
|
||||
|| removeDeadStores(chunk1, indexedInstructions)
|
||||
// || removeLoadrForwarding(chunk1, indexedInstructions) // DISABLED - needs debugging
|
||||
|| removeSelfIdentityOps(chunk1, indexedInstructions)
|
||||
|| simplifyShiftByZero(chunk1, indexedInstructions)
|
||||
|| cancelAdjacentOps(chunk1, indexedInstructions)
|
||||
|| removeNops(chunk1, indexedInstructions) // last time, in case one of the optimizers replaced something with a nop
|
||||
} while (changed)
|
||||
}
|
||||
@@ -544,6 +539,98 @@ jump p8_label_gen_2
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeSelfIdentityOps(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
when(ins.opcode) {
|
||||
Opcode.LOADR -> {
|
||||
if((ins.reg1 != null && ins.reg1 == ins.reg2) || (ins.fpReg1 != null && ins.fpReg1 == ins.fpReg2)) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.ANDR, Opcode.ORR -> {
|
||||
if(ins.reg1 == ins.reg2) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.XORR -> {
|
||||
if(ins.reg1 == ins.reg2) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.LOAD, ins.type, reg1 = ins.reg1, immediate = 0)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun simplifyShiftByZero(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx>0 && ins.opcode in setOf(Opcode.LSLN, Opcode.LSRN, Opcode.ASRN) && ins.reg2 != null) {
|
||||
val prev = indexedInstructions[idx-1].value
|
||||
if(prev.opcode == Opcode.LOAD && prev.reg1 == ins.reg2 && prev.immediate == 0) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions.removeAt(idx-1)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun cancelAdjacentOps(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
if(idx >= chunk.instructions.size - 1)
|
||||
return@forEach
|
||||
val insAfter = chunk.instructions[idx+1]
|
||||
|
||||
when(ins.opcode) {
|
||||
Opcode.INV -> {
|
||||
if(insAfter.opcode == Opcode.INV && insAfter.reg1 == ins.reg1) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.NEG -> {
|
||||
val sameReg = (ins.reg1 != null && ins.reg1 == insAfter.reg1) || (ins.fpReg1 != null && ins.fpReg1 == insAfter.fpReg1)
|
||||
if(insAfter.opcode == Opcode.NEG && sameReg) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.EXT, Opcode.EXTS -> {
|
||||
if(insAfter.opcode == ins.opcode && insAfter.reg1 == ins.reg1 && insAfter.type == ins.type) {
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.INC -> {
|
||||
if(insAfter.opcode == Opcode.DEC && insAfter.reg1 == ins.reg1) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
Opcode.DEC -> {
|
||||
if(insAfter.opcode == Opcode.INC && insAfter.reg1 == ins.reg1) {
|
||||
chunk.instructions[idx] = IRInstruction(Opcode.NOP)
|
||||
chunk.instructions[idx+1] = IRInstruction(Opcode.NOP)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun removeNops(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
var changed = false
|
||||
indexedInstructions.reversed().forEach { (idx, ins) ->
|
||||
@@ -686,18 +773,6 @@ jump p8_label_gen_2
|
||||
return changed
|
||||
}
|
||||
|
||||
private fun isRegisterRead(ins: IRInstruction, reg: Int): Boolean {
|
||||
// Check if the instruction reads from the given register
|
||||
val formats = instructionFormats.getValue(ins.opcode)
|
||||
val format = formats[ins.type] ?: formats[null]
|
||||
|
||||
val reg1Read = (format?.reg1 == OperandDirection.READ || format?.reg1 == OperandDirection.READWRITE) && (ins.reg1 == reg || ins.fpReg1?.value == reg)
|
||||
val reg2Read = (format?.reg2 == OperandDirection.READ || format?.reg2 == OperandDirection.READWRITE) && (ins.reg2 == reg || ins.fpReg2?.value == reg)
|
||||
val reg3Read = (format?.reg3 == OperandDirection.READ || format?.reg3 == OperandDirection.READWRITE) && (ins.reg3 == reg)
|
||||
|
||||
return reg1Read || reg2Read || reg3Read
|
||||
}
|
||||
|
||||
//private fun removeLoadrForwarding(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
|
||||
// Forward LOADR instructions to their original source.
|
||||
// Example:
|
||||
|
||||
@@ -8,7 +8,7 @@ import prog8.intermediate.*
|
||||
* Converter object for transforming SymbolTable nodes to IR symbol table nodes.
|
||||
* Groups all conversion functions together for better organization.
|
||||
*/
|
||||
private object StToIrConverter {
|
||||
private class StToIrConverter(val romable: Boolean) {
|
||||
fun convert(struct: StStruct): IRStStructDef =
|
||||
IRStStructDef(struct.scopedNameString, struct.fields, struct.size)
|
||||
|
||||
@@ -72,16 +72,21 @@ private object StToIrConverter {
|
||||
}
|
||||
|
||||
val name = if('.' in variable.name) variable.name else variable.scopedNameString
|
||||
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty)
|
||||
val hasInit = initValue != null
|
||||
val inBss = romable && hasInit
|
||||
val readonly = romable && hasInit
|
||||
return IRStStaticVariable(name, variable.dt, initValue, variable.length, variable.zpwish, variable.align, variable.dirty, inBss, readonly)
|
||||
}
|
||||
|
||||
fun convert(variable: StMemVar): IRStMemVar {
|
||||
val readonly = romable && variable.address > 255u
|
||||
if('.' in variable.name) {
|
||||
return IRStMemVar(
|
||||
variable.name,
|
||||
variable.dt,
|
||||
variable.address,
|
||||
variable.length
|
||||
variable.length,
|
||||
readonly
|
||||
)
|
||||
} else {
|
||||
val scopedName = try {
|
||||
@@ -89,7 +94,7 @@ private object StToIrConverter {
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
variable.name
|
||||
}
|
||||
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
|
||||
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length, readonly)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,33 +139,34 @@ private object StToIrConverter {
|
||||
}
|
||||
}
|
||||
|
||||
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
|
||||
fun convertStToIRSt(sourceSt: SymbolTable?, romable: Boolean = false): IRSymbolTable {
|
||||
val st = IRSymbolTable()
|
||||
val converter = StToIrConverter(romable)
|
||||
if (sourceSt != null) {
|
||||
sourceSt.flat.forEach {
|
||||
when(it.value.type) {
|
||||
StNodeType.STATICVAR -> st.add(StToIrConverter.convert(it.value as StStaticVariable))
|
||||
StNodeType.MEMVAR -> st.add(StToIrConverter.convert(it.value as StMemVar))
|
||||
StNodeType.STATICVAR -> st.add(converter.convert(it.value as StStaticVariable))
|
||||
StNodeType.MEMVAR -> st.add(converter.convert(it.value as StMemVar))
|
||||
StNodeType.CONSTANT -> {
|
||||
val constant = it.value as StConstant
|
||||
// If the constant has a memory() slab, add the slab to the IR symbol table first
|
||||
constant.memorySlab?.let { slab ->
|
||||
val converted = StToIrConverter.convert(slab)
|
||||
val converted = converter.convert(slab)
|
||||
st.add(converted)
|
||||
}
|
||||
st.add(StToIrConverter.convert(constant))
|
||||
st.add(converter.convert(constant))
|
||||
}
|
||||
StNodeType.MEMORYSLAB -> {
|
||||
val slab = it.value as StMemorySlab
|
||||
val converted = StToIrConverter.convert(slab)
|
||||
val converted = converter.convert(slab)
|
||||
st.add(converted)
|
||||
}
|
||||
StNodeType.STRUCTINSTANCE -> {
|
||||
val instance = it.value as StStructInstance
|
||||
val struct = sourceSt.lookup(instance.structName) as StStruct
|
||||
st.add(StToIrConverter.convert(instance, struct.fields))
|
||||
st.add(converter.convert(instance, struct.fields))
|
||||
}
|
||||
StNodeType.STRUCT -> st.add(StToIrConverter.convert(it.value as StStruct))
|
||||
StNodeType.STRUCT -> st.add(converter.convert(it.value as StStruct))
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.OutputType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.intermediate.IRPeepholeOptimizer
|
||||
import prog8.intermediate.*
|
||||
@@ -289,4 +292,71 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
modInstr[0].opcode shouldBe Opcode.MOD
|
||||
modInstr[0].immediate shouldBe 7
|
||||
}
|
||||
})
|
||||
|
||||
test("remove self identity operations") {
|
||||
// LOADR with same fp register is allowed (constructor only blocks integer reg1==reg2)
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.LOADR, IRDataType.FLOAT, fpReg1=RegisterNum(10), fpReg2=RegisterNum(10)),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 2
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr[0].opcode shouldBe Opcode.LOAD
|
||||
instr[0].reg1 shouldBe 99
|
||||
instr[0].immediate shouldBe 42
|
||||
}
|
||||
|
||||
test("remove shift by zero") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=10, immediate=0),
|
||||
IRInstruction(Opcode.LSLN, IRDataType.BYTE, reg1=1, reg2=10),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=11, immediate=0),
|
||||
IRInstruction(Opcode.LSRN, IRDataType.BYTE, reg1=2, reg2=11),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=12, immediate=0),
|
||||
IRInstruction(Opcode.ASRN, IRDataType.BYTE, reg1=3, reg2=12),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 7
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 1
|
||||
instr[0].opcode shouldBe Opcode.LOAD
|
||||
instr[0].reg1 shouldBe 99
|
||||
instr[0].immediate shouldBe 42
|
||||
}
|
||||
|
||||
test("cancel adjacent ops") {
|
||||
val irProg = makeIRProgram(listOf(
|
||||
IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1),
|
||||
IRInstruction(Opcode.INV, IRDataType.BYTE, reg1=1),
|
||||
IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2),
|
||||
IRInstruction(Opcode.NEG, IRDataType.BYTE, reg1=2),
|
||||
IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33),
|
||||
IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=3, reg2=33),
|
||||
IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44),
|
||||
IRInstruction(Opcode.EXTS, IRDataType.WORD, reg1=4, reg2=44),
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=5),
|
||||
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=5),
|
||||
IRInstruction(Opcode.DEC, IRDataType.BYTE, reg1=6),
|
||||
IRInstruction(Opcode.INC, IRDataType.BYTE, reg1=6),
|
||||
IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=99, immediate=42) // keep chunk non-empty
|
||||
))
|
||||
irProg.chunks().single().instructions.size shouldBe 13
|
||||
val opt = IRPeepholeOptimizer(irProg, false)
|
||||
opt.optimize(true, ErrorReporterForTests())
|
||||
val instr = irProg.chunks().single().instructions
|
||||
instr.size shouldBe 3
|
||||
instr[0].opcode shouldBe Opcode.EXT
|
||||
instr[0].reg1 shouldBe 3
|
||||
instr[0].reg2 shouldBe 33
|
||||
instr[1].opcode shouldBe Opcode.EXTS
|
||||
instr[1].reg1 shouldBe 4
|
||||
instr[1].reg2 shouldBe 44
|
||||
instr[2].opcode shouldBe Opcode.LOAD
|
||||
instr[2].immediate shouldBe 42
|
||||
}
|
||||
})
|
||||
@@ -32,6 +32,12 @@ class VarConstantValueTypeAdjuster(
|
||||
|
||||
private fun isConstantInitializer(expr: Expression): Boolean = getConstantInitializer(expr) != null
|
||||
|
||||
private fun isMemorySlabInit(expr: Expression): Boolean =
|
||||
expr is MemorySlabRef || (expr is IFunctionCall && expr.isMemoryCall)
|
||||
|
||||
private fun requiresZp(decl: VarDecl): Boolean =
|
||||
decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<AstModification> {
|
||||
|
||||
if(decl.parent is AnonymousScope)
|
||||
@@ -47,95 +53,95 @@ class VarConstantValueTypeAdjuster(
|
||||
}
|
||||
|
||||
// replace variables by constants, if possible
|
||||
if(options.optimize) {
|
||||
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE
|
||||
|| !(decl.datatype.isNumericOrBool || decl.datatype.isPointer))
|
||||
return noModifications
|
||||
if (decl.value != null && !isConstantInitializer(decl.value!!))
|
||||
return noModifications
|
||||
val usages = callGraph.usages(decl)
|
||||
val (writes, reads) = usages
|
||||
.partition {
|
||||
it is InlineAssembly // can't really tell if it's written to or only read, assume the worst
|
||||
|| it.parent is AssignTarget
|
||||
|| it.parent is ForLoop
|
||||
|| (it.parent as? IFunctionCall)?.target?.nameInSource?.singleOrNull() in InplaceModifyingBuiltinFunctions
|
||||
}
|
||||
|
||||
// If the address of the variable is taken, we cannot replace it with a constant.
|
||||
if (reads.any { it.parent is AddressOf })
|
||||
return noModifications
|
||||
|
||||
var singleAssignment: Assignment? = null
|
||||
val singleWrite=writes.singleOrNull()
|
||||
if(singleWrite!=null) {
|
||||
singleAssignment = singleWrite.parent as? Assignment
|
||||
if (decl.sharedWithAsm || decl.type != VarDeclType.VAR || decl.origin != VarDeclOrigin.USERCODE
|
||||
|| !(decl.datatype.isNumericOrBool || decl.datatype.isPointer))
|
||||
return noModifications
|
||||
if (decl.value != null && !isConstantInitializer(decl.value!!))
|
||||
return noModifications
|
||||
if (decl.value != null && isMemorySlabInit(decl.value!!))
|
||||
return noModifications // a pointer to a memory slab is usually accessed often and/or in ways that benefit more from the pointer being an actual variable (preferrably in zeropage)
|
||||
val usages = callGraph.usages(decl)
|
||||
val (writes, reads) = usages
|
||||
.partition {
|
||||
it is InlineAssembly // can't really tell if it's written to or only read, assume the worst
|
||||
|| it.parent is AssignTarget
|
||||
|| it.parent is ForLoop
|
||||
|| (it.parent as? IFunctionCall)?.target?.nameInSource?.singleOrNull() in InplaceModifyingBuiltinFunctions
|
||||
}
|
||||
|
||||
// If the address of the variable is taken, we cannot replace it with a constant.
|
||||
if (reads.any { it.parent is AddressOf })
|
||||
return noModifications
|
||||
|
||||
var singleAssignment: Assignment? = null
|
||||
val singleWrite=writes.singleOrNull()
|
||||
if(singleWrite!=null) {
|
||||
singleAssignment = singleWrite.parent as? Assignment
|
||||
if(singleAssignment==null) {
|
||||
singleAssignment = singleWrite.parent.parent as? Assignment
|
||||
if(singleAssignment==null) {
|
||||
singleAssignment = singleWrite.parent.parent as? Assignment
|
||||
if(singleAssignment==null) {
|
||||
// we could be part of a multi-assign
|
||||
if(singleWrite.parent is AssignTarget && singleWrite.parent.parent is AssignTarget)
|
||||
singleAssignment = singleWrite.parent.parent.parent as? Assignment
|
||||
}
|
||||
// we could be part of a multi-assign
|
||||
if(singleWrite.parent is AssignTarget && singleWrite.parent.parent is AssignTarget)
|
||||
singleAssignment = singleWrite.parent.parent.parent as? Assignment
|
||||
}
|
||||
}
|
||||
|
||||
if (singleAssignment == null) {
|
||||
if (writes.isEmpty()) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether
|
||||
if ("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(AstRemove(decl, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
val declValue = if (decl.value != null) getConstantInitializer(decl.value!!) else null
|
||||
if (declValue != null && canBeMadeConst(decl, usages)) {
|
||||
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was made const", decl.position)
|
||||
val const = VarDecl.builder(decl.datatype, decl.position)
|
||||
.copyFrom(decl)
|
||||
.type(VarDeclType.CONST)
|
||||
.value(declValue)
|
||||
.build()
|
||||
decl.value = null
|
||||
return listOf(
|
||||
AstReplaceNode(decl, const, parent)
|
||||
)
|
||||
}
|
||||
|
||||
if (singleAssignment == null) {
|
||||
if (writes.isEmpty()) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether
|
||||
if ("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(AstRemove(decl, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (singleAssignment.origin == AssignmentOrigin.VARINIT && isConstantInitializer(singleAssignment.value) && canBeMadeConst(decl, usages)) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether, including the single assignment
|
||||
if("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
AstRemove(decl, parent as IStatementContainer),
|
||||
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// variable only has a single write, and it is the initialization value, so it can be replaced with a constant, but only IF the value is a constant
|
||||
val declValue = if (decl.value != null) getConstantInitializer(decl.value!!) else null
|
||||
if (declValue != null && !requiresZp(decl) && canBeMadeConst(decl, usages)) {
|
||||
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was made const", decl.position)
|
||||
val const = VarDecl.builder(decl.datatype, decl.position)
|
||||
.copyFrom(decl)
|
||||
.type(VarDeclType.CONST)
|
||||
.value(singleAssignment.value)
|
||||
.value(declValue)
|
||||
.build()
|
||||
decl.value = null
|
||||
return listOf(
|
||||
AstReplaceNode(decl, const, parent),
|
||||
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
AstReplaceNode(decl, const, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (singleAssignment.origin == AssignmentOrigin.VARINIT && isConstantInitializer(singleAssignment.value) && !isMemorySlabInit(singleAssignment.value) && !requiresZp(decl) && canBeMadeConst(decl, usages)) {
|
||||
if(reads.isEmpty()) {
|
||||
if(decl.names.size>1) {
|
||||
errors.info("unused variable '${decl.name}'", decl.position)
|
||||
} else {
|
||||
// variable is never used AT ALL so we just remove it altogether, including the single assignment
|
||||
if("ignore_unused" !in decl.definingBlock.options())
|
||||
errors.info("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
AstRemove(decl, parent as IStatementContainer),
|
||||
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// variable only has a single write, and it is the initialization value, so it can be replaced with a constant, but only IF the value is a constant
|
||||
errors.info("variable '${decl.name}' is never written to and was made const", decl.position)
|
||||
val const = VarDecl.builder(decl.datatype, decl.position)
|
||||
.copyFrom(decl)
|
||||
.type(VarDeclType.CONST)
|
||||
.value(singleAssignment.value)
|
||||
.build()
|
||||
return listOf(
|
||||
AstReplaceNode(decl, const, parent),
|
||||
AstRemove(singleAssignment, singleAssignment.parent as IStatementContainer)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@@ -148,10 +154,11 @@ class VarConstantValueTypeAdjuster(
|
||||
val p = it.parent
|
||||
val gp = p.parent
|
||||
it is PtrDereference || it is ArrayIndexedPtrDereference
|
||||
|| p is ArrayIndexedExpression || p is DirectMemoryRead || p is DirectMemoryWrite
|
||||
|| gp is DirectMemoryRead || gp is DirectMemoryWrite
|
||||
|| p is ArrayIndexedExpression || p is DirectMemoryRead || p is DirectMemoryWrite
|
||||
|| gp is DirectMemoryRead || gp is DirectMemoryWrite
|
||||
}
|
||||
if (derefCount > 2) return false
|
||||
if (derefCount > 0 && (decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -340,6 +347,12 @@ internal class ConstantIdentifierReplacer(
|
||||
override fun before(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
|
||||
val constValue = addressOf.constValue(program)
|
||||
if(constValue!=null) {
|
||||
// don't replace &constVar (without array index); leave it for AstChecker to reject as invalid address-of.
|
||||
// But still allow &constArray[idx] (those are valid address-of operations).
|
||||
val target = addressOf.identifier?.targetVarDecl()
|
||||
if(target?.type==VarDeclType.CONST && addressOf.arrayIndex==null) {
|
||||
return noModifications
|
||||
}
|
||||
return listOf(AstReplaceNode(addressOf, constValue, parent))
|
||||
}
|
||||
return noModifications
|
||||
@@ -348,7 +361,7 @@ internal class ConstantIdentifierReplacer(
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if (identifier.parent is AssignTarget || identifier.parent is Alias)
|
||||
if (identifier.parent is AssignTarget || identifier.parent is Alias || identifier.parent is AddressOf)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if (forloop == null)
|
||||
|
||||
@@ -463,12 +463,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -476,12 +476,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -489,12 +489,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> constFalse
|
||||
rightVal?.number==0.0 -> constFalse
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> expr.right
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> expr.right
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ private fun isEmptyReturn(stmt: Statement): Boolean = stmt is Return && stmt.va
|
||||
|
||||
class Inliner(private val program: Program, private val options: CompilationOptions): AstWalker() {
|
||||
|
||||
class DetermineInlineSubs(val program: Program): IAstVisitor {
|
||||
inner class DetermineInlineSubs(val program: Program): IAstVisitor {
|
||||
private val modifications = mutableListOf<AstModification>()
|
||||
|
||||
init {
|
||||
@@ -32,23 +32,26 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
return true
|
||||
|
||||
return stmt.values.all { value ->
|
||||
when (value) {
|
||||
is NumericLiteral -> true
|
||||
is IdentifierReference -> {
|
||||
makeFullyScoped(value)
|
||||
true
|
||||
}
|
||||
is FunctionCallExpression -> {
|
||||
if (value.args.size <= 1 && value.args.all {
|
||||
it is NumericLiteral || it is IdentifierReference
|
||||
}) {
|
||||
makeFullyScoped(value)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
if (isSimpleReturnExpression(value)) {
|
||||
val walker = object : AstWalker() {
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
|
||||
makeFullyScoped(identifier)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
else -> false
|
||||
value.accept(walker, stmt)
|
||||
true
|
||||
} else if (value is FunctionCallExpression) {
|
||||
if (value.args.size <= 1 && value.args.all {
|
||||
it is NumericLiteral || it is IdentifierReference
|
||||
}) {
|
||||
makeFullyScoped(value)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,13 +226,12 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
private fun canInlineAtCallSiteWithReason(sub: Subroutine, fcall: IFunctionCall): Pair<Boolean, String> {
|
||||
if (!sub.inline)
|
||||
return false to "subroutine not marked as inlineable"
|
||||
|
||||
|
||||
// Check parameter argument complexity
|
||||
if (sub.parameters.isNotEmpty()) {
|
||||
// Only allow inlining for void statement calls, not expression context
|
||||
if (fcall !is FunctionCallStatement || !fcall.void)
|
||||
return false to "expression context calls with parameters not supported yet"
|
||||
|
||||
if (sub.isAsmSubroutine) {
|
||||
return false to "parameterized asmsub cannot be inlined"
|
||||
}
|
||||
// Only inline if all arguments are simple (literals or identifiers)
|
||||
if (!fcall.args.all { it is NumericLiteral || it is IdentifierReference }) {
|
||||
val complexArgs = fcall.args.filterNot { it is NumericLiteral || it is IdentifierReference }
|
||||
@@ -279,13 +281,12 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
|
||||
fun inlineFunctionBody(toInline: Return): Iterable<AstModification> {
|
||||
// call site is an expression, so we have to have a Return here in the inlined sub to provide the values
|
||||
// note that we don't have to process any args, because we are currently only inlining parameterless subroutines.
|
||||
return if(toInline.values.size==1 && functionCallExpr!==toInline.values[0]) {
|
||||
return if (toInline.values.size == 1 && functionCallExpr !== toInline.values[0]) {
|
||||
// println(">>> INLINER: INLINED expression '${sub.name}' at ${functionCallExpr.position} (return value substituted)")
|
||||
sub.hasBeenInlined =true
|
||||
listOf(AstReplaceNode(functionCallExpr, toInline.values[0].copy(), parent))
|
||||
}
|
||||
else
|
||||
sub.hasBeenInlined = true
|
||||
val substitutedReturn = substituteParameters(sub, functionCallExpr, toInline.values[0]) as Expression
|
||||
listOf(AstReplaceNode(functionCallExpr, substitutedReturn, parent))
|
||||
} else
|
||||
noModifications
|
||||
}
|
||||
|
||||
@@ -303,7 +304,9 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
private fun possiblyInlineFunctioncallStmt(sub: Subroutine, origNode: Node, parent: Node): Iterable<AstModification> {
|
||||
|
||||
fun possiblyShortCircuitFunctionCall(toInline: Return): Iterable<AstModification> {
|
||||
val functionCalls = toInline.values.filterIsInstance<FunctionCallExpression>()
|
||||
// Substitute parameters in the return values first
|
||||
val substitutedReturn = substituteParameters(sub, origNode as IFunctionCall, toInline) as Return
|
||||
val functionCalls = substitutedReturn.values.filterIsInstance<FunctionCallExpression>()
|
||||
|
||||
if (functionCalls.isEmpty()) {
|
||||
// No function calls in the return values - the void call has no side effects and can be removed.
|
||||
@@ -331,15 +334,10 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
}
|
||||
|
||||
fun possiblyInlineFunctionBody(toInline: Statement): Iterable<AstModification> {
|
||||
// For void calls with parameters, we already verified in canInlineAtCallSiteWithReason()
|
||||
// that the parameter is unused in the body (so no substitution needed).
|
||||
// For expression context calls with parameters, those are rejected in canInlineAtCallSiteWithReason()
|
||||
// so we never reach here.
|
||||
// Just copy the statement - parameters are unused so no substitution needed.
|
||||
val inlinedStatement = toInline.copy()
|
||||
|
||||
val inlinedStatement = substituteParameters(sub, origNode as IFunctionCall, toInline) as Statement
|
||||
|
||||
// println(">>> INLINER: INLINED '${sub.name}' at ${functionCallStatement.position} (body inserted, ${sub.parameters.size} param(s))")
|
||||
return if(origNode !== toInline) {
|
||||
return if (origNode !== toInline) {
|
||||
sub.hasBeenInlined = true
|
||||
listOf(AstReplaceNode(origNode, inlinedStatement, parent))
|
||||
} else
|
||||
@@ -361,7 +359,6 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
sub.hasBeenInlined=true
|
||||
listOf(AstReplaceNode(origNode, bodyStmt.copy(), parent))
|
||||
} else {
|
||||
// note that we don't have to process any args, because we only inline parameterless subroutines.
|
||||
when (val toInline = bodyStmt) {
|
||||
is Return -> possiblyShortCircuitFunctionCall(toInline)
|
||||
else -> possiblyInlineFunctionBody(toInline)
|
||||
@@ -371,6 +368,19 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
private fun isSimpleReturnExpression(expr: Expression): Boolean {
|
||||
return when (expr) {
|
||||
is NumericLiteral -> true
|
||||
is IdentifierReference -> true
|
||||
is PrefixExpression -> isSimpleReturnExpression(expr.expression)
|
||||
is BinaryExpression ->
|
||||
(expr.left is NumericLiteral || expr.left is IdentifierReference) &&
|
||||
(expr.right is NumericLiteral || expr.right is IdentifierReference)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Substitutes parameter references in a statement with argument values.
|
||||
*/
|
||||
@@ -382,22 +392,25 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
val sub = fcall.target.targetStatement(program.builtinFunctions) as? Subroutine
|
||||
?: return noModifications
|
||||
|
||||
// Only handle parameterless subroutines with simple returns
|
||||
if (!sub.inline || sub.parameters.isNotEmpty() || !canInlineAtCallSite(sub, fcall))
|
||||
// Only handle inlining if sub is marked as such and call site is okay
|
||||
if (!sub.inline || !canInlineAtCallSite(sub, fcall))
|
||||
return noModifications
|
||||
|
||||
val toInline = sub.statements.firstOrNull() as? Return ?: return noModifications
|
||||
val toInline = sub.statements.firstOrNull { it !is VarDecl || it.origin != VarDeclOrigin.SUBROUTINEPARAM } as? Return ?: return noModifications
|
||||
|
||||
// Check return count matches target count
|
||||
if (toInline.values.size != multiTargets.size)
|
||||
return noModifications
|
||||
|
||||
// Only allow simple return values (literals or identifiers, no function calls)
|
||||
if (!toInline.values.all { it is NumericLiteral || it is IdentifierReference })
|
||||
// Only allow simple return values to prevent code bloat on 6502
|
||||
if (!toInline.values.all { isSimpleReturnExpression(it) })
|
||||
return noModifications
|
||||
|
||||
// Substitute parameters in all return values
|
||||
val substitutedReturn = substituteParameters(sub, fcall, toInline) as Return
|
||||
|
||||
// Create multiple single assignments, skipping void targets (they discard the value)
|
||||
val newAssignments = multiTargets.zip(toInline.values)
|
||||
val newAssignments = multiTargets.zip(substitutedReturn.values)
|
||||
.filter { (target, _) -> !target.void }
|
||||
.map { (target, value) ->
|
||||
Assignment(
|
||||
@@ -420,4 +433,59 @@ class Inliner(private val program: Program, private val options: CompilationOpti
|
||||
val scope = AnonymousScope(newAssignments.toMutableList(), assignment.position)
|
||||
return listOf(AstReplaceNode(assignment, scope, parent))
|
||||
}
|
||||
|
||||
private fun substituteParameters(sub: Subroutine, fcall: IFunctionCall, node: Node): Node {
|
||||
val paramVarDecls = sub.statements.filterIsInstance<VarDecl>().filter { it.origin == VarDeclOrigin.SUBROUTINEPARAM }
|
||||
val paramMap: Map<VarDecl, Expression> = paramVarDecls.zip(fcall.args).associate { it.first to it.second }
|
||||
|
||||
fun substitute(n: Node): Node {
|
||||
if (n is IdentifierReference) {
|
||||
val target = n.targetStatement(program.builtinFunctions)
|
||||
val arg = if (target is VarDecl) paramMap[target] else null
|
||||
if (arg != null) return arg.copy()
|
||||
}
|
||||
|
||||
return when (n) {
|
||||
is BinaryExpression -> BinaryExpression(substitute(n.left) as Expression, n.operator, substitute(n.right) as Expression, n.position)
|
||||
is PrefixExpression -> PrefixExpression(n.operator, substitute(n.expression) as Expression, n.position)
|
||||
is Return -> Return(n.values.map { substitute(it) as Expression }.toTypedArray(), n.position)
|
||||
is Assignment -> {
|
||||
val newTarget = n.target.copy()
|
||||
if (n.target.identifier != null) {
|
||||
val substituted = substitute(n.target.identifier!!)
|
||||
if (substituted is IdentifierReference) {
|
||||
newTarget.identifier = substituted
|
||||
}
|
||||
// if substituted is not an identifier, we leave the original (copied) identifier
|
||||
// in the target. This is not ideal but avoids the ClassCastException.
|
||||
}
|
||||
Assignment(
|
||||
newTarget,
|
||||
substitute(n.value) as Expression,
|
||||
n.origin,
|
||||
n.position
|
||||
)
|
||||
}
|
||||
is FunctionCallExpression -> FunctionCallExpression(
|
||||
substitute(n.target) as IdentifierReference,
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
n.position
|
||||
)
|
||||
is FunctionCallStatement -> FunctionCallStatement(
|
||||
substitute(n.target) as IdentifierReference,
|
||||
n.args.map { substitute(it) as Expression }.toMutableList(),
|
||||
n.void,
|
||||
n.position
|
||||
)
|
||||
is AnonymousScope -> AnonymousScope(
|
||||
n.statements.map { substitute(it) as Statement }.toMutableList(),
|
||||
n.position
|
||||
)
|
||||
is Jump -> Jump(substitute(n.target) as Expression, n.position)
|
||||
else -> n.copy()
|
||||
}
|
||||
}
|
||||
|
||||
return substitute(node)
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ dependencies {
|
||||
testImplementation(project(":intermediate"))
|
||||
testImplementation("io.kotest:kotest-runner-junit5")
|
||||
testImplementation("io.kotest:kotest-framework-datatest")
|
||||
testImplementation("com.github.irmen:ksim65:v2.0")
|
||||
testImplementation("com.github.irmen:ksim65:v2.1")
|
||||
}
|
||||
|
||||
// Exclude transitive antlr4 dependency (we only need it in parser module)
|
||||
|
||||
@@ -14,7 +14,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -13,7 +13,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -872,7 +872,7 @@ exit:
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL)
|
||||
|
||||
@@ -185,7 +185,7 @@ gfx_hires {
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
|
||||
; highres 4c
|
||||
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
; use TWO vera address pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
if lheight==0
|
||||
return
|
||||
position2(xx,yy,true)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
; optimized graphics routines for just the single screen mode: lores 320*240, 256c (8bpp)
|
||||
; bitmap image needs to start at VRAM addres $00000.
|
||||
; bitmap image needs to start at VRAM address $00000.
|
||||
; This is compatible with the CX16's screen mode 128.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ gfx_lores {
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
if verafx.available() {
|
||||
; use verafx cache writes to quicly clear the screen
|
||||
; use verafx cache writes to quickly clear the screen
|
||||
const ubyte vbank = 0
|
||||
const uword vaddr = 0
|
||||
cx16.VERA_CTRL = 0
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
; serial routines for the serial/wifi card of the X16.
|
||||
; assumes the wifi part is supported via zimodem.
|
||||
|
||||
%import strings
|
||||
|
||||
serial {
|
||||
|
||||
%option ignore_unused
|
||||
|
||||
const ubyte REG_TXRX_BUFFER = 0
|
||||
const ubyte REG_INTERRUPT_ENABLE = 1
|
||||
const ubyte REG_INTERRUPT_IDENT = 2
|
||||
const ubyte REG_FIFO_CONTROL = 2
|
||||
const ubyte REG_LINE_CONTROL = 3
|
||||
const ubyte REG_MODEM_CONTROL = 4
|
||||
const ubyte REG_LINE_STATUS = 5
|
||||
const ubyte REG_MODEM_STATUS = 6
|
||||
const ubyte REG_SCRATCH = 7
|
||||
const ubyte REG_DIVISOR_LATCH_LOW = 0 ;same as TXRX, but when bit-7 of line control high
|
||||
const ubyte REG_DIVISOR_LATCH_HI = 1 ;same as IRQE, but when bit-7 of line control high
|
||||
|
||||
; Baud rate divisor values for TL16C2550 with 14.7456 MHz clock
|
||||
; Formula: divisor = 14745600 / (16 * baudrate)
|
||||
enum BAUD {
|
||||
B921600 = $0001,
|
||||
B460800 = $0002,
|
||||
B230400 = $0004,
|
||||
B115200 = $0008,
|
||||
B57600 = $0010,
|
||||
B38400 = $0018,
|
||||
B28800 = $0020,
|
||||
B19200 = $0030,
|
||||
B14400 = $0040,
|
||||
B9600 = $0060,
|
||||
B4800 = $00C0,
|
||||
B2400 = $0180,
|
||||
B1200 = $0300,
|
||||
B600 = $0600,
|
||||
B300 = $0C00,
|
||||
}
|
||||
|
||||
; Bitfield masks for TL16C2550's Line Status Register
|
||||
enum LSR {
|
||||
DR = %00000001, ; Data Ready
|
||||
OE = %00000010, ; Overrun Error
|
||||
PE = %00000100, ; Parity Error
|
||||
FE = %00001000, ; Framing Error
|
||||
BI = %00010000, ; Break Interrupt
|
||||
THRE = %00100000, ; Transmit Holding Register Empty
|
||||
TEMT = %01000000, ; Transmitter Empty
|
||||
RCVR = %10000000, ; Error in RCVR FIFO
|
||||
}
|
||||
|
||||
; generic UART routines (not ZiModem specific):
|
||||
|
||||
sub detect_uarts() -> uword, uword {
|
||||
; Detects the presence of UART chips (up to 2) on the system.
|
||||
; Returns the discovered UART chip addresses, or 0 if none found.
|
||||
|
||||
const uword FIRST_UART_ADDRESS = $9F60
|
||||
const uword LAST_UART_ADDRESS = $9FF8
|
||||
|
||||
alias uart1 = cx16.r1
|
||||
alias uart2 = cx16.r2
|
||||
uart1 = uart2 = 0
|
||||
|
||||
for cx16.r0 in FIRST_UART_ADDRESS to LAST_UART_ADDRESS step 8 {
|
||||
if probe(cx16.r0) {
|
||||
if uart1 == 0
|
||||
uart1 = cx16.r0
|
||||
else if uart2 == 0 {
|
||||
uart2 = cx16.r0
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return uart1, uart2
|
||||
|
||||
private sub probe(uword address) -> bool {
|
||||
sys.set_irqd()
|
||||
address[REG_INTERRUPT_ENABLE] = $f0
|
||||
bool found = address[REG_INTERRUPT_ENABLE] & $f0 == 0 ; The high nybble of IER is always clear
|
||||
address[REG_INTERRUPT_ENABLE] = 0
|
||||
if found {
|
||||
address[REG_MODEM_CONTROL] = $ff
|
||||
found = address[REG_MODEM_CONTROL] == $3f ; Bits 7 and 6 of MCR are always clear
|
||||
address[REG_MODEM_CONTROL] = 0
|
||||
}
|
||||
sys.clear_irqd()
|
||||
return found
|
||||
}
|
||||
}
|
||||
|
||||
sub initialize_uart(uword uart_addr) {
|
||||
; 921600 baud, 8,N,1, AutoFlow Control, FIFOS, no interrupts
|
||||
uart_addr[REG_LINE_CONTROL] = $80 ; Set DLAB
|
||||
uart_addr[REG_DIVISOR_LATCH_HI] = msb(BAUD::B921600)
|
||||
uart_addr[REG_DIVISOR_LATCH_LOW] = lsb(BAUD::B921600)
|
||||
uart_addr[REG_LINE_CONTROL] = $03 ; 8,N,1, DLAB off
|
||||
uart_addr[REG_FIFO_CONTROL] = $C3 ; FIFO enable & reset, trigger 14
|
||||
uart_addr[REG_MODEM_CONTROL] = $23 ; DTR/RTS + AutoFlow Control
|
||||
uart_addr[REG_INTERRUPT_ENABLE] = $00 ; No interrupts
|
||||
}
|
||||
|
||||
sub write(uword @zp uart_addr, str data) {
|
||||
; Writes the string data to the uart.
|
||||
alias data_ptr = cx16.r0
|
||||
data_ptr = data
|
||||
while @(data_ptr) != 0 {
|
||||
while uart_addr[REG_LINE_STATUS] & LSR::THRE == 0 {
|
||||
; wait until data can be written
|
||||
}
|
||||
@(uart_addr) = @(data_ptr)
|
||||
data_ptr++
|
||||
}
|
||||
}
|
||||
|
||||
sub read_until(uword @zp uart_addr, str match, ^^ubyte buffer, uword max_size) -> uword {
|
||||
; Reads bytes from the uart into buffer until the match string is found.
|
||||
; The match string itself is also included in the buffer.
|
||||
; Stops when the match string is found or max_size bytes have been read.
|
||||
; Returns the number of bytes read.
|
||||
|
||||
alias buffer_ptr = cx16.r0
|
||||
alias match_ptr = cx16.r1
|
||||
alias data = cx16.r2L
|
||||
alias read_size = cx16.r3
|
||||
alias max_read_size = cx16.r4
|
||||
|
||||
match_ptr = match
|
||||
buffer_ptr = buffer
|
||||
max_read_size = max_size
|
||||
read_size = 0
|
||||
|
||||
while read_size < max_read_size {
|
||||
while uart_addr[REG_LINE_STATUS] & LSR::DR == 0 {
|
||||
; wait until data is present
|
||||
}
|
||||
@(buffer_ptr) = data = @(uart_addr)
|
||||
buffer_ptr++
|
||||
read_size++
|
||||
if data == @(match_ptr) {
|
||||
match_ptr++
|
||||
if @(match_ptr)==0
|
||||
break
|
||||
}
|
||||
else
|
||||
match_ptr = match
|
||||
}
|
||||
|
||||
return read_size
|
||||
}
|
||||
|
||||
sub discard_until(uword @zp uart_addr, str match) {
|
||||
; Reads bytes from the uart until the match string is found, discards all data.
|
||||
; The match string itself is also discarded.
|
||||
|
||||
alias match_ptr = cx16.r0
|
||||
match_ptr = match
|
||||
|
||||
repeat {
|
||||
while uart_addr[REG_LINE_STATUS] & LSR::DR == 0 {
|
||||
; wait until data is present
|
||||
}
|
||||
if @(uart_addr) == @(match_ptr) {
|
||||
match_ptr++
|
||||
if @(match_ptr)==0
|
||||
break
|
||||
}
|
||||
else
|
||||
match_ptr = match
|
||||
}
|
||||
}
|
||||
|
||||
; zimodem routines:
|
||||
|
||||
sub zi_initialize(uword uart_addr) {
|
||||
; initializes the ZiModem on the given uart address
|
||||
zi_uart = uart_addr
|
||||
if zi_uart == 0
|
||||
return ; no uart present
|
||||
|
||||
initialize_uart(zi_uart)
|
||||
; ZiModem sends a version banner of the `ati` command when the ESP32 boots up.
|
||||
; Read it off if present. It is not always ready immediately so wait a tiny bit
|
||||
sys.wait(5)
|
||||
write(zi_uart, "\x03") ; first send CTRL-C (+ no echo) to abort any previous file streams
|
||||
sys.wait(10)
|
||||
zi_write_cmd(iso:"ate0q0v1x1f0r1s45=3&p0&k3")
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_reset() {
|
||||
; reset all zimodem connections
|
||||
write(zi_uart, "\x03") ; first send CTRL-C (+ no echo) to abort any previous file streams
|
||||
sys.wait(10)
|
||||
serial.zi_write_cmd(iso:"atz")
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_write_cmd(str command) {
|
||||
; writes the zimodem command (followed by a proper line ending)
|
||||
write(zi_uart, command)
|
||||
write_eol(zi_uart)
|
||||
}
|
||||
|
||||
sub zi_start_get_file_hexmode(str filename) -> bool {
|
||||
; starts a file download from the given url. Returns true if ok, false if file could not be found
|
||||
; you can download chunks of the file by repeatedly calling zi_get_file_chunk_hexmode() until that returns 0.
|
||||
; Note: this uses zimodem hex mode transfer for now because binary mode transfer is broken at the moment.
|
||||
zi_write_cmd(iso:"ats45=1") ; enable hex mode transfer (unfortunately this also forces caching mode on the esp32)
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
write(zi_uart, iso:"at&g\"")
|
||||
write(zi_uart, filename)
|
||||
write(zi_uart, "\"\x0d\x0a")
|
||||
|
||||
if read_one(zi_uart)=='[' {
|
||||
; note: zimodem file size in hex mode is bogus (only can go up to $FFFF), just skip the whole header line.
|
||||
discard_until(zi_uart, "\x0d\x0a")
|
||||
return true
|
||||
} else {
|
||||
discard_until(zi_uart, iso:"RROR\x0d\x0a")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
sub zi_start_get_file(str filename) -> long {
|
||||
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
|
||||
; starts a file download from the given url, returns the size of the file to download.
|
||||
; you can download chunks of the file by repeatedly calling zi_get_file_chunk() until that returns 0
|
||||
write(zi_uart, iso:"at&g\"")
|
||||
write(zi_uart, filename)
|
||||
write(zi_uart, "\"\x0d\x0a")
|
||||
|
||||
if read_one(zi_uart)=='[' {
|
||||
; parse header [ 0 <filesize> <sum> ]
|
||||
ubyte[25] header_buf
|
||||
void read_until(zi_uart, iso:"]\x0d\x0a", &header_buf, sizeof(header_buf))
|
||||
return conv.str2long(&header_buf + 3)
|
||||
} else {
|
||||
discard_until(zi_uart, iso:"RROR\x0d\x0a")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
sub zi_get_file_chunk_hexmode(^^ubyte @zp buffer, uword buffer_size) -> uword {
|
||||
; read the next chunk of data from the file (opened in hex mode), into buffer, up to buffer_size bytes (minimum 40 bytes!!)
|
||||
; returns the number of bytes read. 0 if no more data was available.
|
||||
ubyte[90] linebuffer
|
||||
cx16.r0 = read_until(zi_uart, "\x0d\x0a", linebuffer, sizeof(linebuffer))
|
||||
if cx16.r0 == 4 and linebuffer[0] == iso:'O' ; OK\r\n was received -- end of transmission
|
||||
return 0
|
||||
|
||||
ubyte digits = lsb(cx16.r0)-2
|
||||
decode(digits)
|
||||
return digits/2
|
||||
|
||||
private sub decode(ubyte numdigits) {
|
||||
cx16.r1L = 0
|
||||
for cx16.r0L in 0 to numdigits-1 step 2 {
|
||||
buffer[cx16.r1L] = decodehex(cx16.r0L)
|
||||
cx16.r1L++
|
||||
}
|
||||
|
||||
asmsub decodehex(ubyte digitindex @Y) -> ubyte @A {
|
||||
%asm {{
|
||||
lda p8v_linebuffer,y
|
||||
sec
|
||||
sbc #$30
|
||||
cmp #$0a
|
||||
bmi +
|
||||
sbc #$07
|
||||
+ asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda p8v_linebuffer+1,y
|
||||
sec
|
||||
sbc #$30
|
||||
cmp #$0a
|
||||
bmi +
|
||||
sbc #$07
|
||||
+ ora P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub zi_get_file_chunk(^^ubyte @zp buffer, uword buffer_size, long remaining_file_size) -> uword {
|
||||
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
|
||||
; read the next chunk of data from the file, into buffer, up to buffer_size bytes.
|
||||
; returns the number of bytes read. 0 if no more data was available.
|
||||
|
||||
uword bytes_to_read = if msw(remaining_file_size)!=0 then buffer_size else min(lsw(remaining_file_size), buffer_size)
|
||||
|
||||
repeat bytes_to_read {
|
||||
while zi_uart[REG_LINE_STATUS] & LSR::DR == 0 {
|
||||
; wait until data is present
|
||||
}
|
||||
@(buffer) = @(zi_uart)
|
||||
buffer++
|
||||
}
|
||||
|
||||
return bytes_to_read
|
||||
}
|
||||
|
||||
sub zi_end_get_file() {
|
||||
; makes sure the 'OK' response after the actual file data is also read away
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_get_ip_address() -> str {
|
||||
; returns the ip address of the zimodem wifi connection. Assumes IPV4 only.
|
||||
ubyte[25] ip_buffer
|
||||
zi_write_cmd(iso:"ati2")
|
||||
void read_until(zi_uart, iso:"OK\x0d\x0a", &ip_buffer, len(ip_buffer))
|
||||
cx16.r0 = &ip_buffer
|
||||
while not strings.isspace(@(cx16.r0)) {
|
||||
cx16.r0++
|
||||
}
|
||||
@(cx16.r0) = 0
|
||||
return &ip_buffer
|
||||
}
|
||||
|
||||
|
||||
; private stuff
|
||||
|
||||
private uword @zp zi_uart ; the uart address to use for ZiModem
|
||||
|
||||
private sub read_one(uword @zp uart_addr) -> ubyte {
|
||||
while uart_addr[REG_LINE_STATUS] & LSR::DR == 0 {
|
||||
; wait until data is present
|
||||
}
|
||||
return @(uart_addr)
|
||||
}
|
||||
|
||||
private sub write_eol(uword uart_addr) {
|
||||
write(uart_addr, "\x0d\x0a")
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ const ubyte DEFAULT_WIDTH = 80
|
||||
const ubyte DEFAULT_HEIGHT = 60
|
||||
const long VERA_TEXTMATRIX = $1b000
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
%import math
|
||||
|
||||
lineclip {
|
||||
|
||||
; Based on Mark Moxon's Elite deep dive: https://elite.bbcelite.com/deep_dives/line-clipping.html
|
||||
|
||||
private word cx1, cy1, cx2, cy2
|
||||
|
||||
; Set the clipping rectangle coordinates (all inclusive)
|
||||
sub set_cliprect(word x1, word y1, word x2, word y2) {
|
||||
cx1 = x1
|
||||
cy1 = y1
|
||||
cx2 = x2
|
||||
cy2 = y2
|
||||
}
|
||||
|
||||
; returns true if (x,y) is inside the clipping rectangle set by set_cliprect()
|
||||
sub inside(word x, word y) -> bool {
|
||||
return x >= cx1 and x <= cx2 and y >= cy1 and y <= cy2
|
||||
}
|
||||
|
||||
; Clip line segment (x1,y1)-(x2,y2) against the current clipping rectangle, set by set_cliprect().
|
||||
; Returns (visible, clipped_x1, clipped_y1, clipped_x2, clipped_y2).
|
||||
sub clip(word x1, word y1, word x2, word y2) -> bool, word, word, word, word {
|
||||
bool a_inside = inside(x1, y1)
|
||||
bool b_inside = inside(x2, y2)
|
||||
|
||||
if a_inside and b_inside
|
||||
return true, x1, y1, x2, y2
|
||||
|
||||
; Quick reject: both off-screen on same side
|
||||
ubyte a_code = 0
|
||||
ubyte b_code = 0
|
||||
if x1 < cx1 a_code |= 1
|
||||
if x1 > cx2 a_code |= 2
|
||||
if y1 < cy1 a_code |= 4
|
||||
if y1 > cy2 a_code |= 8
|
||||
if x2 < cx1 b_code |= 1
|
||||
if x2 > cx2 b_code |= 2
|
||||
if y2 < cy1 b_code |= 4
|
||||
if y2 > cy2 b_code |= 8
|
||||
|
||||
if (a_code & b_code) != 0
|
||||
return false, 0, 0, 0, 0
|
||||
|
||||
word ddx = x2 - x1
|
||||
word ddy = y2 - y1
|
||||
if not a_inside
|
||||
x1, y1 = clip_one(x1, y1, ddx, ddy)
|
||||
if not b_inside
|
||||
x2, y2 = clip_one(x2, y2, -ddx, -ddy)
|
||||
|
||||
if not (inside(x1, y1) and inside(x2, y2))
|
||||
return false, 0, 0, 0, 0
|
||||
|
||||
return true, x1, y1, x2, y2
|
||||
|
||||
|
||||
sub clip_one(word ax, word ay, word odx, word ody) -> word, word {
|
||||
|
||||
word x, y
|
||||
bool did_clip
|
||||
|
||||
repeat 2 {
|
||||
x = ax
|
||||
y = ay
|
||||
did_clip = false
|
||||
|
||||
if x < cx1 and odx > 0 {
|
||||
y += frac(cx1 - x, ody, odx)
|
||||
x = cx1
|
||||
did_clip = true
|
||||
} else if x > cx2 and odx < 0 {
|
||||
y += frac(cx2 - x, ody, odx)
|
||||
x = cx2
|
||||
did_clip = true
|
||||
}
|
||||
|
||||
if y < cy1 and ody > 0 {
|
||||
x += frac(cy1 - y, odx, ody)
|
||||
y = cy1
|
||||
did_clip = true
|
||||
} else if y > cy2 and ody < 0 {
|
||||
x += frac(cy2 - y, odx, ody)
|
||||
y = cy2
|
||||
did_clip = true
|
||||
}
|
||||
|
||||
ax = x
|
||||
ay = y
|
||||
if not did_clip
|
||||
break
|
||||
}
|
||||
return ax, ay
|
||||
|
||||
; (int16_t)((int32_t)dist * num / den)
|
||||
sub frac(word dist, word num, word den) -> word {
|
||||
; 16×16→32 multiply, R starts as upper word of product
|
||||
alias Q_lo = cx16.r0
|
||||
alias Q_hi = cx16.r1
|
||||
alias R = cx16.r2
|
||||
alias aden = cx16.r3
|
||||
|
||||
; No long division available on 6502, so we use restoring division 32/16->16
|
||||
aden = abs(den) as uword
|
||||
Q_hi = abs(dist) * abs(num)
|
||||
R = math.mul16_last_upper()
|
||||
Q_lo = 0
|
||||
repeat 16 {
|
||||
R = R << 1
|
||||
if (Q_hi & $8000) != 0
|
||||
R |= 1
|
||||
Q_hi <<= 1
|
||||
if (Q_lo & $8000) != 0
|
||||
Q_hi |= 1
|
||||
Q_lo <<= 1
|
||||
if R >= aden {
|
||||
R -= aden
|
||||
Q_lo |= 1
|
||||
}
|
||||
}
|
||||
|
||||
if (dist < 0) xor (num < 0) xor (den < 0)
|
||||
return -(Q_lo as word)
|
||||
return Q_lo as word
|
||||
|
||||
/*
|
||||
here is the implementation using a 32/32 long division (shorter but less efficient than 32/16):
|
||||
|
||||
uword adist = abs(dist) as uword
|
||||
uword anum = abs(num) as uword
|
||||
uword aden = abs(den) as uword
|
||||
|
||||
; do 16*16->32 bit multiplication instead of slower full 32*32
|
||||
cx16.r2 = adist * anum
|
||||
long product32 = mklong2(math.mul16_last_upper(), cx16.r2)
|
||||
|
||||
; unfortunately the long division is still needed in prog8
|
||||
uword result_u = (product32 / (aden as long)) as uword
|
||||
word result = result_u as word
|
||||
if (dist < 0) xor (num < 0) xor (den < 0)
|
||||
result = -result
|
||||
return result
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -439,7 +439,7 @@ divmod_uw_asm .proc
|
||||
|
||||
dividend = P8ZP_SCRATCH_W1
|
||||
remainder = P8ZP_SCRATCH_W2
|
||||
result = dividend ;save memory by reusing divident to store the result
|
||||
result = dividend ;save memory by reusing dividend to store the result
|
||||
|
||||
cpy #0
|
||||
bne _nonzero
|
||||
@@ -1216,3 +1216,173 @@ _nosqadd:
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
|
||||
div_longs .proc
|
||||
; 32-bit SIGNED division.
|
||||
; long1 in cx16.r12/r13, long2 in cx16.r14/r15, result in cx16.r14/r15
|
||||
; clobbers A,X, cx16.r0, cx16.r1, cx16.r2, P8ZP_SCRATCH_W1, P8ZP_SCRATCH_W2
|
||||
|
||||
dividend_lo = cx16.r12 ; low word of dividend (also receives quotient bits)
|
||||
dividend_hi = cx16.r13 ; high word of dividend
|
||||
divisor_lo = cx16.r14 ; low word of divisor (also output quotient)
|
||||
divisor_hi = cx16.r15 ; high word of divisor
|
||||
saved_div_lo = cx16.r0 ; absolute divisor saved copy (low word)
|
||||
saved_div_hi = cx16.r1 ; absolute divisor saved copy (high word)
|
||||
remainder_lo = P8ZP_SCRATCH_W1 ; remainder low word (zeropage)
|
||||
remainder_hi = P8ZP_SCRATCH_W2 ; remainder high word (zeropage)
|
||||
sign = P8ZP_SCRATCH_B1 ; result sign
|
||||
temp = P8ZP_SCRATCH_REG ; temporary byte storage
|
||||
|
||||
; Check for division by zero
|
||||
lda divisor_lo
|
||||
ora divisor_lo+1
|
||||
ora divisor_hi
|
||||
ora divisor_hi+1
|
||||
bne _non_zero
|
||||
lda #0
|
||||
sta divisor_lo
|
||||
sta divisor_lo+1
|
||||
sta divisor_hi
|
||||
sta divisor_hi+1
|
||||
rts
|
||||
|
||||
_non_zero
|
||||
; Determine result sign: XOR MSBs of dividend and divisor
|
||||
lda dividend_hi+1
|
||||
eor divisor_hi+1
|
||||
sta sign
|
||||
|
||||
; Take absolute value of dividend if negative
|
||||
lda dividend_hi+1
|
||||
bpl _abs_divisor
|
||||
jsr _neg_dividend
|
||||
|
||||
_abs_divisor
|
||||
; Take absolute value of divisor if negative
|
||||
lda divisor_hi+1
|
||||
bpl _save_div
|
||||
jsr _neg_divisor
|
||||
|
||||
_save_div
|
||||
; Save absolute divisor into saved_div
|
||||
lda divisor_lo
|
||||
sta saved_div_lo
|
||||
lda divisor_lo+1
|
||||
sta saved_div_lo+1
|
||||
lda divisor_hi
|
||||
sta saved_div_hi
|
||||
lda divisor_hi+1
|
||||
sta saved_div_hi+1
|
||||
|
||||
; Clear remainder
|
||||
lda #0
|
||||
sta remainder_lo
|
||||
sta remainder_lo+1
|
||||
sta remainder_hi
|
||||
sta remainder_hi+1
|
||||
|
||||
; 32-bit restoring division loop (32 iterations)
|
||||
ldx #32
|
||||
|
||||
_loop
|
||||
; Shift (remainder : dividend) left 1 bit
|
||||
asl dividend_lo
|
||||
rol dividend_lo+1
|
||||
rol dividend_hi
|
||||
rol dividend_hi+1
|
||||
rol remainder_lo
|
||||
rol remainder_lo+1
|
||||
rol remainder_hi
|
||||
rol remainder_hi+1
|
||||
|
||||
; Subtract saved_div from remainder, check for borrow
|
||||
sec
|
||||
lda remainder_lo
|
||||
sbc saved_div_lo
|
||||
sta cx16.r2L ; byte 0 result -> r2L (NOT tax! X is loop counter)
|
||||
|
||||
lda remainder_lo+1
|
||||
sbc saved_div_lo+1
|
||||
sta cx16.r2H ; byte 1 result -> r2H
|
||||
|
||||
lda remainder_hi
|
||||
sbc saved_div_hi
|
||||
sta temp ; byte 2 result -> temp
|
||||
|
||||
lda remainder_hi+1
|
||||
sbc saved_div_hi+1
|
||||
bcc _next ; borrow means divisor didn't fit
|
||||
|
||||
; Divisor fits - store subtraction result as new remainder
|
||||
sta remainder_hi+1 ; byte 3 result from A
|
||||
lda temp
|
||||
sta remainder_hi ; byte 2
|
||||
lda cx16.r2H
|
||||
sta remainder_lo+1 ; byte 1
|
||||
lda cx16.r2L
|
||||
sta remainder_lo ; byte 0
|
||||
inc dividend_lo ; set quotient bit
|
||||
|
||||
_next
|
||||
dex
|
||||
bne _loop
|
||||
|
||||
; Copy quotient from dividend (r12/r13) to divisor (r14/r15)
|
||||
lda dividend_lo
|
||||
sta divisor_lo
|
||||
lda dividend_lo+1
|
||||
sta divisor_lo+1
|
||||
lda dividend_hi
|
||||
sta divisor_hi
|
||||
lda dividend_hi+1
|
||||
sta divisor_hi+1
|
||||
|
||||
; Apply sign if needed
|
||||
lda sign
|
||||
bpl _done
|
||||
jsr _neg_result
|
||||
|
||||
_done
|
||||
rts
|
||||
|
||||
; Helper: negate dividend (R12R13) in place
|
||||
_neg_dividend
|
||||
sec
|
||||
lda #0
|
||||
sbc dividend_lo
|
||||
sta dividend_lo
|
||||
lda #0
|
||||
sbc dividend_lo+1
|
||||
sta dividend_lo+1
|
||||
lda #0
|
||||
sbc dividend_hi
|
||||
sta dividend_hi
|
||||
lda #0
|
||||
sbc dividend_hi+1
|
||||
sta dividend_hi+1
|
||||
rts
|
||||
|
||||
; Helper: negate divisor (R14R15) in place
|
||||
_neg_divisor
|
||||
sec
|
||||
lda #0
|
||||
sbc divisor_lo
|
||||
sta divisor_lo
|
||||
lda #0
|
||||
sbc divisor_lo+1
|
||||
sta divisor_lo+1
|
||||
lda #0
|
||||
sbc divisor_hi
|
||||
sta divisor_hi
|
||||
lda #0
|
||||
sbc divisor_hi+1
|
||||
sta divisor_hi+1
|
||||
rts
|
||||
|
||||
; Helper: negate result (R14R15) — same as negating divisor
|
||||
_neg_result = _neg_divisor
|
||||
|
||||
.pend
|
||||
@@ -212,7 +212,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
|
||||
; - THE RESULT IS ONLY VALID IF THE MULTIPLICATION WAS DONE WITH UWORD ARGUMENTS (or two positive WORD arguments)
|
||||
; as soon as a negative word value (or 2) was used in the multiplication, these upper 16 bits are not valid!!
|
||||
; Suggestion (if you are on the Commander X16): use verafx.muls() to get a hardware accelerated 32 bit signed multplication.
|
||||
; Suggestion (if you are on the Commander X16): use verafx.muls() to get a hardware accelerated 32 bit signed multiplication.
|
||||
%asm {{
|
||||
lda prog8_math.multiply_words.result+2
|
||||
ldy prog8_math.multiply_words.result+3
|
||||
@@ -385,7 +385,7 @@ _quadrant_region_to_direction:
|
||||
|
||||
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
|
||||
;; Calculate the angle, in a 256-degree circle, between two points into A.
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
|
||||
;; 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.
|
||||
|
||||
|
||||
@@ -90,9 +90,9 @@ io_error:
|
||||
if filenames_buf_size<=20
|
||||
return 0
|
||||
|
||||
uword filenames_buffer_start = filenames_buffer
|
||||
ubyte files_found = 0
|
||||
uword buf_ptr = filenames_buffer
|
||||
@(buf_ptr) = 0
|
||||
@(filenames_buffer) = 0
|
||||
cbm.SETNAM(1, "$")
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
ubyte status = 1
|
||||
@@ -138,8 +138,12 @@ io_error:
|
||||
@(name_ptr) = 0
|
||||
|
||||
if pattern_ptr==0 or strings.pattern_match(list_filename, pattern_ptr) {
|
||||
buf_ptr += strings.copy(list_filename, buf_ptr) + 1
|
||||
filenames_buffer += strings.copy(list_filename, filenames_buffer) as uword + 1
|
||||
files_found++
|
||||
if filenames_buffer - filenames_buffer_start > filenames_buf_size-20 {
|
||||
@(filenames_buffer)=0
|
||||
goto end_listing_more
|
||||
}
|
||||
}
|
||||
|
||||
while cbm.CHRIN()!=0 {
|
||||
@@ -155,8 +159,13 @@ io_error:
|
||||
}
|
||||
|
||||
end_listing:
|
||||
@(buf_ptr) = 0
|
||||
@(filenames_buffer) = 0
|
||||
status = cbm.READST()
|
||||
sys.clear_carry()
|
||||
goto io_error
|
||||
|
||||
end_listing_more:
|
||||
sys.set_carry()
|
||||
|
||||
io_error:
|
||||
cbm.CLRCHN() ; restore default i/o devices
|
||||
@@ -407,7 +416,7 @@ exit:
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
|
||||
@@ -12,7 +12,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -482,7 +482,7 @@ from_scratchW1
|
||||
.pend
|
||||
|
||||
func_peekl .proc
|
||||
; -- read the ;pmg value on the address in AY, into R14:R15
|
||||
; -- read the long value on the address in AY, into R14:R15
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
from_scratchW1
|
||||
|
||||
@@ -716,7 +716,7 @@ exit:
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL)
|
||||
|
||||
@@ -293,7 +293,7 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
|
||||
}
|
||||
|
||||
asmsub internal_long_AY_to_FAC() {
|
||||
; Used by the compiler to cast a long to a a float in FAC
|
||||
; Used by the compiler to cast a long to a float in FAC
|
||||
; convert the long pointed to by AY into a float and store it in FAC
|
||||
%asm {{
|
||||
sta cx16.r1L
|
||||
@@ -308,7 +308,7 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
|
||||
}
|
||||
|
||||
private sub internal_long_R1_to_float_AY() {
|
||||
; Used by the compiler to cast a long to a a float variable:
|
||||
; Used by the compiler to cast a long to a float variable:
|
||||
; make the float pointed to by AY equal to the long pointed to by R1 (as float)
|
||||
%asm {{
|
||||
sta cx16.r0L
|
||||
|
||||
@@ -44,7 +44,7 @@ _done
|
||||
}
|
||||
|
||||
sub gnomesort_by_ub(^^ubyte @requirezp ub_keys, ^^uword wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
|
||||
ubyte @zp pos=1
|
||||
while pos != num_elements {
|
||||
if ub_keys[pos]>=ub_keys[pos-1]
|
||||
@@ -88,7 +88,7 @@ _done
|
||||
}
|
||||
|
||||
sub gnomesort_by_uw(uword @requirezp uw_keys, uword wordvalues, ubyte num_elements) {
|
||||
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
; Max number of elements is 128 to keep indexing code size small and fast. Clobbers R0 and R1.
|
||||
ubyte @zp pos=2
|
||||
@@ -161,7 +161,7 @@ _done
|
||||
}
|
||||
|
||||
sub shellsort_by_ub(^^ubyte @requirezp ub_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
@@ -187,7 +187,7 @@ _done
|
||||
}
|
||||
|
||||
sub shellsort_by_uw(^^uword @requirezp uw_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
num_elements--
|
||||
ubyte gap
|
||||
|
||||
@@ -7,7 +7,7 @@ strings {
|
||||
|
||||
asmsub length(str string @AY) clobbers(A) -> ubyte @Y {
|
||||
; Returns the number of bytes in the string.
|
||||
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
|
||||
; This value is determined during runtime and counts up to the first terminating 0 byte in the string,
|
||||
; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof!
|
||||
|
||||
; uses P8ZP_SCRATCH_W1 to store the string address, do not change this, other routines here may depend on it (as optimization implementation detail)
|
||||
@@ -29,7 +29,7 @@ strings {
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
sta P8ZP_SCRATCH_W1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
@@ -56,7 +56,7 @@ _loop dey
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
stx P8ZP_SCRATCH_B1
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
@@ -96,7 +96,7 @@ _loop dey
|
||||
; Also, you have to make sure yourself that start and length are within bounds of the strings.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; substr(source, target, start, length)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0
|
||||
@@ -131,7 +131,7 @@ _startloop dey
|
||||
; Locates the first position of the given character in the string,
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to make this run on C64...
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_B1
|
||||
@@ -157,7 +157,7 @@ _found tya
|
||||
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to make this run on C64...
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
@@ -297,7 +297,7 @@ _max_too_small
|
||||
asmsub compare(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-sensitively.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -311,7 +311,7 @@ _max_too_small
|
||||
asmsub compare_nocase(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-insensitively for PETSCII strings.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -350,7 +350,7 @@ _c1_zero
|
||||
asmsub compare_nocase_iso(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-insensitively for ISO strings.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -525,7 +525,7 @@ _done rts
|
||||
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
|
||||
; * matches any string, including the empty string.
|
||||
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
|
||||
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Both the pattern and the string must be NUL-terminated (that is, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Code taken from http://6502.org/source/strings/patmatch.htm
|
||||
;
|
||||
; Input: cx16.r0: A NUL-terminated, <255-length pattern
|
||||
@@ -562,7 +562,7 @@ next lda $ffff,x ; look at next pattern character MODIFIED
|
||||
cmp #'*' ; is it a star?
|
||||
beq star ; yes, do the complicated stuff
|
||||
iny ; no, let's look at the string
|
||||
cmp #'?' ; is the pattern caracter a ques?
|
||||
cmp #'?' ; is the pattern character a ques?
|
||||
bne reg ; no, it's a regular character
|
||||
lda (strptr),y ; yes, so it will match anything
|
||||
beq fail ; except the end of string
|
||||
@@ -602,7 +602,7 @@ fail clc ; yes, no match found, return with c=0
|
||||
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
|
||||
; * matches any string, including the empty string.
|
||||
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
|
||||
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Both the pattern and the string must be NUL-terminated (that is, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Code taken from http://6502.org/source/strings/patmatch.htm
|
||||
;
|
||||
; Input: cx16.r0: A NUL-terminated, <255-length pattern
|
||||
@@ -651,7 +651,7 @@ next lda $ffff,x ; look at next pattern character MODIFIED
|
||||
cmp #'*' ; is it a star?
|
||||
beq star ; yes, do the complicated stuff
|
||||
iny ; no, let's look at the string
|
||||
cmp #'?' ; is the pattern caracter a ques?
|
||||
cmp #'?' ; is the pattern character a ques?
|
||||
bne reg ; no, it's a regular character
|
||||
lda (strptr),y ; yes, so it will match anything
|
||||
beq fail ; except the end of string
|
||||
|
||||
@@ -300,7 +300,7 @@ diskio {
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
if f_open(filename) {
|
||||
uword address
|
||||
void f_read(&address, 2)
|
||||
|
||||
@@ -277,7 +277,7 @@ math {
|
||||
|
||||
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
|
||||
;; Calculate the angle, in a 256-degree circle, between two points into A.
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the cartesian plane!
|
||||
%ir {{
|
||||
loadm.b r99100,math.atan2.x1
|
||||
loadm.b r99101,math.atan2.y1
|
||||
|
||||
@@ -50,7 +50,7 @@ sorting {
|
||||
|
||||
|
||||
sub shellsort_by_ub(^^ubyte @requirezp ub_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
@@ -76,7 +76,7 @@ sorting {
|
||||
}
|
||||
|
||||
sub shellsort_by_uw(^^uword @requirezp uw_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
num_elements--
|
||||
ubyte gap
|
||||
|
||||
@@ -147,7 +147,7 @@ strings {
|
||||
sub compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting (case sensitive).
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%ir {{
|
||||
loadm.w r99000,strings.compare.st1
|
||||
@@ -160,7 +160,7 @@ strings {
|
||||
sub compare_nocase(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting (case insensitive).
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%ir {{
|
||||
loadm.w r99000,strings.compare_nocase.st1
|
||||
|
||||
@@ -2,19 +2,22 @@ package prog8
|
||||
|
||||
import kotlinx.cli.*
|
||||
import prog8.ast.AstException
|
||||
import prog8.code.source.ImportFileSystem
|
||||
import prog8.code.source.ImportFileSystem.expandTilde
|
||||
import prog8.code.target.CompilationTargets
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.code.target.getCompilationTargetByName
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.CompilerArguments
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.*
|
||||
import prog8.intermediate.IRFileReader
|
||||
import prog8.intermediate.Opcode
|
||||
import java.io.File
|
||||
import java.io.*
|
||||
import java.net.ConnectException
|
||||
import java.net.StandardProtocolFamily
|
||||
import java.net.URI
|
||||
import java.net.UnixDomainSocketAddress
|
||||
import java.nio.channels.Channels
|
||||
import java.nio.channels.SocketChannel
|
||||
import java.nio.file.*
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.io.path.*
|
||||
@@ -22,6 +25,11 @@ import kotlin.system.exitProcess
|
||||
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
if (args.contains("--daemon-server")) {
|
||||
val socketPath = CompilerDaemon.getDefaultSocketPath()
|
||||
CompilerDaemon(socketPath).run()
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: if your compiler/IDE complains here about "Unresolved reference: buildversion",
|
||||
// it means that you have to run the gradle task once to generate this file.
|
||||
@@ -77,6 +85,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
||||
val version by cli.option(ArgType.Boolean, fullName = "version", description = "print compiler version and exit")
|
||||
val compareIR by cli.option(ArgType.String, fullName = "compareir", description = "compare generated .p8ir file with existing .p8ir file")
|
||||
val daemonMode by cli.option(ArgType.Boolean, fullName = "daemon", description = "use the prog8c compilation daemon (auto-starts it if not running)")
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").optional().multiple(999)
|
||||
|
||||
try {
|
||||
@@ -188,6 +197,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val results = mutableListOf<CompilationResult>()
|
||||
while(true) {
|
||||
println("Continuous watch mode active. Modules: $moduleFiles")
|
||||
ImportFileSystem.clearCaches()
|
||||
results.clear()
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
@@ -223,9 +233,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
errors = ErrorReporter(txtcolors)
|
||||
)
|
||||
val compilationResult = compileProgram(compilerArgs)
|
||||
// Note: ImportFileSystem.clearCaches() removed - it caused race conditions with parallel tests
|
||||
// where one test's cache clearing would break another test's source line lookups.
|
||||
// The cache is bounded by unique source files and doesn't need manual clearing.
|
||||
|
||||
if(checkSource==true)
|
||||
println("No output produced.")
|
||||
@@ -272,6 +279,44 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
println("\u001b[H\u001b[2J") // clear the screen
|
||||
}
|
||||
|
||||
} else if (daemonMode == true) {
|
||||
for(filepathRaw in moduleFiles) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val txtcolors = if(plainText==true) ErrorReporter.PlainText else ErrorReporter.AnsiColors
|
||||
val compilerArgs = CompilerArguments(
|
||||
filepath,
|
||||
if(checkSource==true) false else dontOptimize != true,
|
||||
if(checkSource==true) false else dontWriteAssembly != true,
|
||||
warnSymbolShadowing == true,
|
||||
warnImplicitTypeCasts == true,
|
||||
quietAll == true,
|
||||
quietAll == true || quietAssembler == true,
|
||||
showTimings == true,
|
||||
asmListfile == true,
|
||||
dontIncludeSourcelines != true,
|
||||
experimentalCodegen == true,
|
||||
dumpVariables == true,
|
||||
dumpSymbols == true,
|
||||
varsHighBank,
|
||||
varsGolden == true,
|
||||
slabsHighBank,
|
||||
slabsGolden == true,
|
||||
compilationTarget!!,
|
||||
breakpointCpuInstruction,
|
||||
printAst1 == true,
|
||||
printAst2 == true,
|
||||
ignoreFootguns == true,
|
||||
profilingInstrumentation == true,
|
||||
nostdlib == true,
|
||||
processedSymbols,
|
||||
srcdirs,
|
||||
outputPath,
|
||||
errors = ErrorReporter(txtcolors)
|
||||
)
|
||||
if (!compileViaDaemon(compilerArgs))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
if((startEmulator1==true || startEmulator2==true) && moduleFiles.size>1) {
|
||||
System.err.println("can't start emulator when multiple module files are specified")
|
||||
@@ -595,3 +640,150 @@ private fun scanLibraryFiles(dump: String?, searchPattern: String?) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---- daemon client ----
|
||||
|
||||
private fun compileViaDaemon(compilerArgs: CompilerArguments): Boolean {
|
||||
val socketPath = CompilerDaemon.getDefaultSocketPath()
|
||||
var channel = connectToDaemon(socketPath)
|
||||
|
||||
if (channel == null) {
|
||||
println("Starting prog8c daemon...")
|
||||
if (!startDaemonProcess()) {
|
||||
System.err.println("Failed to start prog8c daemon")
|
||||
return false
|
||||
}
|
||||
channel = connectToDaemon(socketPath, 4_000)
|
||||
if (channel == null) {
|
||||
System.err.println("prog8c daemon did not start in time")
|
||||
return false
|
||||
}
|
||||
println("prog8c daemon started.")
|
||||
}
|
||||
|
||||
val ok = communicateWithDaemon(channel, compilerArgs)
|
||||
try { channel.close() } catch (_: Exception) {}
|
||||
return ok
|
||||
}
|
||||
|
||||
private fun startDaemonProcess(): Boolean {
|
||||
return try {
|
||||
val javaHome = System.getProperty("java.home")
|
||||
val javaCmd = "$javaHome/bin/java"
|
||||
val classpath = System.getProperty("java.class.path")
|
||||
|
||||
// Shadow JAR (fat JAR with Main-Class manifest) takes priority
|
||||
val shadowJar = classpath.split(File.pathSeparatorChar)
|
||||
.firstOrNull { it.contains("prog8c") && it.endsWith("-all.jar") }
|
||||
|
||||
val cmd = mutableListOf<String>()
|
||||
cmd.add(javaCmd)
|
||||
if (shadowJar != null) {
|
||||
cmd.addAll(listOf("-jar", shadowJar))
|
||||
} else {
|
||||
cmd.addAll(listOf("-cp", classpath, "prog8.CompilerMainKt"))
|
||||
}
|
||||
cmd.add("--daemon-server")
|
||||
|
||||
val pb = ProcessBuilder(cmd)
|
||||
pb.inheritIO()
|
||||
pb.start()
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
System.err.println("Cannot start daemon: ${e.message}")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectToDaemon(socketPath: Path): SocketChannel? {
|
||||
return try {
|
||||
val address = UnixDomainSocketAddress.of(socketPath)
|
||||
val channel = SocketChannel.open(StandardProtocolFamily.UNIX)
|
||||
channel.connect(address)
|
||||
channel
|
||||
} catch (_: ConnectException) {
|
||||
null
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun connectToDaemon(socketPath: Path, timeoutMs: Long): SocketChannel? {
|
||||
val deadline = System.currentTimeMillis() + timeoutMs
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
connectToDaemon(socketPath)?.let { return it }
|
||||
Thread.sleep(100)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun communicateWithDaemon(channel: SocketChannel, compilerArgs: CompilerArguments): Boolean {
|
||||
try {
|
||||
val writer = BufferedWriter(OutputStreamWriter(Channels.newOutputStream(channel), Charsets.UTF_8))
|
||||
val reader = BufferedReader(InputStreamReader(Channels.newInputStream(channel), Charsets.UTF_8))
|
||||
|
||||
val request = DaemonRequest(
|
||||
version = prog8.buildversion.GIT_SHA,
|
||||
filepath = compilerArgs.filepath.toString(),
|
||||
optimize = compilerArgs.optimize,
|
||||
writeAssembly = compilerArgs.writeAssembly,
|
||||
warnSymbolShadowing = compilerArgs.warnSymbolShadowing,
|
||||
warnImplicitTypeCasts = compilerArgs.warnImplicitTypeCasts,
|
||||
quietAll = compilerArgs.quietAll,
|
||||
quietAssembler = compilerArgs.quietAssembler,
|
||||
showTimings = compilerArgs.showTimings,
|
||||
asmListfile = compilerArgs.asmListfile,
|
||||
includeSourcelines = compilerArgs.includeSourcelines,
|
||||
experimentalCodegen = compilerArgs.experimentalCodegen,
|
||||
dumpVariables = compilerArgs.dumpVariables,
|
||||
dumpSymbols = compilerArgs.dumpSymbols,
|
||||
varsHighBank = compilerArgs.varsHighBank,
|
||||
varsGolden = compilerArgs.varsGolden,
|
||||
slabsHighBank = compilerArgs.slabsHighBank,
|
||||
slabsGolden = compilerArgs.slabsGolden,
|
||||
compilationTarget = compilerArgs.compilationTarget,
|
||||
breakpointCpuInstruction = compilerArgs.breakpointCpuInstruction,
|
||||
printAst1 = compilerArgs.printAst1,
|
||||
printAst2 = compilerArgs.printAst2,
|
||||
ignoreFootguns = compilerArgs.ignoreFootguns,
|
||||
profilingInstrumentation = compilerArgs.profilingInstrumentation,
|
||||
nostdlib = compilerArgs.nostdlib,
|
||||
symbolDefs = compilerArgs.symbolDefs,
|
||||
sourceDirs = compilerArgs.sourceDirs,
|
||||
outputDir = compilerArgs.outputDir.toString()
|
||||
)
|
||||
|
||||
val requestJson = DaemonProtocol.encodeRequest(request)
|
||||
writer.write(requestJson)
|
||||
writer.newLine()
|
||||
writer.flush()
|
||||
|
||||
val responseJson = reader.readLine()
|
||||
if (responseJson == null) {
|
||||
System.err.println("daemon connection lost")
|
||||
return false
|
||||
}
|
||||
|
||||
val response = DaemonProtocol.decodeResponse(responseJson)
|
||||
|
||||
if (response.versionError != null) {
|
||||
System.err.println(response.versionError)
|
||||
return false
|
||||
}
|
||||
|
||||
System.out.print(response.output)
|
||||
System.out.flush()
|
||||
|
||||
for (error in response.errors) {
|
||||
val stream = if (error.severity == "ERROR") System.err else System.out
|
||||
stream.println("${error.file?.let { "$it:" } ?: ""}${error.line}:${error.col} ${error.severity} ${error.message}")
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
|
||||
return response.ok
|
||||
} finally {
|
||||
try { channel.close() } catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
@@ -539,6 +539,8 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
program.checkPrivateAccess(errors)
|
||||
errors.report()
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.buildversion.GIT_SHA
|
||||
import prog8.code.core.Position
|
||||
import java.io.*
|
||||
import java.net.StandardProtocolFamily
|
||||
import java.net.UnixDomainSocketAddress
|
||||
import java.nio.channels.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.measureTime
|
||||
|
||||
|
||||
internal class CompilerDaemon(private val socketPath: Path) {
|
||||
companion object {
|
||||
private const val IDLE_TIMEOUT_MS = 5 * 60_000L
|
||||
|
||||
fun getDefaultSocketPath(): Path {
|
||||
val baseDir = System.getenv("XDG_RUNTIME_DIR")
|
||||
?.let { Path.of(it) }
|
||||
?: Path.of(System.getProperty("user.home"), ".cache")
|
||||
return baseDir.resolve("prog8c-daemon.sock")
|
||||
}
|
||||
|
||||
private fun myPid(): String? = try {
|
||||
ProcessHandle.current().pid().toString()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
socketPath.deleteIfExists()
|
||||
socketPath.parent?.let { Files.createDirectories(it) }
|
||||
|
||||
val address = UnixDomainSocketAddress.of(socketPath)
|
||||
val server = ServerSocketChannel.open(StandardProtocolFamily.UNIX).bind(address)
|
||||
server.configureBlocking(false)
|
||||
val selector = Selector.open()
|
||||
server.register(selector, SelectionKey.OP_ACCEPT)
|
||||
|
||||
val pid = myPid()
|
||||
System.err.println(buildString {
|
||||
append("prog8c daemon")
|
||||
if (pid != null) append(" (pid $pid)")
|
||||
append(" running at ${socketPath}")
|
||||
})
|
||||
|
||||
var lastRequestTime = System.currentTimeMillis()
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
val idleTime = System.currentTimeMillis() - lastRequestTime
|
||||
if (idleTime > IDLE_TIMEOUT_MS && lastRequestTime > 0) {
|
||||
// daemon idle timeout reached, shutting down
|
||||
break
|
||||
}
|
||||
val timeout = maxOf(1L, IDLE_TIMEOUT_MS - idleTime)
|
||||
if (selector.select(timeout) == 0)
|
||||
continue
|
||||
|
||||
val selected = selector.selectedKeys()
|
||||
val it = selected.iterator()
|
||||
if (it.hasNext()) {
|
||||
it.next()
|
||||
it.remove()
|
||||
val client = server.accept()
|
||||
if (client != null) {
|
||||
handleClient(client)
|
||||
client.close()
|
||||
lastRequestTime = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
selector.close()
|
||||
server.close()
|
||||
socketPath.deleteIfExists()
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleClient(clientChannel: SocketChannel) {
|
||||
try {
|
||||
val reader = BufferedReader(InputStreamReader(Channels.newInputStream(clientChannel), Charsets.UTF_8))
|
||||
val writer = BufferedWriter(OutputStreamWriter(Channels.newOutputStream(clientChannel), Charsets.UTF_8))
|
||||
|
||||
val reqJson = reader.readLine() ?: return
|
||||
val request: DaemonRequest = try {
|
||||
DaemonProtocol.decodeRequest(reqJson)
|
||||
} catch (e: Exception) {
|
||||
System.err.println("daemon: failed to decode request: ${e.message}")
|
||||
return
|
||||
}
|
||||
|
||||
// Version check
|
||||
if (request.version != GIT_SHA) {
|
||||
val resp = DaemonResponse(
|
||||
ok = false,
|
||||
versionError = "daemon version mismatch: client=${request.version.take(8)}, daemon=${GIT_SHA.take(8)}",
|
||||
errors = emptyList(),
|
||||
output = "",
|
||||
t_ms = 0,
|
||||
outputFiles = emptyList(),
|
||||
importedFiles = emptyList()
|
||||
)
|
||||
writer.write(DaemonProtocol.encodeResponse(resp))
|
||||
writer.newLine()
|
||||
writer.flush()
|
||||
return
|
||||
}
|
||||
|
||||
// Capture stdout/stderr
|
||||
val outBytes = ByteArrayOutputStream()
|
||||
val errBytes = ByteArrayOutputStream()
|
||||
val oldOut = System.out
|
||||
val oldErr = System.err
|
||||
System.setOut(PrintStream(outBytes, true, Charsets.UTF_8.name()))
|
||||
System.setErr(PrintStream(errBytes, true, Charsets.UTF_8.name()))
|
||||
|
||||
val daemonErr = DaemonErrorReporter()
|
||||
var result: CompilationResult? = null
|
||||
val t_ms = try {
|
||||
measureTime {
|
||||
val compilerArgs = request.toCompilerArguments(daemonErr)
|
||||
result = compileProgram(compilerArgs)
|
||||
}.toLong(DurationUnit.MILLISECONDS)
|
||||
} catch (e: Exception) {
|
||||
System.err.println("daemon: compilation crash: ${e.message}")
|
||||
daemonErr.err("internal daemon error: ${e.message}", Position.DUMMY)
|
||||
0L
|
||||
} finally {
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
System.setOut(oldOut)
|
||||
System.setErr(oldErr)
|
||||
}
|
||||
|
||||
val outputFiles = if (result != null)
|
||||
listOfNotNull(
|
||||
request.outputDir.let { d ->
|
||||
val name = request.filepath.substringBeforeLast('.').substringAfterLast('/').substringAfterLast('\\')
|
||||
val dir = if (d == ".") "" else "$d/"
|
||||
listOf(
|
||||
"${dir}${name}.prg".takeIf { request.writeAssembly },
|
||||
"${dir}${name}.asm".takeIf { request.writeAssembly },
|
||||
"${dir}${name}.p8ir".takeIf { !request.writeAssembly }
|
||||
).filterNotNull()
|
||||
}
|
||||
).flatten()
|
||||
else
|
||||
emptyList()
|
||||
|
||||
val importedFiles = result?.importedFiles?.map { it.toString() } ?: emptyList()
|
||||
|
||||
val combinedOutput = outBytes.toString(Charsets.UTF_8.name()) + errBytes.toString(Charsets.UTF_8.name())
|
||||
|
||||
val resp = DaemonResponse(
|
||||
ok = result != null,
|
||||
versionError = null,
|
||||
errors = daemonErr.getMessages().map { msg ->
|
||||
DaemonError(
|
||||
severity = msg.severity,
|
||||
message = msg.message,
|
||||
file = msg.position?.file,
|
||||
line = msg.position?.line ?: 1,
|
||||
col = msg.position?.startCol ?: 1
|
||||
)
|
||||
},
|
||||
output = combinedOutput,
|
||||
t_ms = t_ms,
|
||||
outputFiles = outputFiles,
|
||||
importedFiles = importedFiles
|
||||
)
|
||||
|
||||
val respJson = DaemonProtocol.encodeResponse(resp)
|
||||
writer.write(respJson)
|
||||
writer.newLine()
|
||||
writer.flush()
|
||||
} catch (e: IOException) {
|
||||
System.err.println("daemon: client I/O error: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun DaemonRequest.toCompilerArguments(daemonErr: DaemonErrorReporter): CompilerArguments {
|
||||
return CompilerArguments(
|
||||
filepath = Path.of(filepath),
|
||||
optimize = optimize,
|
||||
writeAssembly = writeAssembly,
|
||||
warnSymbolShadowing = warnSymbolShadowing,
|
||||
warnImplicitTypeCasts = warnImplicitTypeCasts,
|
||||
quietAll = quietAll,
|
||||
quietAssembler = quietAssembler,
|
||||
showTimings = showTimings,
|
||||
asmListfile = asmListfile,
|
||||
includeSourcelines = includeSourcelines,
|
||||
experimentalCodegen = experimentalCodegen,
|
||||
dumpVariables = dumpVariables,
|
||||
dumpSymbols = dumpSymbols,
|
||||
varsHighBank = varsHighBank,
|
||||
varsGolden = varsGolden,
|
||||
slabsHighBank = slabsHighBank,
|
||||
slabsGolden = slabsGolden,
|
||||
compilationTarget = compilationTarget,
|
||||
breakpointCpuInstruction = breakpointCpuInstruction,
|
||||
printAst1 = printAst1,
|
||||
printAst2 = printAst2,
|
||||
ignoreFootguns = ignoreFootguns,
|
||||
profilingInstrumentation = profilingInstrumentation,
|
||||
nostdlib = nostdlib,
|
||||
symbolDefs = symbolDefs,
|
||||
sourceDirs = sourceDirs,
|
||||
outputDir = Path.of(outputDir),
|
||||
errors = daemonErr
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.code.core.ErrorsReportedException
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
|
||||
|
||||
internal class DaemonErrorReporter : IErrorReporter {
|
||||
data class CompilerMessage(val severity: String, val message: String, val position: Position?)
|
||||
|
||||
private val messages = mutableListOf<CompilerMessage>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
messages.add(CompilerMessage("ERROR", msg, position))
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
messages.add(CompilerMessage("WARNING", msg, position))
|
||||
}
|
||||
|
||||
override fun info(msg: String, position: Position) {
|
||||
messages.add(CompilerMessage("INFO", msg, position))
|
||||
}
|
||||
|
||||
override fun undefined(symbol: List<String>, suggestImport: Boolean, position: Position) {
|
||||
if (suggestImport && symbol.size > 1)
|
||||
err("undefined symbol: ${symbol.joinToString(".")} (maybe you forgot to import a module that defines ${symbol.first()}?)", position)
|
||||
else
|
||||
err("undefined symbol: ${symbol.joinToString(".")}", position)
|
||||
}
|
||||
|
||||
override fun noErrors() = messages.none { it.severity == "ERROR" }
|
||||
|
||||
override fun report() {
|
||||
// Don't print anything, but throw so compileProgram() stops and returns null
|
||||
if (!noErrors())
|
||||
throw ErrorsReportedException("")
|
||||
}
|
||||
|
||||
override fun noErrorForLine(position: Position) =
|
||||
!messages.any { it.position?.line == position.line && it.severity != "INFO" }
|
||||
|
||||
override fun printSingleError(errormessage: String) {
|
||||
messages.add(CompilerMessage("ERROR", errormessage, null))
|
||||
}
|
||||
|
||||
fun getMessages() = messages.toList()
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package prog8.compiler
|
||||
|
||||
|
||||
internal data class DaemonRequest(
|
||||
val version: String,
|
||||
val filepath: String,
|
||||
val optimize: Boolean,
|
||||
val writeAssembly: Boolean,
|
||||
val warnSymbolShadowing: Boolean,
|
||||
val warnImplicitTypeCasts: Boolean,
|
||||
val quietAll: Boolean,
|
||||
val quietAssembler: Boolean,
|
||||
val showTimings: Boolean,
|
||||
val asmListfile: Boolean,
|
||||
val includeSourcelines: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val dumpVariables: Boolean,
|
||||
val dumpSymbols: Boolean,
|
||||
val varsHighBank: Int?,
|
||||
val varsGolden: Boolean,
|
||||
val slabsHighBank: Int?,
|
||||
val slabsGolden: Boolean,
|
||||
val compilationTarget: String,
|
||||
val breakpointCpuInstruction: String?,
|
||||
val printAst1: Boolean,
|
||||
val printAst2: Boolean,
|
||||
val ignoreFootguns: Boolean,
|
||||
val profilingInstrumentation: Boolean,
|
||||
val nostdlib: Boolean,
|
||||
val symbolDefs: Map<String, String>,
|
||||
val sourceDirs: List<String>,
|
||||
val outputDir: String
|
||||
)
|
||||
|
||||
internal data class DaemonResponse(
|
||||
val ok: Boolean,
|
||||
val versionError: String?,
|
||||
val errors: List<DaemonError>,
|
||||
val output: String,
|
||||
val t_ms: Long,
|
||||
val outputFiles: List<String>,
|
||||
val importedFiles: List<String>
|
||||
)
|
||||
|
||||
internal data class DaemonError(
|
||||
val severity: String,
|
||||
val message: String,
|
||||
val file: String?,
|
||||
val line: Int,
|
||||
val col: Int
|
||||
)
|
||||
|
||||
|
||||
// Minimal JSON encoder/decoder for the daemon protocol
|
||||
internal object DaemonProtocol {
|
||||
|
||||
fun encodeRequest(req: DaemonRequest): String = buildString {
|
||||
append('{')
|
||||
append(prop("version", req.version))
|
||||
append(prop("filepath", req.filepath))
|
||||
append(prop("optimize", req.optimize))
|
||||
append(prop("writeAssembly", req.writeAssembly))
|
||||
append(prop("warnSymbolShadowing", req.warnSymbolShadowing))
|
||||
append(prop("warnImplicitTypeCasts", req.warnImplicitTypeCasts))
|
||||
append(prop("quietAll", req.quietAll))
|
||||
append(prop("quietAssembler", req.quietAssembler))
|
||||
append(prop("showTimings", req.showTimings))
|
||||
append(prop("asmListfile", req.asmListfile))
|
||||
append(prop("includeSourcelines", req.includeSourcelines))
|
||||
append(prop("experimentalCodegen", req.experimentalCodegen))
|
||||
append(prop("dumpVariables", req.dumpVariables))
|
||||
append(prop("dumpSymbols", req.dumpSymbols))
|
||||
append(prop("varsHighBank", req.varsHighBank))
|
||||
append(prop("varsGolden", req.varsGolden))
|
||||
append(prop("slabsHighBank", req.slabsHighBank))
|
||||
append(prop("slabsGolden", req.slabsGolden))
|
||||
append(prop("compilationTarget", req.compilationTarget))
|
||||
append(propOpt("breakpointCpuInstruction", req.breakpointCpuInstruction))
|
||||
append(prop("printAst1", req.printAst1))
|
||||
append(prop("printAst2", req.printAst2))
|
||||
append(prop("ignoreFootguns", req.ignoreFootguns))
|
||||
append(prop("profilingInstrumentation", req.profilingInstrumentation))
|
||||
append(prop("nostdlib", req.nostdlib))
|
||||
append(prop("symbolDefs", req.symbolDefs))
|
||||
append(prop("sourceDirs", req.sourceDirs))
|
||||
append(prop("outputDir", req.outputDir))
|
||||
append("null") // placeholder, gets overwritten by trimEnd
|
||||
setLength(length - 5)
|
||||
append('}')
|
||||
}
|
||||
|
||||
fun encodeResponse(resp: DaemonResponse): String = buildString {
|
||||
append('{')
|
||||
append(prop("ok", resp.ok))
|
||||
append(propOpt("versionError", resp.versionError))
|
||||
append("\"errors\":[")
|
||||
for ((i, e) in resp.errors.withIndex()) {
|
||||
if (i > 0) append(',')
|
||||
append('{')
|
||||
append(prop("severity", e.severity))
|
||||
append(prop("message", e.message))
|
||||
append(propOpt("file", e.file))
|
||||
append(prop("line", e.line))
|
||||
append(prop("col", e.col))
|
||||
append("null")
|
||||
setLength(length - 4)
|
||||
append('}')
|
||||
}
|
||||
append("],")
|
||||
append(prop("output", resp.output))
|
||||
append(prop("t_ms", resp.t_ms))
|
||||
append(prop("outputFiles", resp.outputFiles))
|
||||
append(prop("importedFiles", resp.importedFiles))
|
||||
append("null")
|
||||
setLength(length - 5)
|
||||
append('}')
|
||||
}
|
||||
|
||||
fun decodeResponse(json: String): DaemonResponse {
|
||||
val map = parseJsonObject(json)
|
||||
val errorsJson = map["errors"] as? List<*> ?: emptyList<Any>()
|
||||
val errors = errorsJson.map { e ->
|
||||
val em = e as Map<*, *>
|
||||
DaemonError(
|
||||
severity = em["severity"] as String,
|
||||
message = em["message"] as String,
|
||||
file = em["file"] as? String,
|
||||
line = (em["line"] as Number).toInt(),
|
||||
col = (em["col"] as Number).toInt()
|
||||
)
|
||||
}
|
||||
val outputFiles = (map["outputFiles"] as? List<*>)?.map { it as String } ?: emptyList()
|
||||
val importedFiles = (map["importedFiles"] as? List<*>)?.map { it as String } ?: emptyList()
|
||||
return DaemonResponse(
|
||||
ok = map["ok"] as Boolean,
|
||||
versionError = map["versionError"] as? String,
|
||||
errors = errors,
|
||||
output = map["output"] as String,
|
||||
t_ms = (map["t_ms"] as Number).toLong(),
|
||||
outputFiles = outputFiles,
|
||||
importedFiles = importedFiles
|
||||
)
|
||||
}
|
||||
|
||||
fun decodeRequest(json: String): DaemonRequest {
|
||||
val map = parseJsonObject(json)
|
||||
return DaemonRequest(
|
||||
version = map["version"] as String,
|
||||
filepath = map["filepath"] as String,
|
||||
optimize = map["optimize"] as Boolean,
|
||||
writeAssembly = map["writeAssembly"] as Boolean,
|
||||
warnSymbolShadowing = map["warnSymbolShadowing"] as Boolean,
|
||||
warnImplicitTypeCasts = map["warnImplicitTypeCasts"] as Boolean,
|
||||
quietAll = map["quietAll"] as Boolean,
|
||||
quietAssembler = map["quietAssembler"] as Boolean,
|
||||
showTimings = map["showTimings"] as Boolean,
|
||||
asmListfile = map["asmListfile"] as Boolean,
|
||||
includeSourcelines = map["includeSourcelines"] as Boolean,
|
||||
experimentalCodegen = map["experimentalCodegen"] as Boolean,
|
||||
dumpVariables = map["dumpVariables"] as Boolean,
|
||||
dumpSymbols = map["dumpSymbols"] as Boolean,
|
||||
varsHighBank = map["varsHighBank"] as? Int,
|
||||
varsGolden = map["varsGolden"] as Boolean,
|
||||
slabsHighBank = map["slabsHighBank"] as? Int,
|
||||
slabsGolden = map["slabsGolden"] as Boolean,
|
||||
compilationTarget = map["compilationTarget"] as String,
|
||||
breakpointCpuInstruction = map["breakpointCpuInstruction"] as? String,
|
||||
printAst1 = map["printAst1"] as Boolean,
|
||||
printAst2 = map["printAst2"] as Boolean,
|
||||
ignoreFootguns = map["ignoreFootguns"] as Boolean,
|
||||
profilingInstrumentation = map["profilingInstrumentation"] as Boolean,
|
||||
nostdlib = map["nostdlib"] as Boolean,
|
||||
symbolDefs = (map["symbolDefs"] as? Map<*, *>)?.mapKeys { it.key as String }?.mapValues { it.value as String } ?: emptyMap(),
|
||||
sourceDirs = (map["sourceDirs"] as? List<*>)?.map { it as String } ?: emptyList(),
|
||||
outputDir = map["outputDir"] as String
|
||||
)
|
||||
}
|
||||
|
||||
private fun encodeJsonString(s: String): String {
|
||||
val sb = StringBuilder(s.length + 2)
|
||||
sb.append('"')
|
||||
for (c in s) {
|
||||
when (c) {
|
||||
'"' -> sb.append("\\\"")
|
||||
'\\' -> sb.append("\\\\")
|
||||
'\n' -> sb.append("\\n")
|
||||
'\r' -> sb.append("\\r")
|
||||
'\t' -> sb.append("\\t")
|
||||
else -> sb.append(c)
|
||||
}
|
||||
}
|
||||
sb.append('"')
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun prop(name: String, value: String): String = "\"$name\":${encodeJsonString(value)},"
|
||||
private fun prop(name: String, value: Boolean): String = "\"$name\":$value,"
|
||||
private fun prop(name: String, value: Int): String = "\"$name\":$value,"
|
||||
private fun prop(name: String, value: Long): String = "\"$name\":$value,"
|
||||
private fun prop(name: String, value: Int?): String = value?.let { "\"$name\":$it," } ?: "\"$name\":null,"
|
||||
private fun prop(name: String, value: Map<String, String>): String {
|
||||
val entries = value.entries.joinToString(",") { "${encodeJsonString(it.key)}:${encodeJsonString(it.value)}" }
|
||||
return "\"$name\":{$entries},"
|
||||
}
|
||||
private fun prop(name: String, value: List<String>): String {
|
||||
val items = value.joinToString(",") { encodeJsonString(it) }
|
||||
return "\"$name\":[$items],"
|
||||
}
|
||||
private fun propOpt(name: String, value: String?): String = value?.let { "\"$name\":${encodeJsonString(it)}," } ?: ""
|
||||
|
||||
private fun parseJsonObject(json: String): Map<String, Any?> {
|
||||
val trimmed = json.trim()
|
||||
if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
|
||||
throw IllegalArgumentException("not a JSON object: $json")
|
||||
val inner = trimmed.substring(1, trimmed.length - 1)
|
||||
return parsePairs(inner)
|
||||
}
|
||||
|
||||
private fun parsePairs(s: String): Map<String, Any?> {
|
||||
val result = LinkedHashMap<String, Any?>()
|
||||
var pos = 0
|
||||
while (pos < s.length) {
|
||||
skipWhitespace(s, pos).let { pos = it }
|
||||
if (pos >= s.length) break
|
||||
// key
|
||||
check(s[pos] == '"') { "expected '\"' at pos $pos in: $s" }
|
||||
pos++
|
||||
val keyEnd = s.indexOf('"', pos)
|
||||
check(keyEnd > 0) { "unterminated string at pos $pos" }
|
||||
val key = unescapeJson(s.substring(pos, keyEnd))
|
||||
pos = keyEnd + 1
|
||||
skipWhitespace(s, pos).let { pos = it }
|
||||
check(s[pos] == ':') { "expected ':' at pos $pos" }
|
||||
pos++
|
||||
skipWhitespace(s, pos).let { pos = it }
|
||||
// value
|
||||
val (value, newPos) = parseValue(s, pos)
|
||||
result[key] = value
|
||||
pos = newPos
|
||||
skipWhitespace(s, pos).let { pos = it }
|
||||
if (pos < s.length && s[pos] == ',') pos++
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun parseValue(s: String, start: Int): Pair<Any?, Int> {
|
||||
var pos = start
|
||||
skipWhitespace(s, pos).let { pos = it }
|
||||
return when {
|
||||
pos >= s.length -> null to pos
|
||||
s[pos] == '"' -> parseStringValue(s, pos)
|
||||
s[pos] == '{' -> parseMapValue(s, pos)
|
||||
s[pos] == '[' -> parseListValue(s, pos)
|
||||
s.startsWith("null", pos) -> null to (pos + 4)
|
||||
s.startsWith("true", pos) -> true to (pos + 4)
|
||||
s.startsWith("false", pos) -> false to (pos + 5)
|
||||
else -> parseNumberValue(s, pos)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStringValue(s: String, start: Int): Pair<String, Int> {
|
||||
check(s[start] == '"')
|
||||
pos@ for (pos in (start + 1) until s.length) {
|
||||
when (s[pos]) {
|
||||
'\\' -> continue@pos // skip next char
|
||||
'"' -> return unescapeJson(s.substring(start + 1, pos)) to (pos + 1)
|
||||
}
|
||||
}
|
||||
error("unterminated string in JSON")
|
||||
}
|
||||
|
||||
private fun parseMapValue(s: String, start: Int): Pair<Map<String, Any?>, Int> {
|
||||
check(s[start] == '{')
|
||||
var depth = 1
|
||||
var idx = start + 1
|
||||
while (idx < s.length) {
|
||||
when (s[idx]) {
|
||||
'{' -> depth++
|
||||
'}' -> {
|
||||
depth--
|
||||
if (depth == 0)
|
||||
return parsePairs(s.substring(start + 1, idx)) to (idx + 1)
|
||||
}
|
||||
'"' -> {
|
||||
// skip string content
|
||||
var p = idx + 1
|
||||
while (p < s.length) {
|
||||
when (s[p]) {
|
||||
'\\' -> p += 2
|
||||
'"' -> { idx = p; break }
|
||||
else -> p++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
idx++
|
||||
}
|
||||
error("unterminated JSON object")
|
||||
}
|
||||
|
||||
private fun parseListValue(s: String, start: Int): Pair<List<Any?>, Int> {
|
||||
check(s[start] == '[')
|
||||
val items = mutableListOf<Any?>()
|
||||
var pos = start + 1
|
||||
while (pos < s.length) {
|
||||
skipWhitespace(s, pos).let { pos = it }
|
||||
if (s[pos] == ']') return items to (pos + 1)
|
||||
val (value, newPos) = parseValue(s, pos)
|
||||
items.add(value)
|
||||
pos = newPos
|
||||
skipWhitespace(s, pos).let { pos = it }
|
||||
if (pos < s.length && s[pos] == ',') pos++
|
||||
}
|
||||
error("unterminated JSON array")
|
||||
}
|
||||
|
||||
private fun parseNumberValue(s: String, start: Int): Pair<Number, Int> {
|
||||
var end = start
|
||||
while (end < s.length && (s[end].isDigit() || s[end] == '-' || s[end] == '+' || s[end] == '.' || s[end] == 'e' || s[end] == 'E'))
|
||||
end++
|
||||
val num = s.substring(start, end)
|
||||
return if (num.contains('.') || num.contains('e') || num.contains('E'))
|
||||
num.toDouble() to end
|
||||
else
|
||||
num.toLong() to end
|
||||
}
|
||||
|
||||
private fun skipWhitespace(s: String, pos: Int): Int {
|
||||
var p = pos
|
||||
while (p < s.length && s[p].isWhitespace()) p++
|
||||
return p
|
||||
}
|
||||
|
||||
private fun unescapeJson(s: String): String {
|
||||
if ('\\' !in s) return s
|
||||
val sb = StringBuilder(s.length)
|
||||
var i = 0
|
||||
while (i < s.length) {
|
||||
if (s[i] == '\\' && i + 1 < s.length) {
|
||||
when (s[i + 1]) {
|
||||
'"' -> { sb.append('"'); i += 2 }
|
||||
'\\' -> { sb.append('\\'); i += 2 }
|
||||
'/' -> { sb.append('/'); i += 2 }
|
||||
'n' -> { sb.append('\n'); i += 2 }
|
||||
'r' -> { sb.append('\r'); i += 2 }
|
||||
't' -> { sb.append('\t'); i += 2 }
|
||||
'u' -> {
|
||||
val hex = s.substring(i + 2, i + 6)
|
||||
sb.append(hex.toInt(16).toChar())
|
||||
i += 6
|
||||
}
|
||||
else -> { sb.append(s[i]); i++ }
|
||||
}
|
||||
} else {
|
||||
sb.append(s[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun check(condition: Boolean, lazyMessage: () -> String) {
|
||||
if (!condition) throw IllegalArgumentException(lazyMessage())
|
||||
}
|
||||
|
||||
private fun error(message: String): Nothing = throw IllegalArgumentException(message)
|
||||
}
|
||||
@@ -38,6 +38,8 @@ internal class AstChecker(private val program: Program,
|
||||
} else {
|
||||
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
|
||||
errors.err("program entrypoint subroutine can't have parameters and/or return values", startSub.position)
|
||||
if (startSub.isPrivate)
|
||||
errors.err("program entrypoint subroutine 'start' cannot be private", startSub.position)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,26 +102,6 @@ internal class AstChecker(private val program: Program,
|
||||
errors.undefined(identifier.nameInSource, identifier.firstTarget(program.builtinFunctions)==null, identifier.position)
|
||||
}
|
||||
else {
|
||||
// Check for private symbol access
|
||||
val privateError = when (stmt) {
|
||||
is VarDecl -> {
|
||||
if (stmt.isPrivate && !isAccessWithinSameBlock(identifier, stmt.definingBlock))
|
||||
"private variable '${stmt.scopedName.joinToString(".")}'"
|
||||
else
|
||||
null
|
||||
}
|
||||
is Subroutine -> {
|
||||
if (stmt.isPrivate && !isAccessWithinSameBlock(identifier, stmt.definingBlock))
|
||||
"private subroutine '${stmt.scopedName.joinToString(".")}'"
|
||||
else
|
||||
null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if (privateError != null) {
|
||||
errors.err("cannot access $privateError from outside its block", identifier.position)
|
||||
}
|
||||
|
||||
val target = stmt as? VarDecl
|
||||
if (target != null && target.origin == VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
if (target.definingSubroutine!!.isAsmSubroutine) {
|
||||
@@ -137,15 +119,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAccessWithinSameBlock(identifier: IdentifierReference, targetBlock: Block): Boolean {
|
||||
var current: Node? = identifier.parent
|
||||
while (current != null) {
|
||||
if (current is Block) {
|
||||
return current === targetBlock
|
||||
}
|
||||
current = current.parent
|
||||
private fun checkPrivateTypeAccess(type: DataType, position: Position, currentBlock: Block) {
|
||||
val struct = type.subType
|
||||
if(struct is StructDecl && struct.isPrivate) {
|
||||
val structBlock = struct.definingBlock
|
||||
if(currentBlock !== structBlock)
|
||||
errors.err("cannot access private struct '${struct.scopedName.joinToString(".")}' from outside its block", position)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(unrollLoop: UnrollLoop) {
|
||||
@@ -294,8 +274,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
BaseDataType.LONG -> {
|
||||
if(!iterableDt.elementType().isInteger)
|
||||
errors.err("long loop variable can only loop over integers", forLoop.position)
|
||||
errors.warn("for loop using a long counter could be very slow", forLoop.position)
|
||||
}
|
||||
|
||||
BaseDataType.FLOAT -> {
|
||||
@@ -507,6 +486,16 @@ internal class AstChecker(private val program: Program,
|
||||
if(address != null && address !is NumericLiteral)
|
||||
err("address must be a constant")
|
||||
|
||||
val currentBlock = subroutine.definingBlock
|
||||
subroutine.parameters.forEach { param ->
|
||||
if(param.type.isPointer || param.type.isStructInstance)
|
||||
checkPrivateTypeAccess(param.type, subroutine.position, currentBlock)
|
||||
}
|
||||
subroutine.returntypes.forEach { rt ->
|
||||
if(rt.isPointer || rt.isStructInstance)
|
||||
checkPrivateTypeAccess(rt, subroutine.position, currentBlock)
|
||||
}
|
||||
|
||||
super.visit(subroutine)
|
||||
|
||||
// subroutine must contain at least one 'return' or 'goto'
|
||||
@@ -708,7 +697,12 @@ internal class AstChecker(private val program: Program,
|
||||
val iterations = repeatLoop.iterations?.constValue(program)
|
||||
if (iterations != null) {
|
||||
require(floor(iterations.number)==iterations.number)
|
||||
if (iterations.number.toInt() > 65536) errors.err("repeat cannot exceed 65536 iterations", iterations.position)
|
||||
if (iterations.number.toInt() > 65536)
|
||||
errors.warn("repeat using a long counter could be very slow", iterations.position)
|
||||
}
|
||||
|
||||
if(repeatLoop.iterations?.inferType(program)?.isLong==true) {
|
||||
errors.warn("repeat using a long counter could be very slow", repeatLoop.iterations!!.position)
|
||||
}
|
||||
|
||||
val ident = repeatLoop.iterations as? IdentifierReference
|
||||
@@ -917,7 +911,7 @@ internal class AstChecker(private val program: Program,
|
||||
val variable=addressOf.identifier?.targetVarDecl()
|
||||
if (variable!=null) {
|
||||
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
|
||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||
errors.err("invalid address-of operand type", addressOf.position)
|
||||
}
|
||||
|
||||
if(addressOf.msb) {
|
||||
@@ -959,9 +953,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
|
||||
if(decl.names.size>1)
|
||||
throw InternalCompilerException("vardecls with multiple names should have been converted into individual vardecls")
|
||||
|
||||
if(decl.datatype.isPointer || decl.datatype.isStructInstance)
|
||||
checkPrivateTypeAccess(decl.datatype, decl.position, decl.definingBlock)
|
||||
|
||||
if(decl.type!=VarDeclType.CONST && "::" in decl.name)
|
||||
errors.err("only enum members can be accessed with '::' syntax", decl.position)
|
||||
|
||||
@@ -1759,6 +1757,12 @@ internal class AstChecker(private val program: Program,
|
||||
if(!typecast.expression.inferType(program).isKnown)
|
||||
errors.err("this expression doesn't return a value", typecast.expression.position)
|
||||
|
||||
if(typecast.type.isPointer || typecast.type.isStructInstance) {
|
||||
val currentBlock = findParentNode<Block>(typecast)
|
||||
if(currentBlock!=null)
|
||||
checkPrivateTypeAccess(typecast.type, typecast.position, currentBlock)
|
||||
}
|
||||
|
||||
val cv = typecast.expression.constValue(program)
|
||||
if(cv != null) {
|
||||
if(typecast.type.isBasic) {
|
||||
@@ -1862,7 +1866,7 @@ internal class AstChecker(private val program: Program,
|
||||
checkPointer(binexpr.left as IdentifierReference)
|
||||
}
|
||||
|
||||
if(builtinFunctionName=="memory") {
|
||||
if(builtinFunctionName=="memory" || builtinFunctionName=="memory__ref") {
|
||||
errors.err("memory() function call should have been desugared into dedicated AST nodes", functionCallExpr.position)
|
||||
}
|
||||
|
||||
@@ -1870,7 +1874,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
if(functionCallStatement.isMemoryCall) {
|
||||
if(functionCallStatement.isMemoryCall || functionCallStatement.isMemoryRefCall) {
|
||||
errors.err("memory() function call should have been desugared into dedicated AST nodes", functionCallStatement.position)
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,12 @@ internal fun Program.verifyFunctionArgTypes(errors: IErrorReporter, options: Com
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.checkPrivateAccess(errors: IErrorReporter) {
|
||||
val checker = PrivateAccessChecker(this, errors)
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.preprocessAst(errors: IErrorReporter, options: CompilationOptions) {
|
||||
val mergeBlocks = BlockMerger(errors)
|
||||
mergeBlocks.visit(this)
|
||||
|
||||
@@ -179,6 +179,13 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
super.visit(subroutine)
|
||||
}
|
||||
|
||||
override fun visit(alias: Alias) {
|
||||
val existing = alias.definingScope.lookup(listOf(alias.alias))
|
||||
if(existing != null && existing !== alias)
|
||||
nameError(alias.alias, alias.position, existing)
|
||||
super.visit(alias)
|
||||
}
|
||||
|
||||
override fun visit(label: Label) {
|
||||
if(label.name in BuiltinFunctions) {
|
||||
errors.err("builtin function cannot be redefined", label.position)
|
||||
|
||||
@@ -356,6 +356,10 @@ class AstPreprocessor(val program: Program,
|
||||
|
||||
checkStringParam(functionCallExpr as IFunctionCall, stmtOfExpression)
|
||||
|
||||
if (functionCallExpr.isMemoryCall && functionCallExpr.args.size == 1) {
|
||||
functionCallExpr.target = IdentifierReference(listOf("memory__ref"), functionCallExpr.target.position).apply { linkParents(functionCallExpr) }
|
||||
}
|
||||
|
||||
if(functionCallExpr.target.nameInSource==listOf("sizeof")) {
|
||||
val arg = functionCallExpr.args.firstOrNull()
|
||||
if (arg is PtrDereference) {
|
||||
@@ -373,6 +377,11 @@ class AstPreprocessor(val program: Program,
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<AstModification> {
|
||||
checkStringParam(functionCallStatement as IFunctionCall, functionCallStatement)
|
||||
|
||||
if (functionCallStatement.isMemoryCall && functionCallStatement.args.size == 1) {
|
||||
functionCallStatement.target = IdentifierReference(listOf("memory__ref"), functionCallStatement.target.position).apply { linkParents(functionCallStatement) }
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@@ -400,15 +409,15 @@ class AstPreprocessor(val program: Program,
|
||||
val replacement = if(tgt2!=null) {
|
||||
if(tgt2 is BuiltinFunctionPlaceholder) {
|
||||
val unscopedTarget = IdentifierReference(listOf(tgt2.name), alias.position)
|
||||
Alias(alias.alias, unscopedTarget, alias.position)
|
||||
Alias(alias.alias, unscopedTarget, alias.isPrivate, alias.position)
|
||||
} else if(tgt2.scopedName != chainedTargetName.nameInSource) {
|
||||
val scopedTarget = IdentifierReference(tgt2.scopedName, alias.position)
|
||||
Alias(alias.alias, scopedTarget, alias.position)
|
||||
Alias(alias.alias, scopedTarget, alias.isPrivate, alias.position)
|
||||
} else {
|
||||
Alias(alias.alias, chainedTargetName, alias.position)
|
||||
Alias(alias.alias, chainedTargetName, alias.isPrivate, alias.position)
|
||||
}
|
||||
} else {
|
||||
Alias(alias.alias, chainedTargetName, alias.position)
|
||||
Alias(alias.alias, chainedTargetName, alias.isPrivate, alias.position)
|
||||
}
|
||||
return listOf(AstReplaceNode(alias, replacement, parent))
|
||||
}
|
||||
@@ -469,6 +478,10 @@ class AstPreprocessor(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(enum: Enumeration, parent: Node): Iterable<AstModification> {
|
||||
if (enum.members.isEmpty()) {
|
||||
errors.err("enumeration must contain at least one member", enum.position)
|
||||
}
|
||||
|
||||
// first check that there is no name conflict
|
||||
(parent as? IStatementContainer)?.let {
|
||||
val allNamed = it.statements.asSequence()
|
||||
@@ -510,6 +523,7 @@ class AstPreprocessor(val program: Program,
|
||||
.names(membername)
|
||||
.type(VarDeclType.CONST)
|
||||
.value(membervalue)
|
||||
.isPrivate(enum.isPrivate)
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,36 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
}
|
||||
} else if(sourceDt.isString && typecast.type.isPointer && typecast.type.sub==BaseDataType.UBYTE) {
|
||||
// casting a string to a ^^ubyte is just taking the address of the string.
|
||||
val addr = AddressOf(typecast.expression as IdentifierReference, null, null, false, true, typecast.position)
|
||||
return listOf(AstReplaceNode(typecast, addr, parent))
|
||||
when (typecast.expression) {
|
||||
is IfExpression -> {
|
||||
val ifExpr = typecast.expression as IfExpression
|
||||
val trueCast = TypecastExpression(ifExpr.truevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
val falseCast = TypecastExpression(ifExpr.falsevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
trueCast.linkParents(ifExpr)
|
||||
falseCast.linkParents(ifExpr)
|
||||
ifExpr.truevalue = trueCast
|
||||
ifExpr.falsevalue = falseCast
|
||||
return listOf(AstReplaceNode(typecast, ifExpr, parent))
|
||||
}
|
||||
is BranchConditionExpression -> {
|
||||
val branchExpr = typecast.expression as BranchConditionExpression
|
||||
val trueCast = TypecastExpression(branchExpr.truevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
val falseCast = TypecastExpression(branchExpr.falsevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
trueCast.linkParents(branchExpr)
|
||||
falseCast.linkParents(branchExpr)
|
||||
branchExpr.truevalue = trueCast
|
||||
branchExpr.falsevalue = falseCast
|
||||
return listOf(AstReplaceNode(typecast, branchExpr, parent))
|
||||
}
|
||||
else -> {
|
||||
val identifier = typecast.expression as? IdentifierReference
|
||||
if (identifier != null) {
|
||||
val addr = AddressOf(identifier, null, null, false, true, typecast.position)
|
||||
return listOf(AstReplaceNode(typecast, addr, parent))
|
||||
}
|
||||
errors.err("cannot take address of this expression", typecast.position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
|
||||
@@ -26,18 +26,18 @@ internal class CodeDesugarer(val program: Program, private val target: ICompilat
|
||||
// - replace ptr^^ by @(ptr) if ptr is just an uword.
|
||||
// - replace p1^^ = p2^^ by memcopy.
|
||||
|
||||
private val globalReservedSlabs = mutableSetOf<String>()
|
||||
private val globalReservedSlabs = mutableMapOf<String, MemorySlabReservation>()
|
||||
|
||||
override fun before(program: Program): Iterable<AstModification> {
|
||||
globalReservedSlabs.clear()
|
||||
// Pre-collect existing reservations from the entire program to avoid duplicates in multiple passes
|
||||
val collector = object : IAstVisitor {
|
||||
override fun visit(reservation: MemorySlabReservation) {
|
||||
globalReservedSlabs.add(reservation.slabName)
|
||||
globalReservedSlabs[reservation.slabName] = reservation
|
||||
}
|
||||
}
|
||||
program.modules.forEach { module ->
|
||||
module.statements.forEach { it.accept(collector) }
|
||||
module.accept(collector)
|
||||
}
|
||||
return super.before(program)
|
||||
}
|
||||
@@ -262,6 +262,20 @@ _after:
|
||||
}
|
||||
|
||||
override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<AstModification> {
|
||||
if (functionCallExpr.isMemoryRefCall) {
|
||||
val str = functionCallExpr.args[0] as? StringLiteral
|
||||
if (str == null) {
|
||||
errors.err("memory name argument must be a string literal", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
} else if (str.value.isEmpty()) {
|
||||
errors.err("memory name argument cannot be empty string", functionCallExpr.args[0].position)
|
||||
return noModifications
|
||||
}
|
||||
val slabName = sanitizeSlabName(str.value)
|
||||
val ref = MemorySlabRef(slabName, functionCallExpr.position)
|
||||
return listOf(AstReplaceNode(functionCallExpr, ref, parent))
|
||||
}
|
||||
|
||||
if (functionCallExpr.isMemoryCall) {
|
||||
val str = functionCallExpr.args[0] as? StringLiteral
|
||||
if (str == null) {
|
||||
@@ -297,9 +311,10 @@ _after:
|
||||
val container = containingStmt.parent as IStatementContainer
|
||||
|
||||
val mods = mutableListOf<AstModification>(AstReplaceNode(functionCallExpr, ref, parent))
|
||||
if (slabName !in globalReservedSlabs) {
|
||||
val existing = globalReservedSlabs[slabName]
|
||||
if (existing == null || existing.size != size || existing.align != align) {
|
||||
mods.add(AstInsert.before(containingStmt, reservation, container))
|
||||
globalReservedSlabs.add(slabName)
|
||||
globalReservedSlabs[slabName] = reservation
|
||||
}
|
||||
return mods
|
||||
}
|
||||
@@ -307,6 +322,11 @@ _after:
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<AstModification> {
|
||||
if (functionCallStatement.isMemoryRefCall) {
|
||||
val container = parent as IStatementContainer
|
||||
return listOf(AstRemove(functionCallStatement, container))
|
||||
}
|
||||
|
||||
if (functionCallStatement.isMemoryCall) {
|
||||
val str = functionCallStatement.args[0] as? StringLiteral
|
||||
if (str == null) {
|
||||
@@ -336,11 +356,12 @@ _after:
|
||||
val align = alignNum.toUInt()
|
||||
|
||||
val container = parent as IStatementContainer
|
||||
return if (slabName in globalReservedSlabs) {
|
||||
val existing = globalReservedSlabs[slabName]
|
||||
return if (existing != null && existing.size == size && existing.align == align) {
|
||||
listOf(AstRemove(functionCallStatement, container))
|
||||
} else {
|
||||
globalReservedSlabs.add(slabName)
|
||||
val reservation = MemorySlabReservation(slabName, size, align, functionCallStatement.position)
|
||||
globalReservedSlabs[slabName] = reservation
|
||||
listOf(AstReplaceNode(functionCallStatement, reservation, parent))
|
||||
}
|
||||
}
|
||||
|
||||
+25
-1
@@ -20,7 +20,7 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
val parentFunc = (string.parent as? IFunctionCall)?.target
|
||||
if(parentFunc!=null) {
|
||||
val parentCall = string.parent as? IFunctionCall
|
||||
if(parentCall!=null && parentCall.isMemoryCall) {
|
||||
if(parentCall!=null && (parentCall.isMemoryCall || parentCall.isMemoryRefCall)) {
|
||||
// memory() builtin function just uses the string as a label name
|
||||
return noModifications
|
||||
}
|
||||
@@ -149,6 +149,14 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
// First component might be an alias
|
||||
val tgt2 = identifier.definingScope.lookup(identifier.nameInSource[0]) as? Alias
|
||||
if(tgt2!=null && parent !is Alias) {
|
||||
if(tgt2.isPrivate) {
|
||||
val referencingBlock = findParentNode<Block>(identifier)
|
||||
val aliasBlock = findParentNode<Block>(tgt2)
|
||||
if(referencingBlock!=null && aliasBlock!=null && referencingBlock!==aliasBlock) {
|
||||
errors.err("cannot access private alias '${tgt2.alias}' from outside its block", identifier.position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
val aliasTarget = resolveAliasTarget(tgt2)
|
||||
if(aliasTarget != null) {
|
||||
val actual = when(aliasTarget) {
|
||||
@@ -161,6 +169,14 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
}
|
||||
|
||||
if(target is Alias && parent !is Alias) {
|
||||
if(target.isPrivate) {
|
||||
val referencingBlock = findParentNode<Block>(identifier)
|
||||
val aliasBlock = findParentNode<Block>(target)
|
||||
if(referencingBlock!=null && aliasBlock!=null && referencingBlock!==aliasBlock) {
|
||||
errors.err("cannot access private alias '${target.alias}' from outside its block", identifier.position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
val targetStatement = resolveAliasTarget(target)
|
||||
if(targetStatement == null) {
|
||||
return noModifications // alias loop or unresolved
|
||||
@@ -220,6 +236,14 @@ internal class LiteralsToAutoVarsAndRecombineIdentifiers(private val program: Pr
|
||||
if(deref.chain.isEmpty()) return emptyList()
|
||||
val tgt2 = deref.definingScope.lookup(deref.chain[0]) as? Alias
|
||||
if(tgt2!=null && parent !is Alias) {
|
||||
if(tgt2.isPrivate) {
|
||||
val referencingBlock = findParentNode<Block>(deref)
|
||||
val aliasBlock = findParentNode<Block>(tgt2)
|
||||
if(referencingBlock!=null && aliasBlock!=null && referencingBlock!==aliasBlock) {
|
||||
errors.err("cannot access private alias '${tgt2.alias}' from outside its block", deref.position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
val aliasTarget = resolveAliasTarget(tgt2)
|
||||
if(aliasTarget != null) {
|
||||
val unaliased = when(aliasTarget) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.ParentSentinel
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.IErrorReporter
|
||||
|
||||
internal class PrivateAccessChecker(
|
||||
private val program: Program,
|
||||
private val errors: IErrorReporter
|
||||
) : IAstVisitor {
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
val parentExpr = identifier.parent as? BinaryExpression
|
||||
if (parentExpr?.operator == ".") {
|
||||
return // identifiers will be checked over at the BinaryExpression itself
|
||||
}
|
||||
|
||||
val stmt = identifier.targetStatement(program.builtinFunctions)
|
||||
if (stmt != null) {
|
||||
val privateError = when (stmt) {
|
||||
is VarDecl -> {
|
||||
if (stmt.isPrivate && !isAccessWithinSameBlock(identifier, stmt.definingBlock))
|
||||
"private variable '${stmt.scopedName.joinToString(".")}'"
|
||||
else null
|
||||
}
|
||||
is Subroutine -> {
|
||||
if (stmt.isPrivate && !isAccessWithinSameBlock(identifier, stmt.definingBlock))
|
||||
"private subroutine '${stmt.scopedName.joinToString(".")}'"
|
||||
else null
|
||||
}
|
||||
is StructDecl -> {
|
||||
if (stmt.isPrivate && !isAccessWithinSameBlock(identifier, stmt.definingBlock))
|
||||
"private struct '${stmt.scopedName.joinToString(".")}'"
|
||||
else null
|
||||
}
|
||||
is Enumeration -> {
|
||||
if (stmt.isPrivate && !isAccessWithinSameBlock(identifier, stmt.definingBlock))
|
||||
"private enum '${stmt.scopedName.joinToString(".")}'"
|
||||
else null
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if (privateError != null) {
|
||||
errors.err("cannot access $privateError from outside its block", identifier.position)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(identifier)
|
||||
}
|
||||
|
||||
private fun isAccessWithinSameBlock(identifier: IdentifierReference, definingBlock: Block): Boolean {
|
||||
var node: Node = identifier
|
||||
while (node !is Block) {
|
||||
node = node.parent
|
||||
if (node is ParentSentinel)
|
||||
return false
|
||||
}
|
||||
return node === definingBlock
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,14 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
|
||||
}
|
||||
}
|
||||
|
||||
// check for missing reservations
|
||||
val slabNames = memorySlabs.map { it.name }.toSet()
|
||||
for (ref in memorySlabRefs) {
|
||||
if (ref.slabName !in slabNames) {
|
||||
errors.err("missing memory reservation with name '${ref.slabName}'", ref.position)
|
||||
}
|
||||
}
|
||||
|
||||
// remove unused strings from interned strings block
|
||||
val internedBlock = program.allBlocks.singleOrNull { it.name == INTERNED_STRINGS_MODULENAME }
|
||||
internedBlock?.statements?.withIndex()?.reversed()?.forEach { (index, st) ->
|
||||
@@ -40,12 +48,14 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
|
||||
|
||||
private class Slab(val name: String, val size: Int, val align: Int, val position: Position)
|
||||
private val memorySlabs = mutableListOf<Slab>()
|
||||
private val memorySlabRefs = mutableListOf<MemorySlabRef>()
|
||||
|
||||
override fun visit(reservation: MemorySlabReservation) {
|
||||
memorySlabs.add(Slab(reservation.slabName, reservation.size.toInt(), reservation.align.toInt(), reservation.position))
|
||||
}
|
||||
|
||||
override fun visit(ref: MemorySlabRef) {
|
||||
memorySlabRefs.add(ref)
|
||||
}
|
||||
|
||||
override fun visit(functionCallExpr: FunctionCallExpression) {
|
||||
|
||||
@@ -65,7 +65,7 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
main {
|
||||
extsub $FFD2 = chrout(ubyte ch @ A)
|
||||
sub start() {
|
||||
ubyte ch = '\n'
|
||||
ubyte @shared ch = '\n'
|
||||
chrout(ch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package prog8tests.compiler
|
||||
|
||||
import io.kotest.common.ExperimentalKotest
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.datatest.withData
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
@@ -146,24 +145,22 @@ private fun verifyIrRegisterCount(result: CompilationResult, expectedCount: Int)
|
||||
class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val onlyC64 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("balloonflight", c64SizeOptimized=1674, c64SizeUnoptimized=1745),
|
||||
ExampleSizes("banking", c64SizeOptimized=680, c64SizeUnoptimized=1451),
|
||||
ExampleSizes("bdmusic-irq", c64SizeOptimized=1001, c64SizeUnoptimized=1019),
|
||||
ExampleSizes("bdmusic", c64SizeOptimized=944, c64SizeUnoptimized=1011),
|
||||
ExampleSizes("charset", c64SizeOptimized=841, c64SizeUnoptimized=860),
|
||||
ExampleSizes("bdmusic", c64SizeOptimized=938, c64SizeUnoptimized=1011),
|
||||
ExampleSizes("charset", c64SizeOptimized=835, c64SizeUnoptimized=860),
|
||||
ExampleSizes("cube3d-sprites", c64SizeOptimized=2712, c64SizeUnoptimized=2764),
|
||||
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=1696, c64SizeUnoptimized=1731),
|
||||
ExampleSizes("plasma", c64SizeOptimized=1690, c64SizeUnoptimized=1731),
|
||||
ExampleSizes("rasterbars", c64SizeOptimized=845, c64SizeUnoptimized=892),
|
||||
ExampleSizes("simplemultiplexer", c64SizeOptimized=843, c64SizeUnoptimized=914),
|
||||
ExampleSizes("sprites", c64SizeOptimized=963, c64SizeUnoptimized=1052),
|
||||
@@ -176,17 +173,17 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
)
|
||||
|
||||
val target = C64Target()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyC64.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedSize = if (optimize) params.first.c64SizeOptimized else params.first.c64SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
onlyC64.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = if (optimize) params.c64SizeOptimized else params.c64SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -194,16 +191,14 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("vtui/testvtui", cx16SizeOptimized=3041, cx16SizeUnoptimized=3041),
|
||||
ExampleSizes("pcmaudio/play-adpcm", cx16SizeOptimized=36147, cx16SizeUnoptimized=36645),
|
||||
ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5627, cx16SizeUnoptimized=7863),
|
||||
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/vumeter", cx16SizeOptimized=3888, cx16SizeUnoptimized=6381),
|
||||
@@ -225,51 +220,52 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
ExampleSizes("balloonflight", cx16SizeOptimized=3187, cx16SizeUnoptimized=4163),
|
||||
ExampleSizes("bdmusic", cx16SizeOptimized=2386, cx16SizeUnoptimized=2761),
|
||||
ExampleSizes("bobs", cx16SizeOptimized=2800, cx16SizeUnoptimized=3414),
|
||||
ExampleSizes("bubbleuniverse", cx16SizeOptimized=1449, cx16SizeUnoptimized=1958),
|
||||
ExampleSizes("bubbleuniverse", cx16SizeOptimized=1450, cx16SizeUnoptimized=1958),
|
||||
ExampleSizes("charfade", cx16SizeOptimized=1054, cx16SizeUnoptimized=1362),
|
||||
ExampleSizes("charsets", cx16SizeOptimized=1125, cx16SizeUnoptimized=1417),
|
||||
ExampleSizes("circles", cx16SizeOptimized=2392, cx16SizeUnoptimized=4547),
|
||||
ExampleSizes("circles", cx16SizeOptimized=2385, cx16SizeUnoptimized=4547),
|
||||
ExampleSizes("cobramk3-gfx", cx16SizeOptimized=7705, cx16SizeUnoptimized=10612),
|
||||
ExampleSizes("colorbars", cx16SizeOptimized=1606, cx16SizeUnoptimized=1904),
|
||||
ExampleSizes("cxlogo", cx16SizeOptimized=599, cx16SizeUnoptimized=895),
|
||||
ExampleSizes("diskspeed", cx16SizeOptimized=6280, cx16SizeUnoptimized=8283),
|
||||
ExampleSizes("fileseek", cx16SizeOptimized=2474, cx16SizeUnoptimized=4496),
|
||||
ExampleSizes("fileseek", cx16SizeOptimized=2468, cx16SizeUnoptimized=4496),
|
||||
ExampleSizes("floatparse", cx16SizeOptimized=8303, cx16SizeUnoptimized=9190),
|
||||
ExampleSizes("interpolation", cx16SizeOptimized=3791, cx16SizeUnoptimized=4768),
|
||||
ExampleSizes("kefrenbars", cx16SizeOptimized=2392, cx16SizeUnoptimized=4654),
|
||||
ExampleSizes("kefrenbars", cx16SizeOptimized=2386, cx16SizeUnoptimized=4654),
|
||||
ExampleSizes("keyboardhandler", cx16SizeOptimized=412, cx16SizeUnoptimized=717),
|
||||
ExampleSizes("landscape", cx16SizeOptimized=1699, cx16SizeUnoptimized=1997),
|
||||
ExampleSizes("life", cx16SizeOptimized=1304, cx16SizeUnoptimized=1751),
|
||||
ExampleSizes("landscape", cx16SizeOptimized=1673, cx16SizeUnoptimized=1997),
|
||||
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("rasterbars", cx16SizeOptimized=1268, cx16SizeUnoptimized=1685),
|
||||
ExampleSizes("serialdownload", cx16SizeOptimized=2516, cx16SizeUnoptimized=3945),
|
||||
ExampleSizes("showbmx", cx16SizeOptimized=2745, cx16SizeUnoptimized=5269),
|
||||
ExampleSizes("snow", cx16SizeOptimized=3127, cx16SizeUnoptimized=5295),
|
||||
ExampleSizes("snow", cx16SizeOptimized=3120, cx16SizeUnoptimized=5295),
|
||||
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=3847),
|
||||
ExampleSizes("spotlight", cx16SizeOptimized=908, cx16SizeUnoptimized=1364),
|
||||
ExampleSizes("starszoom", cx16SizeOptimized=2406, cx16SizeUnoptimized=4863),
|
||||
ExampleSizes("starszoom", cx16SizeOptimized=2399, cx16SizeUnoptimized=4863),
|
||||
ExampleSizes("test_gfx_hires", cx16SizeOptimized=4835, cx16SizeUnoptimized=7514),
|
||||
ExampleSizes("test_gfx_lores", cx16SizeOptimized=5863, cx16SizeUnoptimized=7188),
|
||||
ExampleSizes("testmonogfx", cx16SizeOptimized=10494, cx16SizeUnoptimized=11982),
|
||||
ExampleSizes("test_gfx_lores", cx16SizeOptimized=5841, cx16SizeUnoptimized=7188),
|
||||
ExampleSizes("testmonogfx", cx16SizeOptimized=10463, cx16SizeUnoptimized=11982),
|
||||
ExampleSizes("textspotlight", cx16SizeOptimized=3431, cx16SizeUnoptimized=3788),
|
||||
),
|
||||
listOf(false, true)
|
||||
)
|
||||
|
||||
val target = Cx16Target()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyCx16.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedSize = if (optimize) params.first.cx16SizeOptimized else params.first.cx16SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
onlyCx16.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = if (optimize) params.cx16SizeOptimized else params.cx16SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -277,22 +273,20 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("balls", c64SizeOptimized=885, c64SizeUnoptimized=919, cx16SizeOptimized=874, cx16SizeUnoptimized=1188),
|
||||
ExampleSizes("boingball", c64SizeOptimized=1484, c64SizeUnoptimized=1544, cx16SizeOptimized=1486, cx16SizeUnoptimized=1962),
|
||||
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("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("maze", c64SizeOptimized=3100, c64SizeUnoptimized=3189, cx16SizeOptimized=2873, cx16SizeUnoptimized=3251),
|
||||
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("multitasking", c64SizeOptimized=1956, c64SizeUnoptimized=2029, cx16SizeOptimized=1826, cx16SizeUnoptimized=2159),
|
||||
@@ -302,45 +296,42 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
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=4339, c64SizeUnoptimized=4504, cx16SizeOptimized=6211, cx16SizeUnoptimized=6742),
|
||||
ExampleSizes("textelite", c64SizeOptimized=11440, c64SizeUnoptimized=12423, cx16SizeOptimized=10778, cx16SizeUnoptimized=13051),
|
||||
ExampleSizes("tehtriz", c64SizeOptimized=4333, c64SizeUnoptimized=4504, cx16SizeOptimized=6211, cx16SizeUnoptimized=6742),
|
||||
ExampleSizes("textelite", c64SizeOptimized=11440, c64SizeUnoptimized=12423, cx16SizeOptimized=10778, cx16SizeUnoptimized=13046),
|
||||
ExampleSizes("pointers/animalgame", c64SizeOptimized=1570, c64SizeUnoptimized=2126, cx16SizeOptimized=1605, cx16SizeUnoptimized=2412),
|
||||
ExampleSizes("pointers/binarytree", c64SizeOptimized=2552, c64SizeUnoptimized=2640, cx16SizeOptimized=2085, cx16SizeUnoptimized=2389),
|
||||
ExampleSizes("pointers/hashtable", c64SizeOptimized=3288, c64SizeUnoptimized=4140, cx16SizeOptimized=2832, cx16SizeUnoptimized=3786),
|
||||
ExampleSizes("pointers/sortedlist", c64SizeOptimized=1268, c64SizeUnoptimized=1319, cx16SizeOptimized=1223, cx16SizeUnoptimized=1532),
|
||||
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/sorting", c64SizeOptimized=1776, c64SizeUnoptimized=2676, cx16SizeOptimized=1788, cx16SizeUnoptimized=2659),
|
||||
),
|
||||
listOf(false, true),
|
||||
listOf(C64Target(), Cx16Target())
|
||||
)
|
||||
|
||||
withData(
|
||||
nameFn = { "${it.first.name}: ${it.third.first.name}, optimize=${it.second}" },
|
||||
bothCx16AndC64.map {
|
||||
val prep = prepareTestFiles(it.first.name, it.second, it.third)
|
||||
Triple(it.first, it.second, Triple(it.third, prep.first, prep.second))
|
||||
}
|
||||
) { params ->
|
||||
val filepath = params.third.third
|
||||
val optimize = params.second
|
||||
val target = params.third.first
|
||||
val exampleSizes = params.first
|
||||
val expectedSize = when (target) {
|
||||
is C64Target -> if (optimize) exampleSizes.c64SizeOptimized else exampleSizes.c64SizeUnoptimized
|
||||
is Cx16Target -> if (optimize) exampleSizes.cx16SizeOptimized else exampleSizes.cx16SizeUnoptimized
|
||||
else -> 0
|
||||
}
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
bothCx16AndC64.forEach { (exampleSizes, optimize, target) ->
|
||||
val prep = prepareTestFiles(exampleSizes.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = when (target) {
|
||||
is C64Target -> if (optimize) exampleSizes.c64SizeOptimized else exampleSizes.c64SizeUnoptimized
|
||||
is Cx16Target -> if (optimize) exampleSizes.cx16SizeOptimized else exampleSizes.cx16SizeUnoptimized
|
||||
else -> 0
|
||||
}
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
this.concurrency = 4
|
||||
this.threads = 4
|
||||
|
||||
val onlyVirtual = cartesianProduct(
|
||||
listOf(
|
||||
@@ -360,19 +351,19 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
)
|
||||
|
||||
val target = VMTarget()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyVirtual.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedInstrCount = if (optimize) params.first.virtualInstrCountOptimized else params.first.virtualInstrCountUnoptimized
|
||||
val expectedRegCount = if (optimize) params.first.virtualRegCountOptimized else params.first.virtualRegCountUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedInstrCount > 0) {
|
||||
verifyIrInstructionCount(result!!, expectedInstrCount)
|
||||
verifyIrRegisterCount(result, expectedRegCount)
|
||||
onlyVirtual.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedInstrCount = if (optimize) params.virtualInstrCountOptimized else params.virtualInstrCountUnoptimized
|
||||
val expectedRegCount = if (optimize) params.virtualRegCountOptimized else params.virtualRegCountUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedInstrCount > 0) {
|
||||
verifyIrInstructionCount(result!!, expectedInstrCount)
|
||||
verifyIrRegisterCount(result, expectedRegCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -380,10 +371,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
class TestCompilerOnExamplesPET32: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val onlyPET32 = cartesianProduct(
|
||||
listOf(
|
||||
@@ -394,17 +383,17 @@ class TestCompilerOnExamplesPET32: FunSpec({
|
||||
)
|
||||
|
||||
val target = PETTarget()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyPET32.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedSize = if (optimize) params.first.pet32SizeOptimized else params.first.pet32SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
onlyPET32.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = if (optimize) params.pet32SizeOptimized else params.pet32SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -28,10 +28,8 @@ class TestLookupSemantics: FunSpec({
|
||||
|
||||
fun compileAndGetSymbolTable(source: String): SymbolTable {
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = compileText(C64Target(), false, source, outputDir, errors = errors, writeAssembly = true)
|
||||
if(result == null) {
|
||||
throw AssertionError("Compilation failed: ${errors.errors.joinToString("; ")}")
|
||||
}
|
||||
val result = compileText(C64Target(), false, source, outputDir, errors = errors)
|
||||
?: throw AssertionError("Compilation failed: ${errors.errors.joinToString("; ")}")
|
||||
return result.codegenSymboltable
|
||||
?: throw AssertionError("No symbol table available")
|
||||
}
|
||||
@@ -53,9 +51,9 @@ class TestLookupSemantics: FunSpec({
|
||||
test("qualified lookup always starts from root") {
|
||||
val src = """
|
||||
main {
|
||||
uword value = 10
|
||||
uword @shared value = 10
|
||||
sub start() {
|
||||
uword result = main.value
|
||||
uword @shared result = main.value
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -66,9 +64,9 @@ class TestLookupSemantics: FunSpec({
|
||||
test("unqualified lookup walks parent chain") {
|
||||
val src = """
|
||||
main {
|
||||
uword value = 10
|
||||
uword @shared value = 10
|
||||
sub start() {
|
||||
uword result = value
|
||||
uword @shared result = value
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -82,9 +80,9 @@ class TestLookupSemantics: FunSpec({
|
||||
test("qualified lookup with multiple path segments") {
|
||||
val src = """
|
||||
main {
|
||||
uword deep = 100
|
||||
uword @shared deep = 100
|
||||
sub start() {
|
||||
uword result = main.deep
|
||||
uword @shared result = main.deep
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -99,10 +97,10 @@ class TestLookupSemantics: FunSpec({
|
||||
test("local variable shadows parent scope unqualified lookup") {
|
||||
val src = """
|
||||
main {
|
||||
uword value = 10
|
||||
uword @shared value = 10
|
||||
sub start() {
|
||||
uword value = 5
|
||||
uword result = value
|
||||
uword @shared value = 5
|
||||
uword @shared result = value
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -114,10 +112,10 @@ class TestLookupSemantics: FunSpec({
|
||||
test("qualified lookup unaffected by shadowing") {
|
||||
val src = """
|
||||
main {
|
||||
uword value = 10
|
||||
uword @shared value = 10
|
||||
sub start() {
|
||||
uword value = 5
|
||||
uword result = main.value
|
||||
uword @shared value = 5
|
||||
uword @shared result = main.value
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -129,11 +127,11 @@ class TestLookupSemantics: FunSpec({
|
||||
test("multiple levels of shadowing") {
|
||||
val src = """
|
||||
main {
|
||||
uword value = 1
|
||||
uword @shared value = 1
|
||||
sub start() {
|
||||
uword value = 2
|
||||
uword v1 = value
|
||||
uword v2 = main.value
|
||||
uword @shared value = 2
|
||||
uword @shared v1 = value
|
||||
uword @shared v2 = main.value
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -149,11 +147,11 @@ class TestLookupSemantics: FunSpec({
|
||||
test("qualified lookup across modules") {
|
||||
val src = """
|
||||
module1 {
|
||||
uword m1_var = 111
|
||||
uword @shared m1_var = 111
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
uword result = module1.m1_var
|
||||
uword @shared result = module1.m1_var
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -178,9 +176,9 @@ class TestLookupSemantics: FunSpec({
|
||||
test("nested block can access outer module symbols") {
|
||||
val src = """
|
||||
main {
|
||||
uword outer = 100
|
||||
uword @shared outer = 100
|
||||
sub start() {
|
||||
uword result = outer
|
||||
uword @shared result = outer
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -197,7 +195,7 @@ class TestLookupSemantics: FunSpec({
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
uword result = abs(-5)
|
||||
uword @shared result = abs(-5)
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -209,8 +207,8 @@ class TestLookupSemantics: FunSpec({
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
uword myabs = 10
|
||||
uword result = myabs
|
||||
uword @shared myabs = 10
|
||||
uword @shared result = myabs
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -232,7 +230,7 @@ class TestLookupSemantics: FunSpec({
|
||||
}
|
||||
sub start() {
|
||||
^^Point ptr
|
||||
word result = ptr.x
|
||||
word @shared result = ptr.x
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -210,9 +210,9 @@ class TestNumbers: FunSpec({
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
uword uw1 = -32768
|
||||
uword uw = -1
|
||||
ubyte ub = -1
|
||||
uword @shared uw1 = -32768
|
||||
uword @shared uw = -1
|
||||
ubyte @shared ub = -1
|
||||
uw = -2 as uword
|
||||
ub = -2 as ubyte
|
||||
}
|
||||
|
||||
@@ -76,6 +76,26 @@ main {
|
||||
compileText(C64Target(), true, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("do not inline asmsub with parameters") {
|
||||
// Parameterized asmsubs cannot be inlined by the AST Inliner because it doesn't understand
|
||||
// the low-level register mapping (like @A) and cannot generate the necessary code to load
|
||||
// the arguments into those registers.
|
||||
val src="""
|
||||
main {
|
||||
inline asmsub foo(ubyte a @A) {
|
||||
%asm {{
|
||||
inx
|
||||
}}
|
||||
}
|
||||
sub start() {
|
||||
foo(10)
|
||||
}
|
||||
}"""
|
||||
val result = compileText(C64Target(), true, src, outputDir)!!
|
||||
val startSub = result.compilerAst.entrypoint
|
||||
startSub.statements.any { it is FunctionCallStatement && it.target.nameInSource.last() == "foo" } shouldBe true
|
||||
}
|
||||
|
||||
test("don't remove empty subroutine if it's referenced") {
|
||||
val sourcecode = """
|
||||
main {
|
||||
@@ -1693,8 +1713,7 @@ main {
|
||||
hasVoidCall shouldBe false
|
||||
}
|
||||
|
||||
xtest("inline call with one return value and one parameter") {
|
||||
// DISABLED: Requires parameter substitution in expression context (not yet implemented)
|
||||
test("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 = """
|
||||
@@ -1724,8 +1743,7 @@ main {
|
||||
binExpr.right shouldBe instanceOf<NumericLiteral>()
|
||||
}
|
||||
|
||||
xtest("inline call with two return values and two parameters") {
|
||||
// DISABLED: Requires parameter substitution for multi-return calls (not yet implemented)
|
||||
test("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 = """
|
||||
|
||||
@@ -1213,9 +1213,9 @@ main {
|
||||
byte bb
|
||||
}
|
||||
|
||||
^^List l1 = 2000
|
||||
^^List l2 = 3000
|
||||
^^Foo f1 = 4000
|
||||
^^List @shared l1 = 2000
|
||||
^^List @shared l2 = 3000
|
||||
^^Foo @shared f1 = 4000
|
||||
|
||||
l1^^ = l2^^ ; memcpy1
|
||||
l1[3] = l2^^ ; memcpy2
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package prog8tests.compiler
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestPtrConstPromotion : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
val vmTarget = VMTarget()
|
||||
|
||||
test("pointer variable with 2 dereferences is promoted to const") {
|
||||
val src = """
|
||||
%import textio
|
||||
main {
|
||||
sub start() {
|
||||
^^ubyte ptr = memory("slab", 10, 1)
|
||||
ubyte a = ptr^^
|
||||
ubyte b = ptr^^
|
||||
txt.print_ub(a)
|
||||
txt.print_ub(b)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(vmTarget, true, src, outputDir)
|
||||
val mainBlock = result!!.compilerAst.allBlocks.first { it.name == "main" }
|
||||
val startSub = mainBlock.subScope("start")!!
|
||||
val ptrVar = startSub.lookup(listOf("ptr"))!!
|
||||
(ptrVar as VarDecl).type shouldBe VarDeclType.CONST
|
||||
}
|
||||
|
||||
test("pointer variable with 3 dereferences is NOT promoted to const") {
|
||||
val src = """
|
||||
%import textio
|
||||
main {
|
||||
sub start() {
|
||||
^^ubyte ptr = memory("slab", 10, 1)
|
||||
ubyte a = ptr^^
|
||||
ubyte b = ptr^^
|
||||
ubyte c = ptr^^
|
||||
txt.print_ub(a)
|
||||
txt.print_ub(b)
|
||||
txt.print_ub(c)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(vmTarget, true, src, outputDir)
|
||||
val mainBlock = result!!.compilerAst.allBlocks.first { it.name == "main" }
|
||||
val startSub = mainBlock.subScope("start")!!
|
||||
val ptrVar = startSub.lookup(listOf("ptr"))!!
|
||||
(ptrVar as VarDecl).type shouldBe VarDeclType.VAR
|
||||
}
|
||||
|
||||
test("pointer variable with indexing dereferences is handled") {
|
||||
val src = """
|
||||
%import textio
|
||||
main {
|
||||
sub start() {
|
||||
^^ubyte ptr = memory("slab", 10, 1)
|
||||
ubyte a = ptr[0]
|
||||
ubyte b = ptr[1]
|
||||
ubyte c = ptr[2]
|
||||
txt.print_ub(a)
|
||||
txt.print_ub(b)
|
||||
txt.print_ub(c)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(vmTarget, true, src, outputDir)
|
||||
val mainBlock = result!!.compilerAst.allBlocks.first { it.name == "main" }
|
||||
val startSub = mainBlock.subScope("start")!!
|
||||
val ptrVar = startSub.lookup(listOf("ptr"))!!
|
||||
(ptrVar as VarDecl).type shouldBe VarDeclType.VAR
|
||||
}
|
||||
})
|
||||
@@ -317,7 +317,7 @@ class TestScoping: FunSpec({
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
byte wrongaddress = 100
|
||||
byte @shared wrongaddress = 100
|
||||
|
||||
goto wrongaddress ; must be uword
|
||||
goto main.routine ; can't take args
|
||||
|
||||
@@ -361,8 +361,8 @@ class TestStringEncodings: FunSpec({
|
||||
str string1 = "default"
|
||||
str string2 = petscii:"petscii"
|
||||
|
||||
ubyte char1 = 'd'
|
||||
ubyte char2 = petscii:'p'
|
||||
ubyte @shared char1 = 'd'
|
||||
ubyte @shared char2 = petscii:'p'
|
||||
|
||||
sub start() {
|
||||
}
|
||||
@@ -389,8 +389,8 @@ class TestStringEncodings: FunSpec({
|
||||
str string1 = "default"
|
||||
str string2 = petscii:"petscii"
|
||||
|
||||
ubyte char1 = 'd'
|
||||
ubyte char2 = petscii:'p'
|
||||
ubyte @shared char1 = 'd'
|
||||
ubyte @shared char2 = petscii:'p'
|
||||
|
||||
sub start() {
|
||||
}
|
||||
|
||||
@@ -570,4 +570,32 @@ main {
|
||||
}"""
|
||||
compileText(C64Target(), optimize=false, src, outputDir) shouldNotBe null
|
||||
}
|
||||
|
||||
test("empty enum declaration should give error") {
|
||||
val src="""
|
||||
main {
|
||||
enum Color {
|
||||
}
|
||||
sub start() { }
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors, false)
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "enumeration must contain at least one member"
|
||||
}
|
||||
|
||||
test("empty struct declaration should give error") {
|
||||
val src="""
|
||||
main {
|
||||
struct Node {
|
||||
}
|
||||
sub start() { }
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors, false)
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "struct must contain at least one field"
|
||||
}
|
||||
})
|
||||
@@ -13,37 +13,72 @@ import prog8tests.helpers.compileText
|
||||
class TestPointerConstPromotion : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("pointer constant promotion scenarios") {
|
||||
test("zp pointer variable with a dereference is NOT promoted to const") {
|
||||
val src = """
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
main {
|
||||
sub start() {
|
||||
^^uword ptr1 = 4444 ; size 2 > 1, no promotion
|
||||
^^ubyte ptr2 = 5555 ; size 1, promotion
|
||||
uword mem1 = memory("mem1", 10, 0) ; not a pointer, promotion
|
||||
^^uword mem1b = memory("mem2", 10, 0) ; size 2 > 1, no promotion
|
||||
^^ubyte mem3 = memory("mem3", 10, 0) ; size 1, promotion
|
||||
|
||||
cx16.r0 = ptr1
|
||||
cx16.r1 = ptr2 as uword
|
||||
cx16.r2 = mem1
|
||||
cx16.r3 = mem1b
|
||||
cx16.r4 = mem3 as uword
|
||||
^^ubyte @zp ptr = 9999
|
||||
ubyte @shared a = ptr^^
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(VMTarget(), true, src, outputDir)!!
|
||||
val mainSub = result.compilerAst.entrypoint
|
||||
val decls = mainSub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||
|
||||
decls["ptr1"]?.type shouldBe VarDeclType.VAR
|
||||
decls["ptr2"]?.type shouldBe VarDeclType.CONST
|
||||
decls["mem1"]?.type shouldBe VarDeclType.CONST
|
||||
decls["mem1b"]?.type shouldBe VarDeclType.VAR
|
||||
decls["mem3"]?.type shouldBe VarDeclType.CONST
|
||||
val result = compileText(VMTarget(), true, src, outputDir)
|
||||
val mainBlock = result!!.compilerAst.allBlocks.first { it.name == "main" }
|
||||
val startSub = mainBlock.subScope("start")!!
|
||||
val ptrVar = startSub.lookup(listOf("ptr"))!!
|
||||
(ptrVar as VarDecl).type shouldBe VarDeclType.VAR
|
||||
}
|
||||
|
||||
test("nonzp pointer variable with a dereference is promoted to const") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
^^ubyte @nozp ptr = 9999
|
||||
ubyte @shared a = ptr^^
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(VMTarget(), true, src, outputDir)
|
||||
val mainBlock = result!!.compilerAst.allBlocks.first { it.name == "main" }
|
||||
val startSub = mainBlock.subScope("start")!!
|
||||
val ptrVar = startSub.lookup(listOf("ptr"))!!
|
||||
(ptrVar as VarDecl).type shouldBe VarDeclType.CONST
|
||||
}
|
||||
|
||||
test("pointer variable with memory is not promoted to const") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
^^ubyte ptr = memory("slab", 10, 1)
|
||||
ubyte @shared a = ptr^^
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(VMTarget(), true, src, outputDir)
|
||||
val mainBlock = result!!.compilerAst.allBlocks.first { it.name == "main" }
|
||||
val startSub = mainBlock.subScope("start")!!
|
||||
val ptrVar = startSub.lookup(listOf("ptr"))!!
|
||||
(ptrVar as VarDecl).type shouldBe VarDeclType.VAR
|
||||
}
|
||||
|
||||
test("pointer variable with 3 dereferences is NOT promoted to const") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
^^ubyte ptr = 9999
|
||||
ubyte @shared a = ptr^^
|
||||
ubyte @shared b = ptr^^
|
||||
ubyte @shared c = ptr^^
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(VMTarget(), true, src, outputDir)
|
||||
val mainBlock = result!!.compilerAst.allBlocks.first { it.name == "main" }
|
||||
val startSub = mainBlock.subScope("start")!!
|
||||
val ptrVar = startSub.lookup(listOf("ptr"))!!
|
||||
(ptrVar as VarDecl).type shouldBe VarDeclType.VAR
|
||||
}
|
||||
|
||||
test("explicit const pointer to >1 byte type gives error") {
|
||||
val src = """
|
||||
main {
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package prog8tests.ast
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestPrivateAccess : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("cannot access private variable from outside its block") {
|
||||
val text = """
|
||||
M {
|
||||
private ubyte x = 10
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
ubyte v = M.x
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(VMTarget(), true, text, outputDir, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "cannot access private variable 'M.x' from outside its block"
|
||||
}
|
||||
|
||||
test("cannot access private subroutine from outside its block") {
|
||||
val text = """
|
||||
M {
|
||||
private sub foo() {}
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
M.foo()
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(VMTarget(), true, text, outputDir, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "cannot access private subroutine 'M.foo' from outside its block"
|
||||
}
|
||||
|
||||
test("cannot access private struct from outside its block") {
|
||||
val text = """
|
||||
M {
|
||||
private struct S { ubyte f }
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
ubyte v = sizeof(M.S)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(VMTarget(), true, text, outputDir, errors = errors) shouldBe null
|
||||
errors.errors.any { it.contains("cannot access private struct 'M.S' from outside its block") } shouldBe true
|
||||
}
|
||||
|
||||
test("cannot access private enum from outside its block") {
|
||||
val text = """
|
||||
M {
|
||||
private enum E { A, B }
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
ubyte v = M.E::A
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(VMTarget(), true, text, outputDir, errors = errors) shouldBe null
|
||||
// Enum members are reported as private variable 'M.E::A' because they're converted to constant variables
|
||||
// that inherit the private status of the enum.
|
||||
errors.errors.any { it.contains("cannot access private variable 'M.E::A' from outside its block") } shouldBe true
|
||||
}
|
||||
|
||||
test("cannot access private asmsub from outside its block") {
|
||||
val text = """
|
||||
M {
|
||||
private asmsub foo() {
|
||||
%asm {{
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
M.foo()
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(VMTarget(), true, text, outputDir, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "cannot access private subroutine 'M.foo' from outside its block"
|
||||
}
|
||||
|
||||
test("cannot access private extsub from outside its block") {
|
||||
val text = $$"""
|
||||
M {
|
||||
private extsub $c000 = foo()
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
M.foo()
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(VMTarget(), true, text, outputDir, errors = errors) shouldBe null
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "cannot access private subroutine 'M.foo' from outside its block"
|
||||
}
|
||||
|
||||
test("can access private elements from within the same block") {
|
||||
val text = """
|
||||
other {
|
||||
private ubyte x = 10
|
||||
private sub foo() {}
|
||||
private asmsub bar_asm() {
|
||||
rts
|
||||
}
|
||||
private struct S { ubyte f }
|
||||
|
||||
sub bar() {
|
||||
x = 20
|
||||
foo()
|
||||
bar_asm()
|
||||
S* myS = 0
|
||||
}
|
||||
}
|
||||
main {
|
||||
sub start() {
|
||||
other.bar()
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(VMTarget(), true, text, outputDir, errors = errors, writeAssembly = false)
|
||||
errors.errors.size shouldBe 0
|
||||
}
|
||||
})
|
||||
@@ -918,9 +918,9 @@ class TestProg8Parser: FunSpec( {
|
||||
main {
|
||||
sub start() {
|
||||
str string = "\x00\xff\u0041"
|
||||
ubyte zero = '\x00'
|
||||
ubyte ff = '\xff'
|
||||
ubyte letter = '\u0041'
|
||||
ubyte @shared zero = '\x00'
|
||||
ubyte @shared ff = '\xff'
|
||||
ubyte @shared letter = '\u0041'
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -997,10 +997,10 @@ class TestProg8Parser: FunSpec( {
|
||||
sub start() {
|
||||
ubyte @zp @shared @requirezp var1
|
||||
ubyte @shared @zp var2
|
||||
ubyte @zp var3
|
||||
ubyte @zp @shared var3
|
||||
ubyte @shared var4
|
||||
ubyte @requirezp var5
|
||||
ubyte var6
|
||||
ubyte @requirezp @shared var5
|
||||
ubyte @shared var6
|
||||
}
|
||||
}
|
||||
"""
|
||||
@@ -1014,16 +1014,16 @@ class TestProg8Parser: FunSpec( {
|
||||
var2.sharedWithAsm shouldBe true
|
||||
var2.zeropage shouldBe ZeropageWish.PREFER_ZEROPAGE
|
||||
val var3 = stmt[4] as VarDecl
|
||||
var3.sharedWithAsm shouldBe false
|
||||
var3.sharedWithAsm shouldBe true
|
||||
var3.zeropage shouldBe ZeropageWish.PREFER_ZEROPAGE
|
||||
val var4 = stmt[6] as VarDecl
|
||||
var4.sharedWithAsm shouldBe true
|
||||
var4.zeropage shouldBe ZeropageWish.DONTCARE
|
||||
val var5 = stmt[8] as VarDecl
|
||||
var5.sharedWithAsm shouldBe false
|
||||
var5.sharedWithAsm shouldBe true
|
||||
var5.zeropage shouldBe ZeropageWish.REQUIRE_ZEROPAGE
|
||||
val var6 = stmt[10] as VarDecl
|
||||
var6.sharedWithAsm shouldBe false
|
||||
var6.sharedWithAsm shouldBe true
|
||||
var6.zeropage shouldBe ZeropageWish.DONTCARE
|
||||
}
|
||||
|
||||
@@ -1089,10 +1089,10 @@ main {
|
||||
%option enable_floats
|
||||
main {
|
||||
sub start() {
|
||||
uword w1 = 000_1234_5__
|
||||
uword w2 = ${'$'}ff_ee
|
||||
uword w3 = %11_0000_111111__0000
|
||||
float fl = 3_000_001.141_592_654
|
||||
uword @shared w1 = 000_1234_5__
|
||||
uword @shared w2 = ${'$'}ff_ee
|
||||
uword @shared w3 = %11_0000_111111__0000
|
||||
float @shared fl = 3_000_001.141_592_654
|
||||
}
|
||||
}"""
|
||||
val result = compileText(VMTarget(), false, src, outputDir, writeAssembly = false)!!
|
||||
|
||||
@@ -14,6 +14,7 @@ import prog8.code.core.DataType
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
@@ -279,4 +280,24 @@ main {
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "doesn't end with a return"
|
||||
}
|
||||
|
||||
test("asmsub body restriction") {
|
||||
val src="""
|
||||
main {
|
||||
asmsub foo() {
|
||||
ubyte x = 1
|
||||
}
|
||||
asmsub ok1() {
|
||||
%asm {{ nop }}
|
||||
}
|
||||
asmsub ok2() {
|
||||
%asm {{ nop }}
|
||||
%asm {{ nop }}
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests()
|
||||
compileText(VMTarget(), false, src, outputDir, errors, false)
|
||||
errors.errors.size shouldBe 1
|
||||
errors.errors[0] shouldContain "asmsub can only contain inline assembly (%asm)"
|
||||
}
|
||||
})
|
||||
@@ -458,11 +458,11 @@ main {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte w
|
||||
ubyte @shared w
|
||||
|
||||
for w in 0 to 20 {
|
||||
ubyte @zp x,y,z=13
|
||||
ubyte q,r,s
|
||||
ubyte @shared @zp x,y,z=13
|
||||
ubyte @shared q,r,s
|
||||
x++
|
||||
y++
|
||||
z++
|
||||
@@ -471,28 +471,6 @@ main {
|
||||
}"""
|
||||
val result = compileText(VMTarget(), optimize = false, src, outputDir, writeAssembly = false)!!
|
||||
val st = result.compilerAst.entrypoint.statements
|
||||
/*
|
||||
sub start () {
|
||||
ubyte s
|
||||
s = 0
|
||||
ubyte r
|
||||
r = 0
|
||||
ubyte q
|
||||
q = 0
|
||||
ubyte @zp z
|
||||
ubyte @zp y
|
||||
ubyte @zp x
|
||||
ubyte w
|
||||
for w in 0 to 20 step 1 {
|
||||
z = 13
|
||||
y = 13
|
||||
x = 13
|
||||
x++
|
||||
y++
|
||||
z++
|
||||
}
|
||||
}
|
||||
*/
|
||||
val vars = st.filterIsInstance<VarDecl>()
|
||||
vars.size shouldBe 7
|
||||
vars.all { it.names.size<=1 } shouldBe true
|
||||
@@ -724,8 +702,8 @@ main {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
ubyte shift = 10
|
||||
uword value = 1<<shift
|
||||
ubyte @shared shift = 10
|
||||
uword @shared value = 1<<shift
|
||||
value++
|
||||
value = 1<<shift
|
||||
value++
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.assertions.fail
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
@@ -24,7 +25,7 @@ class Test2DArrays: FunSpec({
|
||||
ubyte[3][4] m
|
||||
m[0][0] = 1
|
||||
m[2][3] = 12
|
||||
ubyte v = m[0][0]
|
||||
ubyte @shared v = m[0][0]
|
||||
}
|
||||
}""",
|
||||
// Variable indexing
|
||||
@@ -32,10 +33,10 @@ class Test2DArrays: FunSpec({
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[3][4] m
|
||||
ubyte r = 1
|
||||
ubyte c = 2
|
||||
ubyte @shared r = 1
|
||||
ubyte @shared c = 2
|
||||
m[r][c] = 42
|
||||
ubyte v = m[r][c]
|
||||
ubyte @shared v = m[r][c]
|
||||
}
|
||||
}""",
|
||||
// Used in expressions
|
||||
@@ -45,7 +46,7 @@ class Test2DArrays: FunSpec({
|
||||
ubyte[2][3] m
|
||||
m[0][0] = 10
|
||||
m[1][1] = 15
|
||||
ubyte s = m[0][0] + m[1][1]
|
||||
ubyte @shared s = m[0][0] + m[1][1]
|
||||
}
|
||||
}""",
|
||||
// As function argument
|
||||
@@ -73,8 +74,8 @@ class Test2DArrays: FunSpec({
|
||||
main {
|
||||
ubyte[4][4] m
|
||||
sub start() {
|
||||
ubyte r = 0
|
||||
ubyte c = 0
|
||||
ubyte @shared r = 0
|
||||
ubyte @shared c = 0
|
||||
for r in 0 to 3 {
|
||||
for c in 0 to 3 {
|
||||
m[r][c] = r * c
|
||||
@@ -87,13 +88,14 @@ class Test2DArrays: FunSpec({
|
||||
main {
|
||||
sub start() {
|
||||
ubyte[3][4] m
|
||||
uword addr = &m
|
||||
uword @shared addr = &m
|
||||
}
|
||||
}"""
|
||||
)
|
||||
for(src in sources) {
|
||||
compileText(VMTarget(), false, src, outputDir) shouldNotBe null
|
||||
compileText(C64Target(), false, src, outputDir) shouldNotBe null
|
||||
val result = compileText(VMTarget(), false, src, outputDir) ?: fail("Compilation failed for source:\n$src")
|
||||
val resultc64 =
|
||||
compileText(C64Target(), false, src, outputDir) ?: fail("Compilation failed for source:\n$src")
|
||||
}
|
||||
// Also verify optimized compilation
|
||||
compileText(VMTarget(), true, sources[0], outputDir) shouldNotBe null
|
||||
|
||||
@@ -14,8 +14,8 @@ class TestComparisonIssues : FunSpec({
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long a = 1000
|
||||
long b = 2000
|
||||
long @shared a = 1000
|
||||
long @shared b = 2000
|
||||
a += b
|
||||
}
|
||||
}
|
||||
@@ -23,15 +23,19 @@ class TestComparisonIssues : FunSpec({
|
||||
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
|
||||
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val asm = asmFile.readText()
|
||||
asm.shouldContainInOrder("ldx", "#252", "lda", "p8v_a+4,x", "adc", "p8v_b+4,x", "sta", "p8v_a+4,x", "inx", "bne")
|
||||
asm.shouldContainInOrder("clc",
|
||||
"lda", "p8v_a+0", "adc", "p8v_b+0", "sta", "p8v_a+0",
|
||||
"lda", "p8v_a+1", "adc", "p8v_b+1", "sta", "p8v_a+1",
|
||||
"lda", "p8v_a+2", "adc", "p8v_b+2", "sta", "p8v_a+2",
|
||||
"lda", "p8v_a+3", "adc", "p8v_b+3", "sta", "p8v_a+3")
|
||||
}
|
||||
|
||||
test("long augmented subtraction generates loop") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long a = 2000
|
||||
long b = 1000
|
||||
long @shared a = 2000
|
||||
long @shared b = 1000
|
||||
a -= b
|
||||
}
|
||||
}
|
||||
@@ -39,14 +43,35 @@ class TestComparisonIssues : FunSpec({
|
||||
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
|
||||
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val asm = asmFile.readText()
|
||||
asm.shouldContainInOrder("ldx", "#252", "lda", "p8v_a+4,x", "sbc", "p8v_b+4,x", "sta", "p8v_a+4,x", "inx", "bne")
|
||||
asm.shouldContainInOrder("sec",
|
||||
"lda", "p8v_a+0", "sbc", "p8v_b+0", "sta", "p8v_a+0",
|
||||
"lda", "p8v_a+1", "sbc", "p8v_b+1", "sta", "p8v_a+1",
|
||||
"lda", "p8v_a+2", "sbc", "p8v_b+2", "sta", "p8v_a+2",
|
||||
"lda", "p8v_a+3", "sbc", "p8v_b+3", "sta", "p8v_a+3")
|
||||
}
|
||||
|
||||
test("long augmented addition generates loop with nozp variables") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @nozp a = 1000
|
||||
long @nozp b = 2000
|
||||
a += b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
|
||||
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val asm = asmFile.readText()
|
||||
// verify unrolled structure
|
||||
asm.shouldContainInOrder("clc", "lda", "adc", "sta", "lda", "adc", "sta", "lda", "adc", "sta", "lda", "adc", "sta")
|
||||
}
|
||||
|
||||
test("word <= 0 signed comparison (identifier)") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
word w = -1
|
||||
word @shared w = -1
|
||||
if w <= 0 {
|
||||
cx16.r0 = 1
|
||||
}
|
||||
@@ -64,7 +89,7 @@ class TestComparisonIssues : FunSpec({
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
word w = 1
|
||||
word @shared w = 1
|
||||
if w > 0 {
|
||||
cx16.r0 = 1
|
||||
}
|
||||
@@ -82,7 +107,7 @@ class TestComparisonIssues : FunSpec({
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
uword uw = 0
|
||||
uword @shared uw = 0
|
||||
if uw <= 0 {
|
||||
cx16.r0 = 1
|
||||
}
|
||||
@@ -100,7 +125,7 @@ class TestComparisonIssues : FunSpec({
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
word w = 1
|
||||
word @shared w = 1
|
||||
if w <= 0 {
|
||||
cx16.r0 = 1
|
||||
} else {
|
||||
|
||||
@@ -115,10 +115,63 @@ class TestExecution6502 : FunSpec({
|
||||
}
|
||||
""".trimIndent()
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
val machine = compileResult!!.simulate()
|
||||
val machine = compileResult!!.simulate(1000)
|
||||
machine.cpu.totalCycles shouldBeLessThan 200
|
||||
}
|
||||
|
||||
test("long repeat loops") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
&ubyte poweroff = $f203
|
||||
&long result = $02 ; result is stored in memory at $02
|
||||
|
||||
sub start() {
|
||||
long @shared qq = 0
|
||||
repeat 70000 {
|
||||
qq++
|
||||
}
|
||||
result = qq
|
||||
|
||||
long @shared iters = 70000
|
||||
repeat iters {
|
||||
qq++
|
||||
}
|
||||
result = qq
|
||||
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
// 140000 iterations * ~40 cycles = 5.6 million. Let's use 10 million to be safe.
|
||||
val machine = compileResult!!.simulate(maxCycles = 10000000L)
|
||||
machine.assertMemory(0x02, (140000L and 0xff).toInt())
|
||||
machine.assertMemory(0x03, ((140000L shr 8) and 0xff).toInt())
|
||||
machine.assertMemory(0x04, ((140000L shr 16) and 0xff).toInt())
|
||||
machine.assertMemory(0x05, ((140000L shr 24) and 0xff).toInt())
|
||||
}
|
||||
|
||||
test("top level RTS exits simulator") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
main {
|
||||
sub start() {
|
||||
; do nothing just RTS
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
val machine = compileResult!!.simulate(1000)
|
||||
machine.cpu.totalCycles shouldBeLessThan 250
|
||||
}
|
||||
|
||||
test("reset register") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
@@ -287,4 +340,42 @@ class TestExecution6502 : FunSpec({
|
||||
compileResult!!.simulate(maxCycles = 100)
|
||||
}
|
||||
}
|
||||
|
||||
test("div_longs positive 1234567890 / 88888 = 13889") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
&ubyte poweroff = $f203
|
||||
sub start() {
|
||||
long @shared v = 1234567890
|
||||
v /= 88888
|
||||
%asm {{
|
||||
lda p8v_v
|
||||
sta $2000
|
||||
lda p8v_v+1
|
||||
sta $2001
|
||||
lda p8v_v+2
|
||||
sta $2002
|
||||
lda p8v_v+3
|
||||
sta $2003
|
||||
}}
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
val machine = compileResult!!.simulate(maxCycles = 30000)
|
||||
val b0 = machine.ram[0x2000].toInt() and 0xff
|
||||
val b1 = machine.ram[0x2001].toInt() and 0xff
|
||||
val b2 = machine.ram[0x2002].toInt() and 0xff
|
||||
val b3 = machine.ram[0x2003].toInt() and 0xff
|
||||
println("div_longs result bytes: $b0 $b1 $b2 $b3 (hex: ${b0.toString(16)} ${b1.toString(16)} ${b2.toString(16)} ${b3.toString(16)})")
|
||||
machine.assertMemory(0x2000, 0x41)
|
||||
machine.assertMemory(0x2001, 0x36)
|
||||
machine.assertMemory(0x2002, 0x00)
|
||||
machine.assertMemory(0x2003, 0x00)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,46 @@
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestExperiCodeGen: FunSpec({
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("banked extsub with multiple return values (constbank)") {
|
||||
val src = $$"""
|
||||
main {
|
||||
sub start() {
|
||||
uword ptr
|
||||
ubyte bank
|
||||
void, ptr, bank = mymodule.myfunc(0)
|
||||
}
|
||||
}
|
||||
|
||||
mymodule {
|
||||
const ubyte MYBANK = 1
|
||||
extsub @bank MYBANK $A000 = myfunc(ubyte x @X) -> bool @Pc, uword @XY, ubyte @A
|
||||
}"""
|
||||
compileText(Cx16Target(), false, src, outputDir, experimentalCodegen = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("banked extsub with multiple return values (varbank)") {
|
||||
val src = $$"""
|
||||
main {
|
||||
sub start() {
|
||||
uword ptr
|
||||
ubyte bank
|
||||
void, ptr, bank = mymodule.myfunc(0)
|
||||
}
|
||||
}
|
||||
|
||||
mymodule {
|
||||
ubyte @shared bankvar = 1
|
||||
extsub @bank bankvar $A000 = myfunc(ubyte x @X) -> bool @Pc, uword @XY, ubyte @A
|
||||
}"""
|
||||
compileText(Cx16Target(), false, src, outputDir, experimentalCodegen = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -210,8 +210,8 @@ test("if-expression bitwise AND optimization (BIT instruction)") {
|
||||
main {
|
||||
word[10] @shared words
|
||||
sub start() {
|
||||
ubyte i = 5
|
||||
ubyte result = if words[i] < 0 then 1 else 0
|
||||
ubyte @shared i = 5
|
||||
ubyte @shared result = if words[i] < 0 then 1 else 0
|
||||
cx16.r1L = result
|
||||
}
|
||||
}
|
||||
@@ -228,8 +228,8 @@ test("if-expression bitwise AND optimization (BIT instruction)") {
|
||||
main {
|
||||
long[10] @shared longs
|
||||
sub start() {
|
||||
ubyte i = 2
|
||||
ubyte result = if longs[i] > 0 then 1 else 0
|
||||
ubyte @shared i = 2
|
||||
ubyte @shared result = if longs[i] > 0 then 1 else 0
|
||||
cx16.r1L = result
|
||||
}
|
||||
}
|
||||
@@ -279,8 +279,8 @@ test("if-expression bitwise AND optimization (BIT instruction)") {
|
||||
main {
|
||||
uword[10] @shared words
|
||||
sub start() {
|
||||
ubyte i = 5
|
||||
ubyte result = if words[i] == 0 then 1 else 0
|
||||
ubyte @shared i = 5
|
||||
ubyte @shared result = if words[i] == 0 then 1 else 0
|
||||
cx16.r1L = result
|
||||
}
|
||||
}
|
||||
@@ -295,8 +295,8 @@ test("if-expression bitwise AND optimization (BIT instruction)") {
|
||||
main {
|
||||
word[10] @shared words
|
||||
sub start() {
|
||||
ubyte i = 2
|
||||
ubyte result = if words[i] != 0 then 1 else 0
|
||||
ubyte @shared i = 2
|
||||
ubyte @shared result = if words[i] != 0 then 1 else 0
|
||||
cx16.r1L = result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestLongAugmentedAssignment : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("long augmented assignment with literal") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared a = 1000
|
||||
a += 500
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
// Should compile successfully
|
||||
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
|
||||
}
|
||||
|
||||
test("long augmented assignment with expression") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared a = 1000
|
||||
long @shared b = 500
|
||||
a += b + 100
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
|
||||
}
|
||||
|
||||
test("long augmented assignment with ubyte") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared a = 1000
|
||||
ubyte @shared b = 200
|
||||
a += b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
|
||||
}
|
||||
})
|
||||
@@ -18,6 +18,7 @@ import prog8.code.target.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.shouldContainInOrder
|
||||
import prog8tests.helpers.simulate
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class TestVariousCodeGen: FunSpec({
|
||||
@@ -97,9 +98,9 @@ main {
|
||||
main {
|
||||
sub start() {
|
||||
uword[3] arr
|
||||
ubyte i = 42
|
||||
uword ww = peekw(arr[i])
|
||||
ubyte xx = peek(arr[i])
|
||||
ubyte @shared i = 42
|
||||
uword @shared ww = peekw(arr[i])
|
||||
ubyte @shared xx = peek(arr[i])
|
||||
xx = @(arr[i])
|
||||
|
||||
@(arr[i]) = 42
|
||||
@@ -114,7 +115,7 @@ main {
|
||||
val text="""
|
||||
main {
|
||||
sub start() {
|
||||
uword factor1
|
||||
uword @shared factor1
|
||||
ubyte[3] bytearray
|
||||
uword[3] wordarray
|
||||
@(factor1) = bytearray[0]
|
||||
@@ -308,10 +309,10 @@ main
|
||||
{
|
||||
sub start()
|
||||
{
|
||||
uword uw = 54321
|
||||
ubyte ub = 123
|
||||
word sw = -12345
|
||||
byte sb = -123
|
||||
uword @shared uw = 54321
|
||||
ubyte @shared ub = 123
|
||||
word @shared sw = -12345
|
||||
byte @shared sb = -123
|
||||
|
||||
func_uw(~ub as uword)
|
||||
func_ub(~uw as ubyte)
|
||||
@@ -904,4 +905,93 @@ main {
|
||||
asm shouldContain "sta (p8v_s),y"
|
||||
}
|
||||
}
|
||||
|
||||
test("IfExpression string to pointer typecast") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
str a = "hello"
|
||||
str b = "world"
|
||||
bool flag = true
|
||||
^^ubyte p = if flag then a else b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// This should not crash during compilation
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
|
||||
// Also test with virtual target
|
||||
val compileResultVM = compileText(VMTarget(), false, src, outputDir)
|
||||
compileResultVM shouldNotBe null
|
||||
}
|
||||
|
||||
test("BranchConditionExpression string to pointer typecast") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
str a = "hello"
|
||||
str b = "world"
|
||||
^^ubyte p = if_z then a else b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// This should not crash during compilation
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
}
|
||||
|
||||
test("pointer assignment to variable in 6502 backend") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
ubyte value = 42
|
||||
^^ubyte @shared ptr = 0
|
||||
|
||||
sub start() {
|
||||
ptr = &value
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// This should not crash during compilation
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
|
||||
// Verify it actually works by simulating
|
||||
compileResult!!.simulate()
|
||||
}
|
||||
|
||||
test("pointer assignment to array element in 6502 backend") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
ubyte value = 42
|
||||
^^ubyte[2] ptrs
|
||||
|
||||
sub start() {
|
||||
ptrs[1] = &value
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -11,7 +11,7 @@ fun String.shouldContainInOrder(vararg substrings: String) {
|
||||
for (substring in substrings) {
|
||||
val index = this.indexOf(substring, currentSearchIndex)
|
||||
if (index == -1) {
|
||||
fail("Could not find '$substring' after index $currentSearchIndex in string:\n$this")
|
||||
fail("Could not find '$substring' after index $currentSearchIndex")
|
||||
}
|
||||
currentSearchIndex = index + substring.length
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ internal fun compileFile(
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
varshigh: Int? = null,
|
||||
slabshigh: Int? = null
|
||||
slabshigh: Int? = null,
|
||||
experimentalCodegen: Boolean = false
|
||||
) : CompilationResult? {
|
||||
val filepath = fileDir.resolve(fileName)
|
||||
assumeReadableFile(filepath)
|
||||
@@ -33,7 +34,7 @@ internal fun compileFile(
|
||||
showTimings = false,
|
||||
asmListfile = false,
|
||||
includeSourcelines = false,
|
||||
experimentalCodegen = false,
|
||||
experimentalCodegen = experimentalCodegen,
|
||||
dumpVariables = false,
|
||||
dumpSymbols = false,
|
||||
varsHighBank = varshigh,
|
||||
@@ -67,11 +68,13 @@ internal fun compileText(
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
varshigh: Int? = null,
|
||||
slabshigh: Int? = null
|
||||
slabshigh: Int? = null,
|
||||
experimentalCodegen: Boolean = false
|
||||
) : CompilationResult? {
|
||||
val filePath = outputDir.resolve("on_the_fly_test_${sourceText.hashCode().toUInt().toString(16)}.p8")
|
||||
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||
filePath.toFile().writeText(sourceText)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name, outputDir,
|
||||
errors=errors, writeAssembly=writeAssembly, varshigh=varshigh, slabshigh=slabshigh)
|
||||
errors=errors, writeAssembly=writeAssembly, varshigh=varshigh, slabshigh=slabshigh,
|
||||
experimentalCodegen = experimentalCodegen)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package prog8tests.optimizer
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestExpressionSimplifier : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("long & -1 is correctly simplified") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared x = 1000
|
||||
long @shared y = x & -1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(Cx16Target(), true, text, outputDir, writeAssembly = false)!!
|
||||
val s = result.compilerAst.entrypoint.statements
|
||||
val expr = (s[3] as Assignment).value as BinaryExpression
|
||||
expr.operator shouldBe "&"
|
||||
(expr.left as IdentifierReference).nameInSource shouldBe listOf("x")
|
||||
(expr.right as NumericLiteral).number shouldBe -1.0
|
||||
}
|
||||
|
||||
test("long & 255 is correctly handled") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared x = 1000
|
||||
long @shared y = x & 255
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(Cx16Target(), true, text, outputDir, writeAssembly = false)!!
|
||||
val s = result.compilerAst.entrypoint.statements
|
||||
val expr = (s[3] as Assignment).value as BinaryExpression
|
||||
expr.operator shouldBe "&"
|
||||
(expr.left as IdentifierReference).nameInSource shouldBe listOf("x")
|
||||
(expr.right as NumericLiteral).number shouldBe 255.0
|
||||
}
|
||||
})
|
||||
@@ -375,7 +375,7 @@ main {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
bool flag = true
|
||||
bool @shared flag = true
|
||||
cx16.r0L = if flag then 1 else 0
|
||||
}
|
||||
}"""
|
||||
@@ -398,7 +398,7 @@ main {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
bool flag = true
|
||||
bool @shared flag = true
|
||||
if flag {
|
||||
cx16.r0L = 1
|
||||
} else {
|
||||
|
||||
@@ -127,6 +127,9 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
if(decl.origin==VarDeclOrigin.SUBROUTINEPARAM)
|
||||
return
|
||||
|
||||
if(decl.isPrivate)
|
||||
output("private ")
|
||||
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {}
|
||||
VarDeclType.CONST -> output("const ")
|
||||
@@ -187,6 +190,8 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(struct: StructDecl) {
|
||||
if(struct.isPrivate)
|
||||
output("private ")
|
||||
outputln("struct ${struct.name} {")
|
||||
for(member in struct.fields) {
|
||||
outputlni( " ${member.first} ${member.second}")
|
||||
@@ -194,9 +199,25 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
outputlni("}")
|
||||
}
|
||||
|
||||
override fun visit(enum: Enumeration) {
|
||||
if(enum.isPrivate)
|
||||
output("private ")
|
||||
output("enum ${enum.name} { ")
|
||||
for(member in enum.members) {
|
||||
output(member.first)
|
||||
if(member.second!=null)
|
||||
output(" = ${member.second}")
|
||||
if(member !== enum.members.last())
|
||||
output(", ")
|
||||
}
|
||||
outputln(" }")
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
output("\n")
|
||||
outputi("")
|
||||
if(subroutine.isPrivate)
|
||||
output("private ")
|
||||
if(subroutine.inline)
|
||||
output("inline ")
|
||||
if(subroutine.isAsmSubroutine) {
|
||||
@@ -569,6 +590,8 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program:
|
||||
}
|
||||
|
||||
override fun visit(alias: Alias) {
|
||||
if(alias.isPrivate)
|
||||
output("private ")
|
||||
output("alias ${alias.alias} = ${alias.target.nameInSource.joinToString(".")}")
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,9 @@ interface IFunctionCall {
|
||||
val IFunctionCall.isMemoryCall: Boolean
|
||||
get() = target.nameInSource == listOf("memory")
|
||||
|
||||
val IFunctionCall.isMemoryRefCall: Boolean
|
||||
get() = target.nameInSource == listOf("memory__ref")
|
||||
|
||||
interface IStatementContainer {
|
||||
val statements: MutableList<Statement>
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
override fun visitAlias(ctx: AliasContext): Alias {
|
||||
val identifier = getname(ctx.identifier())
|
||||
val target = ctx.scoped_identifier().accept(this) as IdentifierReference
|
||||
return Alias(identifier, target, ctx.toPosition())
|
||||
return Alias(identifier, target, ctx.PRIVATE() != null, ctx.toPosition())
|
||||
}
|
||||
|
||||
override fun visitDefer(ctx: DeferContext): Defer {
|
||||
@@ -652,7 +652,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
val address = Subroutine.Address(constbank, varbank, addr)
|
||||
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||
subdecl.asmClobbers, address, true, inline = false, isPrivate = false, statements = mutableListOf(), position = ctx.toPosition()
|
||||
subdecl.asmClobbers, address, true, inline = false, isPrivate = ctx.PRIVATE() != null, statements = mutableListOf(), position = ctx.toPosition()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -795,7 +795,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
val name = getname(ctx.identifier())
|
||||
val fields: List<Pair<DataType, List<String>>> = ctx.structfielddecl().map { getStructField(it) }
|
||||
val flattened = fields.flatMap { (dt, names) -> names.map { dt to it}}
|
||||
return StructDecl(name, flattened.toTypedArray(), ctx.toPosition())
|
||||
return StructDecl(name, flattened.toTypedArray(), ctx.PRIVATE() != null, ctx.toPosition())
|
||||
}
|
||||
|
||||
private fun getStructField(ctx: StructfielddeclContext): Pair<DataType, List<String>> {
|
||||
@@ -819,7 +819,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
val members = members1.map {
|
||||
it.first to it.second?.number?.toInt()
|
||||
}.toTypedArray()
|
||||
return Enumeration(name, largestType, members, ctx.toPosition())
|
||||
return Enumeration(name, largestType, members, ctx.PRIVATE() != null, ctx.toPosition())
|
||||
}
|
||||
|
||||
|
||||
@@ -985,7 +985,7 @@ class Antlr2KotlinVisitor(val source: SourceCode): AbstractParseTreeVisitor<Node
|
||||
val pointer = pointerDatatypeFor(dtctx.pointertype())
|
||||
if(pointer!=null)
|
||||
return pointer
|
||||
val struct = dtctx.structtype.identifier().map { dtctx.text }
|
||||
val struct = dtctx.structtype.identifier().map { it.text }
|
||||
return DataType.structInstanceFromAntlr(struct)
|
||||
}
|
||||
|
||||
|
||||
@@ -654,7 +654,7 @@ data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: Array
|
||||
val target = this.identifier?.targetStatement()
|
||||
val targetVar = target as? VarDecl
|
||||
if(targetVar!=null) {
|
||||
if (targetVar.type == VarDeclType.MEMORY || targetVar.type == VarDeclType.CONST) {
|
||||
if (targetVar.type == VarDeclType.MEMORY || (targetVar.type == VarDeclType.CONST && arrayIndex != null)) {
|
||||
var address = targetVar.value?.constValue(program)?.number
|
||||
if (address != null) {
|
||||
if (arrayIndex != null) {
|
||||
|
||||
@@ -153,7 +153,7 @@ data class DirectiveArg(val string: String?, val int: UInt?, override val positi
|
||||
override fun referencesIdentifier(nameInSource: List<String>): Boolean = false
|
||||
}
|
||||
|
||||
data class Alias(val alias: String, val target: IdentifierReference, override val position: Position) : Statement() {
|
||||
data class Alias(val alias: String, val target: IdentifierReference, val isPrivate: Boolean = false, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -161,7 +161,7 @@ data class Alias(val alias: String, val target: IdentifierReference, override va
|
||||
target.parent = this
|
||||
}
|
||||
|
||||
override fun copy(): Statement = Alias(alias, target.copy(), position)
|
||||
override fun copy(): Statement = Alias(alias, target.copy(), isPrivate, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
@@ -235,7 +235,7 @@ class Continue(override val position: Position) : Statement() {
|
||||
}
|
||||
|
||||
|
||||
class MemorySlabReservation(
|
||||
data class MemorySlabReservation(
|
||||
val slabName: String,
|
||||
val size: UInt,
|
||||
val align: UInt,
|
||||
@@ -512,7 +512,7 @@ class VarDecl(
|
||||
}
|
||||
}
|
||||
|
||||
class StructDecl(override val name: String, val fields: Array<Pair<DataType, String>>, override val position: Position) : Statement(), INamedStatement, ISubType {
|
||||
class StructDecl(override val name: String, val fields: Array<Pair<DataType, String>>, val isPrivate: Boolean, override val position: Position) : Statement(), INamedStatement, ISubType {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -521,7 +521,7 @@ class StructDecl(override val name: String, val fields: Array<Pair<DataType, Str
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = false
|
||||
override fun copy() = StructDecl(name, fields.clone(), position)
|
||||
override fun copy() = StructDecl(name, fields.clone(), isPrivate, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun memsize(sizer: IMemSizer): Int = fields.sumOf { sizer.memorySize(it.first, 1) }
|
||||
@@ -562,7 +562,7 @@ class StructFieldRef(val pointer: IdentifierReference, val struct: StructDecl, v
|
||||
|
||||
}
|
||||
|
||||
class Enumeration(override val name: String, val type: BaseDataType, val members: Array<Pair<String, Int?>>, override val position: Position) : Statement(), INamedStatement {
|
||||
class Enumeration(override val name: String, val type: BaseDataType, val members: Array<Pair<String, Int?>>, val isPrivate: Boolean, override val position: Position) : Statement(), INamedStatement {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@@ -571,7 +571,7 @@ class Enumeration(override val name: String, val type: BaseDataType, val members
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun referencesIdentifier(nameInSource: List<String>) = false
|
||||
override fun copy(): Enumeration = Enumeration(name, type, members.toList().toTypedArray(), position)
|
||||
override fun copy(): Enumeration = Enumeration(name, type, members.toList().toTypedArray(), isPrivate, position)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
@@ -116,9 +116,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
val target = identifier.targetStatement(program.builtinFunctions)
|
||||
val definingSub = identifier.definingSubroutine
|
||||
if(target!=null) {
|
||||
allIdentifiersAndTargets.add(identifier to target)
|
||||
if(target is Subroutine)
|
||||
if(target is Subroutine && target !== definingSub)
|
||||
notCalledButReferenced += target
|
||||
}
|
||||
|
||||
@@ -128,9 +129,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
while(name.size>1) {
|
||||
name.removeLast()
|
||||
val scopeTarget = scope.lookup(name)
|
||||
if(scopeTarget is Subroutine)
|
||||
notCalledButReferenced += scopeTarget
|
||||
else if(scopeTarget is VarDecl) {
|
||||
if(scopeTarget is Subroutine) {
|
||||
if (scopeTarget !== definingSub)
|
||||
notCalledButReferenced += scopeTarget
|
||||
} else if(scopeTarget is VarDecl) {
|
||||
allIdentifiersAndTargets.add(identifier to scopeTarget)
|
||||
break
|
||||
} else if(scopeTarget is StructFieldRef) {
|
||||
@@ -164,13 +166,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(deref: PtrDereference) {
|
||||
val chain = deref.chain.toMutableList()
|
||||
val definingSub = deref.definingSubroutine
|
||||
while(chain.isNotEmpty()) {
|
||||
val variable = deref.definingScope.lookup(chain)
|
||||
if(variable is VarDecl) {
|
||||
allIdentifiersAndTargets.add(deref to variable)
|
||||
}
|
||||
else if(variable is Subroutine) {
|
||||
notCalledButReferenced += variable
|
||||
if (variable !== definingSub)
|
||||
notCalledButReferenced += variable
|
||||
}
|
||||
chain.removeLastOrNull()
|
||||
}
|
||||
@@ -179,13 +183,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(deref: ArrayIndexedPtrDereference) {
|
||||
val chain = deref.chain.map { it.first }.toMutableList()
|
||||
val definingSub = deref.definingSubroutine
|
||||
while(chain.isNotEmpty()) {
|
||||
val variable = deref.definingScope.lookup(chain)
|
||||
if(variable is VarDecl) {
|
||||
allIdentifiersAndTargets.add(deref to variable)
|
||||
}
|
||||
else if(variable is Subroutine) {
|
||||
notCalledButReferenced += variable
|
||||
if (variable !== definingSub)
|
||||
notCalledButReferenced += variable
|
||||
}
|
||||
chain.removeLastOrNull()
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
%import cx16logo
|
||||
%import diskio
|
||||
;;%import floats
|
||||
%import lineclip
|
||||
%import math
|
||||
%import petgfx
|
||||
%import prog8_lib
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
%import diskio
|
||||
%import floats
|
||||
%import graphics
|
||||
%import lineclip
|
||||
%import math
|
||||
%import petgfx
|
||||
%import prog8_lib
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
%import gfx_hires
|
||||
%import gfx_lores
|
||||
%import graphics
|
||||
%import lineclip
|
||||
%import math
|
||||
%import monogfx
|
||||
%import palette
|
||||
%import prog8_lib
|
||||
%import psg
|
||||
%import psg2
|
||||
%import serial
|
||||
%import sorting
|
||||
%import sprites
|
||||
%import strings
|
||||
|
||||
Loaded 100 of 156 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user