I found this incomplete code on my disk that I worked on several months
ago but didn't finish. But I still think it's pretty cool, and with all
the other "meta" threads, I thought I might get some useful feedback or
at the very least some entertaining insults.
First is a header file which implements a macro assembler for 8086
machine code in C preprocessor macros.
$ cat asm8086.h
#define LEA(to,m,r,r_m) to+0x8d,MRM(m,r,r_m)
#define MOV(to,m,r,r_m) to+0x8b,MRM(m,r,r_m)
#define ADD(to,m,r,r_m) to+0x03,MRM(m,r,r_m)
#define SUB(to,m,r,r_m) to+0x2b,MRM(m,r,r_m)
#define F -2
#define MRM(m,r,r_m) 0##m##r##r_m
#define Z 0
#define B 1
#define W 2
#define R 3
#define AX 0
#define CX 1
#define DX 2
#define BX 3
#define SP 4
#define BP 5
#define SI 6
#define DI 7
#define BX_SI 0
#define BX_DI 1
#define BP_SI 2
#define BP_DI 3
#define SI_ 4
#define DI_ 5
#define BP_ 6
#define BX_ 7
#define TEST(m,r,r_m) 0x85,MRM(m,r,r_m)
#define IMUL(m, r_m) 0xf7,MRM(m,5,r_m)
#define INC_(m, r_m) 0xff,MRM(m,0,r_m)
#define DEC_(m, r_m) 0xff,MRM(m,1,r_m)
#define JMP_(m, r_m) 0xff,MRM(m,5,r_m)
#define POP(r) 0x58+r
#define PUSH(r) 0x50+r
#define ADDAX 0x05
#define LODS 0xAD
#define JZ 0x74
#define HALT 0xF4
//eof
Next is the macros which build the dictionary in memory.
$ cat fdict.h
typedef unsigned char UC;
typedef unsigned short US;
enum { MAX_NAME = 8, MAX_CODE_PARAM = 20, MAX_WORD_PARAM = 10 };
struct code_entry {
US link;
UC name_len;
UC name[ MAX_NAME ];
US code;
UC param[ MAX_CODE_PARAM ];
};
struct word_entry {
US link;
UC name_len;
UC name[ MAX_NAME ];
US code;
US param[ MAX_WORD_PARAM ];
}l
#define CODE(n, e, ...) \
const US c_ ## e = P_PARAM_PTR; \
{ \
code_entry x = { \
.link = - sizeof x, \
.name_len = sizeof( # n ) - 1, \
.name = # n , \
.code = P_PARAM_PTR, \
.param = { __VA_ARGS__ , NEXT } \
}; \
memcpy( p, x, sizeof x ); \
p += sizeof x; \
} \
/*end CODE()*/
#define P_PARAM_PTR ( p - start ) + offset_of( code_entry, param )
#define WORD(n, e, ...) \
const US c_ ## e = P_CODE_PTR; \
{ \
word_entry x = { \
.link = - sizeof x, \
.name_len = sizeof( # n ) - 1, \
.name = # n , \
.code = c_enter, \
.param = { __VA_ARGS__ , c_exit } \
}; \
memcpy( p, x, sizeof x ); \
p += sizeof x; \
} \
/*end WORD()*/
#define P_CODE_PTR ( p - start ) + offset_of( word_entry, code )
//eof
It's not quite as transparent as I'd ideally like. The fixed MAX
sizes that I have to manually maintain. But it's close.
Next is the function which uses these macros to build the
memory image. Missing lots of higher functionality, especially
the inner interpreter loop and text interpreter routines.
$ cat forth3.h
#include "asm8086.h"
#include "fdict.h"
/* W = BX
IP = SI
PSP = SP
RSP = BP
X = AX
TOS_in_memory */
#define NEXT LODS, JMP_(R,AX)
#define PUSHRSP(r) LEA(,B,BP,BP_),minus(4), MOV(F,B,r,BP_),0
#define POPRSP(r) MOV(,B,r,BP_),0, LEA(,B,BP,BP_),4
#define minus(x) 1+(0xff^x)
static inline int
forth(char *start){
char *p = start;
{ UC x[] = { CALL, 00 }; memcpy( p, x, sizeof x ); p += 16; }
CODE(enter, enter, PUSHRSP(SI), ADDAX,4,0, MOV(,R,DI,AX))
CODE(exit, exit, POPRSP(SI))
CODE(lit, lit, LODS, PUSH(AX))
CODE(drop, drop, POP(AX))
CODE(swap, swap, POP(AX), POP(BX), PUSH(AX), PUSH(BX))
CODE(dup, dup, MOV(,R,BX,SP), MOV(,B,AX,BX_),0, PUSH(AX))
CODE(over, over, MOV(,R,BX,SP), MOV(,B,AX,BX_),2, PUSH(AX))
CODE(rot, rot, POP(AX), POP(BX), POP(CX), PUSH(BX), PUSH(AX), PUSH(CX))
CODE(-rot, nrot, POP(AX), POP(BX), POP(CX), PUSH(AX), PUSH(CX), PUSH(BX))
CODE(2drop, 2drop, POP(AX), POP(AX))
CODE(2dup, 2dup, MOV(,R,BX,SP), MOV(,B,AX,BX_),0, MOV(,B,CX,BX_),2, PUSH(AX), PUSH(CX))
CODE(1+, oneplus, MOV(,R,BX,SP), INC_(B,BX_),0)
CODE(1-, oneminus, MOV(,R,BX,SP), DEC_(B,BX_),0)
CODE(+, plus, POP(AX), MOV(,R,BX,SP), ADD(F,B,AX,BX_),0)
CODE(-, minus, POP(AX), MOV(,R,BX,SP), SUB(F,B,AX,BX_),0)
CODE(*, star, POP(AX), POP(BX), IMUL(R,BX), PUSH(AX))
CODE(!, bang, POP(BX), POP(AX), MOV(F,Z,AX,BX_))
CODE(@, at, POP(BX), MOV(,Z,AX,BX_), PUSH(AX))
CODE(+!, plusbang, POP(BX), POP(AX), ADD(F,Z,AX,BX_))
CODE(-!, minusbang, POP(BX), POP(AX), SUB(F,Z,AX,BX_))
CODE(bye, bye, HALT)
WORD(double, double, c_dup, c_plus)
WORD(dubdub, dubdub, c_double, c_double)
return 0;
}
//eof
These are all intended to work with my 8086 emulator, also in C.
https://github.com/luser-dr00g/8086
Materials consulted in writing this so far:
http://www.forth.org/fig-forth/fig-forth_8086-8088_ver_10.pdf
http://forthfiles.net/ting/sysguidefig.pdf
ftp://ftp.oldskool.org/pub/misc/temp/8086_family_Users_Manual.pdf
Any comments welcome. What should I work on next, or fix, or add?