Compare commits

...
49 Commits
Author SHA1 Message Date
Irmen de Jong e6079dfd71 don't always use pha/pla in pointer expression code 2021-02-27 16:21:46 +01:00
Irmen de Jong 2b435fe6a5 vtui example updated to vtui 0.6 2021-02-27 03:30:21 +01:00
Irmen de Jong 4e640b11fd added kernal bank switch trick to rasterbars 2021-02-26 01:16:06 +01:00
Irmen de Jong 8b1e1e68fa switch to Kotlin's new JVM IR compilation 2021-02-26 01:10:00 +01:00
Irmen de Jong fd11927708 optimized highres 4c position calc a bit 2021-02-26 00:43:51 +01:00
Irmen de Jong cd500fee8c wording 2021-02-25 00:52:27 +01:00
Irmen de Jong 1bd32c0f19 added animal guessing game example 2021-02-24 22:58:16 +01:00
Irmen de Jong 7aefca3de0 target 2021-02-24 00:17:52 +01:00
Irmen de Jong f275ed96ea optimized palette.set_color() 2021-02-24 00:01:27 +01:00
Irmen de Jong d14dac3872 got rid of final traces of heapid, fixed compiler warnings 2021-02-24 00:01:04 +01:00
Irmen de Jong b0213b0565 vtui lib 2021-02-23 23:31:32 +01:00
Irmen de Jong c677f0a875 fixed string interning to also consider the alt-encoding 2021-02-23 23:27:44 +01:00
Irmen de Jong 6e65cb2c0a added sounds to cx16 tehtriz 2021-02-23 01:29:45 +01:00
Irmen de Jong e65c5402d7 added cx16 rasterbars example 2021-02-22 02:11:44 +01:00
Irmen de Jong 334f86480a added irq routines for cx16 2021-02-22 00:48:41 +01:00
Irmen de Jong 0e62f5b759 don't remove subroutines in a block marked with "force_output" 2021-02-21 23:25:26 +01:00
Irmen de Jong edf9a500d3 kernel -> kernal 2021-02-21 22:48:06 +01:00
Irmen de Jong 001d01fdaf slight tweak to 64tass .cpu to enable wdc65c02 variant on cx16 with its extra opcodes 2021-02-21 22:45:23 +01:00
Irmen de Jong a95677564e changed system irq/rasterirq setting routines 2021-02-21 22:23:50 +01:00
Irmen de Jong 4aca8bb8df also track subroutines in the callgraph that only get their address taken 2021-02-21 22:09:49 +01:00
Irmen de Jong 5540482888 compiler error for duplicate when choice labels 2021-02-21 21:26:15 +01:00
Irmen de Jong 00d735249b fix pointer write outside zeropage 2021-02-21 16:22:44 +01:00
Irmen de Jong b5289511ba don't remove empty when choice from the list of choices! 2021-02-21 15:11:19 +01:00
Irmen de Jong b6ded8501f added 'align_word' and 'align_page' block options to control block start address alignment in the assembler 2021-02-21 01:24:44 +01:00
Irmen de Jong 781915d2cf reducing dependencies 2021-02-20 17:54:33 +01:00
Irmen de Jong f4cef3eaf2 reducing dependencies 2021-02-20 17:19:54 +01:00
Irmen de Jong d23c2eed86 test 2021-02-20 16:58:24 +01:00
Irmen de Jong 15695a304e start address of blocks without explicit memory address, is now word-aligned in memory 2021-02-20 03:06:00 +01:00
Irmen de Jong 6319269976 underscore '_' is now also mapped to petscii, to the graphical symbol 2021-02-20 02:55:06 +01:00
Irmen de Jong 0ed3d951a7 don't require carry parameter Pc to asmsubs to be last 2021-02-20 02:27:57 +01:00
Irmen de Jong 2aa39757b4 reduce dependencies on global compilationtarget 2021-02-19 19:02:29 +01:00
Irmen de Jong 39d32a3600 refactor cpuCheck 2021-02-19 18:48:12 +01:00
Irmen de Jong 219d17de34 reduce dependencies on global compilaiontarget 2021-02-19 18:33:54 +01:00
Irmen de Jong 9bb5b454e4 reduce dependencies on global compilaiontarget 2021-02-18 23:44:26 +01:00
Irmen de Jong 2412f8c531 added cx16 vtui example 2021-02-18 23:16:38 +01:00
Irmen de Jong 8701d684e6 added cx16 vtui example 2021-02-18 03:45:06 +01:00
Irmen de Jong b543cc34cd no longer warn about removing unused asmsubs 2021-02-18 01:52:56 +01:00
Irmen de Jong 791dbbab9b fixed block label itself not getting the correct memory address in the assembly
fixed %asmbinary relative path issues
2021-02-18 01:28:33 +01:00
Irmen de Jong ac0b1da3fc machinedefinition doesn't import system libs itself anymore 2021-02-18 00:43:32 +01:00
Irmen de Jong 2f97aedc3c fixed invalid removal of string tag from memory() 2021-02-16 23:58:31 +01:00
Irmen de Jong ab544ee965 improved string constant interning; no longer output duplicate strings in the Ast 2021-02-16 23:43:38 +01:00
Irmen de Jong fa527f8624 restored optimization of txt.print() with strings of lengths 1 or 2 2021-02-16 23:37:11 +01:00
Irmen de Jong 92ee0aefee docs: replaced old invalid c64scr names with txt 2021-02-16 23:28:35 +01:00
Irmen de Jong 99759ae853 enhanced tehtriz blocks to have light edges 2021-02-15 17:48:10 +01:00
Irmen de Jong 81930312ff added textio.setcc2() on commanderX16 to enable setting fg+bg colors. 2021-02-15 17:47:48 +01:00
Irmen de Jong 194fbcdd91 todos 2021-02-15 04:41:33 +01:00
Irmen de Jong 1e3930aae2 fix bug in evaluating logical expressions if one of the operands was not boolean 1 or 0 2021-02-14 18:29:05 +01:00
Irmen de Jong 62dda4d891 fix asm bug in conv.any2uword 2021-02-14 17:13:56 +01:00
Irmen de Jong 2b870fb9f7 get rid of compiled examples. Just compile them yourself... 2021-02-14 17:13:29 +01:00
104 changed files with 1436 additions and 827 deletions

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" />
<option name="jvmTarget" value="11" />
</component>
</project>
+1 -1
View File
@@ -52,7 +52,7 @@ What does Prog8 provide?
- "c64": Commodore-64 (6510 CPU = almost a 6502)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
+2
View File
@@ -34,6 +34,7 @@ dependencies {
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
@@ -42,6 +43,7 @@ compileKotlin {
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
+54 -54
View File
@@ -11,7 +11,7 @@ c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O
&ubyte STATUS = $90 ; kernal status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@@ -178,7 +178,7 @@ c64 {
; ---- C64 ROM kernal routines ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
@@ -294,6 +294,12 @@ asmsub init_system() {
}}
}
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C64
}}
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
@@ -304,27 +310,13 @@ asmsub disable_runstop_and_charsetswitch() clobbers(A) {
}}
}
asmsub set_irqvec_excl() clobbers(A) {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
asmsub set_irqvec() clobbers(A) {
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta c64.CINV
@@ -333,9 +325,23 @@ asmsub set_irqvec() clobbers(A) {
cli
rts
_irq_handler jsr _irq_handler_init
jsr irq.irq
_modified jsr $ffff ; modified
jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine
lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
@@ -388,7 +394,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irqvec() clobbers(A) {
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda #<c64.IRQDFRT
@@ -404,8 +410,15 @@ asmsub restore_irqvec() clobbers(A) {
}}
}
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
@@ -416,12 +429,21 @@ asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
@@ -445,28 +467,6 @@ _setup_raster_irq
}}
}
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
jsr set_rasterirq._setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
; ---- end of C64 specific system utility routines ----
}
+1 -1
View File
@@ -586,7 +586,7 @@ _colormod sta $ffff ; modified
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
stx P8ZP_SCRATCH_REG
tax
+1 -1
View File
@@ -257,7 +257,7 @@ asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1)
lda (P8ZP_SCRATCH_W1),y
ldy P8ZP_SCRATCH_W1+1
cmp #'$'
beq _hex
+83 -64
View File
@@ -2,7 +2,7 @@
; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; (These modes are not supported by the documented GRAPH_xxxx kernel routines)
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
;
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
@@ -15,13 +15,12 @@
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 monochrome
; mode 2 = bitmap 320 x 240 x 4c (unsupported TODO not yet implemented)
; mode 3 = bitmap 320 x 240 x 16c (unsupported TODO not yet implemented)
; mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented)
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
; mode 4 = bitmap 320 x 240 x 256c
; mode 5 = bitmap 640 x 480 monochrome
; mode 6 = bitmap 640 x 480 x 4c (unsupported TODO being implemented)
; mode 7 = bitmap 640 x 480 x 16c (unsupported due to lack of VRAM)
; mode 8 = bitmap 640 x 480 x 256c (unsupported due to lack of VRAM)
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
@@ -85,7 +84,6 @@ gfx2 {
height = 480
bpp = 2
}
; modes 7 and 8 not supported due to lack of VRAM
else -> {
; back to default text mode and colors
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
@@ -168,7 +166,7 @@ gfx2 {
if separate_pixels as uword > length
separate_pixels = lsb(length)
repeat separate_pixels {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
@@ -210,7 +208,7 @@ _loop lda length
_done
}}
repeat separate_pixels {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
@@ -299,6 +297,7 @@ _done
1, 5 -> {
; monochrome, either resolution
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=5
@@ -326,7 +325,7 @@ _done
}
} else {
; stippling.
height = (height+1)/2
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
%asm {{
lda x
eor y
@@ -375,7 +374,8 @@ _done
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; the bitmap size is small enough to not have to deal with the _H part:
; lda cx16.VERA_ADDR_H
; adc #0
; sta cx16.VERA_ADDR_H
}}
@@ -399,6 +399,7 @@ _done
6 -> {
; highres 4c
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
@@ -812,6 +813,7 @@ _done
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y)
y++
%asm {{
@@ -840,7 +842,7 @@ _done
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop in assembly
; TODO rewrite this inner loop fully in assembly
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 1
@@ -877,15 +879,31 @@ _done
}}
}
sub addr_mul_24_for_highres_4c(uword yy, uword xx) {
; TODO turn into asmsub
asmsub addr_mul_24_for_highres_4c(uword yy @R2, uword xx @R3) clobbers(A, Y) -> uword @R0, uword @R1 {
; yy * 160 + xx/4 (24 bits calculation)
; 24 bits result is in r0 and r1L (highest byte)
cx16.r0 = yy*128
cx16.r2 = yy*32
xx >>= 2
%asm {{
; add r2 and xx to r0 (24-bits)
ldy #5
- asl cx16.r2
rol cx16.r2+1
dey
bne -
lda cx16.r2
sta cx16.r0
lda cx16.r2+1
sta cx16.r0+1
asl cx16.r0
rol cx16.r0+1
asl cx16.r0
rol cx16.r0+1
; xx >>= 2 (xx=R3)
lsr cx16.r3+1
ror cx16.r3
lsr cx16.r3+1
ror cx16.r3
; add r2 and xx (r3) to r0 (24-bits)
stz cx16.r1
clc
lda cx16.r0
@@ -898,60 +916,61 @@ _done
inc cx16.r1
+ clc
lda cx16.r0
adc xx
adc cx16.r3
sta cx16.r0
lda cx16.r0+1
adc xx+1
adc cx16.r3+1
sta cx16.r0+1
bcc +
inc cx16.r1
+
rts
}}
}
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
; yy * 320 + xx (24 bits calculation)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sta cx16.r1
sta P8ZP_SCRATCH_REG
lda cx16.r0
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
sta cx16.r0
lda P8ZP_SCRATCH_B1
clc
adc P8ZP_SCRATCH_REG
sta cx16.r0+1
bcc +
inc cx16.r1
+ ; now add the value to this 24-bits number
lda cx16.r0
clc
adc P8ZP_SCRATCH_W1
sta cx16.r0
lda cx16.r0+1
adc P8ZP_SCRATCH_W1+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ lda cx16.r1
rts
}}
}
; yy * 320 + xx (24 bits calculation)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sta cx16.r1
sta P8ZP_SCRATCH_REG
lda cx16.r0
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
sta cx16.r0
lda P8ZP_SCRATCH_B1
clc
adc P8ZP_SCRATCH_REG
sta cx16.r0+1
bcc +
inc cx16.r1
+ ; now add the value to this 24-bits number
lda cx16.r0
clc
adc P8ZP_SCRATCH_W1
sta cx16.r0
lda cx16.r0+1
adc P8ZP_SCRATCH_W1+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ lda cx16.r1
rts
}}
}
}
+6 -4
View File
@@ -9,8 +9,10 @@ palette {
ubyte c
sub set_color(ubyte index, uword color) {
cx16.vpoke(1, $fa00+index*2, lsb(color))
cx16.vpoke(1, $fa01+index*2, msb(color))
vera_palette_ptr = $fa00+index*2
cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
}
sub set_rgb4(uword palette_bytes_ptr, uword num_colors) {
@@ -98,7 +100,7 @@ palette {
$666, ; 12 = medium grey
$9D8, ; 13 = light green
$65B, ; 14 = light blue
$999 ; 15 = light grey
$999 ; 15 = light grey
]
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
@@ -117,7 +119,7 @@ palette {
$777, ; 12 = medium grey
$af9, ; 13 = light green
$76e, ; 14 = light blue
$bbb ; 15 = light grey
$bbb ; 15 = light grey
]
uword[] C64_colorpalette_light = [ ; this is a lighter palette
+176 -6
View File
@@ -102,10 +102,11 @@ asmsub MEMTOP2() -> ubyte @A {
cx16 {
; 65c02 hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; irq and hardware vectors:
&uword CINV = $0314 ; IRQ vector (in ram)
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
@@ -453,7 +454,7 @@ _loop ldy #0
}
; ---- system stuff -----
asmsub init_system() {
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
%asm {{
@@ -461,7 +462,7 @@ asmsub init_system() {
cld
;stz $00
;stz $01
;stz d1prb ; select rom bank 0
;stz d1prb ; select rom bank 0 (enable kernal)
lda #$80
sta VERA_CTRL
jsr c64.IOINIT
@@ -485,8 +486,177 @@ asmsub init_system() {
}}
}
asmsub init_system_phase2() {
%asm {{
sei
lda cx16.CINV
sta restore_irq._orig_irqvec
lda cx16.CINV+1
sta restore_irq._orig_irqvec+1
cli
rts
}}
}
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta cx16.CINV
lda #>_irq_handler
sta cx16.CINV+1
lda cx16.VERA_IEN
ora #%00000001 ; enable the vsync irq
sta cx16.VERA_IEN
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply
plx
pla
rti
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
cld
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda _orig_irqvec
sta cx16.CINV
lda _orig_irqvec+1
sta cx16.CINV+1
lda cx16.VERA_IEN
and #%11110000 ; disable all Vera IRQs
ora #%00000001 ; enable only the vsync Irq
sta cx16.VERA_IEN
cli
rts
_orig_irqvec .word 0
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
sei
lda cx16.VERA_IEN
and #%11110000 ; clear other IRQs
ora #%00000010 ; enable the line (raster) irq
sta cx16.VERA_IEN
lda cx16.r0
ldy cx16.r0+1
jsr set_rasterline
lda #<_raster_irq_handler
sta cx16.CINV
lda #>_raster_irq_handler
sta cx16.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #%00000010
sta cx16.VERA_ISR ; clear Vera line irq status
ply
plx
pla
rti
}}
}
asmsub set_rasterline(uword line @AY) {
%asm {{
sta cx16.VERA_IRQ_LINE_L
lda cx16.VERA_IEN
and #%01111111
sta cx16.VERA_IEN
tya
lsr a
ror a
and #%10000000
ora cx16.VERA_IEN
sta cx16.VERA_IEN
rts
}}
}
}
sys {
; ------- lowlevel system routines --------
+32 -1
View File
@@ -626,6 +626,8 @@ asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
; ---- set the color in A on the screen matrix at the given position
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
pha
txa
@@ -657,6 +659,8 @@ asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; note: color handling is the same as on the C64: it only sets the foreground color.
; use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors.
%asm {{
phx
lda column
@@ -685,8 +689,35 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
}}
}
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
; ---- set char+color at the given position on the screen
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
phx
lda column
asl a
tax
ldy row
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda colors
sta cx16.VERA_DATA0
plx
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
phx
tax
+1 -1
View File
@@ -1 +1 @@
6.1
6.2
+1 -2
View File
@@ -8,7 +8,6 @@ import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.parser.ParsingFailedError
import java.nio.file.FileSystems
@@ -117,7 +116,7 @@ private fun compileMain(args: Array<String>) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else {
ICompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
}
}
}
@@ -11,7 +11,7 @@ import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
@@ -107,7 +107,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
}
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal 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)
+71 -64
View File
@@ -2,6 +2,7 @@ package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.Position
@@ -48,7 +49,8 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean) {
val noSysInit: Boolean,
val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false
var optimize = false
}
@@ -59,6 +61,7 @@ class CompilerException(message: String?) : Exception(message)
class CompilationResult(val success: Boolean,
val programAst: Program,
val programName: String,
val compTarget: ICompilationTarget,
val importedFiles: List<Path>)
@@ -73,27 +76,28 @@ fun compileProgram(filepath: Path,
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
when(compilationTarget) {
C64Target.name -> ICompilationTarget.instance = C64Target
Cx16Target.name -> ICompilationTarget.instance = Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
val compTarget =
when(compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions, ICompilationTarget.instance)
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions))
postprocessAst(programAst, errors, compilationOptions, ICompilationTarget.instance)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
@@ -103,7 +107,7 @@ fun compileProgram(filepath: Path,
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
@@ -127,8 +131,8 @@ fun compileProgram(filepath: Path,
throw x
}
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions))
return CompilationResult(false, failedProgram, programName, emptyList())
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
}
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
@@ -137,13 +141,13 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? {
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null) {
return try {
exprfunc(args, position, program)
exprfunc(args, position, program, memsizer)
} catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
null
@@ -161,34 +165,34 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
builtinFunctionReturnType(name, args, program)
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = ICompilationTarget.instance.name
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = compTarget.name
println("Compiler target: $compilationTargetName. Parsing...")
val importer = ModuleImporter()
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf)
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
bf.program = programAst
importer.importModule(programAst, filepath, ICompilationTarget.instance, compilationTargetName)
errors.handle()
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
errors.report()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst)
val compilerOptions = determineCompilationOptions(programAst, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
ICompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst, ICompilationTarget.instance, compilationTargetName)
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math", ICompilationTarget.instance, compilationTargetName)
importer.importLibraryModule(programAst, "prog8_lib", ICompilationTarget.instance, compilationTargetName)
errors.handle()
importer.importLibraryModule(programAst, "math", compTarget, compilationTargetName)
importer.importLibraryModule(programAst, "prog8_lib", compTarget, compilationTargetName)
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
@@ -209,7 +213,7 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && ICompilationTarget.instance.name == Cx16Target.name) {
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
@@ -233,78 +237,81 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions, compTarget: ICompilationTarget) {
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compTarget.name}...")
programAst.checkIdentifiers(errors, compTarget)
errors.handle()
programAst.constantFold(errors)
errors.handle()
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
errors.report()
programAst.reorderStatements(errors)
errors.handle()
errors.report()
programAst.addTypecasts(errors)
errors.handle()
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compTarget)
errors.handle()
programAst.checkIdentifiers(errors, compTarget)
errors.handle()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: ErrorReporter, functions: IBuiltinFunctions) {
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions()
val optsDone3 = programAst.optimizeStatements(errors, functions)
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors)
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
errors.report()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions, compTarget: ICompilationTarget) {
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.handle()
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compTarget) // check if final tree is still valid
errors.handle()
val callGraph = CallGraph(programAst)
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
callGraph.checkRecursiveCalls(errors)
errors.handle()
errors.report()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
private fun writeAssembly(programAst: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors, ICompilationTarget.instance)
errors.handle()
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report()
// printAst(programAst)
ICompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(ICompilationTarget.instance,
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst,
errors,
ICompilationTarget.instance.machine.zeropage,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.handle()
errors.report()
return assembly.name
}
+18 -5
View File
@@ -3,7 +3,16 @@ package prog8.compiler
import prog8.ast.base.Position
import prog8.parser.ParsingFailedError
class ErrorReporter {
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun isEmpty(): Boolean
fun report()
}
internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity {
WARNING,
ERROR
@@ -13,10 +22,14 @@ class ErrorReporter {
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
override fun err(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
}
override fun warn(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
}
fun handle() {
override fun report() {
var numErrors = 0
var numWarnings = 0
messages.forEach {
@@ -40,5 +53,5 @@ class ErrorReporter {
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
fun isEmpty() = messages.isEmpty()
override fun isEmpty() = messages.isEmpty()
}
+1 -1
View File
@@ -21,7 +21,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)
@@ -8,7 +8,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilationOptions
import prog8.compiler.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.C64Target
@@ -18,7 +18,7 @@ import java.io.File
internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions,
private val errors: ErrorReporter,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
) : IAstVisitor {
@@ -41,19 +41,6 @@ internal class AstChecker(private val program: Program,
}
}
// there can be an optional single 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block }
if(irqBlocks.size>1)
errors.err("more than one 'irq' block", irqBlocks[0].position)
for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScope("irq") as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
}
}
super.visit(program)
}
@@ -187,7 +174,7 @@ internal class AstChecker(private val program: Program,
else -> false
}
if (!ok) {
errors.err("statement occurs in a block, where it will never be executed. Use it in a subroutine instead.", statement.position)
errors.err("non-declarative statement occurs in block scope, where it will never be executed. Move it to a subroutine instead.", statement.position)
break
}
}
@@ -351,10 +338,6 @@ internal class AstChecker(private val program: Program,
if(statusFlagsNoCarry.isNotEmpty())
err("can only use Carry as status flag parameter")
val carryParameter = subroutine.asmParameterRegisters.singleOrNull { it.statusflag==Statusflag.Pc }
if(carryParameter!=null && carryParameter !== subroutine.asmParameterRegisters.last())
err("carry parameter has to come last")
} else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
@@ -756,7 +739,7 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it })
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
err("invalid option directive argument(s)")
}
"%target" -> {
@@ -1145,12 +1128,18 @@ internal class AstChecker(private val program: Program,
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
if(conditionType !in IntegerDatatypes)
errors.err("when condition must be an integer value", whenStatement.position)
val choiceValues = whenStatement.choiceValues(program)
val occurringValues = choiceValues.map {it.first}
val tally = choiceValues.associate { it.second to occurringValues.count { ov->it.first==ov} }
tally.filter { it.value>1 }.forEach {
errors.err("choice value occurs multiple times", it.key.position)
val tally = mutableSetOf<Int>()
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
if(choices!=null) {
for (c in choices) {
if(c in tally)
errors.err("choice value already occurs earlier", choiceNode.position)
else
tally.add(c)
}
}
}
if(whenStatement.choices.isEmpty())
errors.err("empty when statement", whenStatement.position)
@@ -1361,7 +1350,7 @@ internal class AstChecker(private val program: Program,
val array = value.value.map {
when (it) {
is NumericLiteralValue -> it.number.toInt()
is AddressOf -> it.identifier.heapId(program.namespace)
is AddressOf -> it.identifier.hashCode() and 0xffff
is TypecastExpression -> {
val constVal = it.expression.constValue(program)
val cast = constVal?.cast(it.type)
@@ -1420,7 +1409,10 @@ internal class AstChecker(private val program: Program,
}
false
}
else -> errors.err("cannot assign new value to variable of type $targetDatatype", position)
else -> {
errors.err("cannot assign new value to variable of type $targetDatatype", position)
false
}
}
if(result)
@@ -1,32 +1,32 @@
package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.compiler.ErrorReporter
import prog8.ast.base.FatalAstException
import prog8.ast.statements.Directive
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter, compTarget: ICompilationTarget) {
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors, compTarget)
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter, compTarget: ICompilationTarget) {
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements(errors: ErrorReporter) {
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
reorder.applyModifications()
}
internal fun Program.addTypecasts(errors: ErrorReporter) {
internal fun Program.addTypecasts(errors: IErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
@@ -37,7 +37,7 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Program.checkIdentifiers(errors: ErrorReporter, compTarget: ICompilationTarget) {
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
checker2.visit(this)
@@ -3,7 +3,6 @@ package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.compiler.ErrorReporter
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
@@ -11,10 +10,11 @@ import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.ICompilationTarget
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) {
@@ -32,7 +32,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters:
// For non-kernal 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()
@@ -17,13 +17,10 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// 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())
)
// replace the literal string by a identifier reference to the interned string
val scopedName = program.internString(string)
val identifier = IdentifierReference(scopedName, string.position)
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
}
return noModifications
}
@@ -6,11 +6,11 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
@@ -167,6 +167,38 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
else -> return noModifications
}
}
else if(expr.operator in logicalOperators) {
// make sure that logical expressions like "var and other-logical-expression
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
// generating the wrong results later
fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
return true
if(expr is PrefixExpression && expr.operator in logicalOperators)
return true
return false
}
return if(isLogicalExpr(expr.left)) {
if(isLogicalExpr(expr.right))
noModifications
else
listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr))
} else {
if(isLogicalExpr(expr.right))
listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr))
else {
listOf(
IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr),
IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)
)
}
}
}
return noModifications
}
@@ -345,7 +377,8 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
// TODO use memcopy instead of individual assignments after certain amount of elements
// TODO what does assigning a struct var use?
return alv.value.mapIndexed { index, value ->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
@@ -8,11 +8,11 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
/*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments)
@@ -1,19 +1,19 @@
package prog8.compiler.functions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException
import prog8.compiler.target.ICompilationTarget
import kotlin.math.*
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
@@ -88,6 +88,7 @@ class FSignature(val name: String,
}
}
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place:
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
@@ -97,40 +98,40 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s):
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
// normal functions follow:
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
@@ -271,7 +272,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
}
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinAbs(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position)
@@ -284,7 +286,7 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
}
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("offsetof requires one argument", position)
@@ -301,12 +303,12 @@ private fun builtinOffsetof(args: List<Expression>, position: Position, program:
for(member in struct.statements) {
if((member as VarDecl).name == membername)
return NumericLiteralValue(DataType.UBYTE, offset, position)
offset += ICompilationTarget.instance.memorySize(member.datatype)
offset += memsizer.memorySize(member.datatype)
}
throw SyntaxError("undefined struct member", position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
@@ -319,13 +321,13 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) =
NumericLiteralValue(DataType.UBYTE, target.statements.map { ICompilationTarget.instance.memorySize((it as VarDecl).datatype) }.sum(), position)
NumericLiteralValue(DataType.UBYTE, target.statements.map { memsizer.memorySize((it as VarDecl).datatype) }.sum(), position)
return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(ICompilationTarget.instance.memorySize(elementDt) * length, position)
numericLiteral(memsizer.memorySize(elementDt) * length, position)
}
dt.istype(DataType.STRUCT) -> {
when (target) {
@@ -335,14 +337,15 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
}
}
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, ICompilationTarget.instance.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
@@ -366,7 +369,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.STR -> {
val refLv = target.value as StringLiteralValue
val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
}
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
@@ -376,7 +379,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
}
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -385,7 +389,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, result, position)
}
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -393,7 +398,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
}
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -401,7 +407,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
}
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -409,7 +416,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
}
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -417,7 +425,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
}
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -425,7 +434,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
}
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -433,7 +443,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
}
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -441,7 +452,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
}
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -449,7 +461,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
}
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@@ -1,5 +1,6 @@
package prog8.compiler.target
import prog8.ast.IMemSizer
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.ast.base.*
@@ -7,7 +8,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
@@ -16,16 +17,11 @@ import prog8.compiler.target.cx16.CX16MachineDefinition
import java.nio.file.Path
internal interface ICompilationTarget: IStringEncoding {
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun memorySize(dt: DataType): Int
companion object {
lateinit var instance: ICompilationTarget // TODO reduce dependency on this by just passing the instance as a parameter
}
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
val memAddr = target.memoryAddress
@@ -67,7 +63,6 @@ internal interface ICompilationTarget: IStringEncoding {
else -> return true
}
}
}
@@ -113,7 +108,7 @@ internal object Cx16Target: ICompilationTarget {
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: ErrorReporter,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
@@ -1,23 +1,20 @@
package prog8.compiler.target
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.parser.ModuleImporter
internal interface IMachineFloat {
interface IMachineFloat {
fun toDouble(): Double
fun makeFloatFillAsm(): String
}
internal enum class CpuType {
enum class CpuType {
CPU6502,
CPU65c02
}
internal interface IMachineDefinition {
interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
@@ -34,10 +31,7 @@ internal interface IMachineDefinition {
fun initializeZeropage(compilerOptions: CompilationOptions)
fun getFloat(num: Number): IMachineFloat
// TODO don't do the importing here, just return a list of modules to import...:
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program,
encoder: IStringEncoding, compilationTargetName: String)
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(programName: String)
fun isRegularRAMaddress(address: Int): Boolean
}
@@ -1,12 +1,9 @@
package prog8.compiler.target.c64
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
import java.io.IOException
import kotlin.math.absoluteValue
import kotlin.math.pow
@@ -31,15 +28,11 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(
compilerOptions: CompilationOptions,
importer: ModuleImporter,
program: Program,
encoder: IStringEncoding,
compilationTargetName: String)
{
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib", encoder, compilationTargetName)
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(programName: String) {
@@ -109,7 +102,7 @@ internal object C64MachineDefinition: IMachineDefinition {
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
// 0x90-0xfa is 'kernal work storage area'
))
}
@@ -172,7 +172,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@@ -236,7 +236,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@@ -431,7 +431,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@@ -495,7 +495,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@@ -626,7 +626,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
@@ -885,7 +885,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
@@ -15,6 +15,7 @@ import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.AssignmentAsmGen
import java.io.CharConversionException
import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
@@ -22,10 +23,10 @@ import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program,
val errors: ErrorReporter,
val errors: IErrorReporter,
val zeropage: Zeropage,
val options: CompilationOptions,
val compTarget: ICompilationTarget,
private val compTarget: ICompilationTarget,
private val outputDir: Path): IAssemblyGenerator {
// for expressions and augmented assignments:
@@ -88,12 +89,14 @@ internal class AsmGen(private val program: Program,
return AssemblyProgram(program.name, outputDir, compTarget.name)
}
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
internal fun haveFPWR() = compTarget is Cx16Target
private fun header() {
val ourName = this.javaClass.name
val cpu = when(compTarget.machine.cpu) {
CpuType.CPU6502 -> "6502"
CpuType.CPU65c02 -> "65c02"
CpuType.CPU65c02 -> "w65c02"
else -> "unsupported"
}
@@ -130,12 +133,14 @@ internal class AsmGen(private val program: Program,
out("_prog8_entrypoint\t; assembly code starts here\n")
if(!options.noSysInit)
out(" jsr ${compTarget.name}.init_system")
out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
if(!options.noSysInit)
out(" jsr ${compTarget.name}.init_system")
out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
@@ -176,14 +181,17 @@ internal class AsmGen(private val program: Program,
private fun block2asm(block: Block) {
out("\n\n; ---- block: '${block.name}' ----")
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
val addr = block.address
if(addr!=null) {
out(".cerror * > ${addr.toHex()}, 'block address overlaps by ', *-${addr.toHex()},' bytes'")
out("* = ${addr.toHex()}")
if(block.address!=null)
out("* = ${block.address!!.toHex()}")
else {
if("align_word" in block.options())
out("\t.align 2")
else if("align_page" in block.options())
out("\t.align $100")
}
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
outputSourceLine(block)
zeropagevars2asm(block.statements)
memdefs2asm(block.statements)
@@ -262,7 +270,7 @@ internal class AsmGen(private val program: Program,
try {
val errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.handle()
errors.report()
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = address to variable.datatype
@@ -348,7 +356,7 @@ internal class AsmGen(private val program: Program,
}
private fun memdefs2asm(statements: List<Statement>) {
out("\n; memdefs and kernel subroutines")
out("\n; memdefs and kernal subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) {
if(m.value is NumericLiteralValue)
@@ -361,7 +369,7 @@ internal class AsmGen(private val program: Program,
val addr = sub.asmAddress
if(addr!=null) {
if(sub.statements.isNotEmpty())
throw AssemblyError("kernel subroutine cannot have statements")
throw AssemblyError("kernal subroutine cannot have statements")
out(" ${sub.name} = ${addr.toHex()}")
}
}
@@ -399,8 +407,9 @@ internal class AsmGen(private val program: Program,
}
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
val string = (lastvar.value as StringLiteralValue).value
out("${lastvar.name}\t; ${lastvar.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"")
val sv = lastvar.value as StringLiteralValue
val altEncoding = if(sv.altEncoding) "@" else ""
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString())
@@ -527,7 +536,7 @@ internal class AsmGen(private val program: Program,
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (compTarget.machine.cpu == CpuType.CPU65c02) {
if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
@@ -562,7 +571,7 @@ internal class AsmGen(private val program: Program,
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) {
if (compTarget.machine.cpu == CpuType.CPU65c02) {
if (isTargetCpu(CpuType.CPU65c02)) {
// just use the cpu's stack for all registers, shorter code
when (register) {
CpuRegister.A -> out(" pha")
@@ -591,7 +600,7 @@ internal class AsmGen(private val program: Program,
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" phx")
if (isTargetCpu(CpuType.CPU65c02)) out(" phx")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG")
@@ -600,7 +609,7 @@ internal class AsmGen(private val program: Program,
}
}
CpuRegister.Y -> {
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" phy")
if (isTargetCpu(CpuType.CPU65c02)) out(" phy")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG")
@@ -612,7 +621,7 @@ internal class AsmGen(private val program: Program,
}
internal fun restoreRegisterLocal(register: CpuRegister) {
if (compTarget.machine.cpu == CpuType.CPU65c02) {
if (isTargetCpu(CpuType.CPU65c02)) {
when (register) {
// this just used the stack, for all registers. Shorter code.
CpuRegister.A -> out(" pla")
@@ -637,7 +646,7 @@ internal class AsmGen(private val program: Program,
out(" pla")
}
CpuRegister.X -> {
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" plx")
if (isTargetCpu(CpuType.CPU65c02)) out(" plx")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
@@ -646,7 +655,7 @@ internal class AsmGen(private val program: Program,
}
}
CpuRegister.Y -> {
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" ply")
if (isTargetCpu(CpuType.CPU65c02)) out(" ply")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
@@ -1254,7 +1263,9 @@ $counterVar .word 0""")
"%asmbinary" -> {
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
out(" .binary \"${stmt.args[0].str}\" $offset $length")
val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
val relPath = Paths.get("").relativize(includedSourcePath)
out(" .binary \"./$relPath\" $offset $length")
}
"%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
@@ -1329,7 +1340,7 @@ $label nop""")
// sign extend signed byte on stack to signed word on stack
when(valueDt) {
DataType.UBYTE -> {
if(compTarget.machine.cpu == CpuType.CPU65c02)
if(isTargetCpu(CpuType.CPU65c02))
out(" stz P8ESTACK_HI+1,x")
else
out(" lda #0 | sta P8ESTACK_HI+1,x")
@@ -1343,7 +1354,7 @@ $label nop""")
// sign extend signed byte in a var to a full word in that variable
when(valueDt) {
DataType.UBYTE -> {
if(compTarget.machine.cpu == CpuType.CPU65c02)
if(isTargetCpu(CpuType.CPU65c02))
out(" stz $asmvar+1")
else
out(" lda #0 | sta $asmvar+1")
@@ -1396,39 +1407,44 @@ $label nop""")
internal fun tryOptimizedPointerAccessWithA(expr: BinaryExpression, write: Boolean): Boolean {
// optimize pointer,indexregister if possible
fun evalBytevalueWillClobberA(expr: Expression): Boolean {
val dt = expr.inferType(program)
if(!dt.istype(DataType.UBYTE) && !dt.istype(DataType.BYTE))
return true
return when(expr) {
is IdentifierReference -> false
is NumericLiteralValue -> false
is DirectMemoryRead -> expr.addressExpression !is IdentifierReference && expr.addressExpression !is NumericLiteralValue
is TypecastExpression -> evalBytevalueWillClobberA(expr.expression)
else -> true
}
}
if(expr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
if(ptrAndIndex!=null) {
val pointervar = ptrAndIndex.first as? IdentifierReference
if(write) {
when(ptrAndIndex.second) {
is NumericLiteralValue, is IdentifierReference -> {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" sta (P8ZP_SCRATCH_W2),y")
}
}
else -> {
// same as above but we need to save the A register
if(pointervar!=null && isZpVar(pointervar)) {
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
}
if(pointervar!=null && isZpVar(pointervar)) {
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
@@ -5,7 +5,10 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.functions.FSignature
@@ -68,7 +71,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is FunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall")
val scope = fcall.definingScope()
val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
@@ -85,12 +87,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(src, target, false, fcall.position)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
// remove the variable for the name, it's not used as a variable only as a tag for the assembler.
val nameDecl = scope.statements.single { it is VarDecl && it.name==nameRef.nameInSource.single() }
asmgen.removals.add(Pair(nameDecl, scope))
asmgen.slabs[name] = size
}
@@ -651,12 +649,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W2"),
targetFromExpr(first, datatype),
false, first.position
false, program.memsizer, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W1"),
targetFromExpr(second, datatype),
false, second.position
false, program.memsizer, second.position
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
@@ -668,12 +666,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
targetFromExpr(first, datatype),
false, first.position
false, program.memsizer, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
targetFromExpr(second, datatype),
false, second.position
false, program.memsizer, second.position
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
@@ -683,8 +681,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val index1 = indexValue1.number.toInt() * asmgen.compTarget.memorySize(elementDt)
val index2 = indexValue2.number.toInt() * asmgen.compTarget.memorySize(elementDt)
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
@@ -797,7 +795,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * asmgen.compTarget.memorySize(elementDt)
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
@@ -856,7 +854,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * asmgen.compTarget.memorySize(elementDt)
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
@@ -977,7 +975,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
sta ($varname)
txa
@@ -1028,7 +1026,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
ldy #1
lda ($varname),y
@@ -1292,7 +1290,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, conv.dt, null, variableAsmName = varname)
val assign = AsmAssignment(src, tgt, false, value.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}
conv.reg != null -> {
@@ -1308,7 +1306,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, value.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}
else -> throw AssemblyError("callconv")
@@ -1393,7 +1393,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> {
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
@@ -1459,7 +1459,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
fun assignViaExprEval() {
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (pushResultOnEstack) {
asmgen.out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
} else {
@@ -1673,7 +1673,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
DataType.UWORD -> {
if(amount>=16) {
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
@@ -1888,7 +1888,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
if(arrayExpr.indexer.indexNum!=null) {
val indexValue = arrayExpr.indexer.constIndex()!! * asmgen.compTarget.memorySize(elementDt)
val indexValue = arrayExpr.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
@@ -91,11 +91,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
when {
stmt.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (cx16virtualRegsArgsInfo, otherRegsArgsInfo) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
for(arg in cx16virtualRegsArgsInfo)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in otherRegsArgsInfo)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
@@ -168,7 +172,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
""")
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
else
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
@@ -318,7 +322,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
}
}
}
@@ -67,7 +67,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * asmgen.compTarget.memorySize(elementDt)
val indexValue = targetArrayIdx.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
@@ -1,11 +1,11 @@
package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.c64.codegen.AsmGen
@@ -207,12 +207,13 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
internal class AsmAssignment(val source: AsmAssignSource,
val target: AsmAssignTarget,
val isAugmentable: Boolean,
memsizer: IMemSizer,
val position: Position) {
init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
require(ICompilationTarget.instance.memorySize(source.datatype) <= ICompilationTarget.instance.memorySize(target.datatype)) {
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source storage size must be less or equal to target datatype storage size"
}
}
@@ -22,7 +22,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position)
val assign = AsmAssignment(source, target, assignment.isAugmentable, program.memsizer, assignment.position)
target.origAssign = assign
if(assign.isAugmentable)
@@ -66,7 +66,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
if (value.indexer.indexNum!=null) {
// constant array index value
val indexValue = value.indexer.constIndex()!! * asmgen.compTarget.memorySize(elementDt)
val indexValue = value.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
when (elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue")
@@ -114,7 +114,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
@@ -319,7 +319,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
@@ -441,7 +441,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, value.position)
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
translateNormalAssignment(assign)
}
@@ -473,7 +473,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
DataType.UWORD, DataType.WORD -> {
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@@ -496,7 +496,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
DataType.UWORD -> {
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@@ -592,7 +592,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
}
DataType.UWORD, DataType.WORD -> {
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@@ -620,7 +620,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
}
DataType.UWORD -> {
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@@ -762,7 +762,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * asmgen.compTarget.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
@@ -968,7 +968,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> {
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * asmgen.compTarget.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
@@ -1191,7 +1191,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * asmgen.compTarget.memorySize(target.datatype)
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
@@ -1295,7 +1295,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@@ -1304,7 +1304,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
@@ -1329,7 +1329,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@@ -1571,7 +1571,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
if(word==0 && asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
if(word==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this processor
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@@ -1666,7 +1666,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
if(byte==0.toShort() && asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this cpu
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@@ -1736,7 +1736,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
@@ -1757,7 +1757,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// optimized case for float zero
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
stz ${target.asmVarname}
stz ${target.asmVarname}+1
@@ -1777,8 +1777,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * asmgen.compTarget.memorySize(DataType.FLOAT)
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
stz ${target.asmVarname}+$indexValue
stz ${target.asmVarname}+$indexValue+1
@@ -1842,7 +1842,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> {
val arrayVarName = target.asmVarname
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * asmgen.compTarget.memorySize(DataType.FLOAT)
val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
@@ -1975,7 +1975,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" lda ${address.toHex()} | sta ${wordtarget.asmVarname}")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@@ -1991,7 +1991,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
asmgen.out(" lda ${address.toHex()} | sta P8ESTACK_LO,x")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@@ -2003,7 +2003,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.VARIABLE -> {
asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta ${wordtarget.asmVarname}")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@@ -2023,7 +2023,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.STACK -> {
asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta P8ESTACK_LO,x")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@@ -2041,7 +2041,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
@@ -2051,7 +2051,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.out(" pla")
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
@@ -2063,7 +2063,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" sta ($sourceName)")
@@ -2109,21 +2109,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, expr.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, expr.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, Position.DUMMY)
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign)
}
}
@@ -7,7 +7,6 @@ import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
@@ -201,7 +200,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
with(target.array!!.indexer) {
when {
indexNum!=null -> {
val targetVarName = "${target.asmVarname} + ${indexNum!!.number.toInt()*asmgen.compTarget.memorySize(target.datatype)}"
val targetVarName = "${target.asmVarname} + ${indexNum!!.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when(target.datatype) {
in ByteDatatypes -> {
when {
@@ -245,19 +244,19 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when(target.datatype) {
in ByteDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
}
in WordDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
}
@@ -631,7 +630,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"<<" -> {
if(value>=8) {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@@ -642,7 +641,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(value>0) {
if (dt == DataType.UBYTE) {
if(value>=8) {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@@ -857,14 +856,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"<<" -> {
when {
value>=16 -> {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1")
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value==8 -> {
asmgen.out(" lda $name | sta $name+1")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@@ -884,14 +883,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(dt==DataType.UWORD) {
when {
value>=16 -> {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1")
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value==8 -> {
asmgen.out(" lda $name+1 | sta $name")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@@ -940,13 +939,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> {
when {
value == 0 -> {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1")
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value and 255 == 0 -> {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@@ -954,7 +953,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
value < 0x0100 -> {
asmgen.out(" lda $name | and #$value | sta $name")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@@ -1041,7 +1040,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"*" -> {
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
else
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
@@ -1092,7 +1091,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> {
asmgen.out(" lda $otherName | and $name | sta $name")
if(dt in WordDatatypes) {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@@ -1351,7 +1350,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" and $name | sta $name")
if(dt in WordDatatypes) {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@@ -1474,7 +1473,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(asmgen.compTarget is Cx16Target) {
if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT()
asmgen.out("""
lda #<$name
@@ -1485,15 +1493,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.MOVFM
jsr floats.FPWRT
""")
} else
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.FPWR
""")
}
"+" -> {
asmgen.out("""
@@ -1552,7 +1551,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(asmgen.compTarget is Cx16Target) {
if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT()
asmgen.out("""
lda #<$name
@@ -1563,15 +1571,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.MOVFM
jsr floats.FPWRT
""")
} else
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.FPWR
""")
}
"+" -> {
if (value == 0.0)
@@ -1644,7 +1643,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
DataType.UBYTE, DataType.BYTE -> {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
@@ -1654,7 +1653,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
}
TargetStorageKind.STACK -> {
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
@@ -1,12 +1,9 @@
package prog8.compiler.target.cx16
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.parser.ModuleImporter
import java.io.IOException
internal object CX16MachineDefinition: IMachineDefinition {
@@ -28,16 +25,11 @@ internal object CX16MachineDefinition: IMachineDefinition {
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
override fun importLibs(
compilerOptions: CompilationOptions,
importer: ModuleImporter,
program: Program,
encoder: IStringEncoding,
compilationTargetName: String)
{
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib", encoder, compilationTargetName)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(programName: String) {
@@ -10,7 +10,7 @@ import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BinExprSplitter(private val program: Program) : AstWalker() {
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@@ -80,7 +80,7 @@ X = BinExpr X = LeftExpr
private fun isSimpleTarget(target: AssignTarget, program: Program) =
if (target.identifier!=null || target.memoryAddress!=null)
ICompilationTarget.instance.isInRegularRAM(target, program)
compTarget.isInRegularRAM(target, program)
else
false
+18 -7
View File
@@ -5,25 +5,25 @@ import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.compiler.ErrorReporter
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.loadAsmIncludeFile
import prog8.compiler.IErrorReporter
import java.nio.file.Path
private val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq")
Pair("main", "start")
)
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
class CallGraph(private val program: Program) : IAstVisitor {
class CallGraph(private val program: Program, private val asmFileLoader: (filename: String, source: Path)->String) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
@@ -80,7 +80,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val asm = asmFileLoader(directive.args[0].str!!, thisModule.source)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
@@ -150,6 +150,17 @@ class CallGraph(private val program: Program) : IAstVisitor {
super.visit(functionCallStatement)
}
override fun visit(addressOf: AddressOf) {
val otherSub = addressOf.identifier.targetSubroutine(program)
if(otherSub!=null) {
addressOf.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(thisSub)
}
}
super.visit(addressOf)
}
override fun visit(jump: Jump) {
val otherSub = jump.identifier?.targetSubroutine(program)
if (otherSub != null) {
@@ -213,7 +224,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
}
fun checkRecursiveCalls(errors: ErrorReporter) {
fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
@@ -13,7 +13,7 @@ import prog8.compiler.target.ICompilationTarget
import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
@@ -224,7 +224,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
range.step
}
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, ICompilationTarget.instance, range.position)
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
@@ -10,11 +10,11 @@ import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@@ -39,7 +39,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
// Replace all constant identifiers with their actual value,
// 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() {
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
@@ -192,7 +192,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < ICompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > ICompilationTarget.instance.machine.FLOAT_MAX_POSITIVE)
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.
+13 -8
View File
@@ -2,16 +2,18 @@ package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.compiler.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal fun Program.constantFold(errors: ErrorReporter) {
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.isEmpty()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors)
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
replacer.visit(this)
if (errors.isEmpty()) {
replacer.applyModifications()
@@ -20,7 +22,7 @@ internal fun Program.constantFold(errors: ErrorReporter) {
if(errors.isEmpty()) {
valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this)
val optimizer = ConstantFoldingOptimizer(this, compTarget)
optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
@@ -39,8 +41,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
}
internal fun Program.optimizeStatements(errors: ErrorReporter, functions: IBuiltinFunctions): Int {
val optimizer = StatementOptimizer(this, errors, functions)
internal fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget, asmFileLoader)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
@@ -55,8 +60,8 @@ internal fun Program.simplifyExpressions() : Int {
return opti.applyModifications()
}
internal fun Program.splitBinaryExpressions() : Int {
val opti = BinExprSplitter(this)
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, compTarget)
opti.visit(this)
return opti.applyModifications()
}
@@ -10,23 +10,27 @@ import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
import kotlin.math.floor
internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter,
private val functions: IBuiltinFunctions
private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String
) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program)
private val callgraph = CallGraph(program, asmFileLoader)
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
errors.warn("removing empty block '${block.name}'", block.position)
if(block.name != program.internedStringsModuleName)
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
@@ -52,7 +56,8 @@ internal class StatementOptimizer(private val program: Program,
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
if(!subroutine.isAsmSubroutine)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
@@ -81,11 +86,9 @@ internal class StatementOptimizer(private val program: Program,
}
// 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")) {
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
val stringVar: IdentifierReference? = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
@@ -96,29 +99,29 @@ internal class StatementOptimizer(private val program: Program,
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = ICompilationTarget.instance.encodeString(string.value, string.altEncoding)[0]
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
IdentifierReference(listOf("txt", "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 = ICompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding)
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
IdentifierReference(listOf("txt", "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))
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
}
}
@@ -212,7 +215,7 @@ internal class StatementOptimizer(private val program: Program,
val size = sv.value.length
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = ICompilationTarget.instance.encodeString(sv.value, sv.altEncoding)[0]
val character = compTarget.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.loopVar, null, null, forLoop.position), byte, forLoop.position))
@@ -293,18 +296,6 @@ internal class StatementOptimizer(private val program: Program,
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()
@@ -3,7 +3,6 @@ package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.compiler.ErrorReporter
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.PrefixExpression
@@ -11,20 +10,26 @@ import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() {
internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget,
private val asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program)
val callgraph = CallGraph(program, asmFileLoader)
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.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
val forceOutput = "force_output" in sub.definingBlock().options()
if (sub !== entrypoint && !forceOutput && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope()))
}
}
@@ -96,7 +101,7 @@ internal class UnusedCodeRemover(private val program: Program, private val error
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && ICompilationTarget.instance.isInRegularRAM(assign1.target, program)) {
if (assign1.target.isSameAs(assign2.target, program) && compTarget.isInRegularRAM(assign1.target, program)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {
+67 -41
View File
@@ -5,21 +5,22 @@ import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.IBuiltinFunctions
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import java.io.CharConversionException
import java.nio.file.Path
import kotlin.test.*
@@ -134,7 +135,7 @@ class TestC64Zeropage {
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors)
@@ -147,37 +148,37 @@ class TestC64Zeropage {
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
@@ -187,19 +188,19 @@ class TestC64Zeropage {
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.available())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
@@ -208,7 +209,7 @@ class TestC64Zeropage {
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.available())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
@@ -221,7 +222,7 @@ class TestC64Zeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> {
@@ -244,7 +245,7 @@ class TestC64Zeropage {
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
@@ -274,7 +275,7 @@ class TestC64Zeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
@@ -293,7 +294,7 @@ class TestC64Zeropage {
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@@ -303,9 +304,30 @@ class TestC64Zeropage {
class TestCx16Zeropage {
@Test
fun testReservedLocations() {
val zp = CX16MachineDefinition.CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
@Test
fun testFreeSpaces() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.available())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp3.available())
val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.available())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
}
@@ -408,16 +430,20 @@ class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? = null
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testInValidRamC64_memory_addresses() {
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions())
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
@@ -442,7 +468,7 @@ class TestMemory {
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions())
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
@@ -461,7 +487,7 @@ class TestMemory {
@Test
fun testInValidRamC64_memory_identifiers() {
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
val program = Program("test", mutableListOf(), DummyFunctions())
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
@@ -490,7 +516,7 @@ class TestMemory {
fun testInValidRamC64_memory_expression() {
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val program = Program("test", mutableListOf(), DummyFunctions())
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
}
@@ -501,7 +527,7 @@ class TestMemory {
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions())
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@@ -514,7 +540,7 @@ class TestMemory {
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions())
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@@ -527,7 +553,7 @@ class TestMemory {
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions())
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@@ -540,7 +566,7 @@ class TestMemory {
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions())
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@@ -554,7 +580,7 @@ class TestMemory {
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions())
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@@ -568,7 +594,7 @@ class TestMemory {
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions())
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
+2
View File
@@ -33,6 +33,7 @@ dependencies {
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
@@ -41,6 +42,7 @@ compileKotlin {
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
+43 -7
View File
@@ -1,10 +1,7 @@
package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
@@ -244,10 +241,14 @@ interface IAssignable {
// just a tag for now
}
interface IMemSizer {
fun memorySize(dt: DataType): Int
}
interface IBuiltinFunctions {
val names: Set<String>
val purefunctionNames: Set<String>
fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue?
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue?
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
}
@@ -255,13 +256,32 @@ interface IBuiltinFunctions {
class Program(val name: String, val modules: MutableList<Module>, val builtinFunctions: IBuiltinFunctions): Node {
class Program(val name: String,
val modules: MutableList<Module>,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer): Node {
val namespace = GlobalNamespace(modules, builtinFunctions.names)
val mainModule: Module
get() = modules.first { it.name!=internedStringsModuleName }
val definedLoadAddress: Int
get() = modules.first().loadAddress
get() = mainModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>()
val internedStringsModuleName = "prog8_interned_strings"
init {
// insert a container module for all interned strings later
if(modules.firstOrNull()?.name != internedStringsModuleName) {
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, false, Path.of(""))
modules.add(0, internedStringsModule)
val block = Block(internedStringsModuleName, null, mutableListOf(), false, Position.DUMMY)
internedStringsModule.statements.add(block)
internedStringsModule.linkParents(this)
internedStringsModule.program = this
}
}
fun entrypoint(): Subroutine? {
val mainBlocks = allBlocks().filter { it.name=="main" }
@@ -274,6 +294,22 @@ class Program(val name: String, val modules: MutableList<Module>, val builtinFun
}
}
fun internString(string: StringLiteralValue): List<String> {
val key = Pair(string.value, string.altEncoding)
val existing = internedStrings[key]
if(existing!=null)
return existing
val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName}
(internedStringsBlock as Block).statements.add(decl)
decl.linkParents(internedStringsBlock)
val scopedName = listOf(internedStringsModuleName, decl.name)
internedStrings[key] = scopedName
return scopedName
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY
@@ -13,6 +13,8 @@ import kotlin.math.abs
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val logicalOperators = setOf("and", "or", "xor", "not")
sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
@@ -478,15 +480,11 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
}
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
class StringLiteralValue(val value: String,
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
override val position: Position) : Expression() {
override lateinit var parent: Node
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
}
@@ -516,8 +514,6 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
override val position: Position) : Expression() {
override lateinit var parent: Node
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
value.forEach {it.linkParents(this)}
@@ -763,17 +759,6 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun memberOfStruct(program: Program) = this.targetVarDecl(program)?.struct
fun heapId(namespace: INameScope): Int {
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) {
is IdentifierReference -> value.heapId(namespace)
is StringLiteralValue -> value.heapId
is ArrayLiteralValue -> value.heapId
else -> throw FatalAstException("requires a reference value")
}
}
fun firstStructVarName(program: Program): String? {
// take the name of the first struct member of the structvariable instead
// if it's just a regular variable, return null.
@@ -818,7 +803,7 @@ class FunctionCall(override var target: IdentifierReference,
// lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1)
return null
val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position)
val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position, program.memsizer)
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
if(resultValue==null || resultDt istype resultValue.type)
@@ -176,12 +176,6 @@ open class VarDecl(val type: VarDeclType,
companion object {
private var autoHeapValueSequenceNumber = 0
fun createAuto(string: StringLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
}
fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val arrayDt =
+1 -1
View File
@@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Python 3.8 virtualenv" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+1 -1
View File
@@ -20,7 +20,7 @@ import os
# -- Project information -----------------------------------------------------
project = 'Prog8'
copyright = '2019, Irmen de Jong'
copyright = '2021, Irmen de Jong'
author = 'Irmen de Jong'
+1 -1
View File
@@ -57,7 +57,7 @@ Language features
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the C64.
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
Code example
+1 -1
View File
@@ -182,7 +182,7 @@ Provides string manipulation routines.
floats
------
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
Provides definitions for the ROM/kernal subroutines and utility routines dealing with floating
point variables. This includes ``print_f``, the routine used to print floating point numbers.
+4 -4
View File
@@ -522,12 +522,12 @@ Use a ``when`` statement if you have a set of fixed choices that each should res
action. It is possible to combine several choices to result in the same action::
when value {
4 -> c64scr.print("four")
5 -> c64scr.print("five")
4 -> txt.print("four")
5 -> txt.print("five")
10,20,30 -> {
c64scr.print("ten or twenty or thirty")
txt.print("ten or twenty or thirty")
}
else -> c64scr.print("don't know")
else -> txt.print("don't know")
}
The when-*value* can be any expression but the choice values have to evaluate to
+12 -11
View File
@@ -70,7 +70,7 @@ Directives
It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
are required to perform floating point operations (from the BASIC kernal). No clean exit is possible.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
during normal system operation.
@@ -120,17 +120,18 @@ Directives
Level: module, block.
Sets special compiler options.
- For a module option, there is ``enable_floats``, which will tell the compiler
- ``enable_floats`` (module level) tells the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
importing the ``floats`` library is required anyway and that will enable it for you automatically.
- There's also ``no_sysinit`` which cause the resulting program to *not* include
- ``no_sysinit`` (module level) which cause the resulting program to *not* include
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
take care of that yourself. The program will just start running from whatever state the machine is in when the
program was launched.
- When used in a block with the ``force_output`` option, it will force the block to be outputted
in the final program. Can be useful to make sure some
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
- ``force_output`` (in a block) will force the block to be outputted in the final program.
Can be useful to make sure some data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
- ``align_word`` (in a block) will make the assembler align the start address of this block on a word boundary in memory (so, an even memory address).
- ``align_page`` (in a block) will make the assembler align the start address of this block on a page boundary in memory (so, the LSB of the address is 0).
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
@@ -513,7 +514,7 @@ Multiple return values
^^^^^^^^^^^^^^^^^^^^^^
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
(referencing a routine in kernel ROM) can return more than one return value.
(referencing a routine in kernal ROM) can return more than one return value.
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers.
It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible.
@@ -772,11 +773,11 @@ Choices can result in a single statement or a block of multiple statements in w
case you have to use { } to enclose them::
when value {
4 -> c64scr.print("four")
5 -> c64scr.print("five")
4 -> txt.print("four")
5 -> txt.print("five")
10,20,30 -> {
c64scr.print("ten or twenty or thirty")
txt.print("ten or twenty or thirty")
}
else -> c64scr.print("don't know")
else -> txt.print("don't know")
}
+10 -19
View File
@@ -17,7 +17,7 @@ Currently there are two machines that are supported as compiler target (selectab
This chapter explains the relevant system details of these machines.
.. hint::
If you only use standard kernel and prog8 library routines,
If you only use standard kernal and prog8 library routines,
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
@@ -139,26 +139,17 @@ IRQ Handling
============
Normally, the system's default IRQ handling is not interfered with.
You can however install your own IRQ handler.
This is possible ofcourse by doing it all using customized inline assembly,
but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required).
You can however install your own IRQ handler (for clean separation, it is advised to define it inside its own block).
There are a few library routines available to make setting up C-64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
For the C64 these routines are::
c64.set_irqvec()
c64.set_irqvec_excl()
c64.set_irq(uword handler_address, boolean useKernal)
c64.set_rasterirq(uword handler_address, uword rasterline, boolean useKernal)
c64.restore_irq() ; set everything back to the systems default irq handler
c64.set_rasterirq( <raster line> )
c64.set_rasterirq_excl( <raster line> )
c64.restore_irqvec() ; set it back to the systems default irq handler
If you activate an IRQ handler with one of these, it expects the handler to be defined
as a subroutine ``irq`` in the module ``irq`` so like this::
irq {
sub irq() {
; ... irq handling here ...
}
}
And for the Commander X16:
cx16.set_irq(uword handler_address, boolean useKernal) ; vsync irq
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables kernal irq handler! sys.wait() won't work anymore
cx16.restore_irq() ; set everything back to the systems default irq handler
+1 -1
View File
@@ -50,7 +50,7 @@ Calling the routine is just a simple JSR instruction, but the other two work lik
``asmsub`` routines
^^^^^^^^^^^^^^^^^^^
These are usually declarations of kernel (ROM) routines or low-level assembly only routines,
These are usually declarations of kernal (ROM) routines or low-level assembly only routines,
that have their arguments solely passed into specific registers.
Sometimes even via a processor status flag such as the Carry flag.
Return values also via designated registers.
+11 -29
View File
@@ -2,21 +2,21 @@
TODO
====
- optimize assigning array and struct variables (multi-element assings -> memcopy)
- hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine)
- optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
- optimize several inner loops in gfx2
- try to fix the bresenham line routines in graphics and gfx2 (sometimes they're a pixel 'off')
- add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)
- add a flood fill routine to gfx2?
- add a f_seek() routine for the Cx16 that uses its seek dos api?
- refactor the asmgen into their own submodule?
- refactor the compiler optimizers into their own submodule?
- optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
- optimize several inner loops in gfx2 (highres 4 color mode)
- use the 65c02 bit clear/set/test instructions for single-bit operations
- try to fix the bresenham line routines in graphics and gfx2 (sometimes they're a pixel 'off')
- add a flood fill routine to gfx2?
- add sound to the cx16 tehtriz
- add a f_seek() routine for the Cx16 that uses its seek dos api?
- optimizer: detect variables that are written but never read - mark those as unused too and remove them, such as uword unused = memory("unused222", 20) - also remove the memory slab allocation
- add a compiler option to not remove unused subroutines. this allows for building library programs
- hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine)
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``v_``
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- c64: make the graphics.BITMAP_ADDRESS configurable
- c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
- some support for recursive subroutines?
- via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
- Or via a special recursive call operation that copies the current values of all local vars (including arguments) to the stack, replaces the arguments, jsr subroutine, and after returning copy the stack back to the local variables
@@ -28,30 +28,12 @@ More optimizations
Add more compiler optimizations to the existing ones.
- further optimize assignment codegeneration, such as the following:
- rewrite expression code generator to not use eval stack but a fixed number of predetermined value 'variables' (1 per nesting level?)
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
- more optimizations on the language AST level
- more optimizations on the final assembly source level
Eval stack redesign? (lot of work)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
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?
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.
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?
This could GREATLY improve code size and speed for operations that work on just a single value.
Bug Fixing
^^^^^^^^^^
Ofcourse there are always bugs to fix ;)
Misc
^^^^
+144
View File
@@ -0,0 +1,144 @@
%import textio
%import string
; Animal guessing game where the computer gets smarter every time.
; Note: this program is compatible with C64 and CX16.
main {
const ubyte database_size = 100
uword animal_names_buf
uword questions_buf
uword animal_names_ptr
uword questions_ptr
uword[database_size] animals
uword[database_size] questions
uword[database_size] answers_questions
uword[database_size] answers_animals
ubyte new_animal_number
ubyte new_question_number
str userinput = "x"*80
sub start() {
; initialize the database
animal_names_buf = memory("animalnames", 500)
questions_buf = memory("questions", 2000)
animal_names_ptr = animal_names_buf
questions_ptr = questions_buf
animals[0] = 0
animals[1] = "dolphin"
animals[2] = "eagle"
animals[3] = "horse"
new_animal_number = 4
questions[0] = 0
questions[1] = "does it swim"
questions[2] = "can it fly"
new_question_number = 3
answers_questions[0] = mkword(0, 0)
answers_questions[1] = mkword(0, 2)
answers_questions[2] = mkword(0, 0)
answers_animals[0] = mkword(0, 0)
answers_animals[1] = mkword(1, 0)
answers_animals[2] = mkword(2, 3)
; play the game
game()
}
sub game() {
repeat {
ubyte current_question = 1
txt.print("\n\nanimal guessing game!\nthink of an animal.\n")
ubyte guessed = false
while not guessed {
txt.print(questions[current_question])
txt.print("? ")
if txt.input_chars(userinput) {
txt.nl()
ubyte animal_number
if userinput[0]=='y' {
animal_number = msb(answers_animals[current_question])
if animal_number {
guess(current_question, true, animal_number)
guessed = true
} else {
current_question = msb(answers_questions[current_question])
}
}
else if userinput[0]=='n' {
animal_number = lsb(answers_animals[current_question])
if animal_number {
guess(current_question, false, animal_number)
guessed = true
} else {
current_question = lsb(answers_questions[current_question])
}
}
else {
txt.print("answer (y)es or (n)o please.\n")
}
} else
txt.nl()
}
}
}
sub guess(ubyte question_number, ubyte given_answer_yesno, ubyte animal_number) {
txt.print("is it a ")
txt.print(animals[animal_number])
txt.print("? ")
txt.input_chars(userinput)
if userinput[0] == 'y' {
txt.print("\n\nsee, i knew it!\n")
return
}
str name = "x"*30
txt.print("\n\ni give up. what is it? ")
txt.input_chars(name)
txt.print("\nwhat yes-no question would best articulate the difference\nbetween a ")
txt.print(animals[animal_number])
txt.print(" and a ")
txt.print(name)
txt.print("? ")
txt.input_chars(userinput)
txt.print("\nfor a ")
txt.print(name)
txt.print(", what is the answer to that; yes or no? ")
str answer = "x"*10
txt.input_chars(answer)
animals[new_animal_number] = animal_names_ptr
questions[new_question_number] = questions_ptr
animal_names_ptr += string.copy(name, animal_names_ptr)+1 ; store animal name in buffer
questions_ptr += string.copy(userinput, questions_ptr)+1 ; store question in buffer
answers_questions[new_question_number] = mkword(0, 0)
if answer[0]=='y'
answers_animals[new_question_number] = mkword(new_animal_number, animal_number)
else
answers_animals[new_question_number] = mkword(animal_number, new_animal_number)
uword previous_animals = answers_animals[question_number]
uword previous_questions = answers_questions[question_number]
if given_answer_yesno {
answers_animals[question_number] = mkword(0, lsb(previous_animals))
answers_questions[question_number] = mkword(new_question_number, lsb(previous_questions))
} else {
answers_animals[question_number] = mkword(msb(previous_animals), 0)
answers_questions[question_number] = mkword(msb(previous_questions), new_question_number)
}
new_animal_number++
new_question_number++
txt.print("\n\nthanks, i know more animals now! let's try again.\n")
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ main {
c64.SCROLX &= %11110111 ; 38 column mode
c64.set_rasterirq(200) ; enable animation
c64.set_rasterirq(&irq.irq, 200, false) ; enable animation via raster interrupt
ubyte target_height = 10
ubyte active_height = 24
+1 -1
View File
@@ -7,7 +7,7 @@ main {
sub start() {
txt.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
c64.set_rasterirq(60) ; enable raster irq
c64.set_rasterirq(&irq.irq, 60, true) ; enable playback via raster irq
}
}
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.
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
c1541 -format examples,01 d64 examples.d64
for filename in *.prg; do
c1541 examples.d64 -write $filename ${filename%.*}
done
c1541 examples.d64 -dir
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-9
View File
@@ -1,9 +0,0 @@
These .prg files are the compiled versions of most of the examples.
They can be loaded and ran on a C64 or in an emulator, for example in Vice:
x64sc hello.prg
will load and run the hello example.
You can also attach the d64 disk image and load the examples from there.
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.
+1
View File
@@ -1,3 +1,4 @@
%target cx16
%import textio
%import palette
%import string
+55
View File
@@ -0,0 +1,55 @@
%target cx16
%import textio
%import palette
main {
sub start() {
cx16.screen_set_mode(0)
txt.plot(14,14)
txt.print("raster bars!")
cx16.rombank(0) ; switch to kernal rom for faster irq handling
cx16.set_rasterirq(&irq.irq, 0)
repeat {
; don't exit
}
}
}
irq {
uword[32] colors = [
$011, $112, $213, $214,
$315, $316, $417, $418,
$519, $51a, $62b, $62c,
$73d, $73e, $84f, $94f,
$93e, $83d, $82c, $72b,
$71a, $619, $618, $517,
$516, $415, $414, $313,
$312, $211, $100, $000
]
uword next_irq_line = 0
ubyte color_idx = 0
ubyte yanim = 0
const ubyte barheight = 4
sub irq() {
uword c = colors[color_idx]
color_idx++
color_idx &= 31
if color_idx==0 {
yanim++
next_irq_line = $0030 + sin8u(yanim)
} else {
next_irq_line += barheight
}
palette.set_color(0, c)
cx16.set_rasterline(next_irq_line)
}
}
+105 -87
View File
@@ -5,7 +5,7 @@
; wall kick rotations
; shows next piece
; staged speed increase
; TODO: replicate the simple sound effects from the C64 version
; simplistic sound effects (Vera PSG)
%target cx16
@@ -33,7 +33,7 @@ main {
sub start() {
cx16.screen_set_mode(0) ; low res
void cx16.screen_set_mode(0) ; low res
sound.init()
newGame()
drawBoard()
@@ -49,11 +49,11 @@ waitkey:
if time_lo>=(60-4*speedlevel) {
c64.SETTIM(0,0,0)
drawBlock(xpos, ypos, 32) ; hide block
drawBlock(xpos, ypos, true) ; hide block
if blocklogic.noCollision(xpos, ypos+1) {
; slowly move the block down
ypos++
drawBlock(xpos, ypos, 160) ; show block on new position
drawBlock(xpos, ypos, false) ; show block on new position
} else {
; block can't move further down!
; check if the game area is full, if not, spawn the next block at the top.
@@ -84,31 +84,31 @@ waitkey:
}
sub move_left() {
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
if blocklogic.noCollision(xpos-1, ypos) {
xpos--
}
drawBlock(xpos, ypos, 160)
drawBlock(xpos, ypos, false)
}
sub move_right() {
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
if blocklogic.noCollision(xpos+1, ypos) {
xpos++
}
drawBlock(xpos, ypos, 160)
drawBlock(xpos, ypos, false)
}
sub move_down_faster() {
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
if blocklogic.noCollision(xpos, ypos+1) {
ypos++
}
drawBlock(xpos, ypos, 160)
drawBlock(xpos, ypos, false)
}
sub drop_down_immediately() {
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
ubyte dropypos
for dropypos in ypos+1 to boardOffsetY+boardHeight-1 {
if not blocklogic.noCollision(xpos, dropypos) {
@@ -120,7 +120,7 @@ waitkey:
if dropypos>ypos {
ypos = dropypos
sound.blockdrop()
drawBlock(xpos, ypos, 160)
drawBlock(xpos, ypos, false)
checkForLines()
spawnNextBlock()
score++
@@ -137,7 +137,7 @@ waitkey:
'z' -> {
; no joystick equivalent (there is only 1 fire button)
; rotate counter clockwise
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
if blocklogic.canRotateCCW(xpos, ypos) {
blocklogic.rotateCCW()
sound.blockrotate()
@@ -152,11 +152,11 @@ waitkey:
blocklogic.rotateCCW()
sound.blockrotate()
}
drawBlock(xpos, ypos, 160)
drawBlock(xpos, ypos, false)
}
'x' -> {
; rotate clockwise
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
if blocklogic.canRotateCW(xpos, ypos) {
blocklogic.rotateCW()
sound.blockrotate()
@@ -171,21 +171,21 @@ waitkey:
blocklogic.rotateCW()
sound.blockrotate()
}
drawBlock(xpos, ypos, 160)
drawBlock(xpos, ypos, false)
}
'c' -> {
; hold
if holdingAllowed {
sound.swapping()
if holding<7 {
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
ubyte newholding = blocklogic.currentBlockNum
swapBlock(holding)
holding = newholding
holdingAllowed = false
} else {
holding = blocklogic.currentBlockNum
drawBlock(xpos, ypos, 32)
drawBlock(xpos, ypos, true)
spawnNextBlock()
}
drawHoldBlock()
@@ -206,7 +206,7 @@ waitkey:
num_lines++
ubyte x
for x in boardOffsetX to boardOffsetX+boardWidth-1
txt.setcc(x, linepos, 160, 1)
txt.setcc2(x, linepos, 160, 1)
}
}
if num_lines {
@@ -214,7 +214,7 @@ waitkey:
sound.lineclear_big()
else
sound.lineclear()
sys.wait(20) ; slight delay to flash the line
sys.wait(10) ; slight delay to flash the line
for linepos in complete_lines
if linepos and blocklogic.isLineFull(linepos)
blocklogic.collapse(linepos)
@@ -272,7 +272,7 @@ waitkey:
blocklogic.newCurrentBlock(newblock)
xpos = startXpos
ypos = startYpos
drawBlock(xpos, ypos, 160)
drawBlock(xpos, ypos, false)
}
sub spawnNextBlock() {
@@ -358,14 +358,14 @@ waitkey:
const ubyte nextBlockYpos = 5
ubyte x
for x in nextBlockXpos+3 downto nextBlockXpos {
txt.setcc(x, nextBlockYpos, ' ', 0)
txt.setcc(x, nextBlockYpos+1, ' ', 0)
txt.setcc2(x, nextBlockYpos, ' ', 0)
txt.setcc2(x, nextBlockYpos+1, ' ', 0)
}
; reuse the normal block draw routine (because we can't manipulate array pointers yet)
ubyte prev = blocklogic.currentBlockNum
blocklogic.newCurrentBlock(nextBlock)
drawBlock(nextBlockXpos, nextBlockYpos, 160)
drawBlock(nextBlockXpos, nextBlockYpos, false)
blocklogic.newCurrentBlock(prev)
}
@@ -374,24 +374,35 @@ waitkey:
const ubyte holdBlockYpos = 6
ubyte x
for x in holdBlockXpos+3 downto holdBlockXpos {
txt.setcc(x, holdBlockYpos, '@', 0)
txt.setcc(x, holdBlockYpos+1, '@', 0)
txt.setcc2(x, holdBlockYpos, '@', 0)
txt.setcc2(x, holdBlockYpos+1, '@', 0)
}
if holding < 7 {
; reuse the normal block draw routine (because we can't manipulate array pointers yet)
ubyte prev = blocklogic.currentBlockNum
blocklogic.newCurrentBlock(holding)
drawBlock(holdBlockXpos, holdBlockYpos, 160)
drawBlock(holdBlockXpos, holdBlockYpos, false)
blocklogic.newCurrentBlock(prev)
}
}
sub drawBlock(ubyte x, ubyte y, ubyte character) {
sub drawBlock(ubyte x, ubyte y, ubyte erase) {
ubyte char = 79 ; top left edge
if erase
char = 32 ; space
ubyte @zp i
for i in 15 downto 0 {
ubyte @zp c=blocklogic.currentBlock[i]
if c
txt.setcc((i&3)+x, (i/4)+y, character, c)
if c {
if erase
c=0
else {
ubyte edge = blocklogic.edgecolors[c]
c <<= 4
c |= edge
}
txt.setcc2((i&3)+x, (i/4)+y, char, c)
}
}
}
}
@@ -404,35 +415,37 @@ blocklogic {
ubyte[16] rotated
; the 7 tetrominos
ubyte[] blockI = [0,0,0,0, ; cyan ; note: special rotation (around matrix center)
3,3,3,3,
ubyte[] blockI = [0,0,0,0, ; blue note: special rotation (around matrix center)
6,6,6,6,
0,0,0,0,
0,0,0,0]
ubyte[] blockJ = [6,0,0,0, ; blue
6,6,6,0,
ubyte[] blockJ = [5,0,0,0, ; green
5,5,5,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockL = [0,0,8,0, ; orange
8,8,8,0,
ubyte[] blockL = [0,0,2,0, ; red
2,2,2,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockO = [0,7,7,0, ; yellow ; note: no rotation (square)
0,7,7,0,
ubyte[] blockO = [0,12,12,0, ; grey ; note: no rotation (square)
0,12,12,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockS = [0,5,5,0, ; green
5,5,0,0,
ubyte[] blockS = [0,11,11,0, ; dark grey
11,11,0,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockT = [0,4,0,0, ; purple
4,4,4,0,
ubyte[] blockT = [0,9,0,0, ; brown
9,9,9,0,
0,0,0,0,
0,0,0,0]
ubyte[] blockZ = [2,2,0,0, ; red
0,2,2,0,
ubyte[] blockZ = [4,4,0,0, ; purple
0,4,4,0,
0,0,0,0,
0,0,0,0]
ubyte[16] edgecolors = [11, 1, 10, 0, 10, 13, 14, 1, 7, 7, 11, 12, 15, 1, 1, 1] ; highlighed colors for the edges
uword[] blocks = [&blockI, &blockJ, &blockL, &blockO, &blockS, &blockT, &blockZ]
sub newCurrentBlock(ubyte block) {
@@ -545,9 +558,9 @@ blocklogic {
}
sub isGameOver(ubyte xpos, ubyte ypos) -> ubyte {
main.drawBlock(xpos, ypos, 32)
main.drawBlock(xpos, ypos, true)
ubyte result = ypos==main.startYpos and not noCollision(xpos, ypos+1)
main.drawBlock(xpos, ypos, 160)
main.drawBlock(xpos, ypos, false)
return result
}
@@ -566,7 +579,7 @@ blocklogic {
for x in main.boardOffsetX+main.boardWidth-1 downto main.boardOffsetX {
ubyte char = txt.getchr(x, ypos-1)
ubyte color = txt.getclr(x, ypos-1)
txt.setcc(x, ypos, char, color)
txt.setcc2(x, ypos, char, color)
}
ypos--
}
@@ -575,68 +588,73 @@ blocklogic {
sound {
; TODO stubs for now
sub init() {
; c64.MVOL = 15
cx16.vpoke(1, $f9c2, %00111111) ; volume max, no channels
}
sub blockrotate() {
; soft click
; c64.MVOL = 5
; c64.AD1 = %00100010
; c64.SR1 = %00000000
; c64.FREQ1 = 15600
; c64.CR1 = %10000000
; c64.CR1 = %10000001
; soft click/"tschk" sound
uword freq = 15600
cx16.vpoke(1, $f9c0, lsb(freq))
cx16.vpoke(1, $f9c1, msb(freq))
cx16.vpoke(1, $f9c2, %11110000) ; half volume
cx16.vpoke(1, $f9c3, %11000000) ; noise waveform
sys.wait(2)
cx16.vpoke(1, $f9c2, 0) ; shut off
}
sub blockdrop() {
; swish
; c64.MVOL = 5
; c64.AD1 = %01010111
; c64.SR1 = %00000000
; c64.FREQ1 = 4600
; c64.CR1 = %10000000
; c64.CR1 = %10000001
uword freq = 4600
cx16.vpoke(1, $f9c0, lsb(freq))
cx16.vpoke(1, $f9c1, msb(freq))
cx16.vpoke(1, $f9c2, %11110000) ; half volume
cx16.vpoke(1, $f9c3, %11000000) ; noise waveform
sys.wait(6)
cx16.vpoke(1, $f9c2, 0) ; shut off
}
sub swapping() {
; beep
; c64.MVOL = 8
; c64.AD1 = %01010111
; c64.SR1 = %00000000
; c64.FREQ1 = 5500
; c64.CR1 = %00010000
; c64.CR1 = %00010001
uword freq = 1500
cx16.vpoke(1, $f9c0, lsb(freq))
cx16.vpoke(1, $f9c1, msb(freq))
cx16.vpoke(1, $f9c2, %11110000) ; half volume
cx16.vpoke(1, $f9c3, %10000000) ; triangle waveform
sys.wait(6)
cx16.vpoke(1, $f9c2, 0) ; shut off
}
sub lineclear() {
; explosion
; c64.MVOL = 15
; c64.AD1 = %01100110
; c64.SR1 = %00000000
; c64.FREQ1 = 1600
; c64.CR1 = %10000000
; c64.CR1 = %10000001
uword freq = 1400
cx16.vpoke(1, $f9c0, lsb(freq))
cx16.vpoke(1, $f9c1, msb(freq))
cx16.vpoke(1, $f9c2, %11111111) ; max volume
cx16.vpoke(1, $f9c3, %11000000) ; noise waveform
sys.wait(8)
cx16.vpoke(1, $f9c2, 0) ; shut off
}
sub lineclear_big() {
; big explosion
; c64.MVOL = 15
; c64.AD1 = %01101010
; c64.SR1 = %00000000
; c64.FREQ1 = 2600
; c64.CR1 = %10000000
; c64.CR1 = %10000001
uword freq = 2500
cx16.vpoke(1, $f9c0, lsb(freq))
cx16.vpoke(1, $f9c1, msb(freq))
cx16.vpoke(1, $f9c2, %11111111) ; max volume
cx16.vpoke(1, $f9c3, %11000000) ; noise waveform
sys.wait(30)
cx16.vpoke(1, $f9c2, 0) ; shut off
}
sub gameover() {
; buzz
; c64.MVOL = 15
; c64.FREQ2 = 600
; c64.AD2 = %00111010
; c64.SR2 = %00000000
; c64.CR2 = %00110000
; c64.CR2 = %00110001
; attempt at buzz/boing (but can't get the sawtooth/triangle combined waveform of the sid)
uword freq = 200
cx16.vpoke(1, $f9c0, lsb(freq))
cx16.vpoke(1, $f9c1, msb(freq))
cx16.vpoke(1, $f9c2, %11111111) ; max volume
cx16.vpoke(1, $f9c3, %01000000) ; sawtooth waveform
sys.wait(30)
cx16.vpoke(1, $f9c2, 0) ; shut off
}
}
Binary file not shown.
+115
View File
@@ -0,0 +1,115 @@
%target cx16
%import textio
%option no_sysinit
main {
sub start() {
; todo feedback: new routines at the end of the jump table so existing jump vectors remain unchanged
txt.lowercase()
vtui.initialize()
vtui.screen_set(2)
vtui.clr_scr('%', $50)
vtui.gotoxy(5,5)
vtui.fill_box(':', 70, 50, $c6)
vtui.gotoxy(10,10)
vtui.border(1, 40, 6, $47)
vtui.gotoxy(12,12)
vtui.print_str(@"Hello, world! vtui from Prog8!", $f2, $80)
vtui.gotoxy(12,13)
vtui.print_str("Hello, world! vtui from Prog8!", $f2, $00)
repeat {
}
}
sub logo_mover() {
ubyte xcoord
ubyte ycoord
ubyte newx
ubyte newy
vtui.initialize()
;vtui.screen_set(2)
vtui.gotoxy(30, 32)
vtui.print_str("arrow keys to move!", $61, 0)
vtui.gotoxy(0, 0)
vtui.save_rect(1, 1, $0000, 7, 7)
vtui.gotoxy(0, 0)
vtui.save_rect(1, 1, $0100, 7, 7)
char_loop:
ubyte char = c64.GETIN()
if not char
goto char_loop
when char {
$91 -> {
if newy {
newy--
move_logo()
}
}
$11 -> {
if newy<53 {
newy++
move_logo()
}
}
$9d -> {
if newx {
newx--
move_logo()
}
}
$1d -> {
if newx<70 {
newx++
move_logo()
}
}
}
goto char_loop
sub move_logo() {
vtui.gotoxy(xcoord, ycoord)
vtui.rest_rect(1, 1, $0100, 7, 7)
vtui.gotoxy(newx, newy)
vtui.save_rect(1, 1, $0100, 7, 7)
vtui.gotoxy(newx, newy)
vtui.rest_rect(1, 1, $0000, 7, 7)
xcoord = newx
ycoord = newy
}
}
}
vtui $1000 {
%asmbinary "VTUI0.6.BIN", 2 ; skip the 2 dummy load address bytes
; NOTE: base address $1000 here must be the same as the block's memory address, for obvious reasons!
romsub $1000 = initialize() clobbers(A, X, Y)
romsub $1002 = screen_set(ubyte mode @A) clobbers(A, X, Y)
romsub $1005 = set_bank(ubyte bank @Pc) clobbers(A)
romsub $1008 = set_stride(ubyte stride @A) clobbers(A)
romsub $100b = set_decr(ubyte incrdecr @Pc) clobbers(A)
romsub $100e = clr_scr(ubyte char @A, ubyte colors @X) clobbers(Y)
romsub $1011 = gotoxy(ubyte column @A, ubyte row @Y)
romsub $1014 = plot_char(ubyte char @A, ubyte colors @X)
romsub $1017 = scan_char() -> ubyte @A, ubyte @X
romsub $101a = hline(ubyte char @A, ubyte length @Y, ubyte colors @X) clobbers(A)
romsub $101d = vline(ubyte char @A, ubyte height @Y, ubyte colors @X) clobbers(A)
romsub $1020 = print_str(str string @R0, ubyte colors @X, ubyte convertchars @A) clobbers(A, Y)
romsub $1023 = fill_box(ubyte char @A, ubyte width @R1, ubyte height @R2, ubyte colors @X) clobbers(A, Y)
romsub $1026 = pet2scr(ubyte char @A) -> ubyte @A
romsub $1029 = scr2pet(ubyte char @A) -> ubyte @A
romsub $102c = border(ubyte mode @A, ubyte width @R1, ubyte height @R2, ubyte colors @X) clobbers(Y) ; NOTE: mode 6 means 'custom' characters taken from r3 - r6
romsub $102f = save_rect(ubyte ramtype @A, ubyte vbank @Pc, uword address @R0, ubyte width @R1, ubyte height @R2) clobbers(A, X, Y)
romsub $1032 = rest_rect(ubyte ramtype @A, ubyte vbank @Pc, uword address @R0, ubyte width @R1, ubyte height @R2) clobbers(A, X, Y)
}
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff. Show more