Compare commits

...

53 Commits
v1.90 ... v2.2

Author SHA1 Message Date
76cda82e23 v2.2 2020-06-16 01:43:44 +02:00
37b61d9e6b v2.2 2020-06-16 01:39:11 +02:00
52f0222a6d Got rid of old Ast transformer Api, some compiler error fixes 2020-06-16 01:25:49 +02:00
75ccac2f2c refactoring last of old Ast modification Api 2020-06-16 00:36:02 +02:00
5c771a91f7 refactoring last of old Ast modification Api 2020-06-14 16:56:48 +02:00
a242ad10e6 fix double printing of sub param vardecl 2020-06-14 13:46:46 +02:00
b5086b6a8f refactoring last of old Ast modification Api 2020-06-14 03:17:42 +02:00
3e47dad12a clearer no modifications 2020-06-14 02:54:29 +02:00
235610f40c refactored StatementOptimizer 2020-06-14 02:41:23 +02:00
6b59559c65 memory address assignment codegen 2020-06-14 02:12:40 +02:00
23e954f716 refactoring StatementOptimizer 2020-06-14 02:00:32 +02:00
983c899cad refactor AstIdentifierChecker 2020-06-13 00:14:19 +02:00
c2f9385965 refactor AstIdentifierChecker 2020-06-12 21:34:27 +02:00
ceb2c9e4f8 added string value assignment, leftstr, rightstr, substr functions 2020-06-06 00:05:39 +02:00
68a7f9c665 version 2.1 2020-06-04 23:03:18 +02:00
ffd8d9c7c1 more assignment expression optimizations 2020-06-04 22:57:32 +02:00
c66fc8630c fixed missing repeated constant folding in expression optimization 2020-06-04 20:22:37 +02:00
9ca1c66f2b added some optimizations for >= 0 and <0 comparisons for integers 2020-06-04 01:43:37 +02:00
33647a29d0 be smarter about certain implicit type casts 2020-06-03 23:55:41 +02:00
02b12cc762 optimized swap() for byte and word vars, optimized graphics line routine 2020-06-03 23:27:50 +02:00
3280993e2a stricter type checking in assignments (less implicit typecasts) 2020-06-02 22:36:57 +02:00
3723c22054 fix string param type 2020-06-02 02:09:52 +02:00
0a2c4ea0c4 improved ast printing 2020-06-02 01:51:27 +02:00
58a83c0439 improved code gen for passing string and array types. 2020-06-02 01:44:42 +02:00
d665489054 implemented asm for addressof-assignment 2020-06-02 00:31:56 +02:00
9200992024 slightly improved asm gen error messages 2020-06-02 00:31:20 +02:00
6408cc46a8 cmdrx16 github ref 2020-05-15 00:32:45 +02:00
961bcdb7ae some more todo's noted down 2020-05-15 00:24:25 +02:00
edee70cf31 use new api for ast mods in unused code remover 2020-05-15 00:16:53 +02:00
1978a9815a version 2.0 2020-05-14 23:59:18 +02:00
f5e6db9d66 big compiler speedup due to optimized scope lookups 2020-05-14 23:59:02 +02:00
a94bc40ab0 performance todo's 2020-05-08 20:41:10 +02:00
534b5ced8f updated the compiled examples 2020-04-10 23:36:29 +02:00
5ebd9b54e4 added some more optimized array assignments 2020-04-10 23:30:19 +02:00
cc4e272526 the new assignment code (once complete) really is a big enough change to bump the version to 2.0 2020-04-09 00:24:37 +02:00
295e199bfa optimized asm output for unneeded typecasts, fixed parent node linking issues with replaceChildNode, Assignment aug_op field is now mutable to avoid having to recreate many Assignment nodes 2020-04-09 00:12:50 +02:00
df3371b0f0 slight gfx optimizations 2020-04-08 22:53:23 +02:00
e4fe1d2b8d attempts to optimize in-place assignments 2020-04-08 03:11:38 +02:00
b8b9244ffa merged AddressOfInserter into StatementReorderer 2020-04-06 15:23:54 +02:00
3be3989e1c version 2020-04-06 14:31:23 +02:00
ed54cf680a fixed ast parent link bug in AstWalker, rewrote StatementReorderer using new API, when labels are sorted. 2020-04-06 14:31:02 +02:00
95e76058d3 version 2020-04-03 23:55:29 +02:00
a6bee6a860 some slight tweaks to asm for setting float value in array 2020-04-03 22:44:10 +02:00
d22780ee44 implemented asm for lsl array values 2020-04-03 21:45:52 +02:00
f8b0b9575d implemented asm for rol array values 2020-04-03 21:31:39 +02:00
4274fd168e implemented asm for rol2 array values 2020-04-03 21:24:55 +02:00
be7f5957f3 implemented asm for ror2 array values 2020-04-03 21:04:42 +02:00
f2e5d987a9 implemented asm for ror array values 2020-04-03 00:03:42 +02:00
f01173d8db fixed compilation of clear/set_carry() and clear/set_irqd() functions 2020-04-03 00:00:58 +02:00
15e8e0bf6d implemented asm for lsr array values 2020-04-02 23:38:45 +02:00
2c59cbdece fixed a crash in astchecking of array init values 2020-04-02 18:40:04 +02:00
b73da4ed02 some more obvious optimizations for X+X and X-X 2020-03-31 23:54:01 +02:00
267adb4612 doc 2020-03-29 03:06:51 +02:00
77 changed files with 4892 additions and 3628 deletions

View File

@ -31,13 +31,13 @@ which aims to provide many conveniences over raw assembly code (even when using
Rapid edit-compile-run-debug cycle: Rapid edit-compile-run-debug cycle:
- use modern PC to work on - use a modern PC to do the work on
- quick compilation times (seconds) - very quick compilation times
- option to automatically run the program in the Vice emulator - can automatically run the program in the Vice emulator after succesful compilation
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
It is mainly targeted at the Commodore-64 machine at this time. Prog8 is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome. Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation/manual Documentation/manual

View File

@ -1,11 +1,11 @@
buildscript { buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
} }
} }
plugins { plugins {
// id "org.jetbrains.kotlin.jvm" version "1.3.70" // id "org.jetbrains.kotlin.jvm" version "1.3.72"
id 'application' id 'application'
id 'org.jetbrains.dokka' version "0.9.18" id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0' id 'com.github.johnrengelman.shadow' version '5.2.0'

View File

@ -658,8 +658,8 @@ func_all_f .proc
dey dey
cmp #0 cmp #0
beq + beq +
cpy #255 cpy #255
bne - bne -
lda #1 lda #1
sta c64.ESTACK_LO+1,x sta c64.ESTACK_LO+1,x
rts rts
@ -739,3 +739,45 @@ sign_f .proc
dex dex
rts rts
.pend .pend
set_0_array_float .proc
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
inx
lda c64.ESTACK_LO,x
asl a
asl a
clc
adc c64.ESTACK_LO,x
tay
lda #0
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
set_array_float .proc
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
inx
lda c64.ESTACK_LO,x
asl a
asl a
clc
adc c64.ESTACK_LO,x
clc
adc c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
bcc +
iny
+ jmp copy_float
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
.pend

View File

@ -1772,7 +1772,6 @@ _loop_hi ldy _index_first
ror2_mem_ub .proc ror2_mem_ub .proc
; -- in-place 8-bit ror of byte at memory location on stack ; -- in-place 8-bit ror of byte at memory location on stack
; TODO use self modifying code here
inx inx
lda c64.ESTACK_LO,x lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1 sta c64.SCRATCH_ZPWORD1
@ -1789,7 +1788,6 @@ ror2_mem_ub .proc
rol2_mem_ub .proc rol2_mem_ub .proc
; -- in-place 8-bit rol of byte at memory location on stack ; -- in-place 8-bit rol of byte at memory location on stack
; TODO use self modifying code here
inx inx
lda c64.ESTACK_LO,x lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1 sta c64.SCRATCH_ZPWORD1
@ -1804,57 +1802,403 @@ rol2_mem_ub .proc
.pend .pend
lsl_array_b .proc lsl_array_b .proc
.warn "lsl_array_b" ; TODO ; -- lsl a (u)byte in an array (index and array address on stack)
.pend inx
ldy c64.ESTACK_LO,x
lsl_array_w .proc inx
.warn "lsl_array_w" ; TODO lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend .pend
lsr_array_ub .proc lsr_array_ub .proc
.warn "lsr_array_ub" ; TODO ; -- lsr a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend .pend
lsr_array_b .proc lsr_array_b .proc
.warn "lsr_array_b" ; TODO ; -- lsr a byte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsl_array_w .proc
; -- lsl a (u)word in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend .pend
lsr_array_uw .proc lsr_array_uw .proc
.warn "lsr_array_uw" ; TODO ; -- lsr a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend .pend
lsr_array_w .proc lsr_array_w .proc
.warn "lsr_array_w" ; TODO ; -- lsr a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
asl a
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend .pend
rol_array_ub .proc rol_array_ub .proc
.warn "rol_array_ub" ; TODO ; -- rol a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend .pend
rol_array_uw .proc
.warn "rol_array_uw" ; TODO
.pend
rol2_array_ub .proc
.warn "rol2_array_ub" ; TODO
.pend
rol2_array_uw .proc
.warn "rol2_array_uw" ; TODO
.pend
ror_array_ub .proc ror_array_ub .proc
.warn "ror_array_ub" ; TODO ; -- ror a ubyte in an array (index and array address on stack)
.pend inx
ldy c64.ESTACK_LO,x
ror_array_uw .proc inx
.warn "ror_array_uw" ; TODO lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend .pend
ror2_array_ub .proc ror2_array_ub .proc
.warn "ror2_array_ub" ; TODO ; -- ror2 (8-bit ror) a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
lsr a
bcc +
ora #$80
+ sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_array_ub .proc
; -- rol2 (8-bit rol) a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
cmp #$80
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
ror_array_uw .proc
; -- ror a uword in an array (index and array address on stack)
php
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
plp
ror a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol_array_uw .proc
; -- rol a uword in an array (index and array address on stack)
php
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
plp
rol a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_array_uw .proc
; -- rol2 (16-bit rol) a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
bcc +
dey
lda (c64.SCRATCH_ZPWORD1),y
adc #0
sta (c64.SCRATCH_ZPWORD1),y
+ rts
.pend .pend
ror2_array_uw .proc ror2_array_uw .proc
.warn "ror2_array_uw" ; TODO ; -- ror2 (16-bit ror) a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
bcc +
iny
lda (c64.SCRATCH_ZPWORD1),y
ora #$80
sta (c64.SCRATCH_ZPWORD1),y
+ rts
.pend
strcpy .proc
; copy a string (0-terminated) from A/Y to (ZPWORD1)
; it is assumed the target string is large enough.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #$ff
- iny
lda (c64.SCRATCH_ZPWORD2),y
sta (c64.SCRATCH_ZPWORD1),y
bne -
rts
.pend
func_leftstr .proc
; leftstr(source, target, length) with params on stack
inx
lda c64.ESTACK_LO,x
tay ; length
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD2+1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda #0
sta (c64.SCRATCH_ZPWORD2),y
- dey
cpy #$ff
bne +
rts
+ lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
jmp -
.pend
func_rightstr .proc
; rightstr(source, target, length) with params on stack
; make place for the 4 parameters for substr()
dex
dex
dex
dex
; X-> .
; x+1 -> length of segment
; x+2 -> start index
; X+3 -> target LO+HI
; X+4 -> source LO+HI
; original parameters:
; x+5 -> original length LO
; x+6 -> original targetLO + HI
; x+7 -> original sourceLO + HI
; replicate paramters:
lda c64.ESTACK_LO+5,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_LO+6,x
sta c64.ESTACK_LO+3,x
lda c64.ESTACK_HI+6,x
sta c64.ESTACK_HI+3,x
lda c64.ESTACK_LO+7,x
sta c64.ESTACK_LO+4,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+7,x
sta c64.ESTACK_HI+4,x
sta c64.SCRATCH_ZPWORD1+1
; determine string length
ldy #0
- lda (c64.SCRATCH_ZPWORD1),y
beq +
iny
bne -
+ tya
sec
sbc c64.ESTACK_LO+1,x ; start index = strlen - segment length
sta c64.ESTACK_LO+2,x
jsr func_substr
; unwind original params
inx
inx
inx
rts
.pend
func_substr .proc
; substr(source, target, start, length) with params on stack
inx
ldy c64.ESTACK_LO,x ; length
inx
lda c64.ESTACK_LO,x ; start
sta c64.SCRATCH_ZPB1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD2+1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
; adjust src location
clc
lda c64.SCRATCH_ZPWORD1
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ lda #0
sta (c64.SCRATCH_ZPWORD2),y
jmp _startloop
- lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
_startloop dey
cpy #$ff
bne -
rts
.pend .pend

View File

@ -1 +1 @@
1.90 2.2

View File

@ -102,6 +102,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
} }
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
if(paramNames!=null && decl.name in paramNames)
return
when(decl.type) { when(decl.type) {
VarDeclType.VAR -> {} VarDeclType.VAR -> {}
VarDeclType.CONST -> output("const ") VarDeclType.CONST -> output("const ")
@ -177,8 +183,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun outputStatements(statements: List<Statement>) { private fun outputStatements(statements: List<Statement>) {
for(stmt in statements) { for(stmt in statements) {
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
continue // skip autogenerated decls (to avoid generating a newline)
outputi("") outputi("")
stmt.accept(this) stmt.accept(this)
output("\n") output("\n")
@ -284,7 +288,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(assignment: Assignment) { override fun visit(assignment: Assignment) {
assignment.target.accept(this) assignment.target.accept(this)
if (assignment.aug_op != null) if (assignment.aug_op != null && assignment.aug_op != "setvalue")
output(" ${assignment.aug_op} ") output(" ${assignment.aug_op} ")
else else
output(" = ") output(" = ")

View File

@ -4,7 +4,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
@ -53,32 +52,31 @@ interface INameScope {
fun linkParents(parent: Node) fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> { fun subScope(name: String): INameScope? {
val subscopes = mutableMapOf<String, INameScope>()
for(stmt in statements) { for(stmt in statements) {
when(stmt) { when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here! // NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> subscopes[stmt.body.name] = stmt.body is ForLoop -> if(stmt.body.name==name) return stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body is RepeatLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> { is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars()) if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
subscopes[stmt.elsepart.name] = stmt.elsepart
} }
is IfStatement -> { is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars()) if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
subscopes[stmt.elsepart.name] = stmt.elsepart
} }
is WhenStatement -> { is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements } val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
} }
is INameScope -> subscopes[stmt.name] = stmt is INameScope -> if(stmt.name==name) return stmt
else -> {} else -> {}
} }
} }
return subscopes return null
} }
fun getLabelOrVariable(name: String): Statement? { fun getLabelOrVariable(name: String): Statement? {
@ -126,7 +124,7 @@ interface INameScope {
for(module in localContext.definingModule().program.modules) { for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module var scope: INameScope? = module
for(name in scopedName.dropLast(1)) { for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name) scope = scope?.subScope(name)
if(scope==null) if(scope==null)
break break
} }
@ -134,7 +132,7 @@ interface INameScope {
val result = scope.getLabelOrVariable(scopedName.last()) val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null) if(result!=null)
return result return result
return scope.subScopes()[scopedName.last()] as Statement? return scope.subScope(scopedName.last()) as Statement?
} }
} }
return null return null
@ -146,7 +144,7 @@ interface INameScope {
val result = localScope.getLabelOrVariable(scopedName[0]) val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null) if (result != null)
return result return result
val subscope = localScope.subScopes()[scopedName[0]] as Statement? val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null) if (subscope != null)
return subscope return subscope
// not found in this scope, look one higher up // not found in this scope, look one higher up
@ -212,7 +210,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
return if(mainBlocks.isEmpty()) { return if(mainBlocks.isEmpty()) {
null null
} else { } else {
mainBlocks[0].subScopes()["start"] as Subroutine? mainBlocks[0].subScope("start") as Subroutine?
} }
} }
@ -233,6 +231,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
require(node is Module && replacement is Module) require(node is Module && replacement is Module)
val idx = modules.indexOf(node) val idx = modules.indexOf(node)
modules[idx] = replacement modules[idx] = replacement
replacement.parent = this
} }
} }
@ -259,11 +258,11 @@ class Module(override val name: String,
require(node is Statement && replacement is Statement) require(node is Statement && replacement is Statement)
val idx = statements.indexOf(node) val idx = statements.indexOf(node)
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this
} }
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)" override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }

View File

@ -24,7 +24,7 @@ enum class DataType {
* is the type assignable to the given other type? * is the type assignable to the given other type?
*/ */
infix fun isAssignableTo(targetType: DataType) = infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision? // what types are assignable to others, perhaps via a typecast, without loss of precision?
when(this) { when(this) {
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT) UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType in setOf(BYTE, WORD, FLOAT) BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
@ -150,7 +150,9 @@ object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0) override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this override var parent: Node = this
override fun linkParents(parent: Node) {} override fun linkParents(parent: Node) {}
override fun replaceChildNode(node: Node, replacement: Node) {} override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
} }
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {

View File

@ -2,7 +2,7 @@ package prog8.ast.base
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
class FatalAstException (override var message: String) : Exception(message) open class FatalAstException (override var message: String) : Exception(message)
open class AstException (override var message: String) : Exception(message) open class AstException (override var message: String) : Exception(message)

View File

@ -4,7 +4,8 @@ import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.processing.*
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.target.AsmVariableAndReturnsPreparer import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.optimizer.AssignmentTransformer
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
@ -13,19 +14,16 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: Err
checker.visit(this) checker.visit(this)
} }
internal fun Program.prepareAsmVariablesAndReturns(errors: ErrorReporter) { internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
val fixer = AsmVariableAndReturnsPreparer(this, errors) val fixer = BeforeAsmGenerationAstChanger(this, errors)
fixer.visit(this) fixer.visit(this)
fixer.applyModifications() fixer.applyModifications()
} }
internal fun Program.reorderStatements() { internal fun Program.reorderStatements() {
val initvalueCreator = AddressOfInserter(this) val reorder = StatementReorderer(this)
initvalueCreator.visit(this) reorder.visit(this)
initvalueCreator.applyModifications() reorder.applyModifications()
val checker = StatementReorderer(this)
checker.visit(this)
} }
internal fun Program.addTypecasts(errors: ErrorReporter) { internal fun Program.addTypecasts(errors: ErrorReporter) {
@ -34,6 +32,17 @@ internal fun Program.addTypecasts(errors: ErrorReporter) {
caster.applyModifications() caster.applyModifications()
} }
internal fun Program.transformAssignments(errors: ErrorReporter) {
val transform = AssignmentTransformer(this, errors)
transform.visit(this)
while(transform.optimizationsDone>0 && errors.isEmpty()) {
transform.applyModifications()
transform.optimizationsDone = 0
transform.visit(this)
}
transform.applyModifications()
}
internal fun Module.checkImportedValid() { internal fun Module.checkImportedValid() {
val imr = ImportedModuleDirectiveRemover() val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent) imr.visit(this, this.parent)
@ -47,20 +56,21 @@ internal fun Program.checkRecursion(errors: ErrorReporter) {
} }
internal fun Program.checkIdentifiers(errors: ErrorReporter) { internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker = AstIdentifiersChecker(this, errors)
checker.visit(this) val checker2 = AstIdentifiersChecker(this, errors)
checker2.visit(this)
if(errors.isEmpty()) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
}
if (modules.map { it.name }.toSet().size != modules.size) { if (modules.map { it.name }.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique") throw FatalAstException("modules should all be unique")
} }
} }
internal fun Program.makeForeverLoops() {
val checker = ForeverLoopsMaker()
checker.visit(this)
checker.applyModifications()
}
internal fun Program.removeNopsFlattenAnonScopes() { internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndNopRemover() val flattener = FlattenAnonymousScopesAndNopRemover()
flattener.visit(this) flattener.visit(this)

View File

@ -4,11 +4,11 @@ import prog8.ast.*
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import prog8.functions.CannotEvaluateException
import prog8.functions.NotConstArgumentException import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType import prog8.functions.builtinFunctionReturnType
import java.util.* import java.util.*
@ -20,7 +20,6 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
sealed class Expression: Node { sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue? abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor) abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node) abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
@ -61,10 +60,10 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(node === expression && replacement is Expression) require(node === expression && replacement is Expression)
expression = replacement expression = replacement
replacement.parent = this
} }
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -112,6 +111,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
node===right -> right = replacement node===right -> right = replacement
else -> throw FatalAstException("invalid replace, no child $node") else -> throw FatalAstException("invalid replace, no child $node")
} }
replacement.parent = this
} }
override fun toString(): String { override fun toString(): String {
@ -121,7 +121,6 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
// binary expression should actually have been optimized away into a single value, before const value was requested... // binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -231,10 +230,10 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
node===arrayspec.index -> arrayspec.index = replacement as Expression node===arrayspec.index -> arrayspec.index = replacement as Expression
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -268,9 +267,9 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===expression) require(replacement is Expression && node===expression)
expression = replacement expression = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -299,12 +298,12 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is IdentifierReference && node===identifier) require(replacement is IdentifierReference && node===identifier)
identifier = replacement identifier = replacement
replacement.parent = this
} }
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD) override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
} }
@ -320,9 +319,9 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression) require(replacement is Expression && node===addressExpression)
addressExpression = replacement addressExpression = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -382,7 +381,6 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun referencesIdentifiers(vararg name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun constValue(program: Program) = this override fun constValue(program: Program) = this
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -473,7 +471,6 @@ class StructLiteralValue(var values: List<Expression>,
} }
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -504,7 +501,6 @@ class StringLiteralValue(val value: String,
override fun referencesIdentifiers(vararg name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -535,16 +531,16 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
require(replacement is Expression) require(replacement is Expression)
val idx = value.indexOf(node) val idx = value.indexOf(node)
value[idx] = replacement value[idx] = replacement
replacement.parent = this
} }
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) } override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "$value" override fun toString(): String = "$value"
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isUnknown) type else guessDatatype(program) override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position) operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
override fun hashCode(): Int = Objects.hash(value, type) override fun hashCode(): Int = Objects.hash(value, type)
@ -629,10 +625,10 @@ class RangeExpr(var from: Expression,
step===node -> step=replacement step===node -> step=replacement
else -> throw FatalAstException("invalid replacement") else -> throw FatalAstException("invalid replacement")
} }
replacement.parent = this
} }
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -712,7 +708,6 @@ class RegisterExpr(val register: Register, override val position: Position) : Ex
} }
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -760,7 +755,6 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
return "IdentifierRef($nameInSource)" return "IdentifierRef($nameInSource)"
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -807,6 +801,7 @@ class FunctionCall(override var target: IdentifierReference,
val idx = args.indexOf(node) val idx = args.indexOf(node)
args[idx] = replacement as Expression args[idx] = replacement as Expression
} }
replacement.parent = this
} }
override fun constValue(program: Program) = constValue(program, true) override fun constValue(program: Program) = constValue(program, true)
@ -839,13 +834,16 @@ class FunctionCall(override var target: IdentifierReference,
// const-evaluating the builtin function call failed. // const-evaluating the builtin function call failed.
return null return null
} }
catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
return null
}
} }
override fun toString(): String { override fun toString(): String {
return "FunctionCall(target=$target, pos=$position)" return "FunctionCall(target=$target, pos=$position)"
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)

View File

@ -1,93 +0,0 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Statement
import prog8.ast.statements.Subroutine
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
internal class AddressOfInserter(val program: Program): AstWalker() {
// Insert AddressOf (&) expression where required (string params to a UWORD function param etc).
// TODO join this into the StatementReorderer?
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
var parentStatement: Node = functionCall
while(parentStatement !is Statement)
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
return addAddressOfExprIfNeeded(targetStatement, functionCall.args, functionCall)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, functionCall)
}
return emptyList()
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
return addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
}
return emptyList()
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
val replacements = mutableListOf<IAstModification>()
for(argparam in subroutine.parameters.withIndex().zip(args)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
if(idref!=null) {
val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && variable.datatype in IterableDatatypes) {
replacements += IAstModification.ReplaceNode(
args[argparam.first.index],
AddressOf(idref, idref.position),
parent as Node)
}
}
}
}
return replacements
}
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
val replacements = mutableListOf<IAstModification>()
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
replacements += IAstModification.ReplaceNode(
args[arg.first.index],
AddressOf(argvalue, argvalue.position),
parent as Node)
}
}
return replacements
}
}

View File

@ -22,10 +22,10 @@ internal class AstChecker(private val program: Program,
if(mainBlocks.size>1) if(mainBlocks.size>1)
errors.err("more than one 'main' block", mainBlocks[0].position) errors.err("more than one 'main' block", mainBlocks[0].position)
if(mainBlocks.isEmpty()) if(mainBlocks.isEmpty())
errors.err("there is no 'main' block", program.position) errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
for(mainBlock in mainBlocks) { for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScopes()["start"] as? Subroutine val startSub = mainBlock.subScope("start") as? Subroutine
if (startSub == null) { if (startSub == null) {
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position) errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
} else { } else {
@ -58,7 +58,7 @@ internal class AstChecker(private val program: Program,
if(irqBlocks.size>1) if(irqBlocks.size>1)
errors.err("more than one 'irq' block", irqBlocks[0].position) errors.err("more than one 'irq' block", irqBlocks[0].position)
for(irqBlock in irqBlocks) { for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine val irqSub = irqBlock.subScope("irq") as? Subroutine
if (irqSub != null) { if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty()) if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position) errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
@ -82,7 +82,7 @@ internal class AstChecker(private val program: Program,
override fun visit(returnStmt: Return) { override fun visit(returnStmt: Return) {
val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList() val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList()
if(expectedReturnValues.size>1) { if(expectedReturnValues.size>1) {
throw AstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt") throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
} }
if(expectedReturnValues.isEmpty() && returnStmt.value!=null) { if(expectedReturnValues.isEmpty() && returnStmt.value!=null) {
@ -326,7 +326,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(repeatLoop: RepeatLoop) { override fun visit(repeatLoop: RepeatLoop) {
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position) errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes) if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position) errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
@ -334,7 +334,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(whileLoop: WhileLoop) { override fun visit(whileLoop: WhileLoop) {
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position) errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes) if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", whileLoop.condition.position) errors.err("condition value should be an integer type", whileLoop.condition.position)
@ -423,9 +423,6 @@ internal class AstChecker(private val program: Program,
if (assignment is Assignment) { if (assignment is Assignment) {
if (assignment.aug_op != null)
throw FatalAstException("augmented assignment should have been converted into normal assignment")
val targetDatatype = assignTarget.inferType(program, assignment) val targetDatatype = assignTarget.inferType(program, assignment)
if (targetDatatype.isKnown) { if (targetDatatype.isKnown) {
val constVal = assignment.value.constValue(program) val constVal = assignment.value.constValue(program)
@ -466,6 +463,7 @@ internal class AstChecker(private val program: Program,
} }
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
// TODO use callgraph for check?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) { if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
err("recursive var declaration") err("recursive var declaration")
} }
@ -588,7 +586,7 @@ internal class AstChecker(private val program: Program,
val declValue = decl.value val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype)) if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype))
throw FatalAstException("initialisation value $declValue is of different type (${declValue.inferType(program)} as the variable (${decl.datatype}) at ${decl.position}") err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
super.visit(decl) super.visit(decl)
} }
@ -848,7 +846,7 @@ internal class AstChecker(private val program: Program,
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) { if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
// in-place modification, can't be done on literals // in-place modification, can't be done on literals
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) { if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position) errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
} }
} }
super.visit(functionCallStatement) super.visit(functionCallStatement)
@ -896,6 +894,8 @@ internal class AstChecker(private val program: Program,
} }
} }
} else if(target is Subroutine) { } else if(target is Subroutine) {
if(target.regXasResult())
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
if(args.size!=target.parameters.size) if(args.size!=target.parameters.size)
errors.err("invalid number of arguments", position) errors.err("invalid number of arguments", position)
else { else {
@ -914,7 +914,7 @@ internal class AstChecker(private val program: Program,
if(target.isAsmSubroutine) { if(target.isAsmSubroutine) {
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) { if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference) if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position) errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
} }
// check if the argument types match the register(pairs) // check if the argument types match the register(pairs)
@ -1268,7 +1268,7 @@ internal class AstChecker(private val program: Program,
correct = array.all { it in -32768..32767 } correct = array.all { it in -32768..32767 }
} }
DataType.ARRAY_F -> correct = true DataType.ARRAY_F -> correct = true
else -> throw AstException("invalid array type $type") else -> throw FatalAstException("invalid array type $type")
} }
if (!correct) if (!correct)
errors.err("array value out of range for type $type", value.position) errors.err("array value out of range for type $type", value.position)

View File

@ -1,8 +1,6 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
@ -10,98 +8,69 @@ import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
internal class AstIdentifiersChecker(private val program: Program,
private val errors: ErrorReporter) : IAstModifyingVisitor {
private var blocks = mutableMapOf<String, Block>() private var blocks = mutableMapOf<String, Block>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
private fun nameError(name: String, position: Position, existing: Statement) { private fun nameError(name: String, position: Position, existing: Statement) {
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position) errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
} }
override fun visit(module: Module) { override fun visit(module: Module) {
vardeclsToAdd.clear()
blocks.clear() // blocks may be redefined within a different module blocks.clear() // blocks may be redefined within a different module
super.visit(module) super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
} }
override fun visit(block: Block): Statement { override fun visit(block: Block) {
val existing = blocks[block.name] val existing = blocks[block.name]
if(existing!=null) if(existing!=null)
nameError(block.name, block.position, existing) nameError(block.name, block.position, existing)
else else
blocks[block.name] = block blocks[block.name] = block
return super.visit(block) super.visit(block)
} }
override fun visit(functionCall: FunctionCall): Expression { override fun visit(decl: VarDecl) {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
typecast.linkParents(functionCall.parent)
return super.visit(typecast)
}
return super.visit(functionCall)
}
override fun visit(decl: VarDecl): Statement {
// first, check if there are datatype errors on the vardecl
decl.datatypeErrors.forEach { errors.err(it.message, it.position) } decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
// now check the identifier
if(decl.name in BuiltinFunctions) if(decl.name in BuiltinFunctions)
// the builtin functions can't be redefined
errors.err("builtin function cannot be redefined", decl.position) errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.machine.opcodeNames) if(decl.name in CompilationTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT) { if(decl.datatype==DataType.STRUCT) {
if(decl.structHasBeenFlattened) if (decl.structHasBeenFlattened)
return super.visit(decl) // don't do this multiple times return super.visit(decl) // don't do this multiple times
if(decl.struct==null) { if (decl.struct == null) {
errors.err("undefined struct type", decl.position) errors.err("undefined struct type", decl.position)
return super.visit(decl) return super.visit(decl)
} }
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes}) if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if(decl.value is NumericLiteralValue) { if (decl.value is NumericLiteralValue) {
errors.err("you cannot initialize a struct using a single value", decl.position) errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl) return super.visit(decl)
} }
if(decl.value != null && decl.value !is StructLiteralValue) { if (decl.value != null && decl.value !is StructLiteralValue) {
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position) errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position)
return super.visit(decl) return super.visit(decl)
} }
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
result.linkParents(decl.parent)
return result
} }
val existing = program.namespace.lookup(listOf(decl.name), decl) val existing = program.namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl) if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing) nameError(decl.name, decl.position, existing)
return super.visit(decl) super.visit(decl)
} }
override fun visit(subroutine: Subroutine): Statement { override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.machine.opcodeNames) { if(subroutine.name in CompilationTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) { } else if(subroutine.name in BuiltinFunctions) {
@ -138,30 +107,15 @@ internal class AstIdentifiersChecker(private val program: Program,
nameError(name, sub.position, subroutine) nameError(name, sub.position, subroutine)
} }
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
// NOTE:
// - numeric types BYTE and WORD and FLOAT are passed by value;
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters
.filter { it.name !in namesInSub }
.forEach {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
}
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position) errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
} }
} }
return super.visit(subroutine)
super.visit(subroutine)
} }
override fun visit(label: Label): Statement { override fun visit(label: Label) {
if(label.name in CompilationTarget.machine.opcodeNames) if(label.name in CompilationTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
@ -179,163 +133,39 @@ internal class AstIdentifiersChecker(private val program: Program,
} }
} }
} }
return super.visit(label)
super.visit(label)
} }
override fun visit(forLoop: ForLoop): Statement { override fun visit(forLoop: ForLoop) {
// If the for loop has a decltype, it means to declare the loopvar inside the loop body if (forLoop.loopRegister != null) {
// rather than reusing an already declared loopvar from an outer scope. if (forLoop.loopRegister == Register.X)
// For loops that loop over an interable variable (instead of a range of numbers) get an
// additional interation count variable in their scope.
if(forLoop.loopRegister!=null) {
if(forLoop.loopRegister == Register.X)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position) errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else {
val loopVar = forLoop.loopVar
if (loopVar != null) {
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
val loopvarName = "prog8_loopvar_$validName"
if (forLoop.iterable !is RangeExpr) {
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
if (existing == null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
}
}
}
} }
return super.visit(forLoop)
super.visit(forLoop)
} }
override fun visit(assignTarget: AssignTarget): AssignTarget { override fun visit(assignTarget: AssignTarget) {
if(assignTarget.register== Register.X) if(assignTarget.register== Register.X)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position) errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
return super.visit(assignTarget) super.visit(assignTarget)
} }
override fun visit(arrayLiteral: ArrayLiteralValue): Expression { override fun visit(string: StringLiteralValue) {
val array = super.visit(arrayLiteral) if (string.value.length !in 1..255)
if(array is ArrayLiteralValue) { errors.err("string literal length must be between 1 and 255", string.position)
val vardecl = array.parent as? VarDecl
// adjust the datatype of the array (to an educated guess) super.visit(string)
if(vardecl!=null) {
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null) {
vardecl.value = cast
cast.linkParents(vardecl)
return cast
}
}
return array
}
else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
return if (litval2 != null) {
litval2.parent = array.parent
makeIdentifierFromRefLv(litval2)
} else array
}
}
}
return array
} }
override fun visit(stringLiteral: StringLiteralValue): Expression { override fun visit(structDecl: StructDecl) {
val string = super.visit(stringLiteral)
if(string is StringLiteralValue) {
val vardecl = string.parent as? VarDecl
// intern the string; move it into the heap
if (string.value.length !in 1..255)
errors.err("string literal length must be between 1 and 255", string.position)
return if (vardecl != null)
string
else
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
}
return string
}
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here.
val scope = array.definingScope()
val variable = VarDecl.createAuto(array)
return replaceWithIdentifier(variable, scope, array.parent)
}
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here.
val scope = string.definingScope()
val variable = VarDecl.createAuto(string)
return replaceWithIdentifier(variable, scope, string.parent)
}
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
val variable1 = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
identifier.parent = parent
return identifier
}
override fun visit(structDecl: StructDecl): Statement {
for(member in structDecl.statements){ for(member in structDecl.statements){
val decl = member as? VarDecl val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes) if(decl!=null && decl.datatype !in NumericDatatypes)
errors.err("structs can only contain numerical types", decl.position) errors.err("structs can only contain numerical types", decl.position)
} }
return super.visit(structDecl) super.visit(structDecl)
} }
override fun visit(expr: BinaryExpression): Expression {
return when {
expr.left is StringLiteralValue ->
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
expr.right is StringLiteralValue ->
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
else -> super.visit(expr)
}
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
}
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
}
return expr
}
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
} }

View File

@ -0,0 +1,133 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
return listOf(IAstModification.ReplaceNode(
functionCall, typecast, parent
))
}
return noModifications
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
return listOf(IAstModification.ReplaceNode(
decl, result, parent
))
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
IAstModification.InsertFirst(vardecl, subroutine)
}
}
}
return noModifications
}
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
when {
expr.left is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
parent
))
expr.right is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
parent
))
}
return noModifications
}
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
)
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast!=array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
}
} else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(litval2!=null && litval2!=array) {
val vardecl = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl, array.definingScope() as Node)
)
}
}
}
return noModifications
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
}
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
}
return expr
}
}

View File

@ -1,9 +1,6 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.INameScope import prog8.ast.*
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
@ -15,7 +12,7 @@ interface IAstModification {
class Remove(val node: Node, val parent: Node) : IAstModification { class Remove(val node: Node, val parent: Node) : IAstModification {
override fun perform() { override fun perform() {
if(parent is INameScope) { if(parent is INameScope) {
if (!parent.statements.remove(node)) if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node") throw FatalAstException("attempt to remove non-existing node $node")
} else { } else {
throw FatalAstException("parent of a remove modification is not an INameScope") throw FatalAstException("parent of a remove modification is not an INameScope")
@ -41,6 +38,17 @@ interface IAstModification {
} }
} }
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification { class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() { override fun perform() {
if(parent is INameScope) { if(parent is INameScope) {
@ -56,7 +64,7 @@ interface IAstModification {
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification { class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
override fun perform() { override fun perform() {
parent.replaceChildNode(node, replacement) parent.replaceChildNode(node, replacement)
replacement.parent = parent replacement.linkParents(parent)
} }
} }

View File

@ -1,28 +0,0 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.ForeverLoop
import prog8.ast.statements.RepeatLoop
import prog8.ast.statements.WhileLoop
internal class ForeverLoopsMaker: AstWalker() {
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
if(numeric!=null && numeric.number.toInt() == 0) {
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position)
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent))
}
return emptyList()
}
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val numeric = whileLoop.condition as? NumericLiteralValue
if(numeric!=null && numeric.number.toInt() != 0) {
val forever = ForeverLoop(whileLoop.body, whileLoop.position)
return listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
}
return emptyList()
}
}

View File

@ -1,267 +0,0 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModifyingVisitor {
fun visit(program: Program) {
program.modules.forEach { it.accept(this) }
}
fun visit(module: Module) {
module.statements = module.statements.map { it.accept(this) }.toMutableList()
}
fun visit(expr: PrefixExpression): Expression {
expr.expression = expr.expression.accept(this)
return expr
}
fun visit(expr: BinaryExpression): Expression {
expr.left = expr.left.accept(this)
expr.right = expr.right.accept(this)
return expr
}
fun visit(directive: Directive): Statement {
return directive
}
fun visit(block: Block): Statement {
block.statements = block.statements.map { it.accept(this) }.toMutableList()
return block
}
fun visit(decl: VarDecl): Statement {
decl.value = decl.value?.accept(this)
decl.arraysize?.accept(this)
return decl
}
fun visit(subroutine: Subroutine): Statement {
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
return subroutine
}
fun visit(functionCall: FunctionCall): Expression {
val newtarget = functionCall.target.accept(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCall.args = functionCall.args.map { it.accept(this) }.toMutableList()
return functionCall
}
fun visit(functionCallStatement: FunctionCallStatement): Statement {
val newtarget = functionCallStatement.target.accept(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCallStatement.args = functionCallStatement.args.map { it.accept(this) }.toMutableList()
return functionCallStatement
}
fun visit(identifier: IdentifierReference): Expression {
// note: this is an identifier that is used in an expression.
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
return identifier
}
fun visit(jump: Jump): Statement {
if(jump.identifier!=null) {
val ident = jump.identifier.accept(this)
if(ident is IdentifierReference && ident!==jump.identifier) {
return Jump(null, ident, null, jump.position)
}
}
return jump
}
fun visit(ifStatement: IfStatement): Statement {
ifStatement.condition = ifStatement.condition.accept(this)
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
return ifStatement
}
fun visit(branchStatement: BranchStatement): Statement {
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
return branchStatement
}
fun visit(range: RangeExpr): Expression {
range.from = range.from.accept(this)
range.to = range.to.accept(this)
range.step = range.step.accept(this)
return range
}
fun visit(label: Label): Statement {
return label
}
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
return literalValue
}
fun visit(stringLiteral: StringLiteralValue): Expression {
return stringLiteral
}
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
for(av in arrayLiteral.value.withIndex()) {
val newvalue = av.value.accept(this)
arrayLiteral.value[av.index] = newvalue
}
return arrayLiteral
}
fun visit(assignment: Assignment): Statement {
assignment.target = assignment.target.accept(this)
assignment.value = assignment.value.accept(this)
return assignment
}
fun visit(postIncrDecr: PostIncrDecr): Statement {
postIncrDecr.target = postIncrDecr.target.accept(this)
return postIncrDecr
}
fun visit(contStmt: Continue): Statement {
return contStmt
}
fun visit(breakStmt: Break): Statement {
return breakStmt
}
fun visit(forLoop: ForLoop): Statement {
when(val newloopvar = forLoop.loopVar?.accept(this)) {
is IdentifierReference -> forLoop.loopVar = newloopvar
null -> forLoop.loopVar = null
else -> throw FatalAstException("can't change class of loopvar")
}
forLoop.iterable = forLoop.iterable.accept(this)
forLoop.body = forLoop.body.accept(this) as AnonymousScope
return forLoop
}
fun visit(whileLoop: WhileLoop): Statement {
whileLoop.condition = whileLoop.condition.accept(this)
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
return whileLoop
}
fun visit(foreverLoop: ForeverLoop): Statement {
foreverLoop.body = foreverLoop.body.accept(this) as AnonymousScope
return foreverLoop
}
fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
return repeatLoop
}
fun visit(returnStmt: Return): Statement {
returnStmt.value = returnStmt.value?.accept(this)
return returnStmt
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
val ident = arrayIndexedExpression.identifier.accept(this)
if(ident is IdentifierReference)
arrayIndexedExpression.identifier = ident
arrayIndexedExpression.arrayspec.accept(this)
return arrayIndexedExpression
}
fun visit(assignTarget: AssignTarget): AssignTarget {
when (val ident = assignTarget.identifier?.accept(this)) {
is IdentifierReference -> assignTarget.identifier = ident
null -> assignTarget.identifier = null
else -> throw FatalAstException("can't change class of assign target identifier")
}
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
assignTarget.memoryAddress?.let { visit(it) }
return assignTarget
}
fun visit(scope: AnonymousScope): Statement {
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
return scope
}
fun visit(typecast: TypecastExpression): Expression {
typecast.expression = typecast.expression.accept(this)
return typecast
}
fun visit(memread: DirectMemoryRead): Expression {
memread.addressExpression = memread.addressExpression.accept(this)
return memread
}
fun visit(memwrite: DirectMemoryWrite) {
memwrite.addressExpression = memwrite.addressExpression.accept(this)
}
fun visit(addressOf: AddressOf): Expression {
val ident = addressOf.identifier.accept(this)
if(ident is IdentifierReference)
addressOf.identifier = ident
else
throw FatalAstException("can't change class of addressof identifier")
return addressOf
}
fun visit(inlineAssembly: InlineAssembly): Statement {
return inlineAssembly
}
fun visit(registerExpr: RegisterExpr): Expression {
return registerExpr
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
return builtinFunctionStatementPlaceholder
}
fun visit(nopStatement: NopStatement): Statement {
return nopStatement
}
fun visit(whenStatement: WhenStatement): Statement {
whenStatement.condition = whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) }
return whenStatement
}
fun visit(whenChoice: WhenChoice) {
whenChoice.values = whenChoice.values?.map { it.accept(this) }
val stmt = whenChoice.statements.accept(this)
if(stmt is AnonymousScope)
whenChoice.statements = stmt
else {
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
whenChoice.statements.linkParents(whenChoice)
}
}
fun visit(structDecl: StructDecl): Statement {
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
return structDecl
}
fun visit(structLv: StructLiteralValue): Expression {
structLv.values = structLv.values.map { it.accept(this) }
return structLv
}
}

View File

@ -10,11 +10,12 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
*/ */
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address") private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> { override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) { if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent)) return listOf(IAstModification.Remove(directive, parent))
} }
return emptyList() return noModifications
} }
} }

View File

@ -1,195 +1,125 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.DataType import prog8.ast.base.*
import prog8.ast.base.FatalAstException
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor { internal class StatementReorderer(val program: Program) : AstWalker() {
// Reorders the statements in a way the compiler needs. // Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set. // - 'main' block must be the very first statement UNLESS it has an address set.
// - blocks are ordered by address, where blocks without address are put at the end. // - library blocks are put last.
// - in every scope: // - blocks are ordered by address, where blocks without address are placed last.
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first. // - in every scope, most directives and vardecls are moved to the top.
// -- all vardecls then follow. // - the 'start' subroutine is moved to the top.
// -- the remaining statements then follow in their original order. // - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// // - (syntax desugaring) augmented assignment is turned into regular assignment.
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives. // - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
// - all other subroutines will be moved to the end of their block.
// - sorts the choices in when statement. // - sorts the choices in when statement.
// - a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement. // - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val noModifications = emptyList<IAstModification>()
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
private val addVardecls = mutableMapOf<INameScope, MutableList<VarDecl>>() override fun after(module: Module, parent: Node): Iterable<IAstModification> {
override fun visit(module: Module) {
addVardecls.clear()
super.visit(module)
val (blocks, other) = module.statements.partition { it is Block } val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList() module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
val nonLibraryBlocks = module.statements.withIndex() if(mainBlock!=null && mainBlock.address==null) {
.filter { it.value is Block && !(it.value as Block).isInLibrary } module.statements.remove(mainBlock)
.map { it.index to it.value }
.reversed()
for(nonLibBlock in nonLibraryBlocks)
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if(mainBlock!=null && (mainBlock as Block).address==null) {
module.remove(mainBlock)
module.statements.add(0, mainBlock) module.statements.add(0, mainBlock)
} }
val varDecls = module.statements.filterIsInstance<VarDecl>() reorderVardeclsAndDirectives(module.statements)
module.statements.removeAll(varDecls) return noModifications
module.statements.addAll(0, varDecls)
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
for((where, decls) in addVardecls) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
} }
override fun visit(block: Block): Statement { private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
val subroutines = block.statements.filterIsInstance<Subroutine>() val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
var numSubroutinesAtEnd = 0 statements.removeAll(directives)
// move all subroutines to the end of the block statements.addAll(0, directives)
for (subroutine in subroutines) { }
if(subroutine.name!="start" || block.name!="main") {
block.remove(subroutine) override fun before(block: Block, parent: Node): Iterable<IAstModification> {
block.statements.add(subroutine) parent as Module
} if(block.isInLibrary) {
numSubroutinesAtEnd++ return listOf(
IAstModification.Remove(block, parent),
IAstModification.InsertLast(block, parent)
)
} }
// move the "start" subroutine to the top
if(block.name=="main") { reorderVardeclsAndDirectives(block.statements)
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let { return noModifications
block.remove(it) }
block.statements.add(0, it)
numSubroutinesAtEnd-- override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
} }
} }
return noModifications
val varDecls = block.statements.filterIsInstance<VarDecl>()
block.statements.removeAll(varDecls)
block.statements.addAll(0, varDecls)
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives)
block.statements.addAll(0, directives)
block.linkParents(block.parent)
return super.visit(block)
} }
override fun visit(subroutine: Subroutine): Statement { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
super.visit(subroutine)
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
subroutine.statements.removeAll(varDecls)
subroutine.statements.addAll(0, varDecls)
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
subroutine.statements.removeAll(directives)
subroutine.statements.addAll(0, directives)
return subroutine
}
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in addVardecls)
addVardecls[scope] = mutableListOf()
val declList = addVardecls.getValue(scope)
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
override fun visit(decl: VarDecl): Statement {
val declValue = decl.value val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) { if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program) val declConstValue = declValue.constValue(program)
if(declConstValue==null) { if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment // move the vardecl (without value) to the scope and replace this with a regular assignment
decl.value = null
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, null, declValue, decl.position) val assign = Assignment(target, null, declValue, decl.position)
assign.linkParents(decl.parent) return listOf(
decl.value = null IAstModification.ReplaceNode(decl, assign, parent),
addVarDecl(decl.definingScope(), decl) IAstModification.InsertFirst(decl, decl.definingScope() as Node)
return assign )
} }
} }
return super.visit(decl) return noModifications
} }
override fun visit(assignment: Assignment): Statement { override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val assg = super.visit(assignment) val choices = whenStatement.choiceValues(program).sortedBy {
if(assg !is Assignment) it.first?.first() ?: Int.MAX_VALUE
return assg }
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
return noModifications
}
// see if a typecast is needed to convert the value's type into the proper target type override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueItype = assg.value.inferType(program) if(assignment.aug_op!=null) {
val targetItype = assg.target.inferType(program, assg) return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
}
if(targetItype.isKnown && valueItype.isKnown) { val valueType = assignment.value.inferType(program)
val targettype = targetItype.typeOrElse(DataType.STRUCT) val targetType = assignment.target.inferType(program, assignment)
val valuetype = valueItype.typeOrElse(DataType.STRUCT) if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
val assignments = if (assignment.value is StructLiteralValue) {
// struct assignments will be flattened (if it's not a struct literal) flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = { ..... } '
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) { } else {
val assignments = if (assg.value is StructLiteralValue) { flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
flattenStructAssignmentFromStructLiteral(assg, program) // 'structvar = { ..... } ' }
} else { if(assignments.isNotEmpty()) {
flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2' val modifications = mutableListOf<IAstModification>()
} assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
return if (assignments.isEmpty()) { modifications.add(IAstModification.Remove(assignment, parent))
// something went wrong (probably incompatible struct types) return modifications
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
}
} }
} }
if(assg.aug_op!=null) { return noModifications
// transform augmented assg into normal assg so we have one case less to deal with later
val newTarget: Expression =
when {
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
assg.target.identifier != null -> assg.target.identifier!!
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
else -> throw FatalAstException("strange assg")
}
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
expression.linkParents(assg.parent)
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
convertedAssignment.linkParents(assg.parent)
return super.visit(convertedAssignment)
}
return assg
} }
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> { private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {

View File

@ -4,9 +4,7 @@ import prog8.ast.IFunctionCall
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.*
import prog8.ast.base.ErrorReporter
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
@ -18,6 +16,8 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
* (this includes function call arguments) * (this includes function call arguments)
*/ */
private val noModifications = emptyList<IAstModification>()
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program) val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightDt = expr.right.inferType(program)
@ -34,7 +34,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
} }
} }
} }
return emptyList() return noModifications
} }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
@ -45,13 +45,36 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
val targettype = targetItype.typeOrElse(DataType.STRUCT) val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT) val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) { if (valuetype != targettype) {
return listOf(IAstModification.ReplaceNode( if (valuetype isAssignableTo targettype) {
assignment.value, return listOf(IAstModification.ReplaceNode(
TypecastExpression(assignment.value, targettype, true, assignment.value.position), assignment.value,
assignment)) TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
} else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
val cvalue = assignment.value.constValue(program)
if(cvalue!=null) {
val number = cvalue.number.toDouble()
// more complex comparisons if the type is different, but the constant value is compatible
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
if(number<0x80)
return castLiteral(cvalue)
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
if(number<0x8000)
return castLiteral(cvalue)
}
}
}
} }
} }
return emptyList() return noModifications
} }
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
@ -77,6 +100,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
call.args[arg.second.index], call.args[arg.second.index],
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position), TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
call as Node)) call as Node))
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
return listOf(IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node))
} }
} }
} }
@ -116,7 +145,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) { if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
} }
return emptyList() return noModifications
} }
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
@ -127,7 +156,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position) ?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread)) return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
} }
return emptyList() return noModifications
} }
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> { override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
@ -138,7 +167,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position) ?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite)) return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
} }
return emptyList() return noModifications
} }
override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> {
@ -183,7 +212,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
} }
} }
} }
return emptyList() return noModifications
} }
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> { override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
@ -194,7 +223,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
if(subroutine.returntypes.size==1) { if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first() val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program).istype(subReturnType)) if (returnValue.inferType(program).istype(subReturnType))
return emptyList() return noModifications
if (returnValue is NumericLiteralValue) { if (returnValue is NumericLiteralValue) {
returnStmt.value = returnValue.cast(subroutine.returntypes.single()) returnStmt.value = returnValue.cast(subroutine.returntypes.single())
} else { } else {
@ -205,6 +234,6 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
} }
} }
} }
return emptyList() return noModifications
} }
} }

View File

@ -4,12 +4,10 @@ import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
sealed class Statement : Node { sealed class Statement : Node {
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
abstract fun accept(visitor: IAstVisitor) abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node) abstract fun accept(visitor: AstWalker, parent: Node)
@ -44,11 +42,12 @@ sealed class Statement : Node {
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() { class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {} override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
override fun replaceChildNode(node: Node, replacement: Node) {} override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override val expensiveToInline = false override val expensiveToInline = false
} }
@ -72,9 +71,9 @@ class Block(override val name: String,
require(replacement is Statement) require(replacement is Statement)
val idx = statements.indexOf(node) val idx = statements.indexOf(node)
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -95,7 +94,6 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
} }
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -118,7 +116,6 @@ data class Label(val name: String, override val position: Position) : Statement(
} }
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -139,9 +136,9 @@ open class Return(var value: Expression?, override val position: Position) : Sta
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression) require(replacement is Expression)
value = replacement value = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -151,7 +148,6 @@ open class Return(var value: Expression?, override val position: Position) : Sta
} }
class ReturnFromIrq(override val position: Position) : Return(null, position) { class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String { override fun toString(): String {
@ -169,7 +165,6 @@ class Continue(override val position: Position) : Statement() {
} }
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -183,7 +178,6 @@ class Break(override val position: Position) : Statement() {
} }
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -274,9 +268,9 @@ open class VarDecl(val type: VarDeclType,
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value) require(replacement is Expression && node===value)
value = replacement value = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -324,6 +318,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===index) require(replacement is Expression && node===index)
index = replacement index = replacement
replacement.parent = this
} }
companion object { companion object {
@ -332,11 +327,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
} }
} }
fun accept(visitor: IAstModifyingVisitor) {
index = index.accept(visitor)
}
fun accept(visitor: IAstVisitor) = index.accept(visitor) fun accept(visitor: IAstVisitor) = index.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, parent) fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
override fun toString(): String { override fun toString(): String {
return("ArrayIndex($index, pos=$position)") return("ArrayIndex($index, pos=$position)")
@ -345,7 +337,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
fun size() = (index as? NumericLiteralValue)?.number?.toInt() fun size() = (index as? NumericLiteralValue)?.number?.toInt()
} }
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() { open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline override val expensiveToInline
get() = value !is NumericLiteralValue get() = value !is NumericLiteralValue
@ -362,15 +354,39 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
node===value -> value = replacement as Expression node===value -> value = replacement as Expression
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String { override fun toString(): String {
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)") return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
} }
fun asDesugaredNonaugmented(): Assignment {
val augmented = aug_op ?: return this
val leftOperand: Expression =
when {
target.register != null -> RegisterExpr(target.register!!, target.position)
target.identifier != null -> target.identifier!!
target.arrayindexed != null -> target.arrayindexed!!
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position)
else -> throw FatalAstException("strange this")
}
val assignment =
if(augmented=="setvalue") {
Assignment(target, null, value, position)
} else {
val expression = BinaryExpression(leftOperand, augmented.substringBeforeLast('='), value, position)
Assignment(target, null, expression, position)
}
assignment.linkParents(parent)
return assignment
}
} }
data class AssignTarget(val register: Register?, data class AssignTarget(val register: Register?,
@ -393,9 +409,9 @@ data class AssignTarget(val register: Register?,
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -503,9 +519,9 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AssignTarget && node===target) require(replacement is AssignTarget && node===target)
target = replacement target = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -527,7 +543,6 @@ class Jump(val address: Int?,
} }
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -557,9 +572,9 @@ class FunctionCallStatement(override var target: IdentifierReference,
val idx = args.indexOf(node) val idx = args.indexOf(node)
args[idx] = replacement as Expression args[idx] = replacement as Expression
} }
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -577,7 +592,6 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
} }
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -607,9 +621,9 @@ class AnonymousScope(override var statements: MutableList<Statement>,
require(replacement is Statement) require(replacement is Statement)
val idx = statements.indexOf(node) val idx = statements.indexOf(node)
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -623,7 +637,6 @@ class NopStatement(override val position: Position): Statement() {
} }
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -670,9 +683,9 @@ class Subroutine(override val name: String,
require(replacement is Statement) require(replacement is Statement)
val idx = statements.indexOf(node) val idx = statements.indexOf(node)
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -680,6 +693,8 @@ class Subroutine(override val name: String,
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)" return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
} }
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun amountOfRtsInAsm(): Int = statements fun amountOfRtsInAsm(): Int = statements
.asSequence() .asSequence()
.filter { it is InlineAssembly } .filter { it is InlineAssembly }
@ -724,9 +739,9 @@ class IfStatement(var condition: Expression,
node===elsepart -> elsepart = replacement as AnonymousScope node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -752,9 +767,9 @@ class BranchStatement(var condition: BranchCondition,
node===elsepart -> elsepart = replacement as AnonymousScope node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -782,9 +797,9 @@ class ForLoop(val loopRegister: Register?,
node===body -> body = replacement as AnonymousScope node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
@ -817,9 +832,9 @@ class WhileLoop(var condition: Expression,
node===body -> body = replacement as AnonymousScope node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -836,9 +851,9 @@ class ForeverLoop(var body: AnonymousScope, override val position: Position) : S
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===body) require(replacement is AnonymousScope && node===body)
body = replacement body = replacement
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -861,9 +876,9 @@ class RepeatLoop(var body: AnonymousScope,
node===body -> body = replacement as AnonymousScope node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -887,6 +902,7 @@ class WhenStatement(var condition: Expression,
val idx = choices.indexOf(node) val idx = choices.indexOf(node)
choices[idx] = replacement as WhenChoice choices[idx] = replacement as WhenChoice
} }
replacement.parent = this
} }
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> { fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
@ -907,7 +923,6 @@ class WhenStatement(var condition: Expression,
} }
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -925,6 +940,7 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===statements) require(replacement is AnonymousScope && node===statements)
statements = replacement statements = replacement
replacement.parent = this
} }
override fun toString(): String { override fun toString(): String {
@ -932,7 +948,6 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
} }
fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
@ -953,13 +968,13 @@ class StructDecl(override val name: String,
require(replacement is Statement) require(replacement is Statement)
val idx = statements.indexOf(node) val idx = statements.indexOf(node)
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this
} }
val numberOfElements: Int val numberOfElements: Int
get() = this.statements.size get() = this.statements.size
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun nameOfFirstMember() = (statements.first() as VarDecl).name fun nameOfFirstMember() = (statements.first() as VarDecl).name
@ -976,6 +991,7 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression) require(replacement is Expression && node===addressExpression)
addressExpression = replacement addressExpression = replacement
replacement.parent = this
} }
override fun toString(): String { override fun toString(): String {
@ -983,6 +999,5 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
} }
fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }

View File

@ -0,0 +1,106 @@
package prog8.compiler
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val sub = scope.definingSubroutine()
if (sub != null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing != null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if (!conflicts) {
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
return numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, null, initValue, it.position)
initValue.parent = assign
IAstModification.InsertFirst(assign, scope)
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
}
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if (subroutineStmtIdx > 0
&& outerStatements[subroutineStmtIdx - 1] !is Jump
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
}
return mods
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
else if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
}
return noModifications
}
}

View File

@ -5,6 +5,7 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions import prog8.optimizer.simplifyExpressions
@ -41,11 +42,13 @@ fun compileProgram(filepath: Path,
optimizeAst(programAst, errors) optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions) postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst) // printAst(programAst) // TODO
if(writeAssembly) if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions) programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
} }
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles) return CompilationResult(true, programAst, programName, importedFiles)
@ -141,7 +144,6 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
println("Processing...") println("Processing...")
programAst.checkIdentifiers(errors) programAst.checkIdentifiers(errors)
errors.handle() errors.handle()
programAst.makeForeverLoops()
programAst.constantFold(errors) programAst.constantFold(errors)
errors.handle() errors.handle()
programAst.removeNopsFlattenAnonScopes() programAst.removeNopsFlattenAnonScopes()
@ -161,13 +163,20 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions() val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(errors) val optsDone2 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
errors.handle() errors.handle()
if (optsDone1 + optsDone2 == 0) if (optsDone1 + optsDone2 == 0)
break break
} }
val remover = UnusedCodeRemover()
remover.visit(programAst)
remover.applyModifications()
} }
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) { private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.transformAssignments(errors)
errors.handle()
programAst.addTypecasts(errors) programAst.addTypecasts(errors)
errors.handle() errors.handle()
programAst.removeNopsFlattenAnonScopes() programAst.removeNopsFlattenAnonScopes()
@ -181,14 +190,19 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
optimize: Boolean, compilerOptions: CompilationOptions): String { optimize: Boolean, compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast, // asm generation directly from the Ast,
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions) val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.prepareAsmVariablesAndReturns(errors) programAst.processAstBeforeAsmGeneration(errors)
errors.handle() errors.handle()
// printAst(programAst)
val assembly = CompilationTarget.asmGenerator( val assembly = CompilationTarget.asmGenerator(
programAst, programAst,
errors,
zeropage, zeropage,
compilerOptions, compilerOptions,
outputDir).compileToAssembly(optimize) outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions) assembly.assemble(compilerOptions)
errors.handle()
return assembly.name return assembly.name
} }

View File

@ -1,80 +0,0 @@
package prog8.compiler.target
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
class AsmVariableAndReturnsPreparer(val program: Program, val errors: ErrorReporter): AstWalker() {
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.value==null && decl.type==VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
}
return emptyList()
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val sub = scope.definingSubroutine()
if(sub!=null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing!=null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if(!conflicts) {
val numericVarsWithValue = decls.filter { it.value!=null && it.datatype in NumericDatatypes }
return numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, null, initValue, it.position)
IAstModification.InsertFirst(assign, scope)
} +
decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
}
}
return emptyList()
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if(subroutine.asmAddress==null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm()==0
&& subroutine.statements.lastOrNull {it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertAfter(subroutine.statements.last(), returnStmt, subroutine)
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if(subroutineStmtIdx>0
&& outerStatements[subroutineStmtIdx-1] !is Jump
&& outerStatements[subroutineStmtIdx-1] !is Subroutine
&& outerStatements[subroutineStmtIdx-1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx-1], returnStmt, outerScope as Node)
}
return mods
}
}

View File

@ -1,6 +1,7 @@
package prog8.compiler.target package prog8.compiler.target
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage import prog8.compiler.Zeropage
import java.nio.file.Path import java.nio.file.Path
@ -12,6 +13,6 @@ internal interface CompilationTarget {
lateinit var machine: IMachineDefinition lateinit var machine: IMachineDefinition
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short> lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
} }
} }

View File

@ -26,6 +26,7 @@ import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program, internal class AsmGen(private val program: Program,
private val errors: ErrorReporter,
private val zeropage: Zeropage, private val zeropage: Zeropage,
private val options: CompilationOptions, private val options: CompilationOptions,
private val outputDir: Path): IAssemblyGenerator { private val outputDir: Path): IAssemblyGenerator {
@ -38,7 +39,7 @@ internal class AsmGen(private val program: Program,
private val forloopsAsmGen = ForLoopsAsmGen(program, this) private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this) private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this) private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this) private val assignmentAsmGen = AssignmentAsmGen(program, errors, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this) private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = ArrayDeque<String>() internal val loopEndLabels = ArrayDeque<String>()
internal val loopContinueLabels = ArrayDeque<String>() internal val loopContinueLabels = ArrayDeque<String>()
@ -913,6 +914,7 @@ internal class AsmGen(private val program: Program,
val indexName = asmIdentifierName(index) val indexName = asmIdentifierName(index)
out(" lda $indexName") out(" lda $indexName")
} }
// TODO optimize more cases
else -> { else -> {
expressionsAsmGen.translateExpression(index) expressionsAsmGen.translateExpression(index)
out(" inx | lda $ESTACK_LO_HEX,x") out(" inx | lda $ESTACK_LO_HEX,x")
@ -920,6 +922,28 @@ internal class AsmGen(private val program: Program,
} }
} }
internal fun translateArrayIndexIntoY(expr: ArrayIndexedExpression) {
when (val index = expr.arrayspec.index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
is RegisterExpr -> {
when (index.register) {
Register.A -> out(" tay")
Register.X -> out(" txa | tay")
Register.Y -> {}
}
}
is IdentifierReference -> {
val indexName = asmIdentifierName(index)
out(" ldy $indexName")
}
// TODO optimize more cases, see translateArrayIndexIntoA
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | ldy $ESTACK_LO_HEX,x")
}
}
}
internal fun translateExpression(expression: Expression) = internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression) expressionsAsmGen.translateExpression(expression)

View File

@ -28,7 +28,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is RegisterExpr -> translateExpression(expression) is RegisterExpr -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression) is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateExpression(expression) is FunctionCall -> translateExpression(expression)
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment") is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened") is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
} }
@ -40,8 +40,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if (builtinFunc != null) { if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc) asmgen.translateFunctioncallExpression(expression, builtinFunc)
} else { } else {
asmgen.translateFunctionCall(expression)
val sub = expression.target.targetSubroutine(program.namespace)!! val sub = expression.target.targetSubroutine(program.namespace)!!
asmgen.translateFunctionCall(expression)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters) val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) { for ((_, reg) in returns) {
if (!reg.stack) { if (!reg.stack) {
@ -51,7 +51,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex") RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex") RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex") RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable") RegisterOrPair.X -> {
// return value in X register has been discarded, just push a zero
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
}
RegisterOrPair.AX -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
}
RegisterOrPair.XY -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
}
} }
} }
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc // return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
@ -110,7 +121,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
} }
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else") in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
} }
@ -320,8 +331,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
// the general, non-optimized cases // the general, non-optimized cases
translateExpression(expr.left) translateExpression(expr.left)
translateExpression(expr.right) translateExpression(expr.right)
if(leftDt!=rightDt) if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always? || (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
when (leftDt) { when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt) in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt) in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)

View File

@ -37,7 +37,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
is IdentifierReference -> { is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference) translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
} }
else -> throw AssemblyError("can't iterate over ${stmt.iterable}") else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
} }
} }

View File

@ -19,7 +19,8 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// output the code to setup the parameters and perform the actual call // output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values! // does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(Register.X in sub.asmClobbers) val saveX = Register.X in sub.asmClobbers || sub.regXasResult()
if(saveX)
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y... asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
val subName = asmgen.asmIdentifierName(stmt.target) val subName = asmgen.asmIdentifierName(stmt.target)
@ -30,7 +31,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} }
asmgen.out(" jsr $subName") asmgen.out(" jsr $subName")
if(Register.X in sub.asmClobbers) if(saveX)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
} }
@ -42,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(!argumentTypeCompatible(sourceDt, parameter.value.type)) if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible") throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) { if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a variable // pass parameter via a regular variable (not via registers)
val paramVar = parameter.value val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".") val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position) val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
@ -54,7 +55,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort()) in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt()) in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble()) DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?") in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype") else -> throw AssemblyError("weird parameter datatype")
} }
} }
@ -64,7 +65,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value) in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value) in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value) DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?") in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype") else -> throw AssemblyError("weird parameter datatype")
} }
} }
@ -202,11 +203,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} }
is IdentifierReference -> { is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value) val sourceName = asmgen.asmIdentifierName(value)
when (register) { if(sourceDt in PassByReferenceDatatypes) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1") when (register) {
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1") RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1") RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
else -> {} RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
} else {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
} }
} }
else -> { else -> {
@ -225,6 +235,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType) if(argType isAssignableTo paramType)
return true return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types. // we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa // strings are assignable to UWORD, for example, and vice versa

View File

@ -104,17 +104,14 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
} }
is RegisterExpr -> { is RegisterExpr -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx) asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what) incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
} }
is IdentifierReference -> { is IdentifierReference -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx) asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what) incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
} }
else -> { else -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx) asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what) incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
} }

View File

@ -87,7 +87,20 @@ val BuiltinFunctions = mapOf(
FParam("address", IterableDatatypes + DataType.UWORD), FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)), FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null), FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen) "strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
"substr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
"leftstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
"rightstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null)
) )
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!! fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
@ -172,6 +185,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
class NotConstArgumentException: AstException("not a const argument to a built-in function") class NotConstArgumentException: AstException("not a const argument to a built-in function")
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue { private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
@ -252,17 +266,22 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position) return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference) if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position) throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!! val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) { return when(target.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
arraySize = target.arraysize!!.size()!! arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256) if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}") throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
arraySize = target.arraysize!!.size()!! arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256) if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}") throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)

View File

@ -0,0 +1,157 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.expressions.BinaryExpression
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import prog8.ast.statements.PostIncrDecr
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
var optimizationsDone: Int = 0
private val noModifications = emptyList<IAstModification>()
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
// augmented form as wel (with the operator "setvalue")
if (assignment.aug_op == null) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
if (assignment.target.isSameAs(binExpr.left)) {
assignment.value = binExpr.right
assignment.aug_op = binExpr.operator + "="
assignment.value.parent = assignment
optimizationsDone++
return noModifications
}
}
assignment.aug_op = "setvalue"
optimizationsDone++
} else if(assignment.aug_op == "+=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y + 1 -> x += y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y + 2 -> x += y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y - 1 -> x += y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y - 2 -> x += y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
}
}
} else if(assignment.aug_op == "-=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y + 1 -> x -= y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y + 2 -> x -= y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y - 1 -> x -= y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y - 2 -> x -= y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
}
}
}
return noModifications
}
}

View File

@ -29,7 +29,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() } val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() } val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables // TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
val usedSymbols = mutableSetOf<Statement>() val usedSymbols = mutableSetOf<Statement>()
init { init {
@ -127,7 +127,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) { if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
// make sure autogenerated vardecls are in the used symbols // make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
addNodeAndParentScopes(decl) addNodeAndParentScopes(decl)
} }

View File

@ -1,24 +1,46 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.IFunctionCall import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor { // First thing to do is replace all constant identifiers with their actual value,
var optimizationsDone: Int = 0 // and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun visit(decl: VarDecl): Statement { override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// 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)
return noModifications
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return noModifications
val cval = identifier.constValue(program) ?: return noModifications
return when (cval.type) {
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call tree for this? // TODO: use call graph for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) { if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.err("recursive var declaration", decl.position) errors.err("recursive var declaration", decl.position)
return decl return noModifications
} }
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) { if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
@ -27,15 +49,20 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size // for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) { if(arrayval!=null) {
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position) return listOf(IAstModification.SetExpression(
optimizationsDone++ { decl.arraysize = ArrayIndex(it, decl.position) },
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
decl
))
} }
} }
else if(decl.arraysize?.size()==null) { else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.accept(this) val size = decl.arraysize!!.index.constValue(program)
if(size is NumericLiteralValue) { if(size!=null) {
decl.arraysize = ArrayIndex(size, decl.position) return listOf(IAstModification.SetExpression(
optimizationsDone++ { decl.arraysize = ArrayIndex(it, decl.position) },
size, decl
))
} }
} }
} }
@ -46,9 +73,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
val litval = decl.value as? NumericLiteralValue val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) { if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position) val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
decl.value = newValue return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
optimizationsDone++
return super.visit(decl)
} }
} }
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
@ -62,23 +87,21 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
val constRange = rangeExpr.toConstantIntegerRange() val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) { if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE) val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) { val newValue = if(eltType in ByteDatatypes) {
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
} else { } else {
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
} }
decl.value!!.linkParents(decl) return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
optimizationsDone++
return super.visit(decl)
} }
} }
if(numericLv!=null && numericLv.type== DataType.FLOAT) if(numericLv!=null && numericLv.type==DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position) errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.size() ?: return decl val size = decl.arraysize?.size() ?: return noModifications
if (rangeExpr==null && numericLv!=null) { if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize. // arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt() val fillvalue = numericLv.number.toInt()
@ -104,19 +127,27 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray() val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position) val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
decl.value = refValue return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return decl val size = decl.arraysize?.size() ?: return noModifications
val litval = decl.value as? NumericLiteralValue val litval = decl.value as? NumericLiteralValue
if(litval==null) { val rangeExpr = decl.value as? RangeExpr
// there's no initialization value, but the size is known, so we're ok. if(rangeExpr!=null) {
return super.visit(decl) // convert the initializer range expression to an actual array of floats
} else { val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size. // arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble() val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE) if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
@ -125,10 +156,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray() val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position) val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
decl.value = refValue return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
} }
} }
} }
@ -143,135 +171,69 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
if(declValue!=null && decl.type==VarDeclType.VAR if(declValue!=null && decl.type==VarDeclType.VAR
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) { && declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable // cast the numeric literal to the appropriate datatype of the variable
decl.value = declValue.cast(decl.datatype) return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.cast(decl.datatype), decl))
} }
return super.visit(decl) return noModifications
} }
}
/**
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
*/
override fun visit(identifier: IdentifierReference): Expression {
// don't replace when it's an assignment target or loop variable
if(identifier.parent is AssignTarget)
return identifier
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return identifier
val cval = identifier.constValue(program) ?: return identifier internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
return when (cval.type) { private val noModifications = emptyList<IAstModification>()
in NumericDatatypes -> {
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
copy.parent = identifier.parent
copy
}
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> identifier
}
}
override fun visit(functionCall: FunctionCall): Expression { override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
super.visit(functionCall)
typeCastConstArguments(functionCall)
return functionCall.constValue(program) ?: functionCall
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
super.visit(functionCallStatement)
typeCastConstArguments(functionCallStatement)
return functionCallStatement
}
private fun typeCastConstArguments(functionCall: IFunctionCall) {
if(functionCall.target.nameInSource.size==1) {
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
if(builtinFunction!=null) {
// match the arguments of a builtin function signature.
for(arg in functionCall.args.withIndex().zip(builtinFunction.parameters)) {
val possibleDts = arg.second.possibleDatatypes
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first())
functionCall.args[arg.first.index] = convertedValue
optimizationsDone++
}
}
return
}
}
// match the arguments of a subroutine.
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.args.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt)
functionCall.args[arg.first.index] = convertedValue
optimizationsDone++
}
}
}
}
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing // @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null) return if(addrOf!=null)
return super.visit(addrOf.identifier) listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
return super.visit(memread) else
noModifications
} }
/** override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
* Try to accept a unary prefix expression. // Try to turn a unary prefix expression into a single constant value.
* Compile-time constant sub expressions will be evaluated on the spot. // Compile-time constant sub expressions will be evaluated on the spot.
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5 // For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
*/ val subexpr = expr.expression
override fun visit(expr: PrefixExpression): Expression {
val prefixExpr=super.visit(expr)
if(prefixExpr !is PrefixExpression)
return prefixExpr
val subexpr = prefixExpr.expression
if (subexpr is NumericLiteralValue) { if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true) // accept prefixed literal values (such as -3, not true)
return when (prefixExpr.operator) { return when (expr.operator) {
"+" -> subexpr "+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
"-" -> when (subexpr.type) { "-" -> when (subexpr.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
optimizationsDone++ listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position) NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position),
parent))
} }
DataType.FLOAT -> { DataType.FLOAT -> {
optimizationsDone++ listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position) NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
parent))
} }
else -> throw ExpressionError("can only take negative of int or float", subexpr.position) else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
} }
"~" -> when (subexpr.type) { "~" -> when (subexpr.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
optimizationsDone++ listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position) NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position),
parent))
} }
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position) else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
} }
"not" -> { "not" -> {
optimizationsDone++ listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position) NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
parent))
} }
else -> throw ExpressionError(prefixExpr.operator, subexpr.position) else -> throw ExpressionError(expr.operator, subexpr.position)
} }
} }
return prefixExpr return noModifications
} }
/** /**
* Try to accept a binary expression. * Try to constfold a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot. * Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54. * For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
* *
@ -287,13 +249,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
* (X / c1) * c2 -> X / (c2/c1) * (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2) * (X + c1) - c2 -> X + (c1-c2)
*/ */
override fun visit(expr: BinaryExpression): Expression { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
super.visit(expr)
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program) val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program) val rightconst = expr.right.constValue(program)
@ -307,218 +263,62 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
val subrightconst = subExpr.right.constValue(program) val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) { if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering. // try reordering.
return groupTwoConstsTogether(expr, subExpr, val change = groupTwoConstsTogether(expr, subExpr,
leftconst != null, rightconst != null, leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null) subleftconst != null, subrightconst != null)
return change?.let { listOf(it) } ?: noModifications
} }
} }
// const fold when both operands are a const // const fold when both operands are a const
return when { if(leftconst != null && rightconst != null) {
leftconst != null && rightconst != null -> { val evaluator = ConstExprEvaluator()
optimizationsDone++ return listOf(IAstModification.ReplaceNode(
val evaluator = ConstExprEvaluator() expr,
evaluator.evaluate(leftconst, expr.operator, rightconst) evaluator.evaluate(leftconst, expr.operator, rightconst),
} parent
))
else -> expr
} }
return noModifications
} }
private fun groupTwoConstsTogether(expr: BinaryExpression, override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
subExpr: BinaryExpression, // because constant folding can result in arrays that are now suddenly capable
leftIsConst: Boolean, // of telling the type of all their elements (for instance, when they contained -2 which
rightIsConst: Boolean, // was a prefix expression earlier), we recalculate the array's datatype.
subleftIsConst: Boolean, if(array.type.isKnown)
subrightIsConst: Boolean): Expression return noModifications
{
// todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) {
// both operators are the isSameAs.
// If + or *, we can simply swap the const of expr and Var in subexpr.
if(expr.operator=="+" || expr.operator=="*") {
if(leftIsConst) {
if(subleftIsConst)
expr.left = subExpr.right.also { subExpr.right = expr.left }
else
expr.left = subExpr.left.also { subExpr.left = expr.left }
} else {
if(subleftIsConst)
expr.right = subExpr.right.also {subExpr.right = expr.right }
else
expr.right = subExpr.left.also { subExpr.left = expr.right }
}
optimizationsDone++
return expr
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *) // if the array literalvalue is inside an array vardecl, take the type from that
if(expr.operator=="-" || expr.operator=="/") { // otherwise infer it from the elements of the array
optimizationsDone++ val vardeclType = (array.parent as? VarDecl)?.datatype
if(leftIsConst) { if(vardeclType!=null) {
return if(subleftIsConst) { val newArray = array.cast(vardeclType)
val tmp = subExpr.right if (newArray != null && newArray != array)
subExpr.right = subExpr.left return listOf(IAstModification.ReplaceNode(array, newArray, parent))
subExpr.left = expr.left } else {
expr.left = tmp val arrayDt = array.guessDatatype(program)
expr.operator = if(expr.operator=="-") "+" else "*" if (arrayDt.isKnown) {
expr val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
} else if (newArray != null && newArray != array)
BinaryExpression( return listOf(IAstModification.ReplaceNode(array, newArray, parent))
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position)
} else {
return if(subleftIsConst) {
expr.right = subExpr.right.also { subExpr.right = expr.right }
expr
} else
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position)
}
} }
return expr
} }
return noModifications
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// the args of a fuction are constfolded via recursion already.
val constvalue = functionCall.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
else else
{ noModifications
if(expr.operator=="/" && subExpr.operator=="*") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
optimizationsDone++
if(leftIsConst){
return if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
return expr
}
} }
override fun visit(forLoop: ForLoop): Statement { override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr { fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
val newFrom: NumericLiteralValue val newFrom: NumericLiteralValue
val newTo: NumericLiteralValue val newTo: NumericLiteralValue
@ -536,87 +336,259 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
return RangeExpr(newFrom, newTo, newStep, range.position) return RangeExpr(newFrom, newTo, newStep, range.position)
} }
val forLoop2 = super.visit(forLoop) as ForLoop
// check if we need to adjust an array literal to the loop variable's datatype
val array = forLoop2.iterable as? ArrayLiteralValue
if(array!=null) {
val loopvarDt: DataType = when {
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
forLoop.loopRegister!=null -> DataType.UBYTE
else -> throw FatalAstException("weird for loop")
}
val arrayType = when(loopvarDt) {
DataType.UBYTE -> DataType.ARRAY_UB
DataType.BYTE -> DataType.ARRAY_B
DataType.UWORD -> DataType.ARRAY_UW
DataType.WORD -> DataType.ARRAY_W
DataType.FLOAT -> DataType.ARRAY_F
else -> throw FatalAstException("invalid array elt type")
}
val array2 = array.cast(arrayType)
if(array2!=null && array2!==array) {
forLoop2.iterable = array2
array2.linkParents(forLoop2)
}
}
// adjust the datatype of a range expression in for loops to the loop variable. // adjust the datatype of a range expression in for loops to the loop variable.
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2 val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
val rangeFrom = iterableRange.from as? NumericLiteralValue val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return forLoop2 if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace) val loopvar = forLoop.loopVar?.targetVarDecl(program.namespace)
if(loopvar!=null) { if(loopvar!=null) {
val stepLiteral = iterableRange.step as? NumericLiteralValue val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) { when(loopvar.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) { if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values // attempt to translate the iterable into ubyte values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) { if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values // attempt to translate the iterable into byte values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) { if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values // attempt to translate the iterable into uword values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
} }
} }
DataType.WORD -> { DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) { if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values // attempt to translate the iterable into word values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
} }
} }
else -> throw FatalAstException("invalid loopvar datatype $loopvar") else -> throw FatalAstException("invalid loopvar datatype $loopvar")
} }
} }
return forLoop2
return noModifications
} }
override fun visit(arrayLiteral: ArrayLiteralValue): Expression { private class ShuffleOperands(val expr: BinaryExpression,
// because constant folding can result in arrays that are now suddenly capable val exprOperator: String?,
// of telling the type of all their elements (for instance, when they contained -2 which val subExpr: BinaryExpression,
// was a prefix expression earlier), we recalculate the array's datatype. val newExprLeft: Expression?,
val array = super.visit(arrayLiteral) val newExprRight: Expression?,
if(array is ArrayLiteralValue) { val newSubexprLeft: Expression?,
if(array.type.isKnown) val newSubexprRight: Expression?
return array ): IAstModification {
val arrayDt = array.guessDatatype(program) override fun perform() {
if(arrayDt.isKnown) { if(exprOperator!=null) expr.operator = exprOperator
val newArray = arrayLiteral.cast(arrayDt.typeOrElse(DataType.STRUCT)) if(newExprLeft!=null) expr.left = newExprLeft
if(newArray!=null) if(newExprRight!=null) expr.right = newExprRight
return newArray if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
} if(newSubexprRight!=null) subExpr.right = newSubexprRight
} }
return array
} }
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification?
{
// todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) {
// both operators are the same.
// If + or *, we can simply shuffle the const operands around to optimize.
if(expr.operator=="+" || expr.operator=="*") {
return if(leftIsConst) {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
else
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
} else {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
else
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
}
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
if(expr.operator=="-" || expr.operator=="/") {
if(leftIsConst) {
return if (subleftIsConst) {
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position),
expr.parent)
}
} else {
return if(subleftIsConst) {
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position),
expr.parent)
}
}
}
return null
}
else
{
if(expr.operator=="/" && subExpr.operator=="*") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
if(leftIsConst){
val change = if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
return null
}
}
} }

View File

@ -14,12 +14,6 @@ import kotlin.math.pow
/* /*
todo add more expression optimizations todo add more expression optimizations
x + x -> x << 1 (for words... for bytes too?)
x + x + x + x -> x << 2 (for words... for bytes too?)
x + x + x -> ???? x*3 ??? words/bytes?
x - x -> 0
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*/ */
@ -28,11 +22,12 @@ import kotlin.math.pow
internal class ExpressionSimplifier(private val program: Program) : AstWalker() { internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet() private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet() private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private val noModifications = emptyList<IAstModification>()
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if (assignment.aug_op != null) if (assignment.aug_op != null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment") throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
return emptyList() return noModifications
} }
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
@ -88,10 +83,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (newExpr != null) if (newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent)) return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
} }
else -> return emptyList() else -> return noModifications
} }
} }
return emptyList() return noModifications
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
@ -103,7 +98,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (!leftIDt.isKnown || !rightIDt.isKnown) if (!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr") throw FatalAstException("can't determine datatype of both expression operands $expr")
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue // ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
if (leftVal != null && expr.operator in associativeOperators && rightVal == null) if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
return listOf(IAstModification.SwapOperands(expr)) return listOf(IAstModification.SwapOperands(expr))
@ -180,6 +174,64 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
} }
} }
if(expr.operator == ">=" && rightVal?.number == 0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed >=0 --> signed ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw >=0 --> msb(signedw) ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
}
if(expr.operator == "<" && rightVal?.number == 0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed < 0 --> signed & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw < 0 --> msb(signedw) & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
}
// simplify when a term is constant and directly determines the outcome // simplify when a term is constant and directly determines the outcome
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position) val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position) val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
@ -246,7 +298,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if(newExpr != null) if(newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent)) return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return emptyList() return noModifications
} }
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? { private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
@ -258,6 +310,14 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
} }
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if(expr.left.isSameAs(expr.right)) {
// optimize X+X into X *2
expr.operator = "*"
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position)
expr.right.linkParents(expr)
return expr
}
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null
@ -278,6 +338,11 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
} }
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if(expr.left.isSameAs(expr.right)) {
// optimize X-X into 0
return NumericLiteralValue.optimalInteger(0, expr.position)
}
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null

View File

@ -5,12 +5,21 @@ import prog8.ast.base.ErrorReporter
internal fun Program.constantFold(errors: ErrorReporter) { internal fun Program.constantFold(errors: ErrorReporter) {
val optimizer = ConstantFoldingOptimizer(this, errors) val replacer = ConstantIdentifierReplacer(this, errors)
optimizer.visit(this) replacer.visit(this)
if(errors.isEmpty()) {
replacer.applyModifications()
while(errors.isEmpty() && optimizer.optimizationsDone>0) { val optimizer = ConstantFoldingOptimizer(this, errors)
optimizer.optimizationsDone = 0
optimizer.visit(this) optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
}
if(errors.isEmpty()) {
replacer.visit(this)
replacer.applyModifications()
}
} }
if(errors.isEmpty()) if(errors.isEmpty())
@ -21,9 +30,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
internal fun Program.optimizeStatements(errors: ErrorReporter): Int { internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
val optimizer = StatementOptimizer(this, errors) val optimizer = StatementOptimizer(this, errors)
optimizer.visit(this) optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
return optimizer.optimizationsDone return optimizationCount
} }
internal fun Program.simplifyExpressions() : Int { internal fun Program.simplifyExpressions() : Int {

View File

@ -1,11 +1,13 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Module import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
@ -19,88 +21,33 @@ import kotlin.math.floor
internal class StatementOptimizer(private val program: Program, internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : IAstModifyingVisitor { private val errors: ErrorReporter) : AstWalker() {
var optimizationsDone: Int = 0
private set
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program) private val callgraph = CallGraph(program)
private val vardeclsToRemove = mutableListOf<VarDecl>() private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
override fun visit(program: Program) { override fun after(block: Block, parent: Node): Iterable<IAstModification> {
removeUnusedCode(callgraph)
super.visit(program)
for(decl in vardeclsToRemove) {
decl.definingScope().remove(decl)
}
}
private fun removeUnusedCode(callgraph: CallGraph) {
// remove all subroutines that aren't called, or are empty
val removeSubroutines = mutableSetOf<Subroutine>()
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removeSubroutines.add(sub)
}
}
if (removeSubroutines.isNotEmpty()) {
removeSubroutines.forEach {
it.definingScope().remove(it)
}
}
val removeBlocks = mutableSetOf<Block>()
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removeBlocks.add(block)
}
if (removeBlocks.isNotEmpty()) {
removeBlocks.forEach { it.definingScope().remove(it) }
}
// remove modules that are not imported, or are empty (unless it's a library modules)
val removeModules = mutableSetOf<Module>()
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removeModules.add(it)
}
if (removeModules.isNotEmpty()) {
program.modules.removeAll(removeModules)
}
}
override fun visit(block: Block): Statement {
if("force_output" !in block.options()) { if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) { if (block.containsNoCodeNorVars()) {
optimizationsDone++
errors.warn("removing empty block '${block.name}'", block.position) errors.warn("removing empty block '${block.name}'", block.position)
return NopStatement.insteadOf(block) return listOf(IAstModification.Remove(block, parent))
} }
if (block !in callgraph.usedSymbols) { if (block !in callgraph.usedSymbols) {
optimizationsDone++
errors.warn("removing unused block '${block.name}'", block.position) errors.warn("removing unused block '${block.name}'", block.position)
return NopStatement.insteadOf(block) // remove unused block return listOf(IAstModification.Remove(block, parent))
} }
} }
return noModifications
return super.visit(block)
} }
override fun visit(subroutine: Subroutine): Statement { override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
super.visit(subroutine)
val forceOutput = "force_output" in subroutine.definingBlock().options() val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) { if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) { if(subroutine.containsNoCodeNorVars()) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++ return listOf(IAstModification.Remove(subroutine, parent))
return NopStatement.insteadOf(subroutine)
} }
} }
@ -111,23 +58,341 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine !in callgraph.usedSymbols && !forceOutput) { if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++ return listOf(IAstModification.Remove(subroutine, parent))
return NopStatement.insteadOf(subroutine)
} }
return subroutine return noModifications
} }
override fun visit(decl: VarDecl): Statement { override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val linesToRemove = deduplicateAssignments(scope.statements)
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in decl.definingBlock().options() val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) { if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type == VarDeclType.VAR) if(decl.type == VarDeclType.VAR)
errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position) errors.warn("removing unused variable '${decl.name}'", decl.position)
optimizationsDone++
return NopStatement.insteadOf(decl) return listOf(IAstModification.Remove(decl, parent))
} }
return super.visit(decl) return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent))
}
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// this is a C-64 specific optimization
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue
val pos = functionCallStatement.position
if(string.value.length==1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if(string.value.length==2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
val anonscope = AnonymousScope(mutableListOf(), pos)
anonscope.statements.add(chrout1)
anonscope.statements.add(chrout2)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
}
}
}
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is ReturnFromIrq || first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent))
}
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
}
}
return noModifications
}
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
return listOf(IAstModification.Remove(ifStatement, parent))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
)
}
val constvalue = ifStatement.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
errors.warn("condition is always true", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
} else {
// always false -> keep only else-part
errors.warn("condition is always false", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
}
}
return noModifications
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop
return listOf(IAstModification.Remove(forLoop, parent))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, parent))
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
if(iterable!=null) {
if(iterable.datatype==DataType.STR) {
val sv = iterable.value as StringLiteralValue
val size = sv.value.length
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = CompilationTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
else if(iterable.datatype in ArrayDatatypes) {
val size = iterable.arraysize!!.size()
if(size==1) {
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
}
}
return noModifications
}
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) {
if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no continue and break statements)
errors.warn("condition is always true", repeatLoop.untilCondition.position)
if(!hasContinueOrBreak(repeatLoop.body))
return listOf(IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent))
} else {
// always false
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position)
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent))
}
}
return noModifications
}
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue) {
// always true
val forever = ForeverLoop(whileLoop.body, whileLoop.position)
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, parent))
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
// remove empty choices
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
override fun perform() {
whenStatement.choices.remove(choice)
}
}
return whenStatement.choices
.filter { !it.statements.containsCodeOrVars() }
.map { ChoiceRemover(it) }
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, parent))
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
// remove assignments to self
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace))
return listOf(IAstModification.Remove(assignment, parent))
}
val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
}
}
}
"-" -> {
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
}
}
}
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"<<" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsl(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
}
">>" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsr(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
}
}
}
}
return noModifications
} }
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> { private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
@ -155,223 +420,21 @@ internal class StatementOptimizer(private val program: Program,
return linesToRemove return linesToRemove
} }
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
}
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue
if(string.value.length==1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
functionCallStatement.args.clear()
functionCallStatement.args.add(NumericLiteralValue.optimalInteger(firstCharEncoded.toInt(), functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
vardeclsToRemove.add(vardecl)
optimizationsDone++
return functionCallStatement
} else if(string.value.length==2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[0].toInt(), functionCallStatement.position)),
functionCallStatement.void, functionCallStatement.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[1].toInt(), functionCallStatement.position)),
functionCallStatement.void, functionCallStatement.position))
vardeclsToRemove.add(vardecl)
optimizationsDone++
return scope
}
}
}
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCallStatement(first.identifier, functionCallStatement.args, functionCallStatement.void, functionCallStatement.position)
}
if(first is ReturnFromIrq || first is Return) {
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
}
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): Expression {
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCall(first.identifier, functionCall.args, functionCall.position)
}
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return constval
}
}
return super.visit(functionCall)
}
override fun visit(ifStatement: IfStatement): Statement {
super.visit(ifStatement)
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
optimizationsDone++
return NopStatement.insteadOf(ifStatement)
}
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
// invert the condition and move else part to true part
ifStatement.truepart = ifStatement.elsepart
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
ifStatement.condition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
optimizationsDone++
return ifStatement
}
val constvalue = ifStatement.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
errors.warn("condition is always true", ifStatement.position)
optimizationsDone++
ifStatement.truepart
} else {
// always false -> keep only else-part
errors.warn("condition is always false", ifStatement.position)
optimizationsDone++
ifStatement.elsepart
}
}
return ifStatement
}
override fun visit(forLoop: ForLoop): Statement {
super.visit(forLoop)
if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop
optimizationsDone++
return NopStatement.insteadOf(forLoop)
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop
optimizationsDone++
return NopStatement.insteadOf(forLoop)
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
forLoop.body.statements.add(0, assignment)
optimizationsDone++
return forLoop.body
}
}
return forLoop
}
override fun visit(whileLoop: WhileLoop): Statement {
super.visit(whileLoop)
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> print a warning, and optimize into a forever-loop
errors.warn("condition is always true", whileLoop.condition.position)
optimizationsDone++
ForeverLoop(whileLoop.body, whileLoop.position)
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
optimizationsDone++
NopStatement.insteadOf(whileLoop)
}
}
return whileLoop
}
override fun visit(repeatLoop: RepeatLoop): Statement {
super.visit(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only the statement block (if there are no continue and break statements)
errors.warn("condition is always true", repeatLoop.untilCondition.position)
if(hasContinueOrBreak(repeatLoop.body))
repeatLoop
else {
optimizationsDone++
repeatLoop.body
}
} else {
// always false -> print a warning, and optimize into a forever loop
errors.warn("condition is always false", repeatLoop.untilCondition.position)
optimizationsDone++
ForeverLoop(repeatLoop.body, repeatLoop.position)
}
}
return repeatLoop
}
override fun visit(whenStatement: WhenStatement): Statement {
val choices = whenStatement.choices.toList()
for(choice in choices) {
if(choice.statements.containsNoCodeNorVars())
whenStatement.choices.remove(choice)
}
return super.visit(whenStatement)
}
private fun hasContinueOrBreak(scope: INameScope): Boolean { private fun hasContinueOrBreak(scope: INameScope): Boolean {
class Searcher: IAstModifyingVisitor class Searcher: IAstVisitor
{ {
var count=0 var count=0
override fun visit(breakStmt: Break): Statement { override fun visit(breakStmt: Break) {
count++ count++
return super.visit(breakStmt)
} }
override fun visit(contStmt: Continue): Statement { override fun visit(contStmt: Continue) {
count++ count++
return super.visit(contStmt)
} }
} }
val s=Searcher() val s=Searcher()
for(stmt in scope.statements) { for(stmt in scope.statements) {
stmt.accept(s) stmt.accept(s)
@ -381,185 +444,4 @@ internal class StatementOptimizer(private val program: Program,
return s.count > 0 return s.count > 0
} }
override fun visit(jump: Jump): Statement {
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump) {
optimizationsDone++
return first
}
}
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null) {
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
optimizationsDone++
return NopStatement.insteadOf(jump)
}
}
return jump
}
override fun visit(assignment: Assignment): Statement {
if(assignment.aug_op!=null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace)) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
}
val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv == null) {
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
bexpr.operator = "*"
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++
return assignment
}
}
} else {
if (assignment.target isSameAs bexpr.left) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return decs
}
}
}
"-" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return decs
}
}
}
"*" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"/" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"**" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"|" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"^" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"<<" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsl(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
optimizationsDone++
return scope
}
}
">>" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsr(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
optimizationsDone++
return scope
}
}
}
}
}
}
return super.visit(assignment)
}
override fun visit(scope: AnonymousScope): Statement {
val linesToRemove = deduplicateAssignments(scope.statements)
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
}
return super.visit(scope)
}
override fun visit(label: Label): Statement {
// remove duplicate labels
val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label)
if(startIdx< stmts.lastIndex && stmts[startIdx+1] == label)
return NopStatement.insteadOf(label)
return super.visit(label)
}
} }

View File

@ -0,0 +1,38 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Block
internal class UnusedCodeRemover: AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program)
val removals = mutableListOf<IAstModification>()
// remove all subroutines that aren't called, or are empty
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
}
// remove modules that are not imported, or are empty (unless it's a library modules)
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removals.add(IAstModification.Remove(it, it.parent))
}
return removals
}
}

View File

@ -156,7 +156,7 @@ Design principles and features
- The compiler tries to optimize the program and generated code a bit, but hand-tuning of the - The compiler tries to optimize the program and generated code a bit, but hand-tuning of the
performance or space-critical parts will likely still be required. This is supported by performance or space-critical parts will likely still be required. This is supported by
the ability to easily write embedded assembly code directly in the program source code. the ability to easily write embedded assembly code directly in the program source code.
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse`` - There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
- Assembling the generated code into a program wil be done by an external cross-assembler tool. - Assembling the generated code into a program wil be done by an external cross-assembler tool.

View File

@ -279,16 +279,23 @@ This @-prefix can also be used for character byte values.
You can concatenate two string literals using '+' (not very useful though) or repeat You can concatenate two string literals using '+' (not very useful though) or repeat
a string literal a given number of times using '*':: a string literal a given number of times using '*'. You can also assign a new string
value to another string. No bounds check is done so be sure the destination string is
large enough to contain the new value::
str string1 = "first part" + "second part" str string1 = "first part" + "second part"
str string2 = "hello!" * 10 str string2 = "hello!" * 10
string1 = string2
string1 = "new value"
.. caution:: .. caution::
Avoid changing strings after they've been created. It's probably best to avoid changing strings after they've been created. This
includes changing certain letters by index, or by assigning a new value, or by
modifying the string via other means for example ``substr`` function and its cousins.
This is because if your program exits and is restarted (without loading it again), This is because if your program exits and is restarted (without loading it again),
it will then start working with the changed strings instead of the original ones. it will then start working with the changed strings instead of the original ones!
The same is true for arrays. The same is true for arrays.
@ -802,6 +809,22 @@ memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value. Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself! But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
swap(x, y) swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way. Swap the values of numerical variables (or memory locations) x and y in a fast way.

View File

@ -2,27 +2,11 @@
TODO TODO
==== ====
- implement the asm for bitshift on arrays (last missing assembly code generation) - finalize (most) of the still missing "new" assignment asm code generation
- aliases for imported symbols for example perhaps '%alias print = c64scr.print' - aliases for imported symbols for example perhaps '%alias print = c64scr.print'
- option to load library files from a directory instead of the embedded ones (easier library development/debugging) - option to load library files from a directory instead of the embedded ones (easier library development/debugging)
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
Memory Block Operations integrated in language?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
array/string memory block operations?
- array operations
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
clear (set whole array to the given value, default 0)
- array operations ofcourse work identical on vars and on memory mapped vars of these types.
- strings: identical operations as on array.
For now, we have the ``memcopy`` and ``memset`` builtin functions.
More optimizations More optimizations
@ -30,6 +14,8 @@ More optimizations
Add more compiler optimizations to the existing ones. Add more compiler optimizations to the existing ones.
- more targeted optimizations for assigment asm code, such as the following:
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- remove unreachable code after an exit(), return or goto - remove unreachable code after an exit(), return or goto
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them) - working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game) - add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
@ -45,13 +31,13 @@ Eval stack redesign? (lot of work)
The eval stack is now a split lsb/msb stack using X as the stackpointer. The eval stack is now a split lsb/msb stack using X as the stackpointer.
Is it easier/faster to just use a single page unsplit stack? Is it easier/faster to just use a single page unsplit stack?
It could then even be moved into the zeropage to greatly reduce code size and slowness. It could then even be moved into the zeropage to reduce code size and slowness.
Or just move the LSB portion into a slab of the zeropage. Or just move the LSB portion into a slab of the zeropage.
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
without having to index with X into the eval stack all the time? without having to index with X into the eval stack all the time?
This could GREATLY improvde code size and speed for operatios that work on just a single value. This could GREATLY improve code size and speed for operations that work on just a single value.
Bug Fixing Bug Fixing
@ -64,4 +50,3 @@ Misc
Several ideas were discussed on my reddit post Several ideas were discussed on my reddit post
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/ https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/

View File

@ -106,7 +106,7 @@ main {
check_eval_stack() check_eval_stack()
c64scr.print("\nyou should see no errors above.") c64scr.print("\nyou should see no errors printed above (only at first run).")
} }
sub check_eval_stack() { sub check_eval_stack() {

File diff suppressed because it is too large Load Diff

View File

@ -1,922 +0,0 @@
%import c64utils
;%import c64flt
;%option enable_floats
%zeropage dontuse
main {
sub start() {
c64scr.print("ubyte shift left\n")
A = shiftlb0()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb1()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb2()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb3()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb4()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb5()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb6()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb7()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb8()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftlb9()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
c64scr.print("enter to continue:\n")
void c64.CHRIN()
c64scr.print("ubyte shift right\n")
A = shiftrb0()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb1()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb2()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb3()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb4()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb5()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb6()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb7()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb8()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
A = shiftrb9()
c64scr.print_ubbin(A, true)
c64.CHROUT('\n')
c64scr.print("enter to continue:\n")
void c64.CHRIN()
c64scr.print("signed byte shift left\n")
byte signedb
signedb = shiftlsb0()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb1()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb2()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb3()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb4()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb5()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb6()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb7()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb8()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftlsb9()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
c64scr.print("enter to continue:\n")
void c64.CHRIN()
c64scr.print("signed byte shift right\n")
signedb = shiftrsb0()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb1()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb2()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb3()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb4()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb5()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb6()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb7()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb8()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
signedb = shiftrsb9()
c64scr.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n')
c64scr.print("enter to continue:\n")
void c64.CHRIN()
c64scr.print("uword shift left\n")
uword uw
uw = shiftluw0()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw1()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw2()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw3()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw4()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw5()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw6()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw7()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw8()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw9()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw10()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw11()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw12()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw13()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw14()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw15()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw16()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftluw17()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
c64scr.print("enter to continue:\n")
void c64.CHRIN()
c64scr.print("uword shift right\n")
uw = shiftruw0()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw1()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw2()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw3()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw4()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw5()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw6()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw7()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw8()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw9()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw10()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw11()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw12()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw13()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw14()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw15()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw16()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
uw = shiftruw17()
c64scr.print_uwbin(uw, true)
c64.CHROUT('\n')
c64scr.print("enter to continue:\n")
void c64.CHRIN()
c64scr.print("signed word shift left\n")
word sw
sw = shiftlsw0()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw1()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw2()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw3()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw4()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw5()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw6()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw7()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw8()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw9()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw10()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw11()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw12()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw13()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw14()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw15()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw16()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftlsw17()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
c64scr.print("enter to continue:\n")
void c64.CHRIN()
c64scr.print("signed word shift right\n")
sw = shiftrsw0()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw1()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw2()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw3()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw4()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw5()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw6()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw7()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw8()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw9()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw10()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw11()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw12()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw13()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw14()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw15()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw16()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
sw = shiftrsw17()
c64scr.print_uwbin(sw as uword, true)
c64.CHROUT('\n')
}
sub shiftruw0() -> uword {
uword q = $a49f
return q >> 0
}
sub shiftruw1() -> uword {
uword q = $a49f
return q >> 1
}
sub shiftruw2() -> uword {
uword q = $a49f
return q >> 2
}
sub shiftruw3() -> uword {
uword q = $a49f
return q >> 3
}
sub shiftruw4() -> uword {
uword q = $a49f
return q >> 4
}
sub shiftruw5() -> uword {
uword q = $a49f
return q >> 5
}
sub shiftruw6() -> uword {
uword q = $a49f
return q >> 6
}
sub shiftruw7() -> uword {
uword q = $a49f
return q >> 7
}
sub shiftruw8() -> uword {
uword q = $a49f
return (q >> 8)
}
sub shiftruw9() -> uword {
uword q = $a49f
return (q >> 9)
}
sub shiftruw10() -> uword {
uword q = $a49f
return (q >> 10)
}
sub shiftruw11() -> uword {
uword q = $a49f
return (q >> 11)
}
sub shiftruw12() -> uword {
uword q = $a49f
return (q >> 12)
}
sub shiftruw13() -> uword {
uword q = $a49f
return (q >> 13)
}
sub shiftruw14() -> uword {
uword q = $a49f
return (q >> 14)
}
sub shiftruw15() -> uword {
uword q = $a49f
return (q >> 15)
}
sub shiftruw16() -> uword {
uword q = $a49f
return (q >> 16)
}
sub shiftruw17() -> uword {
uword q = $a49f
return (q >> 17)
}
sub shiftrsw0() -> word {
word q = -12345
return q >> 0
}
sub shiftrsw1() -> word {
word q = -12345
return q >> 1
}
sub shiftrsw2() -> word {
word q = -12345
return q >> 2
}
sub shiftrsw3() -> word {
word q = -12345
return q >> 3
}
sub shiftrsw4() -> word {
word q = -12345
return q >> 4
}
sub shiftrsw5() -> word {
word q = -12345
return q >> 5
}
sub shiftrsw6() -> word {
word q = -12345
return q >> 6
}
sub shiftrsw7() -> word {
word q = -12345
return q >> 7
}
sub shiftrsw8() -> word {
word q = -12345
return (q >> 8)
}
sub shiftrsw9() -> word {
word q = -12345
return (q >> 9)
}
sub shiftrsw10() -> word {
word q = -12345
return (q >> 10)
}
sub shiftrsw11() -> word {
word q = -12345
return (q >> 11)
}
sub shiftrsw12() -> word {
word q = -12345
return (q >> 12)
}
sub shiftrsw13() -> word {
word q = -12345
return (q >> 13)
}
sub shiftrsw14() -> word {
word q = -12345
return (q >> 14)
}
sub shiftrsw15() -> word {
word q = -12345
return (q >> 15)
}
sub shiftrsw16() -> word {
word q = -12345
return (q >> 16)
}
sub shiftrsw17() -> word {
word q = -12345
return (q >> 17)
}
sub shiftluw0() -> uword {
uword q = $a49f
return q << 0
}
sub shiftluw1() -> uword {
uword q = $a49f
return q << 1
}
sub shiftluw2() -> uword {
uword q = $a49f
return q << 2
}
sub shiftluw3() -> uword {
uword q = $a49f
return q << 3
}
sub shiftluw4() -> uword {
uword q = $a49f
return q << 4
}
sub shiftluw5() -> uword {
uword q = $a49f
return q << 5
}
sub shiftluw6() -> uword {
uword q = $a49f
return q << 6
}
sub shiftluw7() -> uword {
uword q = $a49f
return q << 7
}
sub shiftluw8() -> uword {
uword q = $a49f
return q << 8
}
sub shiftluw9() -> uword {
uword q = $a49f
return q << 9
}
sub shiftluw10() -> uword {
uword q = $a49f
return q << 10
}
sub shiftluw11() -> uword {
uword q = $a49f
return q << 11
}
sub shiftluw12() -> uword {
uword q = $a49f
return q << 12
}
sub shiftluw13() -> uword {
uword q = $a49f
return q << 13
}
sub shiftluw14() -> uword {
uword q = $a49f
return q << 14
}
sub shiftluw15() -> uword {
uword q = $a49f
return q << 15
}
sub shiftluw16() -> uword {
uword q = $a49f
return q << 16
}
sub shiftluw17() -> uword {
uword q = $a49f
return q << 17
}
sub shiftlsw0() -> word {
word q = -12345
return q << 0
}
sub shiftlsw1() -> word {
word q = -12345
return q << 1
}
sub shiftlsw2() -> word {
word q = -12345
return q << 2
}
sub shiftlsw3() -> word {
word q = -12345
return q << 3
}
sub shiftlsw4() -> word {
word q = -12345
return q << 4
}
sub shiftlsw5() -> word {
word q = -12345
return q << 5
}
sub shiftlsw6() -> word {
word q = -12345
return q << 6
}
sub shiftlsw7() -> word {
word q = -12345
return q << 7
}
sub shiftlsw8() -> word {
word q = -12345
return q << 8
}
sub shiftlsw9() -> word {
word q = -12345
return q << 9
}
sub shiftlsw10() -> word {
word q = -12345
return q << 10
}
sub shiftlsw11() -> word {
word q = -12345
return q << 11
}
sub shiftlsw12() -> word {
word q = -12345
return q << 12
}
sub shiftlsw13() -> word {
word q = -12345
return q << 13
}
sub shiftlsw14() -> word {
word q = -12345
return q << 14
}
sub shiftlsw15() -> word {
word q = -12345
return q << 15
}
sub shiftlsw16() -> word {
word q = -12345
return q << 16
}
sub shiftlsw17() -> word {
word q = -12345
return q << 17
}
sub shiftlb0() -> ubyte {
ubyte yy=$ed
return yy << 0
}
sub shiftlb1() -> ubyte {
ubyte yy=$ed
return yy << 1
}
sub shiftlb2() -> ubyte {
ubyte yy=$ed
return yy << 2
}
sub shiftlb3() -> ubyte {
ubyte yy=$ed
return yy << 3
}
sub shiftlb4() -> ubyte {
ubyte yy=$ed
return yy << 4
}
sub shiftlb5() -> ubyte {
ubyte yy=$ed
return yy << 5
}
sub shiftlb6() -> ubyte {
ubyte yy=$ed
return yy << 6
}
sub shiftlb7() -> ubyte {
ubyte yy=$ed
return yy << 7
}
sub shiftlb8() -> ubyte {
ubyte yy=$ed
return yy << 8
}
sub shiftlb9() -> ubyte {
ubyte yy=$ed
return yy << 9
}
sub shiftrb0() -> ubyte {
ubyte yy=$ed
return yy >> 0
}
sub shiftrb1() -> ubyte {
ubyte yy=$ed
return yy >> 1
}
sub shiftrb2() -> ubyte {
ubyte yy=$ed
return yy >> 2
}
sub shiftrb3() -> ubyte {
ubyte yy=$ed
return yy >> 3
}
sub shiftrb4() -> ubyte {
ubyte yy=$ed
return yy >> 4
}
sub shiftrb5() -> ubyte {
ubyte yy=$ed
return yy >> 5
}
sub shiftrb6() -> ubyte {
ubyte yy=$ed
return yy >> 6
}
sub shiftrb7() -> ubyte {
ubyte yy=$ed
return yy >> 7
}
sub shiftrb8() -> ubyte {
ubyte yy=$ed
return yy >> 8
}
sub shiftrb9() -> ubyte {
ubyte yy=$ed
return yy >> 9
}
sub shiftlsb0() -> byte {
byte yy=-123
return yy << 0
}
sub shiftlsb1() -> byte {
byte yy=-123
return yy << 1
}
sub shiftlsb2() -> byte {
byte yy=-123
return yy << 2
}
sub shiftlsb3() -> byte {
byte yy=-123
return yy << 3
}
sub shiftlsb4() -> byte {
byte yy=-123
return yy << 4
}
sub shiftlsb5() -> byte {
byte yy=-123
return yy << 5
}
sub shiftlsb6() -> byte {
byte yy=-123
return yy << 6
}
sub shiftlsb7() -> byte {
byte yy=-123
return yy << 7
}
sub shiftlsb8() -> byte {
byte yy=-123
return yy << 8
}
sub shiftlsb9() -> byte {
byte yy=-123
return yy << 9
}
sub shiftrsb0() -> byte {
byte yy=-123
return yy >> 0
}
sub shiftrsb1() -> byte {
byte yy=-123
return yy >> 1
}
sub shiftrsb2() -> byte {
byte yy=-123
return yy >> 2
}
sub shiftrsb3() -> byte {
byte yy=-123
return yy >> 3
}
sub shiftrsb4() -> byte {
byte yy=-123
return yy >> 4
}
sub shiftrsb5() -> byte {
byte yy=-123
return yy >> 5
}
sub shiftrsb6() -> byte {
byte yy=-123
return yy >> 6
}
sub shiftrsb7() -> byte {
byte yy=-123
return yy >> 7
}
sub shiftrsb8() -> byte {
byte yy=-123
return yy >> 8
}
sub shiftrsb9() -> byte {
byte yy=-123
return yy >> 9
}
}

View File

@ -13,7 +13,7 @@ main {
c64.SPXY[0] = 80 c64.SPXY[0] = 80
c64.SPXY[1] = 100 c64.SPXY[1] = 100
c64.SCROLX = c64.SCROLX & %11110111 ; 38 column mode c64.SCROLX &= %11110111 ; 38 column mode
c64utils.set_rasterirq(1) ; enable animation c64utils.set_rasterirq(1) ; enable animation

View File

@ -17,67 +17,64 @@ graphics {
c64scr.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black) c64scr.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black)
} }
sub line(uword x1, ubyte y1, uword x2, ubyte y2) { sub line(uword x1, ubyte y1, uword x2, ubyte y2) {
; Bresenham algorithm ; Bresenham algorithm.
word dx ; This code special cases various quadrant loops to allow simple ++ and -- operations.
word dy if y1>y2 {
byte ix = 1 ; make sure dy is always positive to avoid 8 instead of just 4 special cases
byte iy = 1 swap(x1, x2)
if x2>x1 { swap(y1, y2)
dx = x2-x1
} else {
ix = -1
dx = x1-x2
} }
if y2>y1 {
dy = y2-y1
} else {
iy = -1
dy = y1-y2
}
word dx2 = 2 * dx
word dy2 = 2 * dy
word d = 0 word d = 0
ubyte positive_ix = true
word dx = x2 - x1 as word
word dy = y2 as word - y1 as word
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
plotx = x1 plotx = x1
if dx >= dy { if dx >= dy {
if ix<0 { if positive_ix {
forever { forever {
graphics.plot(y1) plot(y1)
if plotx==x2 if plotx==x2
return return
plotx-- plotx++
d += dy2 d += dy
if d > dx { if d > dx {
y1 += iy y1++
d -= dx2 d -= dx
} }
} }
} else { } else {
forever { forever {
graphics.plot(y1) plot(y1)
if plotx==x2 if plotx==x2
return return
plotx++ plotx--
d += dy2 d += dy
if d > dx { if d > dx {
y1 += iy y1++
d -= dx2 d -= dx
} }
} }
} }
} else { }
if iy<0 { else {
if positive_ix {
forever { forever {
plot(y1) plot(y1)
if y1 == y2 if y1 == y2
return return
y1-- y1++
d += dx2 d += dx
if d > dy { if d > dy {
plotx += ix as word plotx++
d -= dy2 d -= dy
} }
} }
} else { } else {
@ -86,10 +83,10 @@ graphics {
if y1 == y2 if y1 == y2
return return
y1++ y1++
d += dx2 d += dx
if d > dy { if d > dy {
plotx += ix as word plotx--
d -= dy2 d -= dy
} }
} }
} }
@ -101,7 +98,7 @@ graphics {
ubyte ploty ubyte ploty
ubyte xx = radius ubyte xx = radius
ubyte yy = 0 ubyte yy = 0
byte decisionOver2 = 1-xx byte decisionOver2 = 1-xx as byte
while xx>=yy { while xx>=yy {
plotx = xcenter + xx plotx = xcenter + xx
@ -138,24 +135,29 @@ graphics {
; Midpoint algorithm, filled ; Midpoint algorithm, filled
ubyte xx = radius ubyte xx = radius
ubyte yy = 0 ubyte yy = 0
byte decisionOver2 = 1-xx byte decisionOver2 = 1-xx as byte
while xx>=yy { while xx>=yy {
ubyte cy_plus_yy = cy + yy
ubyte cy_min_yy = cy - yy
ubyte cy_plus_xx = cy + xx
ubyte cy_min_xx = cy - xx
for plotx in cx to cx+xx { for plotx in cx to cx+xx {
plot(cy + yy) plot(cy_plus_yy)
plot(cy - yy) plot(cy_min_yy)
} }
for plotx in cx-xx to cx-1 { for plotx in cx-xx to cx-1 {
plot(cy + yy) plot(cy_plus_yy)
plot(cy - yy) plot(cy_min_yy)
} }
for plotx in cx to cx+yy { for plotx in cx to cx+yy {
plot(cy + xx) plot(cy_plus_xx)
plot(cy - xx) plot(cy_min_xx)
} }
for plotx in cx-yy to cx { for plotx in cx-yy to cx {
plot(cy + xx) plot(cy_plus_xx)
plot(cy - xx) plot(cy_min_xx)
} }
yy++ yy++
if decisionOver2<=0 if decisionOver2<=0
@ -175,7 +177,7 @@ graphics {
; @(addr) |= ormask[lsb(px) & 7] ; @(addr) |= ormask[lsb(px) & 7]
; } ; }
uword plotx ; 0..319 uword plotx ; 0..319 ; separate 'parameter' for plot()
asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register
%asm {{ %asm {{
@ -212,7 +214,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey. ; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics ; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encode this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199) ; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
_y_lookup_hi _y_lookup_hi
.byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21 .byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21
.byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23 .byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,7 +1,5 @@
%import c64lib %import c64lib
%import c64graphics %import c64graphics
%zeropage basicsafe
main { main {

View File

@ -71,8 +71,8 @@ main {
ubyte dy = abs(y2 - y1) ubyte dy = abs(y2 - y1)
ubyte dx2 = 2 * dx ubyte dx2 = 2 * dx
ubyte dy2 = 2 * dy ubyte dy2 = 2 * dy
byte ix = sgn(x2 as byte - x1 as byte) ubyte ix = sgn(x2 as byte - x1 as byte) as ubyte
byte iy = sgn(y2 as byte - y1 as byte) ubyte iy = sgn(y2 as byte - y1 as byte) as ubyte
ubyte x = x1 ubyte x = x1
ubyte y = y1 ubyte y = y1
@ -107,7 +107,7 @@ main {
; Midpoint algorithm ; Midpoint algorithm
ubyte x = radius ubyte x = radius
ubyte y = 0 ubyte y = 0
byte decisionOver2 = 1-x byte decisionOver2 = 1-x as byte
while x>=y { while x>=y {
c64scr.setcc(xcenter + x, ycenter + y as ubyte, 81, 1) c64scr.setcc(xcenter + x, ycenter + y as ubyte, 81, 1)
@ -132,7 +132,7 @@ main {
; Midpoint algorithm, filled ; Midpoint algorithm, filled
ubyte x = radius ubyte x = radius
ubyte y = 0 ubyte y = 0
byte decisionOver2 = 1-x byte decisionOver2 = 1-x as byte
ubyte xx ubyte xx
while x>=y { while x>=y {

View File

@ -3,7 +3,6 @@
%import c64flt %import c64flt
%zeropage basicsafe %zeropage basicsafe
main { main {
const uword width = 30 const uword width = 30
const uword height = 20 const uword height = 20

View File

@ -1,7 +1,6 @@
%import c64utils %import c64utils
%import c64lib %import c64lib
main { main {
sub start() { sub start() {
@ -18,7 +17,7 @@ main {
irq { irq {
const ubyte barheight = 4 const ubyte barheight = 4 ; should be big enough to re-trigger the Raster irq properly.
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9] ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
ubyte color = 0 ubyte color = 0
ubyte yanim = 0 ubyte yanim = 0

View File

@ -6,8 +6,6 @@
; shows next piece ; shows next piece
; staged speed increase ; staged speed increase
; some simple sound effects ; some simple sound effects
;
; @todo show ghost?
main { main {

View File

@ -1,11 +1,16 @@
%import c64lib %import c64lib
%import c64utils %import c64utils
%zeropage dontuse %import c64flt
%zeropage basicsafe
main { main {
sub start() { sub start() {
A=42
} }
} }

View File

@ -2,7 +2,6 @@
%import c64flt %import c64flt
%import c64graphics %import c64graphics
%option enable_floats %option enable_floats
%zeropage basicsafe
main { main {