Wolin - a minimal Kotlin-like language compiler for 65xx
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
If I were running this on linux, I might use strace to check that the expected files are being read. I might remove the *.o file and see if there was any different complaint. I might use strings to see what was in the *.o file. I'm afraid I have no special knowledge of cc65 though.
- BitWise
- In Memoriam
- Posts: 996
- Joined: 02 Mar 2004
- Location: Berkshire, UK
- Contact:
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Are you generating a '.import' for the external function before you call it?
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Is it an assembler directive? Nope... Let me look that up...
Ha! That was it!
Ha! That was it!
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Small update - reworked optimizer to use real data flow trees. Up until now, the optimization was rather... erm... guerilla-style... Now I will be able to copy pointer received in function arguments to ZP stack and keep them there thanks to smarter logic in eliminating tree nodes.
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
And here's a neat chart I was able to generate for print function with the new optimizer.
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
With improved flow diagram now the optimizer is able to find long chains of optimizable registers (compare with attached diagram):
Each line means: "each occurence of the penultimate element should be replaced with the first element". Those "chains" also handle reference/dereference gracefully (i.e. keeping them or cancelling them out like in case of *char -> reg10 -> ®9). That also allows setting redundancy flags:
Note - the chains containing "char" are, of course, wrong, as the char variable should be mutable, but this is only for testing. If I change val to var, the chains won't be as nice.
I guess now I am ready to go with refactoring the old code applying above chains/flow graphs.
And this is the source of the diagram:
And this is the optimizer output when "char" is mutable, as it should be:
Code: Select all
#0 -> __wolin_reg2 -> pl.qus.wolin.print..i
#0 -> __wolin_reg11 -> __wolin_reg9
__wolin_reg5 -> __wolin_reg4 -> pl.qus.wolin.print..char -> __wolin_reg10 -> __wolin_reg9
__wolin_reg5 -> __wolin_reg4 -> pl.qus.wolin.print..char -> __wolin_reg14
__wolin_reg5 -> __wolin_reg4 -> pl.qus.wolin.print..char -> __wolin_reg17
__wolin_reg5 -> __wolin_reg4 -> pl.qus.wolin.print..char -> __wolin_reg14
pl.qus.wolin.print.what -> __wolin_reg6 -> __wolin_reg5
pl.qus.wolin.print.what -> __wolin_reg20 -> __wolin_reg19
#__wolin_lab_stringConst_0 -> __wolin_reg24 -> pl.qus.wolin.print.what
Code: Select all
Redundant:__wolin_reg2
Redundant:__wolin_reg4
Redundant:__wolin_reg6
Redundant:pl.qus.wolin.print..char
Redundant:__wolin_reg10
Redundant:__wolin_reg11
Redundant:__wolin_reg20
Redundant:__wolin_reg24I guess now I am ready to go with refactoring the old code applying above chains/flow graphs.
And this is the source of the diagram:
Code: Select all
package pl.qus.wolin
fun chrout^0xFFD2(char: ubyte^CPU.A)
fun plot^0xFFF0(x: ubyte^CPU.X, y: ubyte^CPU.Y)
var carry: bool^CPU.C
fun print(what: string) {
val i = 0
val char: ubyte = what[i] // BLĄD!!! inference myśli, ze char jest ubyte*
while (char != 0) {
chrout(char)
i++
char = what[i]
}
}
fun main() {
print("dupa")
}
Code: Select all
#0 -> __wolin_reg2 -> pl.qus.wolin.print..i
#0 -> __wolin_reg11 -> __wolin_reg9
__wolin_reg5 -> __wolin_reg4 -> pl.qus.wolin.print..char
pl.qus.wolin.print.what -> __wolin_reg6 -> __wolin_reg5
pl.qus.wolin.print.what -> __wolin_reg20 -> __wolin_reg19
pl.qus.wolin.print..char -> __wolin_reg10 -> __wolin_reg9
#__wolin_lab_stringConst_0 -> __wolin_reg24 -> pl.qus.wolin.print.what
Redundant:__wolin_reg2
Redundant:__wolin_reg4
Redundant:__wolin_reg6
Redundant:__wolin_reg10
Redundant:__wolin_reg11
Redundant:__wolin_reg20
Redundant:__wolin_reg24
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Heh, after playing with long register chains for a few days I discovered that nothing beats my "naive" replacement by pairs. But at least I've learned some logic in reference flow... Anyway - here you can see source and optimized diagram of the very same print function.
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
A small step ahead - I was able to mark registers used to dereference a pointer unoptimizable. Look at below code:
Although we could easily live without __wolin_reg18, the optimizer keeps it, as it is used to dereference a string (what: ubyte*) by index (i: ubyte), here:
Since __wolin_reg18 is allocated on ZP, getting a character off it will be wonderfully easy, as:
Code: Select all
setup HEADER
label __wolin_pl_qus_wolin_plot = 65520
label __wolin_pl_qus_wolin_chrout = 65490
setup SPF = 251[ubyte] , 40959[uword]
setup SP = 114[ubyte]
setup HEAP = 176[ubyte]
goto __wolin_pl_qus_wolin_main[uword]
function __wolin_pl_qus_wolin_print
let SP(0)<__wolin_reg2>[ubyte] = #0[ubyte]
let SPF(1)<pl.qus.wolin.print..i>[ubyte] = #0
add SPF(0)<pl.qus.wolin.print..char>[ubyte] = SPF(2)<pl.qus.wolin.print.what>[ubyte*] , SPF(1)<pl.qus.wolin.print..i>[ubyte]
alloc SP<__wolin_reg9> , #1
_scope_ loop , 1
label __wolin_lab_loop_start_1
evalneq SP(2)<__wolin_reg9>[bool] = pl.qus.wolin.print..char , #0
bne SP(0)<__wolin_reg9>[bool] = #1[bool] , __wolin_lab_loop_end_1<label_po_if>[uword]
save SP
save SPF(0)<pl.qus.wolin.print..char>[ubyte]
restore CPU.A[ubyte]
call __wolin_pl_qus_wolin_chrout[uword]
restore SP
add SPF(1)<pl.qus.wolin.print..i>[ubyte] = SPF(1)<pl.qus.wolin.print..i>[ubyte] , #1[ubyte]
alloc SP<__wolin_reg18> , #2
add SP(2)<__wolin_reg18>[ubyte*] = SPF(2)<pl.qus.wolin.print.what>[ubyte*] , SPF(1)<pl.qus.wolin.print..i>[ubyte]
let SPF(0)<pl.qus.wolin.print..char>[ubyte] = &SP(0)<__wolin_reg18>[ubyte*]
free SP<__wolin_reg18> , #2
goto __wolin_lab_loop_start_1[uword]
_endscope_ loop , 1
label __wolin_lab_loop_end_1
free SP<__wolin_reg9> , #1
free SPF<__wolin_pl_qus_wolin_print> , #4
endfunction
function __wolin_pl_qus_wolin_main
alloc SPF<__wolin_pl_qus_wolin_print> , #4
let SPF(2)<pl.qus.wolin.print.what>[ubyte*] = #__wolin_lab_stringConst_0[uword]
call __wolin_pl_qus_wolin_print[uword]
endfunction
string __wolin_lab_stringConst_0[uword] = $"dupa"
Code: Select all
add SP(2)<__wolin_reg18>[ubyte*] = SPF(2)<pl.qus.wolin.print.what>[ubyte*] , SPF(1)<pl.qus.wolin.print..i>[ubyte]
Code: Select all
; 24: let SPF(0)<pl.qus.wolin.print..char>[ubyte] = &SP(0)<__wolin_reg18>[ubyte*]
lda (0,x)
ldy #0
sta (__wolin_spf),y
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Anyone fluent in cc65? I was thinking about interfacing with its libs, since I'm using cl65 and ca65 anyway. So as we know the call convention is:
i = baz(int i, char c)
will result in:
While:
Obviously compiled libs will reference cc64's sp. How do they do it? Is it marked as some kind of external symbol? If yes - will it pick __wolin_spf if I alias it to sp and export somehow?
Meanwhile - calling cc64 library functions that don't use stack works as a charm:
compiles to:
__fastcall__ functions also aren't a problem:
is declared as:
and called as:
And it ever runs on a C64! So Wolin just received a lot of new libraries...
i = baz(int i, char c)
will result in:
Code: Select all
lda _i
ldx _i+1
jsr pushax
lda _c
jst pusha
jsr _baz
sta _i
stx _i+1
Code: Select all
.proc pushax
pha ; (3) zapamietajmy A
zwiększmy stos
lda sp ; (6)
sec ; (8)
sbc #2 ; (10)
sta sp ; (13)
bcs @L1 ; (17)
dec sp+1 ; (+5)
X zapisujemy w SP+1
@L1: ldy #1 ; (19)
txa ; (21)
sta (sp),y ; (27)
A zapisujemy w SP
pla ; (31)
dey ; (33)
sta (sp),y ; (38)
rts ; (44)
.endprocMeanwhile - calling cc64 library functions that don't use stack works as a charm:
Code: Select all
package pl.qus.wolin
cc65 fun clrscr()
cc65 fun wherex(): ubyte
fun main() {
val x: ubyte
clrscr()
x = wherex()
}
Code: Select all
setup HEADER
setup SPF = 251[ubyte] , 40959[uword]
setup SP = 114[ubyte]
setup HEAP = 176[ubyte]
goto __wolin_pl_qus_wolin_main[uword]
import _clrscr
import _wherex
function __wolin_pl_qus_wolin_main
save SP // as called function could trash Wolin SP (6502 X reg)
call _clrscr[uword]
restore SP
alloc SPF<_wherex> , #1
save SP
call _wherex[uword]
let SPF(0)<pl.qus.wolin.wherex.__returnValue>[ubyte] = CPU.A // cc65 return values in A or AX, we have to make them behave Wolin way - return on stack
restore SP
let SPF(1)<pl.qus.wolin.main..x>[ubyte] = SPF(0)<pl.qus.wolin.wherex.__returnValue>[ubyte]
free SPF<pl.qus.wolin.wherex.__returnValue> , #1
free SPF<__wolin_pl_qus_wolin_main> , #1
endfunction
Code: Select all
unsigned char __fastcall__ bordercolor (unsigned char color);Code: Select all
cc65 fun bordercolor(col: ubyte^CPU.A): ubyteCode: Select all
alloc SPF<_bordercolor> , #1
save SP
let CPU.A[ubyte] = #0[ubyte]
save CPU.A
restore CPU.A
call _bordercolor[uword]
let SPF(0)<pl.qus.wolin.bordercolor.__returnValue>[ubyte] = CPU.A
restore SP
And it ever runs on a C64! So Wolin just received a lot of new libraries...
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
OK, so it's actually not that simple - mixing Wolin code with C code that uses some special C ZP locations will require a dedicated linker config, special Wolin startup code, and losing some Wolin stack space for C registers, but it's absolutely doable. Maybe later. Meanwhile you can get the length of a Wolin string with cc65 strlen function without any additional magic 
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Some compiler fun. I've compiled the same code with Wolin, cc65 and vbcc:
Wolin:
C (generated by Wolin C frontend
)
And the results:
Wolin:
vbcc:
cc65:
I wonder if I can make my bool operations any smarter and use V/C/N flags somehow (and obviously win brevity competition)... But I do like the way it works now - by storing the final bool result of an operation in SP reg. MAYBE instead of storing it at SP,x I just pretend I "stored" it at 6502 V/C/N?
Wolin:
Code: Select all
do {
border++
} while ( border < 255)
Code: Select all
do {
(*(unsigned char *)(53280))++;
} while ( (*(unsigned char *)(53280)) < 255);
Wolin:
Code: Select all
__wolin_lab_loop_start_1:
inc 53280
lda #1 ; mniejsze
sta 2,x
lda 53280
cmp #255
bcc :+
lda #0 ; jednak wieksze
sta 2,x
:
lda 0,x
bne __wolin_lab_loop_start_1
__wolin_lab_loop_end_1:
Code: Select all
l14:
lda 53280
clc
adc #1
sta 53280
l16:
lda 53280
cmp #255
bcc l14
Code: Select all
L0015: ldx #$00
lda $D020
inc $D020
ldx #$00
lda $D020
cmp #$FF
jsr boolult
jne L0015
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Returning a boolean in C is common enough - I'd think N and Z are affected by too many operations to be readily preserved, but strictly across a call boundary they'd be fine. V might be useful, if it just so happens to fit the case, in being easily set up.
So, yes, regarding C as an additional, single-bit, register, could well work out.
So, yes, regarding C as an additional, single-bit, register, could well work out.
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
I'm thinking about something like this:
Instead of:
That:
Instead of:
Code: Select all
; 14: evalless SP(2)<__wolin_reg2>[bool] = 53280[ubyte] , #255[ubyte]
lda #1 ; mniejsze
sta 2,x
lda 53280
cmp #255
bcc :+
lda #0 ; jednak wieksze
sta 2,x
:
; 15: beq SP(0)<__wolin_reg2>[bool] = #1[bool] , __wolin_lab_loop_start_1<label_po_if>[uword]
lda 0,x
bne __wolin_lab_loop_start_1
Code: Select all
; 14: evalless CPU.C[bool] = 53280[ubyte] , #255[ubyte]
lda 53280
cmp #255
; 15: beq CPU.C[bool] = #0[bool] , __wolin_lab_loop_start_1<label_po_if>[uword]
bcc __wolin_lab_loop_start_1
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
OK, what I came up with is "storing" equality evaluation results in CPU regs. So when doing simple comparisons, instead of allocating new SP reg and using it as comparison destination, I'll set proper CPU flag as current Woling reg. I.e.
Insteade of this:
I'll do this:
then the branching opcode will get CPU.Z as current register and this code will be generated:
which is translated to:
Now, since checking if unsigned A < unsigned B gives true as CPU.C == 0, I'm creating fake CPU aniti-registers that are true, when they're 0, so:
And this "negative C" will be passed to branching instruction:
meaning: "Branch IF Negative C reg is True" - which is obviously BCC.
Of course this approach will require much more gymnastics for more complicated chain of boolean operations, with copying the CPU.* to proper Wolon SP reg for further processing.
And done:
So - whose's SMALLER now, boys?
Insteade of this:
Code: Select all
alloc SP, #1 // new reg
evaleq SP(0) = val1, val2
Code: Select all
evaleq CPU.Z = val1, val2Code: Select all
bift CPU.Z[bool], ?destCode: Select all
beq {dest}Code: Select all
evalless CPU.NC[bool] = ?s1[ubyte], ?s2[ubyte] -> """
lda {s1}
cmp {s2}"""Code: Select all
bift CPU.NC[bool], ?dest -> """
bcc {dest}"""
Of course this approach will require much more gymnastics for more complicated chain of boolean operations, with copying the CPU.* to proper Wolon SP reg for further processing.
And done:
Code: Select all
__wolin_lab_loop_start_1:
; 12: add 53280[ubyte] = 53280[ubyte] , #1[ubyte]
inc 53280
; 13: evalless CPU.NC[bool] = 53280[ubyte] , #255[ubyte]
lda 53280
cmp #255
; 14: bift CPU.NC[bool] , __wolin_lab_loop_start_1<while_start>[uword]
bcc __wolin_lab_loop_start_1
Last edited by qus on Mon Jun 29, 2020 10:34 am, edited 1 time in total.
Re: Wolin - a minimal Kotlin-like language compiler for 65xx
Is this going to be a massive win? It sounds like it should be!