Skip to content

Instantly share code, notes, and snippets.

@slembcke
Created July 17, 2018 19:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slembcke/0438c6092109ea1f632c81ca581c495a to your computer and use it in GitHub Desktop.
Save slembcke/0438c6092109ea1f632c81ca581c495a to your computer and use it in GitHub Desktop.
libnaco: Nano coroutine library for cc65.
#ifndef NACO_H
#define NACO_H
#include <stdint.h>
#include <stdlib.h>
typedef uintptr_t (*naco_func)(uintptr_t);
void naco_init(naco_func func, void *naco_buffer, size_t buffer_size);
uintptr_t naco_yield(uintptr_t value);
uintptr_t naco_resume(void *naco_buffer, uintptr_t value);
#endif
; TODO Save CORO_BUFF_PTR so coroutines can call coroutines?
.include "zeropage.inc"
.macpack generic
.import pusha, popa
.import pushax, popax
.import addysp, subysp
; Error handler to call when resuming a coroutine that has finished.
.import _exit
CORO_ABORT = _exit
.zeropage
; Pointer to the currently running coroutine.
; TODO document buffer layout.
CORO_BUFF_PTR: .res 2
.code
.proc naco_swap_sp
ldy #0
lda (CORO_BUFF_PTR), y
ldx sp+0
sta sp+0
txa
sta (CORO_BUFF_PTR), y
iny
lda (CORO_BUFF_PTR), y
ldx sp+1
sta sp+1
txa
sta (CORO_BUFF_PTR), y
rts
.endproc
.export _naco_init
.proc _naco_init ; naco_func func, u8 *naco_buffer, size_t buffer_size -> void
func = ptr1
size = sreg
; Stash buffer size.
sta size+0
stx size+1
; Load the buffer pointer.
jsr popax
sta CORO_BUFF_PTR+0
stx CORO_BUFF_PTR+1
; Store the end address into the stack pointer.
add size+0
ldy #0
sta (CORO_BUFF_PTR), y
txa
adc size+1
iny
sta (CORO_BUFF_PTR), y
; Subtract 1 from the function address due to how jsr/ret work.
jsr popax
sub #1
sta func+0
bcs :+
dex
:
stx func+1
jsr naco_swap_sp
lda func+1
ldx func+0
jsr pushax
lda #>(naco_catch - 1)
ldx #<(naco_catch - 1)
jsr pushax
lda #2
tay
sta (CORO_BUFF_PTR), y
; Restore the stack.
jmp naco_swap_sp
.endproc
.proc naco_finish
value = sreg
; Pop the resume address from the coroutine stack.
jsr popax
pha
txa
pha
; Load the return value.
lda value+0
ldx value+1
rts
.endproc
.export _naco_resume
.proc _naco_resume ; void *naco_buffer, u16 value -> u16
value = sreg
tmp = tmp1
; Save the resume value;
sta value+0
stx value+1
jsr popax
sta CORO_BUFF_PTR+0
stx CORO_BUFF_PTR+1
; Push the yield address to the caller's stack.
pla
tax
pla
jsr pushax
jsr naco_swap_sp
; Stash the stack register.
tsx
ldy #2
lda (CORO_BUFF_PTR), y
sta tmp
ldy #0
: lda (sp), y
pha
iny
cpy tmp
bne :-
jsr addysp
; Save the old stack register value.
ldy #2
txa
sta (CORO_BUFF_PTR), y
jmp naco_finish
.endproc
.export _naco_yield
.proc _naco_yield ; u16 value -> u16
value = sreg
; Save the resume value;
sta value+0
stx value+1
; Push the resume address onto the coroutine's stack.
pla
tax
pla
jsr pushax
; Calculate stack offset. -(s - stack_offset)
clc
tsx
txa
ldy #2
sbc (CORO_BUFF_PTR), y
eor #$FF
sta (CORO_BUFF_PTR), y
tay
jsr subysp
: dey
pla
sta (sp), y
cpy #0
bne :-
jsr naco_swap_sp
jmp naco_finish
.endproc
.proc naco_catch ; u16 -> u16
value = sreg
; Save the resume value;
sta value+0
stx value+1
; Push error func.
lda #>(CORO_ABORT - 1)
ldx #<(CORO_ABORT - 1)
jsr pushax
; Zero out the stack offset.
lda #0
ldy #2
sta (CORO_BUFF_PTR), y
jsr naco_swap_sp
jmp naco_finish
.endproc
#include <stdlib.h>
#include <stdio.h>
#include "naco.h"
void func2(uintptr_t n){
n = naco_yield(n);
n = naco_yield(n);
}
uintptr_t func(uintptr_t n){
n = naco_yield(n);
n = naco_yield(n);
func2(n);
n = naco_yield(n);
n = naco_yield(n);
return 0;
}
uint8_t buff[64];
int main(void){
static uintptr_t n;
naco_init(func, buff, sizeof(buff));
for(n = 1; n < 20; ++n){
uintptr_t value;
value = naco_resume(buff, n);
printf("main() n: %d, value: %d\n", n, value);
if(value == 0) break;
}
return EXIT_SUCCESS;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment