Wolin - a minimal Kotlin-like language compiler for 65xx

Programming the 6502 microprocessor and its relatives in assembly and other languages.
Post Reply
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

And here's

Code: Select all

var b  = 2
var c: uword
var d: uword

fun main() {
    b = when(d) {
        0 -> 6
        1 -> 7
        else -> 9
    }
}
compiled:

Code: Select all

__wolin_pl_qus_wolin_main:

; allocSP<__wolin_reg2>,#1

    dex

; allocSP<__wolin_reg3>,#1

    dex

; allocSP<__wolin_reg4>,#2


    dex
    dex

; letSP(0)<__wolin_reg4>[uword]=__wolin_pl_qus_wolin_d<pl.qus.wolin.d>[uword]


    lda #<__wolin_pl_qus_wolin_d
    sta 0,x
    lda #>__wolin_pl_qus_wolin_d
    sta 0+1,x

; allocSP<__wolin_reg5>,#1

    dex

; allocSP<__wolin_reg6>,#1

    dex

; label__wolin_lab_whenLabel_0

__wolin_lab_whenLabel_0:

; letSP(0)<__wolin_reg6>[ubyte]=#0[ubyte]


    lda #0
    sta 0,x

; evaleqSP(1)<__wolin_reg5>[bool]=SP(2)<__wolin_reg4>[uword],SP(0)<__wolin_reg6>[ubyte]


    lda #0 // rozne
    sta 1,x
    lda 2+1,x
    bne +
    lda 2,x
    cmp 0,x
    bne +
    lda #1 // rowne
    sta 1,x
:

; bneSP(1)<__wolin_reg5>[bool]=#1[bool],__wolin_lab_whenLabel_1[adr]


    lda 1,x
    cmp #1
    bne __wolin_lab_whenLabel_1

; letSP(0)<__wolin_reg6>[ubyte]=#6[ubyte]


    lda #6
    sta 0,x

; goto__wolin_lab_whenEndLabel_0[adr]

    jmp __wolin_lab_whenEndLabel_0

; label__wolin_lab_whenLabel_1

__wolin_lab_whenLabel_1:

; letSP(0)<__wolin_reg6>[ubyte]=#1[ubyte]


    lda #1
    sta 0,x

; evaleqSP(1)<__wolin_reg5>[bool]=SP(2)<__wolin_reg4>[uword],SP(0)<__wolin_reg6>[ubyte]


    lda #0 // rozne
    sta 1,x
    lda 2+1,x
    bne +
    lda 2,x
    cmp 0,x
    bne +
    lda #1 // rowne
    sta 1,x
:

; bneSP(1)<__wolin_reg5>[bool]=#1[bool],__wolin_lab_whenLabel_2[adr]


    lda 1,x
    cmp #1
    bne __wolin_lab_whenLabel_2

; letSP(0)<__wolin_reg6>[ubyte]=#7[ubyte]


    lda #7
    sta 0,x

; goto__wolin_lab_whenEndLabel_0[adr]

    jmp __wolin_lab_whenEndLabel_0

; label__wolin_lab_whenLabel_2

__wolin_lab_whenLabel_2:

; letSP(0)<__wolin_reg6>[ubyte]=#9[ubyte]


    lda #9
    sta 0,x

; label__wolin_lab_whenEndLabel_0

__wolin_lab_whenEndLabel_0:

; letSP(4)<__wolin_reg3>[ubyte]=SP(0)<__wolin_reg6>[ubyte]


    lda 0,x
    sta 4,x

; freeSP<__wolin_reg6>,#1

    inx

; freeSP<__wolin_reg5>,#1

    inx

; freeSP<__wolin_reg4>,#2


    inx
    inx

; let__wolin_pl_qus_wolin_b<pl.qus.wolin.b>[ubyte]=SP(0)<__wolin_reg3>[ubyte]


    lda 0,x
    sta __wolin_pl_qus_wolin_b


; freeSP<__wolin_reg3>,#1

    inx

; freeSP<__wolin_reg2>,#1

    inx

; ret

    rts
Anyone care to check? ;)

EDIT:

I guess

Code: Select all

    lda 1,x
    cmp #1
    bne __wolin_lab_whenLabel_2
could be:

Code: Select all

    lda 1,x
    bne __wolin_lab_whenLabel_2
right?
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by barrym95838 »

Those don't look equivalent to me ... in fact, almost opposite. Was the second BNE supposed to be a BEQ?
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

Indeed!

And now a complete Wolin program straight from the compiler!

This thing:

Code: Select all

package pl.qus.wolin

var border: ubyte^53280
var d: uword = 0

fun main() {
    border = if(d == 0) 6
    else 9
}
compiles to:

Code: Select all

; setupHEADER


;**********************************************
;*
;* BASIC header
;*
;* compile with:
;* cl65.exe -o assembler.prg -t c64 -C c64-asm.cfg assembler.s
;*
;**********************************************
            .org 2049
            .export LOADADDR = *
Bas10:      .word BasEnd 
            .word 10   
            .byte 158 ; sys 
            .byte " 2064" 
            .byte 0 
BasEnd:     .word 0 
            .word 0
            ;


; setupSPF=251[ubyte],40959[uword]


; prepare function stack
__wolin_spf = 251 ; function stack ptr
__wolin_spf_top = 40959 ; function stack top
    lda #<__wolin_spf_top ; set function stack top
    sta __wolin_spf
    lda #>__wolin_spf_top
    sta __wolin_spf+1

; setupSP=143[ubyte]


; prepare program stack
__wolin_sp_top = 143 ; program stack top
    ldx #__wolin_sp_top ; set program stack top

; allocSP<__wolin_reg0>,#1

    dex

; letSP(0)<__wolin_reg0>[ubyte]=#0[ubyte]


    lda #0
    sta 0,x

; let__wolin_pl_qus_wolin_d<pl.qus.wolin.d>[uword]=SP(0)<__wolin_reg0>[ubyte]


    lda 0,x
    sta __wolin_pl_qus_wolin_d
    lda #0
    sta __wolin_pl_qus_wolin_d+1


; freeSP<__wolin_reg0>,#1

    inx

; label__wolin_pl_qus_wolin_main

__wolin_pl_qus_wolin_main:

; allocSP<__wolin_reg2>,#1

    dex

; allocSP<__wolin_reg3>,#1

    dex

; allocSP<__wolin_reg4>,#1

    dex

; allocSP<__wolin_reg5>,#2


    dex
    dex

; letSP(0)<__wolin_reg5>[uword]=__wolin_pl_qus_wolin_d<pl.qus.wolin.d>[uword]


    lda #<__wolin_pl_qus_wolin_d
    sta 0,x
    lda #>__wolin_pl_qus_wolin_d
    sta 0+1,x

; allocSP<__wolin_reg6>,#1

    dex

; letSP(0)<__wolin_reg6>[ubyte]=#0[ubyte]


    lda #0
    sta 0,x

; evaleqSP(3)<__wolin_reg4>[bool]=SP(1)<__wolin_reg5>[uword],SP(0)<__wolin_reg6>[ubyte]


    lda #0 ; rozne
    sta 3,x
    lda 1+1,x
    bne :+
    lda 1,x
    cmp 0,x
    bne :+
    lda #1 ; rowne
    sta 3,x
:

; freeSP<__wolin_reg6>,#1

    inx

; freeSP<__wolin_reg5>,#2


    inx
    inx

; allocSP<__wolin_reg7>,#1

    dex

; bneSP(1)<__wolin_reg4>[bool]=#1[bool],__wolin_lab_afterIfExpression_0<label_DO_else>[adr]


    lda 1,x
    beq __wolin_lab_afterIfExpression_0

; letSP(0)<__wolin_reg7>[ubyte]=#6[ubyte]


    lda #6
    sta 0,x

; goto__wolin_lab_afterWholeIf_0[adr]

    jmp __wolin_lab_afterWholeIf_0

; label__wolin_lab_afterIfExpression_0

__wolin_lab_afterIfExpression_0:

; letSP(0)<__wolin_reg7>[ubyte]=#9[ubyte]


    lda #9
    sta 0,x

; label__wolin_lab_afterWholeIf_0

__wolin_lab_afterWholeIf_0:

; letSP(2)<__wolin_reg3>[ubyte]=SP(0)<__wolin_reg7>[ubyte]


    lda 0,x
    sta 2,x

; freeSP<__wolin_reg7>,#1

    inx

; freeSP<__wolin_reg4>,#1

    inx

; let53280[ubyte]=SP(0)<__wolin_reg3>[ubyte]


    lda 0,x
    sta 53280


; freeSP<__wolin_reg3>,#1

    inx

; freeSP<__wolin_reg2>,#1

    inx

; ret

    rts

; label__wolin_indirect_jsr

__wolin_indirect_jsr:

; goto65535[adr]

    jmp 65535

; label__wolin_pl_qus_wolin_d

__wolin_pl_qus_wolin_d:

; alloc0[uword]

    .word 0

qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

As an update - I've fixed function-local variables. Until now compiler treated them like they were package-global variables and allocated them statically. After the fix function-local variables get allocated on function call stack, as they should be, so recurrent functions are now possible. So this:

Code: Select all

fun localTest(a: ubyte, b: ubyte): ubyte {
    val suma: ubyte

    suma = a + b

    return suma
}
becomes this pseudo asm:

Code: Select all

// ****************************************
// funkcja: fun pl.qus.wolin.localTest(pl.qus.wolin.localTest.a: ubyte = 0 (), pl.qus.wolin.localTest.b: ubyte = 0 ()):ubyte
// ****************************************
label __wolin_pl_qus_wolin_localTest
alloc SP<__wolin_reg4>, #1 // For assignment left side
alloc SP<__wolin_reg5>, #1 // for value that gets assigned to left side
let SP(0)<__wolin_reg5>[ubyte] = SPF(3)<pl.qus.wolin.localTest.a>[ubyte] // simple id from var
alloc SP<__wolin_reg6>, #1 // RIGHT adding operator
let SP(0)<__wolin_reg6>[ubyte] = SPF(2)<pl.qus.wolin.localTest.b>[ubyte] // simple id from var
add SP(1)<__wolin_reg5>[ubyte] = SP(1)<__wolin_reg5>[ubyte], SP(0)<__wolin_reg6>[ubyte]
free SP<__wolin_reg6>, #1 // RIGHT adding operator
let SPF(1)<pl.qus.wolin.localTest..suma>[ubyte] = SP(0)<__wolin_reg5>[ubyte] // przez sprawdzacz typow - process assignment
free SP<__wolin_reg5>, #1 // for value that gets assigned to left side, type = ubyte
free SP<__wolin_reg4>, #1 // For assignment left side
alloc SP<__wolin_reg7>, #1 // for expression
let SP(0)<__wolin_reg7>[ubyte] = SPF(1)<pl.qus.wolin.localTest..suma>[ubyte] // simple id from var
let SPF(6)<returnValue>[ubyte] = SP(0)<__wolin_reg7>[ubyte] // przez sprawdzacz typow - jump expression
free SP<__wolin_reg7>, #1 // for expression
free SPF, #6 // free fn arguments and locals for pl.qus.wolin.localTest
ret
and gets translated to:

Code: Select all

; label__wolin_pl_qus_wolin_localTest

__wolin_pl_qus_wolin_localTest:

; allocSP<__wolin_reg4>,#1

    dex

; allocSP<__wolin_reg5>,#1

    dex

; letSP(0)<__wolin_reg5>[ubyte]=SPF(3)<pl.qus.wolin.localTest.a>[ubyte]


    ldy #3
    lda (__wolin_spf),y
    sta 0,x


; allocSP<__wolin_reg6>,#1

    dex

; letSP(0)<__wolin_reg6>[ubyte]=SPF(2)<pl.qus.wolin.localTest.b>[ubyte]


    ldy #2
    lda (__wolin_spf),y
    sta 0,x


; addSP(1)<__wolin_reg5>[ubyte]=SP(1)<__wolin_reg5>[ubyte],SP(0)<__wolin_reg6>[ubyte]


    clc
    lda 1,x
    adc 0,x
    sta 1,x

; freeSP<__wolin_reg6>,#1

    inx

; letSPF(1)<pl.qus.wolin.localTest..suma>[ubyte]=SP(0)<__wolin_reg5>[ubyte]


    lda 0,x
    ldy #1
    sta (__wolin_spf),y

; freeSP<__wolin_reg5>,#1

    inx

; freeSP<__wolin_reg4>,#1

    inx

; allocSP<__wolin_reg7>,#1

    dex

; letSP(0)<__wolin_reg7>[ubyte]=SPF(1)<pl.qus.wolin.localTest..suma>[ubyte]


    ldy #1
    lda (__wolin_spf),y
    sta 0,x


; letSPF(6)<returnValue>[ubyte]=SP(0)<__wolin_reg7>[ubyte]


    lda 0,x
    ldy #6
    sta (__wolin_spf),y

; freeSP<__wolin_reg7>,#1

    inx

; freeSPF,#6


    clc
    lda __wolin_spf
    adc #6
    sta __wolin_spf
    lda __wolin_spf+1
    adc #0
    sta __wolin_spf+1

; ret

    rts
Of course implementing tail recurrency comes next ;)
Chromatix
Posts: 1462
Joined: 21 May 2018

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by Chromatix »

Okay, let me point out a few obvious potential optimisations:

Code: Select all

INX : INX : DEX -> INX ; lazy pointer management
LDA m : ADC #0 : STA m -> BCC + : INC m ; idiomatic for 16b += 8b operation
LDY #3 : … : LDY #2 : … : LDY #1 -> LDY #3 : … : DEY : … : DEY    ; same speed, fewer bytes if you know Y isn't changed meanwhile
STA 0,X : LDA 1,X : ADC 0,X -> ADC 1,X ; addition is commutative, lazy data movement
STA 0,X : LDA 0,X -> NOP ; lazy data movement
LDA (m),Y : STA 0,X : DEY : LDA (m),Y : ADC 0,X -> LDA (m),Y : DEY : ADC (m),Y ; most ALU ops get access to the ZP-indirect modes too!
User avatar
barrym95838
Posts: 2056
Joined: 30 Jun 2013
Location: Sacramento, CA, USA

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by barrym95838 »

It's so easy for a human to sense the optimization potential. It's NOT so easy for a human to encode that sensibility into a compiler. ;-)
Got a kilobyte lying fallow in your 65xx's memory map? Sprinkle some VTL02C on it and see how it grows on you!

Mike B. (about me) (learning how to github)
Chromatix
Posts: 1462
Joined: 21 May 2018

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by Chromatix »

Sure, but these are common patterns that show up frequently in Wolin's output, and should mostly be easy to recognise at code generation time. By pointing them out, it becomes possible for the author to think about ways to incorporate them.

The two most obviously beneficial techniques I think would be lazy pointer management (coalescing runs of X increments and/or decrements) and lazy data movement (eliding unnecessary loads and stores, combining loads with ALU ops), which could potentially be peephole optimisations, or could be incorporated into the repertoire of idiomatic code segments.
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

Well, yes. Some of the optimizations can be done in pseudo asm, some can't (that's why I was asking about "optimizing assembler" some posts ago).

You might think that there's too many registers created, but that is due to all the magic things Kotlin can do, like everything is an expression, i.e.:

Code: Select all

val divisionResult = try { a / b } catch (ex: DivisionByZero) { 666 }
and this magic comes from carefuly designed gramar. Unfortunately to make this gramar work I neeed to shuffle temporary registers a lot. Of course it doesn't mean they can't be optimized out eventually. But I digress.

Let me show you pseudo asm optimizations:
Chromatix wrote:
INX : INX : DEX -> INX ; lazy pointer management
This will be done on free/alloc level. Usualy a register(s) get(s) freed and new register(s) get(s) immediately allocated, i.e.:

Code: Select all

free SP<>,#4  
alloc SP<>,#2
4 - 2 = 2 so this can be simplified to:

Code: Select all

free SP<>,#2
or:

Code: Select all

alloc SP,#1
alloc SP,#1
equals

Code: Select all

alloc SP,#2
obviously.
Chromatix wrote:
LDA m : ADC #0 : STA m -> BCC + : INC m ; idiomatic for 16b += 8b operation
Would that be what you mean?

Code: Select all

add ?dest[word] = ?src[word], #?val[byte] -> """
    clc
    lda {src}
    adc #{val}
    sta {dest}
    bcc +:
    inc {a}+1
:
"""
Chromatix wrote:
LDA (m),Y : STA 0,X : DEY : LDA (m),Y : ADC 0,X -> LDA (m),Y : DEY : ADC (m),Y ; most ALU ops get access to the ZP-indirect modes too!
I know, and it will get optimized easily, but during compile time due to how Kotlin grammar / temp register work, it needs to first copy functions stack register to temp register.

Now points below can't be really done in pseudo asm for various reasons (most of the time - I would need to keep track of what's happening in registers, some flow graphs perhaps)
Chromatix wrote:
STA 0,X : LDA 1,X : ADC 0,X -> ADC 1,X ; addition is commutative, lazy data movement
STA is probably a part of "put variable on operation stack" op, created by register flow logic:

Code: Select all

let op_stack_reg = some_variable
while following LDA/ADC are probably part of add template:

Code: Select all

add SP(?d)[ubyte] = SP(?s1)[ubyte], SP(?s2)[ubyte] -> """
    clc
    lda {s1},x
    adc {s2},x
    sta {d},x"""
Chromatix wrote:
LDY #3 : … : LDY #2 : … : LDY #1 -> LDY #3 : … : DEY : … : DEY ; same speed, fewer bytes if you know Y isn't changed meanwhile
While it is neat it might be not as useful as you may think. Such DEY/INY would occur only if 1) function arguments would be one byte 2) the programmer would access them in the very same order that they're placed on the stack... Not very likely probably.
Chromatix wrote:
STA 0,X : LDA 0,X -> NOP ; lazy data movement
Ah, yes. As these are again tail and head of two templates, there are times I can't find the logic to optimize them in pseudo-asm and "optimizing 6510 asm" would be handy.
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

Meanwhile, after array code was broken by some other changes I fixed it again, and the code below does what it is supposed to do (if you know C64, you know what I mean...):

Code: Select all

package pl.qus.wolin

var ekran: ubyte[]^1024

fun main() {

    ekran[0] = 65
    ekran[1] = 66
    ekran[998] = 66
    ekran[999] = 65
}
and then this:

Code: Select all

package pl.qus.wolin

var border: ubyte^53280
var ekran: ubyte[]^1024

var i: uword

fun main() {
    i = 0

    while(i<1000) {
        ekran[i++] = 65
    }
}
so - Wolin is beginning to be actualy usable.

But I had to correct some silly errors in template code, not even sure why I made them...

I would really appreciate if any of you, 6502 wizards took a look at this:

https://github.com/ssuukk/wolin/blob/ma ... mplate.asm
Chromatix
Posts: 1462
Joined: 21 May 2018

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by Chromatix »

Looking through your templates, it seems your "alloc/free SPF" templates are responsible for the ADC #0 instance I noticed. These should be replaced with a branch over DEC/INC.

I think what you refer to as "flow graphs" is what I mean by "lazy data movement". Once implemented, the ALU templates would no longer include LDA/STA, but the flow analysis would insert them only as strictly required to get the data where it's needed. As part of that, you will need to somehow divide your templates into categories which a: deliberately operate on the accumulator; b: clobber the accumulator as an incidental part of doing something else; c: do neither. This will determine where your flow analysis can and must operate.
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

OK, thanks. alloc SPF fixed.

The second thing you mention would be quite easy - I think all pseudo asm instructions (except allocs) trash A.
Chromatix
Posts: 1462
Joined: 21 May 2018

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by Chromatix »

I draw a deliberate distinction between "clobber A" and "operate on A". The latter expects A to be loaded with one of the operands, to be passed the location of the other, and will normally leave a useful result in A to be saved and/or used subsequently. The former would be something like the following which I found in your templates, which does none of those things, only makes use of the A register and leaves it in a non-useful state:

Code: Select all

alloc SP, #?count -> """
    txa
    sec
    sbc #{count}
    tax"""
So you have to distinguish this case from the ones which allocate only small amounts.

After that, you can take advantage of commutativity. Nearly all of the 6502's ALU operations are commutative, ie. it doesn't matter which operand is in A and which is in RAM. So if you have one of them already in A, you shouldn't save it to RAM only to load the other. The exception is SBC, and even then you can avoid the save-load cycle by substituting EOR #$FF : ADC.
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

Yep, I know what you meant. But I am not sure how to implement it with current atchitecture...

Going back to objects. Object's constructor needs to:

1) obtain memory location to allocate new instance of the class it's making
2) initialize object fields to proper values.

Here's how I've done it in Wolin:

Code: Select all

class Test {
    var x: ubyte = 10
    var y: ubyte = 20
}
Constructor pseudo asm:

Code: Select all

label __wolin_pl_qus_wolin_Test

alloc SP<__wolin_reg4>, #2 // for returning this
alloc SPF, #6 // 2 uwords + return value (= start addr of new instance)
let SPF(2)[uword] = #3[uword] // this object occupies 3 bytes (x: ubyte, y: ubyte, class ID: ubyte)
let SPF(0)[uword] = #1[uword] // dummy parameter for alloc, maybe it will do something in the future
call __wolin_pl_qus_wolin_allocMem[adr]
let SP(0)<__wolin_reg4>[ptr] = SPF(0)<returnValue>[ptr]
free SPF <Any>, #2 // free return value of pl.qus.wolin.allocMem from call stack
let SPF(0)<pl.qus.wolin.Test.returnValue>[ptr] = SP(0)<__wolin_reg4>[ptr] // set constructor return value to allocated space start
setup HEAP = SP(0)<__wolin_reg4>[ptr] // setup ZP heap pointer for initializing object fields
free SP<__wolin_reg4>, #2 // for returning this
free SPF, #2 // free fn arguments and locals for pl.qus.wolin.Test - in the future move to the end, as we can have constructor params used in init
alloc SP<__wolin_reg5>, #1 // for var init expression
let SP(0)<__wolin_reg5>[ubyte] = #10[ubyte] // atomic ex
let HEAP(2)<pl.qus.wolin.Test.x>[ubyte] = SP(0)<__wolin_reg5>[ubyte] // set x on heap to 10
free SP<__wolin_reg5>, #1 // for var init expression
alloc SP<__wolin_reg6>, #1 // for var init expression
let SP(0)<__wolin_reg6>[ubyte] = #20[ubyte] // atomic ex
let HEAP(1)<pl.qus.wolin.Test.y>[ubyte] = SP(0)<__wolin_reg6>[ubyte] // set y on heap to 20
free SP<__wolin_reg6>, #1 // for var init expression// return from constructor
ret
And that gives:

Code: Select all

; label__wolin_pl_qus_wolin_Test

__wolin_pl_qus_wolin_Test:

; allocSP<__wolin_reg4>,#2


    dex
    dex

; allocSPF,#6


    clc
    lda __wolin_spf
    sbc #6
    sta __wolin_spf
    bcs +:
    dec __wolin_spf+1
:

; letSPF(2)[uword]=#3[uword]


    ldy #2
    lda #<3
    sta (__wolin_spf),y
    iny
    lda #>3
    sta (__wolin_spf),y

; letSPF(0)[uword]=#1[uword]


    ldy #0
    lda #<1
    sta (__wolin_spf),y
    iny
    lda #>1
    sta (__wolin_spf),y

; call__wolin_pl_qus_wolin_allocMem[adr]

    jsr __wolin_pl_qus_wolin_allocMem

; letSP(0)<__wolin_reg4>[ptr]=SPF(0)<returnValue>[ptr]


     ldy #0
     lda (__wolin_spf),y
     sta 0,x
     iny
     lda (__wolin_spf),y
     sta 0+1,x

; freeSPF<Any>,#2


    clc
    lda __wolin_spf
    adc #2
    sta __wolin_spf
    bcc +:
    inc __wolin_spf+1
:

; letSPF(0)<pl.qus.wolin.Test.returnValue>[ptr]=SP(0)<__wolin_reg4>[ptr]


    ldy #0
    lda 0,x
    sta (__wolin_spf),y
    iny
    lda 0+1,x
    sta (__wolin_spf),y

; setupHEAP=SP(0)<__wolin_reg4>[ptr]


    lda 0,x
    sta __wolin_this_ptr
    lda 0+1,x
    sta __wolin_this_ptr+1

; freeSP<__wolin_reg4>,#2


    inx
    inx

; freeSPF,#2


    clc
    lda __wolin_spf
    adc #2
    sta __wolin_spf
    bcc +:
    inc __wolin_spf+1
:

; allocSP<__wolin_reg5>,#1

    dex

; letSP(0)<__wolin_reg5>[ubyte]=#10[ubyte]


    lda #10
    sta 0,x

; letHEAP(2)<pl.qus.wolin.Test.x>[ubyte]=SP(0)<__wolin_reg5>[ubyte]


    lda 0,x
    ldy #2
    sta (__wolin_this_ptr),y

; freeSP<__wolin_reg5>,#1

    inx

; allocSP<__wolin_reg6>,#1

    dex

; letSP(0)<__wolin_reg6>[ubyte]=#20[ubyte]


    lda #20
    sta 0,x

; letHEAP(1)<pl.qus.wolin.Test.y>[ubyte]=SP(0)<__wolin_reg6>[ubyte]


    lda 0,x
    ldy #1
    sta (__wolin_this_ptr),y

; freeSP<__wolin_reg6>,#1

    inx

; ret

    rts
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

News: calling class methods on class instance now works. As mentioned earlier - all class methods have intrinsic "this" argument handled by compiler that holds pointer to dereferenced instance. Here's an example with dummy allocMem function that would obviously always alloc the same memory location (30000) for all objects :D

Of course that's only start of the solution, as class methods should be called via jump table depending on real instance being derferenced. Currently bottom field of instance heap contains class id reserved for runtime class discrimination.

EDIT: fixed 6502 asm source.

Code: Select all

package pl.qus.wolin

var border: ubyte^53280
var ekran: ubyte[]^1024

var i: uword = 0
var znak: ubyte = 0

fun allocMem(size: uword, count: uword): Any {
    return 30000
}

class Test {
    var x: ubyte = 3
    var y: ubyte = 7

    fun suma(): ubyte {
        return x+y
    }
}


fun main() {
    val testowa : Test

    testowa = Test()

    border = testowa.suma()
}
pseudoasm:

Code: Select all

setup HEADER
setup SPF = 251[ubyte], 40959[uword] // call stack pointer at 251 = 40959
setup SP = 143[ubyte] // register stack top = 142
setup HEAP = 176[ubyte]
//  main function entry
goto __wolin_pl_qus_wolin_main[adr]


// ****************************************
// funkcja: fun pl.qus.wolin.allocMem(pl.qus.wolin.allocMem.size: uword = 0 (), pl.qus.wolin.allocMem.count: uword = 0 ()):Any
// ****************************************
label __wolin_pl_qus_wolin_allocMem
alloc SP<__wolin_reg1>, #2 // for expression
let SP(0)<__wolin_reg1>[ptr] = #30000[uword] // atomic ex
let SPF(4)<returnValue>[ptr] = SP(0)<__wolin_reg1>[ptr] // przez sprawdzacz typow - jump expression
free SP<__wolin_reg1>, #2 // for expression
free SPF, #4 // free fn arguments and locals for pl.qus.wolin.allocMem
ret


// ****************************************
// konstruktor: fun pl.qus.wolin.Test():pl.qus.wolin.Test
// ****************************************
label __wolin_pl_qus_wolin_Test

alloc SP<__wolin_reg2>, #2 // for returning this
alloc SPF, #6
let SPF(2)[uword] = #3[uword]
let SPF(0)[uword] = #1[uword]
call __wolin_pl_qus_wolin_allocMem[adr]
let SP(0)<__wolin_reg2>[ptr] = SPF(0)<returnValue>[ptr]
free SPF <Any>, #2 // free return value of pl.qus.wolin.allocMem from call stack
let SPF(0)<pl.qus.wolin.Test.returnValue>[ptr] = SP(0)<__wolin_reg2>[ptr] // przez sprawdzacz typow - zwrotka alloc do zwrotki konstruktora
setup HEAP = SP(0)<__wolin_reg2>[ptr]
free SP<__wolin_reg2>, #2 // for returning this
alloc SP<__wolin_reg3>, #1 // for var init expression
let SP(0)<__wolin_reg3>[ubyte] = #3[ubyte] // atomic ex
let HEAP(2)<pl.qus.wolin.Test.x>[ubyte] = SP(0)<__wolin_reg3>[ubyte] // podstawic wynik inicjalizacji expression do zmiennej pl.qus.wolin.Test.x
free SP<__wolin_reg3>, #1 // for var init expression
alloc SP<__wolin_reg4>, #1 // for var init expression
let SP(0)<__wolin_reg4>[ubyte] = #7[ubyte] // atomic ex
let HEAP(1)<pl.qus.wolin.Test.y>[ubyte] = SP(0)<__wolin_reg4>[ubyte] // podstawic wynik inicjalizacji expression do zmiennej pl.qus.wolin.Test.y
free SP<__wolin_reg4>, #1 // for var init expression
ret

// ****************************************
// funkcja: fun pl.qus.wolin.Test.suma(pl.qus.wolin.Test.suma.this: pl.qus.wolin.Test = 65535 ()):ubyte
// ****************************************
label __wolin_pl_qus_wolin_Test_suma
setup HEAP = this
alloc SP<__wolin_reg6>, #1 // for expression
let SP(0)<__wolin_reg6>[ubyte] = HEAP(2)<pl.qus.wolin.Test.x>[ubyte] // simple id from var
alloc SP<__wolin_reg7>, #1 // RIGHT adding operator
let SP(0)<__wolin_reg7>[ubyte] = HEAP(1)<pl.qus.wolin.Test.y>[ubyte] // simple id from var
add SP(1)<__wolin_reg6>[ubyte] = SP(1)<__wolin_reg6>[ubyte], SP(0)<__wolin_reg7>[ubyte]
free SP<__wolin_reg7>, #1 // RIGHT adding operator
let SPF(2)<returnValue>[ubyte] = SP(0)<__wolin_reg6>[ubyte] // przez sprawdzacz typow - jump expression
free SP<__wolin_reg6>, #1 // for expression
free SPF, #2 // free fn arguments and locals for pl.qus.wolin.Test.suma
ret

// ****************************************
// funkcja: fun pl.qus.wolin.main():unit
// ****************************************
label __wolin_pl_qus_wolin_main
alloc SP<__wolin_reg11>, #2 // For assignment left side
alloc SP<__wolin_reg12>, #2 // for value that gets assigned to left side
alloc SPF, #2
call __wolin_pl_qus_wolin_Test[adr]

let SP(0)<__wolin_reg12>[ptr] = SPF(0)<returnValue>[ptr]// copy return parameter - TODO sprawdzić co jeśli wywołanie funkcji było bez podstawienia!!!
free SPF <pl.qus.wolin.Test>, #2
let SPF(0)<pl.qus.wolin.main..testowa>[ptr] = SP(0)<__wolin_reg12>[ptr] // przez sprawdzacz typow - process assignment
free SP<__wolin_reg12>, #2 // for value that gets assigned to left side, type = pl.qus.wolin.Test
free SP<__wolin_reg11>, #2 // For assignment left side
alloc SP<__wolin_reg14>, #1 // For assignment left side
alloc SP<__wolin_reg15>, #1 // for value that gets assigned to left side
alloc SP<__wolin_reg16>, #2 // dereferenced var
let SP(0)<__wolin_reg16>[ptr] = SPF(0)<pl.qus.wolin.main..testowa>[ptr] // simple id from var
alloc SP<__wolin_reg17>, #1 // for right side of deref
alloc SPF, #3
let SPF(0)[ptr] = SP(1)<__wolin_reg16>[ptr]
call __wolin_pl_qus_wolin_Test_suma[adr]

let SP(0)<__wolin_reg17>[ubyte] = SPF(0)<returnValue>[ubyte]// copy return parameter - TODO sprawdzić co jeśli wywołanie funkcji było bez podstawienia!!!
free SPF <ubyte>, #1
free SP<__wolin_reg17>, #1 // for right side of deref
free SP<__wolin_reg16>, #2 // dereferenced var
let __wolin_pl_qus_wolin_result<pl.qus.wolin.result>[ubyte] = SP(0)<__wolin_reg15>[ubyte] // przez sprawdzacz typow - process assignment
free SP<__wolin_reg15>, #1 // for value that gets assigned to left side, type = ubyte
free SP<__wolin_reg14>, #1 // For assignment left side
free SPF, #2 // free fn arguments and locals for pl.qus.wolin.main
ret



// ****************************************
// LAMBDAS
// ****************************************


// ****************************************
// STATIC SPACE
// ****************************************
label __wolin_indirect_jsr
goto 65535[adr]
label __wolin_pl_qus_wolin_result
alloc 0[ubyte]  // pl.qus.wolin.result

Code: Select all

; setupHEADER


;**********************************************
;*
;* BASIC header
;*
;* compile with:
;* cl65.exe -o assembler.prg -t c64 -C c64-asm.cfg -Ln labels.txt assembler.s
;*
;**********************************************
            .org 2049
            .export LOADADDR = *
Bas10:      .word BasEnd
            .word 10
            .byte 158 ; sys
            .byte " 2064"
            .byte 0
BasEnd:     .word 0
            .word 0
            ;


; setupSPF=251[ubyte],40959[uword]


; prepare function stack
__wolin_spf := 251 ; function stack ptr
__wolin_spf_top := 40959 ; function stack top
    lda #<__wolin_spf_top ; set function stack top
    sta __wolin_spf
    lda #>__wolin_spf_top
    sta __wolin_spf+1

; setupSP=143[ubyte]


; prepare program stack
__wolin_sp_top := 143 ; program stack top
    ldx #__wolin_sp_top ; set program stack top

; setupHEAP=176[ubyte]


__wolin_this_ptr := 176


; allocSP<__wolin_reg0>,#1

    dex

; letSP(0)<__wolin_reg0>[ubyte]=#0[ubyte]


    lda #0
    sta 0,x

; let__wolin_pl_qus_wolin_znak<pl.qus.wolin.znak>[ubyte]=SP(0)<__wolin_reg0>[ubyte]


    lda 0,x
    sta __wolin_pl_qus_wolin_znak


; freeSP<__wolin_reg0>,#1

    inx

; allocSP<__wolin_reg1>,#1

    dex

; letSP(0)<__wolin_reg1>[ubyte]=#0[ubyte]


    lda #0
    sta 0,x

; let__wolin_pl_qus_wolin_i<pl.qus.wolin.i>[uword]=SP(0)<__wolin_reg1>[ubyte]


    lda 0,x
    sta __wolin_pl_qus_wolin_i
    lda #0
    sta __wolin_pl_qus_wolin_i+1


; freeSP<__wolin_reg1>,#1

    inx

; goto__wolin_pl_qus_wolin_main[adr]

    jmp __wolin_pl_qus_wolin_main

; label__wolin_pl_qus_wolin_allocMem

__wolin_pl_qus_wolin_allocMem:

; allocSP<__wolin_reg2>,#2


    dex
    dex

; allocSP<__wolin_reg3>,#2


    dex
    dex

; letSP(0)<__wolin_reg3>[ptr]=#30000[uword]


    lda #<30000
    sta 0,x
    lda #>30000
    sta 0+1,x

; letSPF(4)<returnValue>[ptr]=SP(0)<__wolin_reg3>[ptr]


    ldy #4
    lda 0,x
    sta (__wolin_spf),y
    iny
    lda 0+1,x
    sta (__wolin_spf),y

; freeSP<__wolin_reg3>,#2


    inx
    inx

; freeSP<__wolin_reg2>,#2


    inx
    inx

; freeSPF,#4


    clc
    lda __wolin_spf
    adc #4
    sta __wolin_spf
    bcc :+
    inc __wolin_spf+1
:

; ret

    rts

; label__wolin_pl_qus_wolin_Test

__wolin_pl_qus_wolin_Test:

; allocSP<__wolin_reg4>,#2


    dex
    dex

; allocSPF,#6


    clc
    lda __wolin_spf
    sbc #6
    sta __wolin_spf
    bcs :+
    dec __wolin_spf+1
:

; letSPF(2)[uword]=#3[uword]


    ldy #2
    lda #<3
    sta (__wolin_spf),y
    iny
    lda #>3
    sta (__wolin_spf),y

; letSPF(0)[uword]=#1[uword]


    ldy #0
    lda #<1
    sta (__wolin_spf),y
    iny
    lda #>1
    sta (__wolin_spf),y

; call__wolin_pl_qus_wolin_allocMem[adr]

    jsr __wolin_pl_qus_wolin_allocMem

; letSP(0)<__wolin_reg4>[ptr]=SPF(0)<returnValue>[ptr]


     ldy #0
     lda (__wolin_spf),y
     sta 0,x
     iny
     lda (__wolin_spf),y
     sta 0+1,x

; freeSPF<Any>,#2


    clc
    lda __wolin_spf
    adc #2
    sta __wolin_spf
    bcc :+
    inc __wolin_spf+1
:

; letSPF(0)<pl.qus.wolin.Test.returnValue>[ptr]=SP(0)<__wolin_reg4>[ptr]


    ldy #0
    lda 0,x
    sta (__wolin_spf),y
    iny
    lda 0+1,x
    sta (__wolin_spf),y

; setupHEAP=SP(0)<__wolin_reg4>[ptr]


    lda 0,x
    sta __wolin_this_ptr
    lda 0+1,x
    sta __wolin_this_ptr+1

; freeSP<__wolin_reg4>,#2


    inx
    inx

; allocSP<__wolin_reg5>,#1

    dex

; letSP(0)<__wolin_reg5>[ubyte]=#3[ubyte]


    lda #3
    sta 0,x

; letHEAP(2)<pl.qus.wolin.Test.x>[ubyte]=SP(0)<__wolin_reg5>[ubyte]


    lda 0,x
    ldy #2
    sta (__wolin_this_ptr),y

; freeSP<__wolin_reg5>,#1

    inx

; allocSP<__wolin_reg6>,#1

    dex

; letSP(0)<__wolin_reg6>[ubyte]=#7[ubyte]


    lda #7
    sta 0,x

; letHEAP(1)<pl.qus.wolin.Test.y>[ubyte]=SP(0)<__wolin_reg6>[ubyte]


    lda 0,x
    ldy #1
    sta (__wolin_this_ptr),y

; freeSP<__wolin_reg6>,#1

    inx

; ret

    rts

; label__wolin_pl_qus_wolin_Test_suma

__wolin_pl_qus_wolin_Test_suma:

; setupHEAP=this


    ldy #0 ; this pointer from SPF to this pointer on ZP
    lda (__wolin_spf),y
    sta __wolin_this_ptr
    iny
    lda (__wolin_spf),y
    sta __wolin_this_ptr+1

; allocSP<__wolin_reg8>,#1

    dex

; letSP(0)<__wolin_reg8>[ubyte]=HEAP(2)<pl.qus.wolin.Test.x>[ubyte]


    ldy #2 ; assuming this ZP is set!
    lda (__wolin_this_ptr),y
    sta 0,x

; allocSP<__wolin_reg9>,#1

    dex

; letSP(0)<__wolin_reg9>[ubyte]=HEAP(1)<pl.qus.wolin.Test.y>[ubyte]


    ldy #1 ; assuming this ZP is set!
    lda (__wolin_this_ptr),y
    sta 0,x

; addSP(1)<__wolin_reg8>[ubyte]=SP(1)<__wolin_reg8>[ubyte],SP(0)<__wolin_reg9>[ubyte]


    clc
    lda 1,x
    adc 0,x
    sta 1,x

; freeSP<__wolin_reg9>,#1

    inx

; letSPF(2)<returnValue>[ubyte]=SP(0)<__wolin_reg8>[ubyte]


    lda 0,x
    ldy #2
    sta (__wolin_spf),y

; freeSP<__wolin_reg8>,#1

    inx

; freeSPF,#2


    clc
    lda __wolin_spf
    adc #2
    sta __wolin_spf
    bcc :+
    inc __wolin_spf+1
:

; ret

    rts

; label__wolin_pl_qus_wolin_main

__wolin_pl_qus_wolin_main:

; allocSP<__wolin_reg13>,#2


    dex
    dex

; allocSP<__wolin_reg14>,#2


    dex
    dex

; allocSPF,#2


    clc
    lda __wolin_spf
    sbc #2
    sta __wolin_spf
    bcs :+
    dec __wolin_spf+1
:

; call__wolin_pl_qus_wolin_Test[adr]

    jsr __wolin_pl_qus_wolin_Test

; letSP(0)<__wolin_reg14>[ptr]=SPF(0)<returnValue>[ptr]


     ldy #0
     lda (__wolin_spf),y
     sta 0,x
     iny
     lda (__wolin_spf),y
     sta 0+1,x

; freeSPF<pl.qus.wolin.Test>,#2


    clc
    lda __wolin_spf
    adc #2
    sta __wolin_spf
    bcc :+
    inc __wolin_spf+1
:

; letSPF(0)<pl.qus.wolin.main..testowa>[ptr]=SP(0)<__wolin_reg14>[ptr]


    ldy #0
    lda 0,x
    sta (__wolin_spf),y
    iny
    lda 0+1,x
    sta (__wolin_spf),y

; freeSP<__wolin_reg14>,#2


    inx
    inx

; freeSP<__wolin_reg13>,#2


    inx
    inx

; allocSP<__wolin_reg16>,#1

    dex

; allocSP<__wolin_reg17>,#1

    dex

; allocSP<__wolin_reg18>,#2


    dex
    dex

; letSP(0)<__wolin_reg18>[ptr]=SPF(0)<pl.qus.wolin.main..testowa>[ptr]


     ldy #0
     lda (__wolin_spf),y
     sta 0,x
     iny
     lda (__wolin_spf),y
     sta 0+1,x

; allocSP<__wolin_reg19>,#1

    dex

; allocSPF,#3


    clc
    lda __wolin_spf
    sbc #3
    sta __wolin_spf
    bcs :+
    dec __wolin_spf+1
:

; letSPF(0)[ptr]=SP(1)<__wolin_reg18>[ptr]


    ldy #0
    lda 1,x
    sta (__wolin_spf),y
    iny
    lda 1+1,x
    sta (__wolin_spf),y

; call__wolin_pl_qus_wolin_Test_suma[adr]

    jsr __wolin_pl_qus_wolin_Test_suma

; letSP(0)<__wolin_reg19>[ubyte]=SPF(0)<returnValue>[ubyte]


    ldy #0
    lda (__wolin_spf),y
    sta 0,x


; freeSPF<ubyte>,#1


    clc
    lda __wolin_spf
    adc #1
    sta __wolin_spf
    bcc :+
    inc __wolin_spf+1
:

; letSP(3)<__wolin_reg17>[ubyte]=SP(0)<__wolin_reg19>[ubyte]


    lda 0,x
    sta 3,x

; freeSP<__wolin_reg19>,#1

    inx

; freeSP<__wolin_reg18>,#2


    inx
    inx

; let53280[ubyte]=SP(0)<__wolin_reg17>[ubyte]


    lda 0,x
    sta 53280


; freeSP<__wolin_reg17>,#1

    inx

; freeSP<__wolin_reg16>,#1

    inx

; freeSPF,#2


    clc
    lda __wolin_spf
    adc #2
    sta __wolin_spf
    bcc :+
    inc __wolin_spf+1
:

; ret

    rts

; label__wolin_indirect_jsr

__wolin_indirect_jsr:

; goto65535[adr]

    jmp 65535

; label__wolin_pl_qus_wolin_znak

__wolin_pl_qus_wolin_znak:

; alloc0[ubyte]

    .byte 0

; label__wolin_pl_qus_wolin_i

__wolin_pl_qus_wolin_i:

; alloc0[uword]

    .word 0
qus
Posts: 104
Joined: 20 Apr 2019

Re: Wolin - a minimal Kotlin-like language compiler for 65xx

Post by qus »

I was on vacations, so no updates, but last time I did something in Wolin was proper assignment left side handling. Until now it was pretty naive - it allowed either plain variable or array. But of course it mus handle more complex things like:

Code: Select all

objectInstance.someField = 44
or:

Code: Select all

objectArrayInstance[4].someField = 69
or even:

Code: Select all

objectA.objectB[666].objectC[32].someField = 33
Which of course requres non-trivial logic. And it is indeed... non-trivial to code it...
Post Reply