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

Programming the 6502 microprocessor and its relatives in assembly and other languages.
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

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

Post by GARTHWILSON »

qus wrote:
I forgot to mention that before but there was another reason for creating Wolin. An argument with some guy on C64 forum that you can open the border ONLY from assembler. He was unable to provide a reason for this ridiculous statement, though.
Was it that there's too much to do in a short time (because of the speed of the scanning beam which won't wait for you)? Or did he think that interrupts can only be serviced in assembly language (which of course is not true)?
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

drogon wrote:
qus wrote:
Update.

I forgot to mention that before but there was another reason for creating Wolin. An argument with some guy on C64 forum that you can open the border ONLY from assembler. He was unable to provide a reason for this ridiculous statement, though.
Hehe... Well done! I love this sort of stuff!

But no different, really, from being able to point an interrupt (if that's what it takes, I know nothing about the C64 hardware) to a function in C - it's done daily in just about every CPU that supports C ... So if not C, then anything else that can do the right thing at function entry/exit...

-Gordon
Yeah. But the guy knew better...
GARTHWILSON wrote:
qus wrote:
I forgot to mention that before but there was another reason for creating Wolin. An argument with some guy on C64 forum that you can open the border ONLY from assembler. He was unable to provide a reason for this ridiculous statement, though.
Was it that there's too much to do in a short time (because of the speed of the scanning beam which won't wait for you)? Or did he think that interrupts can only be serviced in assembly language (which of course is not true)?
Well, it's hard to tell really, as he didn't elaborate. I can only suspect he was along the line "compilers produce too complicated and too bloated code for it to work..."

Anyway. Trying to implement a simple raster proggy form here:

https://www.c64-wiki.com/wiki/Raster_interrupt

Wolin:

Code: Select all

package pl.qus.wolin

var interruptRoutineVector: uword^0x314 // this is C64 raster interrupt vector
var cia1InerruptCtrlReg: ubyte^0xDC0D
var vicScreenCtrlReg1: ubyte^0xD011
var vicRasterLine: ubyte^0xD012
var vicInterruptStatusReg: ubyte^0xd019
var vicInterruptCtrlReg: ubyte^0xd01a
var vicBorder: ubyte^53280

fun rasterProc() {
	vicBorder = 7 // Turn screen frame yellow
	// Empty loop that "does nothing" for a little under a half millisecond
	vicBorder = 0 // Switch frame color back to black
    vicInterruptStatusReg = 0 // "Acknowledge" the interrupt by clearing the VIC's interrupt flag. (ASL)
    return@0xEA31 // JMP $EA31	Jump into KERNAL's standard interrupt service routine to handle keyboard scan, cursor display etc.
}

fun main() {
    cia1InerruptCtrlReg = 127 // "Switch off" interrupts signals from CIA-1 (bity 0-6 ustawione na 1 przestawia na 0)
    //vicScreenCtrlReg1 &= 127  // vicScreenCtrlReg1 and to, co powyżej // Clear most significant bit in VIC's raster register
    vicRasterLine = 210 // Set the raster line number where interrupt should occur
    interruptRoutineVector = rasterProc // Set the interrupt vector to point to interrupt service routine
    vicInterruptCtrlReg = 1 // Enable raster interrupt signals from VIC
}
Notice new syntax - return@addr that tells the function to goto addr instead of returning from subroutine, so the compiler generates this pseudo asm, without "ret" pseudo-op:

Code: Select all

function __wolin_pl_qus_wolin_rasterProc
let 53280[ubyte] = #7[ubyte]
let 53280[ubyte] = #0[ubyte]
let 53273[ubyte] = #0[ubyte]
goto 59953
Which gives:

Code: Select all

__wolin_pl_qus_wolin_rasterProc:

; let53280[ubyte]=#7[ubyte]


    lda #7
    sta 53280

; let53280[ubyte]=#0[ubyte]


    lda #0
    sta 53280

; let53273[ubyte]=#0[ubyte]


    lda #0
    sta 53273

; goto59953[adr]

    jmp 59953
I will probably add special "interrupt" annotation to functions, so they "rti" instead of "rts" + will complain if the function has arguments/return values.
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

So, gentelmen (hoping it's non-woke forum...) first working raster proggy in Wolin:

Code: Select all

package pl.qus.wolin

// raster interrupt colour band taken from:
// https://gist.github.com/bremensaki/8f33cd7d67b78377881c7eb7147c0f32

var interruptRoutineVector: uword^0x314 // this is C64 raster interrupt vector
var cia1InerruptCtrlReg: ubyte^0xDC0D
var vicScreenCtrlReg1: ubyte^0xD011
var vicRasterLine: ubyte^0xD012
var vicInterruptStatusReg: ubyte^0xd019
var vicInterruptCtrlReg: ubyte^0xd01a
var vicBorder: ubyte^53280
var vicBackground: ubyte^53281
var i: ubyte
var maskInterrupts: bool^CPU.I

fun clearScreen^0xe544()

interrupt fun backgroundToBlue() {
    interruptRoutineVector = backgroundToWhite
    vicRasterLine = 140
    vicBorder = 6
    vicBackground = 6
    vicInterruptStatusReg = 0xff
    return@0xea31
}

interrupt fun backgroundToWhite() {
    interruptRoutineVector = backgroundToBlue
    vicRasterLine = 160
    vicBorder = 1
    vicBackground = 1
    vicInterruptStatusReg = 0xff
    return@0xea31
}

fun main() {
    clearScreen()
    vicBorder = 6                                    // Init screen and border to blue
    vicBackground = 6
    maskInterrupts = true                            // Suspend interrupts during init
    cia1InerruptCtrlReg = 0x7f                       // Disable CIA
    vicInterruptCtrlReg := 1                         // Enable raster interrupts
    vicScreenCtrlReg1 &= 0x7f                        // High bit of raster line cleared, we're only working within single byte ranges
    vicRasterLine = 140                              // We want an interrupt at the top line
    interruptRoutineVector = backgroundToBlue        // IRQ vector addresses
    maskInterrupts = false                           // Enable interrupts again

    do {} while (true)                               // Eternal do-nothing loop, we're done.
}
Of course

Code: Select all

    vicScreenCtrlReg1 .= 128                         // High bit of raster line cleared, we're only working within single byte ranges
would work too!

It compiles to:

Code: Select all


BasEnd:     .word 0
            .word 0
            ;


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


; prepare function stack
__wolin_spf := 251 ; function stack ptr
__wolin_spf_hi := 251+1 ; function stack ptr

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

; setupSP=114[ubyte]


; prepare program stack
__wolin_sp_top := 114 ; program stack top
__wolin_sp_top_hi := 114+1 ; program stack top
    ldx #__wolin_sp_top ; set program stack top

; setupHEAP=176[ubyte]


__wolin_this_ptr := 176
__wolin_this_ptr_hi := 176+1


; allocSPF,#0

 

; call__wolin_pl_qus_wolin_main[adr]

    jsr __wolin_pl_qus_wolin_main

; ret

    rts

; function__wolin_pl_qus_wolin_backgroundToBlue

__wolin_pl_qus_wolin_backgroundToBlue:

; let788[uword]=__wolin_pl_qus_wolin_backgroundToWhite[uword]


    lda #<__wolin_pl_qus_wolin_backgroundToWhite
    sta 788
    lda #>__wolin_pl_qus_wolin_backgroundToWhite
    sta 788+1


; let53266[ubyte]=#140[ubyte]


    lda #140
    sta 53266

; let53280[ubyte]=#6[ubyte]


    lda #6
    sta 53280

; let53281[ubyte]=#6[ubyte]


    lda #6
    sta 53281

; let53273[ubyte]=#255[ubyte]


    lda #255
    sta 53273

; goto59953[adr]

    jmp 59953

; function__wolin_pl_qus_wolin_backgroundToWhite

__wolin_pl_qus_wolin_backgroundToWhite:

; let788[uword]=__wolin_pl_qus_wolin_backgroundToBlue[uword]


    lda #<__wolin_pl_qus_wolin_backgroundToBlue
    sta 788
    lda #>__wolin_pl_qus_wolin_backgroundToBlue
    sta 788+1


; let53266[ubyte]=#160[ubyte]


    lda #160
    sta 53266

; let53280[ubyte]=#1[ubyte]


    lda #1
    sta 53280

; let53281[ubyte]=#1[ubyte]


    lda #1
    sta 53281

; let53273[ubyte]=#255[ubyte]


    lda #255
    sta 53273

; goto59953[adr]

    jmp 59953

; function__wolin_pl_qus_wolin_main

__wolin_pl_qus_wolin_main:

; allocSPF,#0

 

; call58692[adr]

    jsr 58692

; let53280[ubyte]=#6[ubyte]


    lda #6
    sta 53280

; let53281[ubyte]=#6[ubyte]


    lda #6
    sta 53281

; letCPU.I[bool]=#1[bool]


    sei


; let56333[ubyte]=#127[ubyte]


    lda #127
    sta 56333

; bit53274[ubyte]=#1[ubyte],#1[bool]


    lda 53274
    ora 1
    sta 53274


; and53265[ubyte]=#127[ubyte]


    lda 53265
    and #127
    sta 53265


; let53266[ubyte]=#140[ubyte]


    lda #140
    sta 53266

; let788[uword]=__wolin_pl_qus_wolin_backgroundToBlue[uword]


    lda #<__wolin_pl_qus_wolin_backgroundToBlue
    sta 788
    lda #>__wolin_pl_qus_wolin_backgroundToBlue
    sta 788+1


; letCPU.I[bool]=#0[bool]


    cli


; allocSP<__wolin_reg63>,#1

    dex

; label__wolin_lab_loopStart_1

__wolin_lab_loopStart_1:

; letSP(0)<__wolin_reg63>[bool]=#1[bool]


    lda #1
    sta 0,x

; beqSP(0)<__wolin_reg63>[bool]=#1[bool],__wolin_lab_loopStart_1<label_po_if>[uword]


    lda 0,x
    bne __wolin_lab_loopStart_1

; label__wolin_lab_loopEnd_1

__wolin_lab_loopEnd_1:

; freeSP<__wolin_reg63>,#1

    inx

; ret

    rts

; label__wolin_indirect_jsr

__wolin_indirect_jsr:

; goto65535[adr]

    jmp 65535

; label__wolin_pl_qus_wolin_i

__wolin_pl_qus_wolin_i:

; alloc0[ubyte]

    .byte 0
Try it on your C64 or emulator!
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

Aaaawright. Having border open and some sprite there I went back to calling system/kernal procs. Someone suggested stubs, but since I'm still hoping I can make Wolin behave in multi-threaded environment I found such solution clumsy. So...

Code: Select all

package pl.qus.wolin

fun save^0xffd8(zpPointerToStart: ubyte^CPU.A, endAddr: uword^CPU.XY): bool^CPU.C // C == 1 means error occured
fun chrin^0xffcf(): ubyte^CPU.A

val wynik: bool

/*********************************************************
* Main function
**********************************************************/

fun main() {
    wynik = save(10,12345)
}
Since calculating final values of calling args may require Wolin stack (X), which may overwrite our argument located at X, and the routine may trash X anyway, we have to do some smart shuffling...
Will be called like this:

Code: Select all

function __wolin_pl_qus_wolin_main
alloc SPF, #1 // alloc function return value
save SP // push "our" stack pointer (X) to CPU stack
// here we could have some calculations made on Wolin stack and we would be pushingmZP stack regs instead
// also further arguments may overwrite A, X and Y required for function call so...
save #10[ubyte] // we push our final value to CPU stack
// here we could have some other calculations on arguments that would overwrite A na change X, Y
save #12345[uword] // so we push final value to CPU stack
restore CPU.XY[uword] // restore x, y
restore CPU.A[ubyte] // restore a (note - argument processing is always reshuffled with A going last, because storing X and Y on 6502 requires A)
call 65496[adr] // with A,X and Y set we can run!
let SPF(0)<pl.qus.wolin.save.__returnValue>[bool] = CPU.C[bool] // set return bool value to contents of C flag
restore SP // restore Wolin SP (x)
let __wolin_pl_qus_wolin_wynik<pl.qus.wolin.wynik>[bool] = SPF(0)<pl.qus.wolin.save.__returnValue>[bool] // set wynik to our bool
free SPF<pl.qus.wolin.save.__returnValue>, #1
ret 
Tadaaam!

And final code:

Code: Select all

; setupHEADER


;**********************************************
;*
;* BASIC header
;*
;* compile with:
;* cl65.exe -o assembler.prg -t c64 -C c64-asm.cfg -g -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_hi := 251+1 ; function stack ptr

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

; setupSP=114[ubyte]


; prepare program stack
__wolin_sp_top := 114 ; program stack top
__wolin_sp_top_hi := 114+1 ; program stack top
    ldx #__wolin_sp_top ; set program stack top

; setupHEAP=176[ubyte]


__wolin_this_ptr := 176
__wolin_this_ptr_hi := 176+1


; allocSPF,#0

 

; call__wolin_pl_qus_wolin_main[adr]

    jsr __wolin_pl_qus_wolin_main

; ret

    rts

; function__wolin_pl_qus_wolin_main

__wolin_pl_qus_wolin_main:

; allocSPF,#1


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

; saveSP


    txa
    pha

; save#10[ubyte]


    lda #10
    pha


; save#12345[uword]


    lda #<12345
    pha
    lda #>12345
    pha


; restoreCPU.XY[uword]


    pla
    tay
    pla
    tax


; restoreCPU.A[ubyte]


    pla

; call65496[adr]

    jsr 65496

; letSPF(0)<pl.qus.wolin.save.__returnValue>[bool]=CPU.C[bool]


    ldy #0
    lda #1
    bcs :+
    lda #0
:
    sta (__wolin_spf),y



; restoreSP


    pla
    tax

; let__wolin_pl_qus_wolin_wynik<pl.qus.wolin.wynik>[bool]=SPF(0)<pl.qus.wolin.save.__returnValue>[bool]


    ldy #0
    lda (__wolin_spf),y
    sta __wolin_pl_qus_wolin_wynik


; freeSPF<pl.qus.wolin.save.__returnValue>,#1


    clc
    lda __wolin_spf
    adc #1
    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_wynik

__wolin_pl_qus_wolin_wynik:

; alloc0[bool]

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

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

Post by qus »

Optimization can bring surprises. I.e. registers get shifted behind their deallocs:

Code: Select all

alloc SP<__wolin_reg10>, #2
let SP(0)<__wolin_reg10>[ubyte*] = #1024[uword]
add __wolin_pl_qus_wolin_i<pl.qus.wolin.i>[uword] = __wolin_pl_qus_wolin_i<pl.qus.wolin.i>[uword], #1[uword]
add SP(0)<__wolin_reg10>[ubyte*] = SP(0)<__wolin_reg10>[ubyte*], &*__wolin_pl_qus_wolin_i<pl.qus.wolin.i>[uword]
free SP<__wolin_reg10>, #2
add __wolin_pl_qus_wolin_chr<pl.qus.wolin.chr>[ubyte] = __wolin_pl_qus_wolin_chr<pl.qus.wolin.chr>[ubyte], #1[ubyte]
let &SP(-2)<__wolin_reg10>[ubyte*] = &*__wolin_pl_qus_wolin_chr<pl.qus.wolin.chr>[ubyte] <------- oooops reg10 is levitating above the stack!
So I will be working on moving alloc and free around, it will come in handy when I get to loop optimization!

Additionaly, I changed reference substitution logic in optimizer to late resolving. I.e. before when I was substituting pointer reg into deref (&) argument, I would immediately cancel out * and & making it a normal, direct variable. But that meant the possibly useful & (if aonther substitution should occur) was lost and gone, and I would end up with unecessary pointers. That's why you can see "&*variable" in some places, I am going to cancel them out after no more substitutions are possible.

And lastly - I'm doing optimizations from top (newest regs) to bottom (oldert regs) which seems to work better...
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

Today I finally managed to implement last optimization I though of before. Not sure how to call them, but they work like this:

If there's some operation that has register A as target:

op A = whatever

And later on A register is put into B:

let B = A

Then first line can be optimized to:

op B = whatever
// let B = A removed

Thus a simple function:

Code: Select all

fun suma(a: ubyte, b: ubyte): ubyte {
    return a+b
}
Which compiles to bloated:

Code: Select all

// ****************************************
// funkcja: fun pl.qus.wolin.suma(pl.qus.wolin.suma.a: ubyte = 0 () location = null, null, pl.qus.wolin.suma.b: ubyte = 0 () location = null, null):ubyte
// ****************************************
function __wolin_pl_qus_wolin_suma
alloc SP<__wolin_reg1>, #1 // for expression
alloc SP<__wolin_reg2>, #2 // LEFT adding operator
let SP(0)<__wolin_reg2>[ubyte*] = *SPF(1)<pl.qus.wolin.suma.a>[ubyte] // przez sprawdzacz typow - simple id from var
// switchType to:ubyte by type from pl.qus.wolin.suma.a
// top type already set: __wolin_reg2: ubyte* = 0 (LEFT adding operator) location = null, null
alloc SP<__wolin_reg3>, #2 // RIGHT adding operator
let SP(0)<__wolin_reg3>[ubyte*] = *SPF(0)<pl.qus.wolin.suma.b>[ubyte] // przez sprawdzacz typow - simple id from var
// switchType to:ubyte by type from pl.qus.wolin.suma.b
// top type already set: __wolin_reg3: ubyte* = 0 (RIGHT adding operator) location = null, null
add &SP(4)<__wolin_reg1>[ubyte] = &SP(2)<__wolin_reg2>[ubyte*], &SP(0)<__wolin_reg3>[ubyte*]
free SP<__wolin_reg3>, #2 // RIGHT adding operator
free SP<__wolin_reg2>, #2 // LEFT adding operator
// top type already set: __wolin_reg1: ubyte = 0 (for expression) location = null, null
let SPF(2)<pl.qus.wolin.suma.__returnValue>[ubyte] = &SP(0)<__wolin_reg1>[ubyte] // przez sprawdzacz typow - jump expression
// switchType to:ubyte by return expression
// top type already set: __wolin_reg1: ubyte = 0 (for expression) location = null, null
free SP<__wolin_reg1>, #1 // for expression
free SPF<pl.qus.wolin.suma.__fnargs>, #2 // free fn arguments and locals for pl.qus.wolin.suma
// ***** fnDeclFreeStackAndRet usuwanie zwrotki pl.qus.wolin.suma ze stosu
// freeing call stack
// return from function body
ret
Now gets optimized to:

Code: Select all

function __wolin_pl_qus_wolin_suma
add SPF(2)<pl.qus.wolin.suma.__returnValue>[ubyte] = SPF(1)<pl.qus.wolin.suma.a>[ubyte], SPF(0)<pl.qus.wolin.suma.b>[ubyte]
free SPF<pl.qus.wolin.suma.__fnargs>, #2
ret 

How about that?
User avatar
BigEd
Posts: 11463
Joined: 11 Dec 2008
Location: England
Contact:

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

Post by BigEd »

Nice! Would we call that renaming?
Chromatix
Posts: 1462
Joined: 21 May 2018

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

Post by Chromatix »

This could be considered a combination of register allocation with common subexpression elimination, more usually carried out via a transformation to Single Static Assignment form.

Whatever it's called, it's definitely effective at cutting out a lot of cruft.
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

Yes, yes! Something along these lines, like "retrospective register subsitution" ;)
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

This morning I checked if C gets cleared automatically on add/sub, just to be sure (but I guess it was pretty obvious since nobody here roasted me about doing CLC before ADC) and it obviously doesn't, but that made me wonder - why did they design it this way? Setting C on addition/substraction overflow doesn't cost additional cycles, right? So why didn't they want to spare us cycles wasted on CLC and always set or clear C accordingly?

Ha! And coming back to assembler optimizer... This would be probably always safe:

BCS xxxx
CLC
LDA
ADC

optimized to:

BCS xxxx
LDA
ADC
User avatar
GARTHWILSON
Forum Moderator
Posts: 8773
Joined: 30 Aug 2002
Location: Southern California
Contact:

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

Post by GARTHWILSON »

qus wrote:
why did they design it this way?
It's necessary for multi-byte arithmetic.

As I look back over your other recent posts, I think I should point out that subtraction will normally start with SEC, not CLC.

Explanations are in all 65xx programming manuals.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

GARTHWILSON wrote:
qus wrote:
why did they design it this way?
It's necessary for multi-byte arithmetic.
Heh, right. I forgot that one :D
GARTHWILSON wrote:
qus wrote:
why did they design it this way?
As I look back over your other recent posts, I think I should point out that subtraction will normally start with SEC, not CLC.

Explanations are in all 65xx programming manuals.
Well, I see this in my template:

Code: Select all

sub ?dest[uword] = ?src[uword], #?val[uword] -> """
    sec
    lda {src}
    sbc #<{val}
    sta {dest}
    lda {src}+1
    sbc #>{val}
    sta {dest}+1
"""
So if I CLC before SBC then it must be an error somewhere else...

EDIT - ah, yes! It's in alloc SPE/SPF! That would explode!
User avatar
BitWise
In Memoriam
Posts: 996
Joined: 02 Mar 2004
Location: Berkshire, UK
Contact:

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

Post by BitWise »

GARTHWILSON wrote:
qus wrote:
why did they design it this way?
It's necessary for multi-byte arithmetic.

As I look back over your other recent posts, I think I should point out that subtraction will normally start with SEC, not CLC.

Explanations are in all 65xx programming manuals.
Some other processors of the same era (like the 6800 and 8080) had separate ADD and SUB instructions which don't take the carry as input but produce a carry output. So on a 6800 a multibyte add starts with an ADD and then uses ADC for subsequent bytes.

These processors can do this as they have very few addressing modes, only 4 on the 6800 and 2 on the 8080 (register A-L and HL indirect - immediate is handed separately). The 6502 has 8 addressing modes for ADC while the 65C02 has 9. Just having ADC and SBC and using CLC/SEC minimises the total number of opcodes used for these operations leaving room for more addressing modes on other instructions.
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
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

While I am stuck with many things that unlike OO are really hard to implement, I've added some very basic string support. So, this is a piece of code that sends a command to C64 disk drive:

Code: Select all

package pl.qus.wolin

fun chkin^0xFFC6(lfn: ubyte^CPU.X): bool^CPU.C
fun getin^0xFFE4(): bool^CPU.A
fun readst^0xFFB7(): ubyte^CPU.A
fun clrchn^0xFFCC()
fun setlfs^0xFFBA(lfn: ubyte^CPU.A, device: ubyte^CPU.X, sa: ubyte^CPU.Y)
fun setnam^0xFFBD(fnameLen: ubyte^CPU.A, name: string^CPU.XY)
fun kopen^0xFFC0()

fun openCommandChannel(lfn: ubyte, device: ubyte, command: string) {
    setlfs(lfn, device, 15)
    setnam(10, command)
    kopen()
}

fun main() {
    openCommandChannel(15,8,"DUPA")
}
To pseudo asm:

Code: Select all

setup HEADER
setup SPF = 251[ubyte], 40959[uword]
setup SP = 114[ubyte]
setup HEAP = 176[ubyte]
alloc SPF, #0
call __wolin_pl_qus_wolin_main[uword]
endfunction 

function __wolin_pl_qus_wolin_openCommandChannel
alloc SPF, #0
save SP
save SPF(3)<pl.qus.wolin.openCommandChannel.lfn>[ubyte]
save SPF(2)<pl.qus.wolin.openCommandChannel.device>[ubyte]
save #15[ubyte]
restore CPU.Y[ubyte]
restore CPU.X[ubyte]
restore CPU.A[ubyte]
call 65466[uword]
restore SP
alloc SPF, #0
save SP
save #10[ubyte]
save SPF(0)<pl.qus.wolin.openCommandChannel.command>[ubyte*]
restore CPU.XY[ubyte*]
restore CPU.A[ubyte]
call 65469[uword]
restore SP
alloc SPF, #0
save SP
call 65472[uword]
restore SP
free SPF<pl.qus.wolin.openCommandChannel.__fnargs>, #4
endfunction 

function __wolin_pl_qus_wolin_main
alloc SPF, #4
let SPF(3)[ubyte] = #15[ubyte]
let SPF(2)[ubyte] = #8[ubyte]
let SPF(0)[ubyte*] = #__wolin_lab_stringConst_0[uword]
call __wolin_pl_qus_wolin_openCommandChannel[uword]
endfunction 
string __wolin_lab_stringConst_0[uword] = $"DUPA"
And finally to our favorite CPU:

Code: Select all

; setupHEADER


;**********************************************
;*
;* BASIC header
;*
;* compile with:
;* cl65.exe -o assembler.prg -t c64 -C c64-asm.cfg -g -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_hi := 251+1 ; function stack ptr

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

; setupSP=114[ubyte]


; prepare program stack
__wolin_sp_top := 114 ; program stack top
__wolin_sp_top_hi := 114+1 ; program stack top
    ldx #__wolin_sp_top ; set program stack top

; setupHEAP=176[ubyte]


__wolin_this_ptr := 176
__wolin_this_ptr_hi := 176+1


; allocSPF,#0

 

; call__wolin_pl_qus_wolin_main[uword]

    jsr __wolin_pl_qus_wolin_main

; endfunction

    rts

; function__wolin_pl_qus_wolin_openCommandChannel

__wolin_pl_qus_wolin_openCommandChannel:

; allocSPF,#0

 

; saveSP


    txa
    pha

; saveSPF(3)<pl.qus.wolin.openCommandChannel.lfn>[ubyte]


    ldy #3
    lda (__wolin_spf),y
    pha


; saveSPF(2)<pl.qus.wolin.openCommandChannel.device>[ubyte]


    ldy #2
    lda (__wolin_spf),y
    pha


; save#15[ubyte]


    lda #15
    pha


; restoreCPU.Y[ubyte]


    pla
    tay

; restoreCPU.X[ubyte]


    pla
    tax

; restoreCPU.A[ubyte]


    pla

; call65466[uword]

    jsr 65466

; restoreSP


    pla
    tax

; allocSPF,#0

 

; saveSP


    txa
    pha

; save#10[ubyte]


    lda #10
    pha


; saveSPF(0)<pl.qus.wolin.openCommandChannel.command>[ubyte*]


    ldy #0
    lda (__wolin_spf),y
    pha
    iny
    lda (__wolin_spf),y
    pha


; restoreCPU.XY[ubyte*]


    pla
    tay
    pla
    tax


; restoreCPU.A[ubyte]


    pla

; call65469[uword]

    jsr 65469

; restoreSP


    pla
    tax

; allocSPF,#0

 

; saveSP


    txa
    pha

; call65472[uword]

    jsr 65472

; restoreSP


    pla
    tax

; freeSPF<pl.qus.wolin.openCommandChannel.__fnargs>,#4


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

; endfunction

    rts

; function__wolin_pl_qus_wolin_main

__wolin_pl_qus_wolin_main:

; allocSPF,#4


    sec
    lda __wolin_spf
    sbc #4
    sta __wolin_spf
    bcs :+
    dec __wolin_spf+1
:

; letSPF(3)[ubyte]=#15[ubyte]


    ldy #3
    lda #15
    sta (__wolin_spf),y

; letSPF(2)[ubyte]=#8[ubyte]


    ldy #2
    lda #8
    sta (__wolin_spf),y

; letSPF(0)[ubyte*]=#__wolin_lab_stringConst_0[uword]


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

; call__wolin_pl_qus_wolin_openCommandChannel[uword]

    jsr __wolin_pl_qus_wolin_openCommandChannel

; endfunction

    rts

; string__wolin_lab_stringConst_0[uword]=$"DUPA"


__wolin_lab_stringConst_0:
    .str "DUPA"
qus
Posts: 104
Joined: 20 Apr 2019

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

Post by qus »

Anyone able to help me out? I'm looking for code to do this:

Code: Select all

fun print(what: string) {
    val fromArray: ubyte

    fromArray = what[3]

}
This gets translated into:

Code: Select all

let SPF(0)<pl.qus.wolin.print..fromArray>[ubyte] = &SPF(1)<pl.qus.wolin.print.what>[ubyte*], #3[ubyte]
meaning: "assign value stored at (pointer on functionStack(1)+#3) to functionStack(0)"

I access function stack by:

Code: Select all

ldy #stack_position
lda/sta (__wolin_spf), y
So I need to somehow:

1) get address that is stored in two bytes on function stack

2) index it by val (or worst case - add val to it)

3) store the byta that sits in 2) at position d on function stack like this

Code: Select all

ldy #{d}
sta (__wolin_spf), y
Since this is the "fast" array (single byte val indexed by single byte) I was hoping I can use standard X/Y indexing to do this, but I guess it won't be possible in such case, where I keep the array address on function sack, but only if the array is global and attached to some absolute memory location... Unless you have some solution for this case?
Post Reply