c64 0.1.0-alpha.1

Driver for the Commodore 64 platform
Documentation
// Instructions are separated by `\n\t`, not `;`, because the llvm-mos
// assembler treats `;` as a comment-to-end-of-line — which would silently
// eat every instruction past the first.

#include <stdint.h>

#define PEEK(address) (*(volatile uint8_t *)(address))

// https://www.c64-wiki.com/wiki/FACINX
// https://www.the-dreams.de/aay64/ROMB1AA.HTM
int16_t basic_facinx() {
    uint8_t a, y;
    asm volatile(
        "jsr $B1AA"
        : "=a" (a), "=y" (y)
    );
    // TODO: https://www.c64-wiki.com/wiki/FACINX has it both ways round.
    return (int16_t) (((uint16_t) a << 8) | ((uint16_t) y));
}

// https://www.c64-wiki.com/wiki/GIVAYF
// https://www.the-dreams.de/aay64/ROMB391.HTM
void basic_givayf(int16_t value) {
    uint8_t a = (uint8_t) ((uint16_t) value >> 8);
    uint8_t y = (uint8_t) ((uint16_t) value);
    asm volatile(
        "jsr $B391"
        :: "a" (a), "y" (y)
    );
}

// https://www.the-dreams.de/aay64/ROMB850.HTM
void basic_fsub(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    asm volatile(
        "jsr $B850"
        :: "a" (a), "y" (y)
    );
}

// https://www.the-dreams.de/aay64/ROMB850.HTM
void basic_fsubt() {
    asm volatile("jsr $B853");
}

// https://www.the-dreams.de/aay64/ROMB867.HTM
void basic_fadd(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    asm volatile(
        "jsr $B867"
        :: "a" (a), "y" (y)
    );
}

// $B86A is the fall-through entry inside FADD, immediately after `JSR $BA8C`
// (CONUPK). CONUPK leaves three pieces of state for the routine that
// follows: ARG loaded from memory (caller's responsibility, since we're
// skipping the load), the sign-XOR byte at $6F (used by `BIT $6F` at $B8A3),
// and the Z flag reflecting FAC's exponent at $61 (used by `BNE` at $B86A
// itself). We replicate the latter two here.
//
// https://www.the-dreams.de/aay64/ROMB867.HTM
void basic_faddt() {
    asm volatile(
        "lda $66\n\t"     // FAC sign
        "eor $6E\n\t"     // ^ ARG sign
        "sta $6F\n\t"     // -> sign-XOR byte
        "lda $61\n\t"     // FAC exponent (sets Z flag)
        "jsr $B86A"
        ::: "a"
    );
}

// https://www.c64-wiki.com/wiki/LOG
// https://www.the-dreams.de/aay64/ROMB9EA.HTM
void basic_log() {
    asm volatile("jsr $B9EA");
}

// https://www.the-dreams.de/aay64/ROMBA28.HTM
void basic_fmult(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    asm volatile(
        "jsr $BA28"
        :: "a" (a), "y" (y)
    );
}

// https://www.the-dreams.de/aay64/ROMBAE2.HTM
void basic_mul10() {
    asm volatile("jsr $BAE2");
}

// https://www.the-dreams.de/aay64/ROMBAFE.HTM
void basic_div10() {
    asm volatile("jsr $BAFE");
}

// $BB07 is `STX $6F / JSR $BBA2 / JMP $BB12`: it expects X to hold the
// sign-XOR byte (ARG_sign ^ value_sign; bit 7 is read by FDIVT as the
// result sign). The only ROM callsite (SIN at $E274) loads X from $6E
// alone because its divisor is a positive constant — for a generic
// wrapper we must XOR in `value`'s sign byte (the second byte of the F40
// at `addr`).
//
// https://www.the-dreams.de/aay64/ROMBB07.HTM
void basic_fdivarg(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    uint8_t x = PEEK(0x6E) ^ addr[1];
    asm volatile(
        "jsr $BB07"
        :: "a" (a), "y" (y), "x" (x)
    );
}

// https://www.the-dreams.de/aay64/ROMBB0F.HTM
void basic_fdiv(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    asm volatile(
        "jsr $BB0F"
        :: "a" (a), "y" (y)
    );
}

// $BB12 is the fall-through entry inside FDIV, immediately after `JSR $BA8C`
// (CONUPK). Like FADDT, the routine also consumes the sign-XOR byte at $6F
// that CONUPK normally writes; we replicate both that and the Z flag
// reflecting FAC's exponent at $61.
//
// https://www.the-dreams.de/aay64/ROMBB0F.HTM
void basic_fdivt() {
    asm volatile(
        "lda $66\n\t"     // FAC sign
        "eor $6E\n\t"     // ^ ARG sign
        "sta $6F\n\t"     // -> sign-XOR byte
        "lda $61\n\t"     // FAC exponent (sets Z flag)
        "jsr $BB12"
        ::: "a"
    );
}

// https://www.c64-wiki.com/wiki/MOVFM
// https://www.the-dreams.de/aay64/ROMBBA2.HTM
void basic_movfm(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    asm volatile(
        "jsr $BBA2"
        :: "a" (a), "y" (y)
    );
}

// https://www.c64-wiki.com/wiki/MOVMF
// https://www.the-dreams.de/aay64/ROMBBC7.HTM
void basic_movmf(uint8_t* addr) {
    uint8_t x = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    asm volatile(
        "jsr $BBD4"
        :: "x" (x), "y" (y)
    );
}

// https://www.c64-wiki.com/wiki/MOVEF
// https://www.the-dreams.de/aay64/ROMBBFC.HTM
uint8_t basic_movaf() {
    uint8_t a;
    asm volatile(
        "jsr $BBFC"
        : "=a" (a)
    );
    return a;
}

// https://www.c64-wiki.com/wiki/MOVFA
// https://www.the-dreams.de/aay64/ROMBC0C.HTM
uint8_t basic_movfa() {
    uint8_t a;
    asm volatile(
        "jsr $BC0C"
        : "=a" (a)
    );
    return a;
}

// https://www.the-dreams.de/aay64/ROMBC1B.HTM
void basic_round() {
    asm volatile("jsr $BC1B");
}

// SIGN at $BC2B already returns the sign of FAC as a usable value in A:
// $00 if zero, $01 if positive, $FF if negative. No need to capture flags.
//
// https://www.the-dreams.de/aay64/ROMBC2B.HTM
int8_t basic_sign() {
    int8_t a;
    asm volatile(
        "jsr $BC2B"
        : "=a" (a)
    );
    return a;
}

// https://www.c64-wiki.com/wiki/SGN
// https://www.the-dreams.de/aay64/ROMBC39.HTM
void basic_sgn() {
    asm volatile("jsr $BC39");
}

// https://www.c64-wiki.com/wiki/ABS_(ROM_Routine)
// https://www.the-dreams.de/aay64/ROMBC58.HTM
void basic_abs() {
    // The routine at $BC58 is one instruction. Duplicating it is smaller and faster:
    asm volatile("lsr $66");
}

// https://www.the-dreams.de/aay64/ROMBC5B.HTM
int8_t basic_fcomp(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    int8_t ret;
    asm volatile(
        "jsr $BC5B"
        : "=a" (ret)
        : "a" (a), "y" (y)
    );
    return ret;
}

// https://www.c64-wiki.com/wiki/QINT
// https://www.the-dreams.de/aay64/ROMBC9B.HTM
void basic_qint() {
    asm volatile("jsr $BC9B");
}

// https://www.c64-wiki.com/wiki/INT
// https://www.the-dreams.de/aay64/ROMBCCC.HTM
void basic_int() {
    asm volatile("jsr $BCCC");
}

// https://www.c64-wiki.com/wiki/FOUT
// https://www.the-dreams.de/aay64/ROMBDDD.HTM
int8_t* basic_fout() {
    uint8_t a, y;
    asm volatile(
        "jsr $BDDD"
        : "=a" (a), "=y" (y)
    );
    return (int8_t*) (((uint16_t) y << 8) | ((uint16_t) a));
}

// https://www.the-dreams.de/aay64/ROMBF71.HTM
void basic_sqr() {
    asm volatile("jsr $BF71");
}

// $BF78 is the `JSR $BBA2` (Load FAC#1 From Memory) inside the SQR routine,
// which falls through to FPWR at $BF7B (FAC = ARG^FAC). The net effect is to
// load `addr` into FAC and then compute FAC = ARG^addr.
//
// https://www.the-dreams.de/aay64/ROMBF71.HTM
// https://www.the-dreams.de/aay64/ROMBF7B.HTM
void basic_fpwr(uint8_t* addr) {
    uint8_t a = (uint8_t) ((uint16_t) addr);
    uint8_t y = (uint8_t) ((uint16_t) addr >> 8);
    asm volatile(
        "jsr $BF78"
        :: "a" (a), "y" (y)
    );
}

// $BF7B is the body of FPWR, normally fallen into from $BF78 (`JSR $BBA2`,
// MOVFM). It begins with `BEQ`, expecting the Z flag to reflect FAC's
// exponent at $61 (which MOVFM leaves it set to). We replicate that here.
//
// https://www.the-dreams.de/aay64/ROMBF7B.HTM
void basic_fpwrt() {
    asm volatile(
        "lda $61\n\t"
        "jsr $BF7B"
        ::: "a"
    );
}

// https://www.the-dreams.de/aay64/ROMBFB4.HTM
void basic_negop() {
    asm volatile("jsr $BFB4");
}

// https://www.c64-wiki.com/wiki/EXP
// https://www.the-dreams.de/aay64/ROMBFED.HTM
void basic_exp() {
    asm volatile("jsr $BFED");
}