mirror of
https://github.com/bobbimanners/EightBall.git
synced 2024-09-28 19:54:36 +00:00
1327 lines
26 KiB
C
1327 lines
26 KiB
C
/**************************************************************************/
|
|
/* EightBall Virtual Machine */
|
|
/* */
|
|
/* The Eight Bit Algorithmic Language */
|
|
/* For Apple IIe/c/gs (64K), Commodore 64, VIC-20 +32K RAM expansion */
|
|
/* (also builds for Linux as 32 bit executable (gcc -m32) only) */
|
|
/* */
|
|
/* Compiles with cc65 v2.15 for VIC-20, C64, Apple II */
|
|
/* and gcc 7.3 for Linux */
|
|
/* */
|
|
/* Note that this code assumes that sizeof(int) = sizeof(int*), which is */
|
|
/* true for 6502 (16 bits each) and i686 (32 bits each) - but not amd64 */
|
|
/* */
|
|
/* cc65: Define symbol VIC20 to build for Commodore VIC-20 + 32K. */
|
|
/* Define symbol C64 to build for Commodore 64. */
|
|
/* Define symbol A2E to build for Apple //e. */
|
|
/* */
|
|
/* Copyright Bobbi Webber-Manners 2018 */
|
|
/* Reference implementation of EightBall Virtual Machine. */
|
|
/* */
|
|
/* This is not intended to be optimized for speed. I plan to implement */
|
|
/* an optimized version in 6502 assembler later. */
|
|
/* */
|
|
/* Formatted with indent -kr -nut */
|
|
/**************************************************************************/
|
|
|
|
/**************************************************************************/
|
|
/* GNU PUBLIC LICENCE v3 OR LATER */
|
|
/* */
|
|
/* This program is free software: you can redistribute it and/or modify */
|
|
/* it under the terms of the GNU General Public License as published by */
|
|
/* the Free Software Foundation, either version 3 of the License, or */
|
|
/* (at your option) any later version. */
|
|
/* */
|
|
/* This program is distributed in the hope that it will be useful, */
|
|
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
|
|
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
|
|
/* GNU General Public License for more details. */
|
|
/* */
|
|
/* You should have received a copy of the GNU General Public License */
|
|
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
/* */
|
|
/**************************************************************************/
|
|
|
|
/*
|
|
#define DEBUG
|
|
#define EXTRADEBUG
|
|
#define DEBUGREGS
|
|
*/
|
|
|
|
/* Define STACKCHECKS to enable paranoid stack checking */
|
|
#ifdef __GNUC__
|
|
#define STACKCHECKS
|
|
#else
|
|
#undef STACKCHECKS
|
|
#endif
|
|
|
|
#include "eightballvm.h"
|
|
#include "eightballutils.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#ifdef A2E
|
|
#include <conio.h>
|
|
#endif
|
|
|
|
#define EVALSTACKSZ 16
|
|
|
|
/*
|
|
* Call stack grows down from top of memory.
|
|
* If it hits CALLSTACKLIM then VM will quit with error
|
|
*/
|
|
#ifdef __GNUC__
|
|
#define MEMORYSZ (64 * 1024)
|
|
#else
|
|
/* Has to be 64K minus a byte otherwise winds up being zero! */
|
|
#define MEMORYSZ (64 * 1024) - 1
|
|
#endif
|
|
#define CALLSTACKLIM (32 * 1024)
|
|
|
|
#ifdef __GNUC__
|
|
#define UINT16 unsigned short
|
|
#else
|
|
#define UINT16 unsigned short
|
|
#endif
|
|
|
|
#ifndef A2E
|
|
|
|
unsigned char evalptr; /* Points to the empty slot above top of eval stack */
|
|
UINT16 pc; /* Program counter */
|
|
UINT16 sp; /* Stack pointer */
|
|
UINT16 fp; /* Frame pointer */
|
|
UINT16 tempword;
|
|
unsigned short *wordptr;
|
|
unsigned char *byteptr;
|
|
UINT16 evalstack[EVALSTACKSZ]; /* Evaluation stack - 16 bit ints. Addressed by evalptr */
|
|
|
|
#else
|
|
|
|
/*
|
|
* Zero page variables used on Apple II only at present
|
|
*/
|
|
|
|
extern unsigned char evalptr;
|
|
extern UINT16 pc;
|
|
extern UINT16 sp;
|
|
extern UINT16 fp;
|
|
extern UINT16 tempword;
|
|
extern unsigned short *wordptr;
|
|
extern unsigned char *byteptr;
|
|
extern UINT16 evalstack[EVALSTACKSZ];
|
|
|
|
#pragma zpsym ("evalptr");
|
|
#pragma zpsym ("pc");
|
|
#pragma zpsym ("sp");
|
|
#pragma zpsym ("fp");
|
|
#pragma zpsym ("tempword");
|
|
#pragma zpsym ("wordptr");
|
|
#pragma zpsym ("byteptr");
|
|
#pragma zpsym ("evalstack");
|
|
|
|
#endif
|
|
|
|
/*
|
|
* System memory - addressed in bytes.
|
|
* Used for program storage. Addressed by pc.
|
|
* - Programs are stored from address 0 upwards.
|
|
* Used for callstack. Addressed by sp.
|
|
* - Callstack grows down from top of memory.
|
|
*/
|
|
#ifdef __GNUC__
|
|
unsigned char memory[MEMORYSZ];
|
|
#else
|
|
unsigned char *memory = 0;
|
|
#endif
|
|
|
|
/*
|
|
* Macro to ensure efficient code generation on cc65 when accessing memory
|
|
*/
|
|
#ifdef __GNUC__
|
|
#define MEM(x) memory[x]
|
|
#else
|
|
#define MEM(x) (*(unsigned char*)x)
|
|
#endif
|
|
|
|
#define XREG evalstack[evalptr - 1] /* Only valid if evalptr >= 1 */
|
|
#define YREG evalstack[evalptr - 2] /* Only valid if evalptr >= 2 */
|
|
#define ZREG evalstack[evalptr - 3] /* Only valid if evalptr >= 3 */
|
|
#define TREG evalstack[evalptr - 4] /* Only valid if evalptr >= 4 */
|
|
|
|
/*
|
|
* Error checks are called through macros to make it easy to
|
|
* disable them in production. We should not need these checks
|
|
* in production (assuming no bugs in the compiler!) ... but they
|
|
* are helpful for debugging!
|
|
*/
|
|
|
|
#ifdef STACKCHECKS
|
|
|
|
/* Check evaluation stack is not going to underflow */
|
|
#define CHECKUNDERFLOW(level) checkunderflow(level)
|
|
|
|
/* Check evaluation stack is not going to overflow */
|
|
#define CHECKOVERFLOW() checkoverflow()
|
|
|
|
/* Check call stack is not going to underflow */
|
|
#define CHECKSTACKUNDERFLOW(bytes) checkstackunderflow(bytes)
|
|
|
|
/* Check call stack is not going to overflow */
|
|
#define CHECKSTACKOVERFLOW(bytes) checkstackoverflow(bytes)
|
|
|
|
#else
|
|
|
|
/* For production use, do not do these checks */
|
|
#define CHECKUNDERFLOW(level)
|
|
#define CHECKOVERFLOW()
|
|
#define CHECKSTACKUNDERFLOW(bytes)
|
|
#define CHECKSTACKOVERFLOW(bytes)
|
|
#endif
|
|
|
|
/* Handler for unsupported bytecode */
|
|
#define UNSUPPORTED() unsupported()
|
|
|
|
#ifdef STACKCHECKS
|
|
/*
|
|
* Check for evaluation stack underflow.
|
|
* level - Number of 16 bit operands required on eval stack.
|
|
*/
|
|
void checkunderflow(unsigned char level)
|
|
{
|
|
if (evalptr < level) {
|
|
print("Eval stack underflow\nPC=");
|
|
printhex(pc);
|
|
printchar('\n');
|
|
while (1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check evaluation stack is not going to overflow.
|
|
* Assumes evalptr has already been advanced.
|
|
*/
|
|
void checkoverflow()
|
|
{
|
|
if (evalptr > EVALSTACKSZ - 1) {
|
|
print("Eval stack overflow\nPC=");
|
|
printhex(pc);
|
|
printchar('\n');
|
|
while (1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check call stack is not going to underflow.
|
|
* bytes - Number of bytes required on call stack.
|
|
*/
|
|
void checkstackunderflow(unsigned char bytes)
|
|
{
|
|
if ((MEMORYSZ - sp) < bytes) {
|
|
print("Call stack underflow\nPC=");
|
|
printhex(pc);
|
|
printchar('\n');
|
|
while (1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check call stack is not going to overflow.
|
|
* Assumes sp has already been advanced.
|
|
*/
|
|
void checkstackoverflow()
|
|
{
|
|
if (sp < CALLSTACKLIM + 1) {
|
|
print("Call stack overflow\nPC=");
|
|
printhex(pc);
|
|
printchar('\n');
|
|
while (1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Handler for unsupported bytecodes
|
|
*/
|
|
void unsupported()
|
|
{
|
|
print("Unsupported instruction ");
|
|
printhexbyte(MEM(pc));
|
|
print("\nPC=");
|
|
printhex(pc);
|
|
printchar('\n');
|
|
while (1);
|
|
}
|
|
|
|
/*
|
|
* Terminate VM_END
|
|
*/
|
|
void vm_end() {
|
|
if (evalptr > 0) {
|
|
print("WARNING: evalptr ");
|
|
printdec(evalptr);
|
|
printchar('\n');
|
|
}
|
|
#ifdef __GNUC__
|
|
exit(0);
|
|
#else
|
|
for (tempword = 0; tempword < 25000; ++tempword);
|
|
exit(0);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Pushes the following 16 bit word to the evaluation stack
|
|
*/
|
|
void vm_ldimm() {
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
wordptr = (unsigned short *)&MEM(++pc);
|
|
XREG = *wordptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Replaces X with 16 bit value pointed to by X
|
|
*/
|
|
void vm_ldaword() {
|
|
CHECKUNDERFLOW(1);
|
|
wordptr = (unsigned short *)&MEM(XREG);
|
|
XREG = *wordptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - push 16 bit value pointed to by addr after opcode
|
|
*/
|
|
void vm_ldawordimm() {
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
wordptr = (unsigned short *)&MEM(*wordptr); /* Pointer to variable */
|
|
XREG = *wordptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Replaces X with 8 bit value pointed to by X
|
|
*/
|
|
void vm_ldabyte() {
|
|
CHECKUNDERFLOW(1);
|
|
XREG = MEM(XREG);
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - push byte pointed to by addr after opcode
|
|
*/
|
|
void vm_ldabyteimm() {
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
byteptr = (unsigned char *)&MEM(*wordptr); /* Pointer to variable */
|
|
XREG = *byteptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Stores 16 bit value Y in addr pointed to by X. Drops X and Y
|
|
*/
|
|
void vm_staword() {
|
|
CHECKUNDERFLOW(2);
|
|
wordptr = (unsigned short *)&MEM(XREG);
|
|
*wordptr = YREG;
|
|
evalptr -= 2;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - store 16 bit value X in addr after opcode. Drop X
|
|
*/
|
|
void vm_stawordimm() {
|
|
CHECKUNDERFLOW(1);
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
wordptr = (unsigned short *)&MEM(*wordptr); /* Pointer to variable */
|
|
*wordptr = XREG;
|
|
--evalptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Stores 8 bit value Y in addr pointed to by X. Drops X and Y
|
|
*/
|
|
void vm_stabyte() {
|
|
CHECKUNDERFLOW(2);
|
|
MEM(XREG) = YREG;
|
|
evalptr -= 2;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - store 8 bit value X in addr after opcode. Drop X
|
|
*/
|
|
void vm_stabyteimm() {
|
|
CHECKUNDERFLOW(1);
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
byteptr = (unsigned char *)&MEM(*wordptr); /* Pointer to variable */
|
|
*byteptr = XREG;
|
|
--evalptr;
|
|
pc += 2;
|
|
}
|
|
|
|
|
|
/*
|
|
* Replaces X with 16 bit value pointed to by X
|
|
*/
|
|
void vm_ldrword() {
|
|
CHECKUNDERFLOW(1);
|
|
#ifdef __GNUC__
|
|
wordptr = (unsigned short *)&MEM((XREG + fp + 1) & 0xffff);
|
|
#else
|
|
tempword = XREG + fp + 1;
|
|
wordptr = (unsigned short *)&MEM(tempword);
|
|
#endif
|
|
XREG = *wordptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - push 16 bit value pointed to by addr after opcode
|
|
*/
|
|
void vm_ldrwordimm() {
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
#ifdef __GNUC__
|
|
wordptr = (unsigned short *)&MEM((*wordptr + fp + 1) & 0xffff); /* Pointer to variable */
|
|
#else
|
|
tempword = *wordptr + fp + 1;
|
|
wordptr = (unsigned short *)&MEM(tempword); /* Pointer to variable */
|
|
#endif
|
|
XREG = *wordptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Replaces X with 8 bit value pointed to by X.
|
|
*/
|
|
void vm_ldrbyte() {
|
|
CHECKUNDERFLOW(1);
|
|
#ifdef __GNUC__
|
|
XREG = MEM((XREG + fp + 1) & 0xffff);
|
|
#else
|
|
XREG = MEM(XREG + fp + 1);
|
|
#endif
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - push byte pointed to by addr after opcode
|
|
*/
|
|
void vm_ldrbyteimm() {
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
#ifdef __GNUC__
|
|
byteptr = (unsigned char *)&MEM((*wordptr + fp + 1) & 0xffff); /* Pointer to variable */
|
|
#else
|
|
tempword = *wordptr + fp + 1;
|
|
byteptr = (unsigned char *)&MEM(tempword); /* Pointer to variable */
|
|
#endif
|
|
XREG = *byteptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Stores 16 bit value Y in addr pointed to by X. Drops X and Y
|
|
*/
|
|
void vm_strword() {
|
|
CHECKUNDERFLOW(2);
|
|
#ifdef __GNUC__
|
|
wordptr = (unsigned short *)&MEM((XREG + fp + 1) & 0xffff);
|
|
#else
|
|
tempword = XREG + fp + 1;
|
|
wordptr = (unsigned short *)&MEM(tempword);
|
|
#endif
|
|
*wordptr = YREG;
|
|
evalptr -= 2;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - store 16 bit value X in addr after opcode. Drop X
|
|
*/
|
|
void vm_strwordimm() {
|
|
CHECKUNDERFLOW(1);
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
#ifdef __GNUC__
|
|
wordptr = (unsigned short *)&MEM((*wordptr + fp + 1) & 0xffff); /* Pointer to variable */
|
|
#else
|
|
tempword = *wordptr + fp + 1;
|
|
wordptr = (unsigned short *)&MEM(tempword); /* Pointer to variable */
|
|
#endif
|
|
*wordptr = XREG;
|
|
--evalptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Stores 8 bit value Y in addr pointed to by X. Drops X and Y
|
|
*/
|
|
void vm_strbyte() {
|
|
CHECKUNDERFLOW(2);
|
|
#ifdef __GNUC__
|
|
MEM((XREG + fp + 1) & 0xffff) = YREG;
|
|
#else
|
|
tempword = XREG + fp + 1;
|
|
MEM(tempword) = YREG;
|
|
#endif
|
|
evalptr -= 2;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - store 8 bit value X in addr after opcode. Drop X
|
|
*/
|
|
void vm_strbyteimm() {
|
|
CHECKUNDERFLOW(1);
|
|
wordptr = (unsigned short *)&MEM(++pc); /* Pointer to operand */
|
|
#ifdef __GNUC__
|
|
byteptr = (unsigned char *)&MEM((*wordptr + fp + 1) & 0xffff); /* Pointer to variable */
|
|
#else
|
|
tempword = *wordptr + fp + 1;
|
|
byteptr = (unsigned char *)&MEM(tempword); /* Pointer to variable */
|
|
#endif
|
|
*byteptr = XREG;
|
|
--evalptr;
|
|
pc += 2;
|
|
}
|
|
|
|
/*
|
|
* Swaps X and Y
|
|
*/
|
|
void vm_swap() {
|
|
CHECKUNDERFLOW(2);
|
|
tempword = XREG;
|
|
XREG = YREG;
|
|
YREG = tempword;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Duplicates X -> X, Y
|
|
*/
|
|
void vm_dup() {
|
|
CHECKUNDERFLOW(1);
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
XREG = YREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Duplicates X -> X,Z; Y -> Y,T
|
|
*/
|
|
void vm_dup2() {
|
|
CHECKUNDERFLOW(2);
|
|
evalptr += 2;
|
|
CHECKOVERFLOW();
|
|
XREG = ZREG;
|
|
YREG = TREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Drops X
|
|
*/
|
|
void vm_drop() {
|
|
CHECKUNDERFLOW(1);
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Duplicates Y -> X,Z
|
|
*/
|
|
void vm_over() {
|
|
CHECKUNDERFLOW(2);
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
XREG = ZREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Duplicates stack level specified in X+1 -> X
|
|
*/
|
|
void vm_pick() {
|
|
CHECKUNDERFLOW(XREG + 1);
|
|
XREG = evalstack[evalptr - (XREG + 1)];
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Pop 16 bit value from call stack, push onto eval stack [X]
|
|
*/
|
|
void vm_popword() {
|
|
CHECKSTACKUNDERFLOW(2);
|
|
sp += 2;
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
#ifdef __GNUC__
|
|
wordptr = (unsigned short *)&MEM(sp - 1);
|
|
#else
|
|
tempword = sp - 1;
|
|
wordptr = (unsigned short *)&MEM(tempword);
|
|
#endif
|
|
XREG = *wordptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Pop 8 bit value from call stack, push onto eval stack [X]
|
|
*/
|
|
void vm_popbyte() {
|
|
CHECKSTACKUNDERFLOW(1);
|
|
++sp;
|
|
++evalptr;
|
|
CHECKOVERFLOW();
|
|
XREG = MEM(sp);
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Push 16 bit value in X onto call stack. Drop X
|
|
*/
|
|
void vm_pshword() {
|
|
CHECKUNDERFLOW(1);
|
|
byteptr = (unsigned char *)&XREG;
|
|
MEM(sp--) = *(byteptr + 1);
|
|
CHECKSTACKOVERFLOW();
|
|
MEM(sp--) = *byteptr;
|
|
CHECKSTACKOVERFLOW();
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Push 8 bit value in X onto call stack. Drop X
|
|
*/
|
|
void vm_pshbyte() {
|
|
CHECKUNDERFLOW(1);
|
|
MEM(sp--) = XREG & 0x00ff;
|
|
CHECKSTACKOVERFLOW();
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Discard X bytes from call stack. Drop X
|
|
*/
|
|
void vm_discard() {
|
|
CHECKUNDERFLOW(1);
|
|
sp += XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Copy stack pointer to frame pointer. (Enter function scope)
|
|
*/
|
|
void vm_sptofp() {
|
|
/* Push old FP to stack */
|
|
byteptr = (unsigned char *)&fp;
|
|
MEM(sp--) = *(byteptr + 1);
|
|
CHECKSTACKOVERFLOW();
|
|
MEM(sp--) = *byteptr;
|
|
CHECKSTACKOVERFLOW();
|
|
fp = sp;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Copy frame pointer to stack pointer. (Release local vars)
|
|
*/
|
|
void vm_fptosp() {
|
|
sp = fp;
|
|
/* Pop old FP from stack -> FP */
|
|
CHECKSTACKUNDERFLOW(2);
|
|
sp += 2;
|
|
CHECKOVERFLOW();
|
|
#ifdef __GNUC__
|
|
wordptr = (unsigned short *)&MEM(sp - 1);
|
|
#else
|
|
tempword = sp - 1;
|
|
wordptr = (unsigned short *)&MEM(tempword);
|
|
#endif
|
|
fp = *wordptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Convert absolute address in X to relative address
|
|
*/
|
|
void vm_ator() {
|
|
#ifdef __GNUC__
|
|
XREG = (XREG - fp - 1) & 0xffff;
|
|
#else
|
|
XREG = XREG - fp - 1;
|
|
#endif
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Convert relative address in X to absolute address
|
|
*/
|
|
void vm_rtoa() {
|
|
#ifdef __GNUC__
|
|
XREG = (XREG + fp + 1) & 0xffff;
|
|
#else
|
|
XREG = XREG + fp + 1;
|
|
#endif
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = X+1
|
|
*/
|
|
void vm_inc() {
|
|
CHECKUNDERFLOW(1);
|
|
++XREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = X-1
|
|
*/
|
|
void vm_dec() {
|
|
CHECKUNDERFLOW(1);
|
|
--XREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y+X. Y is dropped
|
|
*/
|
|
void vm_add() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG + XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y-X. Y is dropped
|
|
*/
|
|
void vm_sub() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG - XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y*X. Y is dropped
|
|
*/
|
|
void vm_mul() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG * XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y/X. Y is dropped
|
|
*/
|
|
void vm_div() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG / XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y%X. Y is dropped
|
|
*/
|
|
void vm_mod() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG % XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = -X
|
|
*/
|
|
void vm_neg() {
|
|
CHECKUNDERFLOW(1);
|
|
XREG = -XREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y>X. Y is dropped
|
|
*/
|
|
void vm_gt() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG > XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y>=X. Y is dropped
|
|
*/
|
|
void vm_gte() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG >= XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y<X. Y is dropped
|
|
*/
|
|
void vm_lt() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG < XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y<=X. Y is dropped
|
|
*/
|
|
void vm_lte() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG <= XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y==X. Y is dropped
|
|
*/
|
|
void vm_eql() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG == XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y!=X. Y is dropped
|
|
*/
|
|
void vm_neql() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG != XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y&&X. Y is dropped
|
|
*/
|
|
void vm_and() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG && XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y||X. Y is dropped
|
|
*/
|
|
void vm_or() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG || XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = !X
|
|
*/
|
|
void vm_not() {
|
|
CHECKUNDERFLOW(1);
|
|
XREG = !XREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y&X. Y is dropped
|
|
*/
|
|
void vm_bitand() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG & XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y|X. Y is dropped
|
|
*/
|
|
void vm_bitor() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG | XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y^X. Y is dropped
|
|
*/
|
|
void vm_bitxor() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG ^ XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = ~X
|
|
*/
|
|
void vm_bitnot() {
|
|
CHECKUNDERFLOW(1);
|
|
XREG = ~XREG;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y<<X. Y is dropped
|
|
*/
|
|
void vm_lsh() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG << XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* X = Y>>X. Y is dropped
|
|
*/
|
|
void vm_rsh() {
|
|
CHECKUNDERFLOW(2);
|
|
YREG = YREG >> XREG;
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Jump to address X. Drop X
|
|
*/
|
|
void vm_jmp() {
|
|
CHECKUNDERFLOW(1);
|
|
pc = XREG;
|
|
--evalptr;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - jump to 16 bit word following opcode
|
|
*/
|
|
void vm_jmpimm() {
|
|
wordptr = (unsigned short *)&MEM(++pc);
|
|
pc = *wordptr;
|
|
}
|
|
|
|
/*
|
|
* If Y!= 0, jump to address X. Drop X, Y
|
|
*/
|
|
void vm_brnch() {
|
|
CHECKUNDERFLOW(2);
|
|
if (YREG) {
|
|
pc = XREG;
|
|
} else {
|
|
++pc;
|
|
}
|
|
evalptr -= 2;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - if X!=0 branch to 16 bit word following opcode
|
|
*/
|
|
void vm_brnchimm() {
|
|
wordptr = (unsigned short *)&MEM(++pc);
|
|
CHECKUNDERFLOW(1);
|
|
if (XREG) {
|
|
pc = *wordptr;
|
|
} else {
|
|
pc += 2;
|
|
}
|
|
--evalptr;
|
|
}
|
|
|
|
/*
|
|
* Push PC to call stack. Jump to address X. Drop X
|
|
*/
|
|
void vm_jsr() {
|
|
CHECKUNDERFLOW(1);
|
|
byteptr = (unsigned char *) &pc;
|
|
MEM(sp) = *(byteptr + 1);
|
|
--sp;
|
|
CHECKSTACKOVERFLOW();
|
|
MEM(sp) = *byteptr;
|
|
--sp;
|
|
CHECKSTACKOVERFLOW();
|
|
pc = XREG;
|
|
--evalptr;
|
|
}
|
|
|
|
/*
|
|
* Imm mode - push PC to call stack, jump to 16 bit word
|
|
*/
|
|
void vm_jsrimm() {
|
|
wordptr = (unsigned short *)&MEM(++pc);
|
|
++pc;
|
|
byteptr = (unsigned char *) &pc;
|
|
MEM(sp) = *(byteptr + 1);
|
|
--sp;
|
|
CHECKSTACKOVERFLOW();
|
|
MEM(sp) = *byteptr;
|
|
--sp;
|
|
CHECKSTACKOVERFLOW();
|
|
pc = *wordptr;
|
|
}
|
|
|
|
/*
|
|
* Pop call stack, jump to the address popped
|
|
*/
|
|
void vm_rts() {
|
|
CHECKSTACKUNDERFLOW(2);
|
|
++sp;
|
|
wordptr = (unsigned short *)&MEM(sp);
|
|
pc = *wordptr;
|
|
++sp;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Print 16 bit decimal in X. Drop X
|
|
*/
|
|
void vm_prdec() {
|
|
CHECKUNDERFLOW(1);
|
|
printdec(XREG);
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Print 16 bit hex in X. Drop X
|
|
*/
|
|
void vm_prhex() {
|
|
CHECKUNDERFLOW(1);
|
|
printhex(XREG);
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Print character in X. Drop X
|
|
*/
|
|
void vm_prch() {
|
|
CHECKUNDERFLOW(1);
|
|
printchar((unsigned char) XREG);
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Print null terminated string pointed to by X. Drop X
|
|
*/
|
|
void vm_prstr() {
|
|
CHECKUNDERFLOW(1);
|
|
while (MEM(XREG)) {
|
|
printchar(MEM(XREG++));
|
|
}
|
|
--evalptr;
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Print literal string at PC (null terminated)
|
|
*/
|
|
void vm_prmsg() {
|
|
++pc;
|
|
while (MEM(pc)) {
|
|
printchar(MEM(pc++));
|
|
}
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Push character from keyboard onto eval stack
|
|
*/
|
|
void vm_kbdch() {
|
|
CHECKUNDERFLOW(1);
|
|
++evalptr;
|
|
/* Loop until we get a keypress */
|
|
#ifdef A2E
|
|
while (!(XREG = getkey()));
|
|
#elif defined(CBM)
|
|
while (!(*(char *) XREG = cbm_k_getin()));
|
|
#else
|
|
/* TODO: Unimplemented in Linux */
|
|
XREG = 0;
|
|
#endif
|
|
++pc;
|
|
}
|
|
|
|
/*
|
|
* Obtain line from keyboard and write to memory pointed to by
|
|
* Y. X contains the max number of bytes in buf. Drop X, Y
|
|
*/
|
|
void vm_kbdln() {
|
|
CHECKUNDERFLOW(2);
|
|
getln((char *) &MEM(YREG), XREG);
|
|
evalptr -= 2;
|
|
++pc;
|
|
}
|
|
|
|
typedef void (*func)(void);
|
|
|
|
/*
|
|
* Must be in same order as enum bytecode.
|
|
*/
|
|
func jumptbl[] = {
|
|
vm_end,
|
|
vm_ldimm,
|
|
vm_ldaword,
|
|
vm_ldawordimm,
|
|
vm_ldabyte,
|
|
vm_ldabyteimm,
|
|
vm_staword,
|
|
vm_stawordimm,
|
|
vm_stabyte,
|
|
vm_stabyteimm,
|
|
vm_ldrword,
|
|
vm_ldrwordimm,
|
|
vm_ldrbyte,
|
|
vm_ldrbyteimm,
|
|
vm_strword,
|
|
vm_strwordimm,
|
|
vm_strbyte,
|
|
vm_strbyteimm,
|
|
vm_swap,
|
|
vm_dup,
|
|
vm_dup2,
|
|
vm_drop,
|
|
vm_over,
|
|
vm_pick,
|
|
vm_popword,
|
|
vm_popbyte,
|
|
vm_pshword,
|
|
vm_pshbyte,
|
|
vm_discard,
|
|
vm_sptofp,
|
|
vm_fptosp,
|
|
vm_ator,
|
|
vm_rtoa,
|
|
vm_inc,
|
|
vm_dec,
|
|
vm_add,
|
|
vm_sub,
|
|
vm_mul,
|
|
vm_div,
|
|
vm_mod,
|
|
vm_neg,
|
|
vm_gt,
|
|
vm_gte,
|
|
vm_lt,
|
|
vm_lte,
|
|
vm_eql,
|
|
vm_neql,
|
|
vm_and,
|
|
vm_or,
|
|
vm_not,
|
|
vm_bitand,
|
|
vm_bitor,
|
|
vm_bitxor,
|
|
vm_bitnot,
|
|
vm_lsh,
|
|
vm_rsh,
|
|
vm_jmp,
|
|
vm_jmpimm,
|
|
vm_brnch,
|
|
vm_brnchimm,
|
|
vm_jsr,
|
|
vm_jsrimm,
|
|
vm_rts,
|
|
vm_prdec,
|
|
vm_prhex,
|
|
vm_prch,
|
|
vm_prstr,
|
|
vm_prmsg,
|
|
vm_kbdch,
|
|
vm_kbdln};
|
|
|
|
/*
|
|
* Fetch, decode and execute a VM instruction.
|
|
* Advance program counter and loop until VM_END.
|
|
*/
|
|
void execute()
|
|
{
|
|
#ifdef DEBUGREGS
|
|
unsigned short i;
|
|
#endif
|
|
|
|
evalptr = 0;
|
|
pc = RTPCSTART;
|
|
sp = fp = RTCALLSTACKTOP;
|
|
|
|
while (1) {
|
|
|
|
//print("--->PC "); printhex(pc); print(" eval stk: "); printhex(evalptr); print("\n");
|
|
#ifdef DEBUGREGS
|
|
print("\n");
|
|
print("--->PC ");
|
|
printhex(pc);
|
|
print("\n");
|
|
print("--->SP ");
|
|
printhex(sp);
|
|
print("\n");
|
|
print("--->FP ");
|
|
printhex(fp);
|
|
print("\n");
|
|
print("Call Stk: ");
|
|
for (i = sp + 1; i <= RTCALLSTACKTOP; ++i) {
|
|
printhexbyte(MEM(i));
|
|
printchar(' ');
|
|
}
|
|
print("\nEval Stk: ");
|
|
printhex(XREG);
|
|
printchar(' ');
|
|
printhex(YREG);
|
|
printchar(' ');
|
|
printhex(ZREG);
|
|
printchar(' ');
|
|
printhex(TREG);
|
|
printchar('\n');
|
|
#endif
|
|
|
|
#ifdef DEBUG
|
|
#ifdef A2E
|
|
printchar('\r');
|
|
#else
|
|
printchar('\n');
|
|
#endif
|
|
printhex(pc);
|
|
print(": ");
|
|
print(bytecodenames[MEM(pc)]);
|
|
/* TODO: Should encode immediate mode as one bit of opcode to make this more efficient */
|
|
if ((MEM(pc) == VM_LDIMM) ||
|
|
(MEM(pc) == VM_LDAWORDIMM) ||
|
|
(MEM(pc) == VM_LDABYTEIMM) ||
|
|
(MEM(pc) == VM_LDRWORDIMM) ||
|
|
(MEM(pc) == VM_LDRBYTEIMM) ||
|
|
(MEM(pc) == VM_STAWORDIMM) ||
|
|
(MEM(pc) == VM_STABYTEIMM) ||
|
|
(MEM(pc) == VM_STRWORDIMM) ||
|
|
(MEM(pc) == VM_STRBYTEIMM) ||
|
|
(MEM(pc) == VM_JMPIMM) ||
|
|
(MEM(pc) == VM_BRNCHIMM) ||
|
|
(MEM(pc) == VM_JSRIMM)) {
|
|
printchar(' ');
|
|
wordptr = (unsigned short *)&MEM(pc + 1);
|
|
printhex(*wordptr);
|
|
printchar(' ');
|
|
} else {
|
|
print(" ");
|
|
}
|
|
#ifdef EXTRADEBUG
|
|
print("stk: ");
|
|
if (evalptr >= 1) {
|
|
printhex(XREG);
|
|
}
|
|
if (evalptr >= 2) {
|
|
print(", ");
|
|
printhex(YREG);
|
|
}
|
|
if (evalptr >= 3) {
|
|
print(", ");
|
|
printhex(ZREG);
|
|
}
|
|
if (evalptr >= 4) {
|
|
print(", ");
|
|
printhex(TREG);
|
|
}
|
|
if (evalptr >= 5) {
|
|
print(", ");
|
|
printhex(evalstack[evalptr - 5]);
|
|
}
|
|
if (evalptr >= 6) {
|
|
print(" ...");
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
jumptbl[MEM(pc)]();
|
|
|
|
}
|
|
};
|
|
|
|
/*
|
|
* Load bytecode into memory[].
|
|
*/
|
|
void load()
|
|
{
|
|
FILE *fp;
|
|
char ch;
|
|
char *p = (char*)&MEM(RTPCSTART);
|
|
|
|
pc = RTPCSTART;
|
|
do {
|
|
#ifndef VIC20
|
|
/* TODO: Not sure why getln() is blowing up on VIC20 */
|
|
print("\nBytecode file (CR for default)>");
|
|
getln(p, 15);
|
|
#else
|
|
*p = 0;
|
|
#endif
|
|
if (strlen(p) == 0) {
|
|
strcpy(p, "bytecode");
|
|
}
|
|
print("Loading '");
|
|
print(p);
|
|
print("'\n");
|
|
fp = fopen(p, "r");
|
|
} while (!fp);
|
|
while (!feof(fp)) {
|
|
ch = fgetc(fp);
|
|
MEM(pc++) = ch;
|
|
/* Print dot for each page */
|
|
if (pc%0xff == 0) {
|
|
printchar('.');
|
|
}
|
|
}
|
|
fclose(fp);
|
|
pc = RTPCSTART;
|
|
#ifdef A2E
|
|
printchar(7);
|
|
#endif
|
|
}
|
|
|
|
int main()
|
|
{
|
|
print("EightBallVM v" VERSIONSTR "\n");
|
|
#ifdef STACKCHECKS
|
|
print("[Stack Checks ON]\n");
|
|
#endif
|
|
print("(c)Bobbi, 2018\n");
|
|
print("Free Software.\n");
|
|
print("Licenced under GPL.\n\n");
|
|
|
|
load();
|
|
#ifdef __GNUC__
|
|
print(" Done.\n\n");
|
|
#endif
|
|
#ifdef A2E
|
|
videomode(VIDEOMODE_80COL);
|
|
clrscr();
|
|
#elif defined(CBM)
|
|
printchar(147); /* Clear */
|
|
#endif
|
|
execute();
|
|
return 0;
|
|
}
|