code: 9ferno

Download patch

ref: 121d65ecf2a75d9c130db01c721d4bf38e9311fa
parent: 7c4e9c492a69a964b6873a6f5e3a90fcc5ece23a
author: 9ferno <gophone2015@gmail.com>
date: Thu Nov 18 05:54:39 EST 2021

forth assembly files

--- a/os/pc64/ff.s
+++ /dev/null
@@ -1,617 +1,0 @@
-#include "mem.h"
-
-/*
-
-The bigger goal is to replace the dis vm with ff
-ff outputs to screen now.
-But, the input needs to be fixed.
-make this into a devff like device that reads commands and outputs the result.
-replace variable with value (as in open firmware), to avoid exposing addresses
-
- ff kernel, amd64 9front variant
-
- Register usage:
-
-	Original usage
- TOS: AX top of stack register
- SP:  SP parameter stack pointer, grows towards lower memory (downwards)
- RP:  BP (= RARG) return stack pointer, grows towards higher memory (upwards)
- AP:  SI address pointer
- W:   DI work register (holds CFA)
- 	BX, CX, DX, R8-R15 temporary registers
-
-plan9 assembler puts the first argument in BP (RARG), return value in AX.
-
-	Changed to
- Leaving AX, SP, BP (RARG) alone to not mess with the C environment
-
- TOS: BX top of stack register
- PSP: DX parameter stack pointer, grows towards lower memory (downwards)
- RSP: R8 return stack pointer, grows towards higher memory (upwards)
- IP:  R9 instruction pointer
- W:   R10 work register (holds CFA)
-	CX, SI, DI, R11-R15 temporary registers
-
-coding standard
-: <name> (S input-stack --- output-stack) (R --- )
-	(G descriptive glossary text)
-	f1 f2  ( interim stack picture) \ programmers explanatory comment
-		.. fn ;
-
-Memory map:
-Return stack 4096 bytes at FFSTART
-	|
-	|
-	v (grows downwards)
-Others 4096 bytes
-	system variables
-	word buffer
-	tib, text input buffer
-Parameter stack 4096 bytes at FFEND-4096
-	^ (grows upwards)
-	|
-	|
-SSTACK_END = FFEND
-*/
-
-#define SSTACK_SIZE 4096
-#define RSTACK_SIZE 4096
-#define	LAST $centry_c_boot(SB) /* last defined word, should generate this */
-#define	SSTACK_END FFEND
-
-/* putting this above the asm code as the v_dp define is needed by _main */
-/*	m_ for primitive/macro word cfa
-	mc_ for primtive/macro word constants
-	c_ for colon word cfa
-	ci_ for colon immediate word cfa
-	v_ for colon variable word cfa
- */
-#include "primitives.s"
-
-#define PUSHALL \
-	PUSHQ	R13; \
-	PUSHQ	R12; \
-	PUSHQ	R11; \
-	PUSHQ	R10; \
-	PUSHQ	R9; \
-	PUSHQ	R8; \
-	PUSHQ	R8; \
-	PUSHQ	R10; \
-	PUSHQ	R9; \
-	PUSHQ	DX; \
-	PUSHQ	CX; \
-	PUSHQ	BX; \
-	PUSHQ	TOS;
-#define POPALL \
-	POPQ	TOS; \
-	POPQ	BX; \
-	POPQ	CX; \
-	POPQ	DX; \
-	POPQ	R9; \
-	POPQ	R10; \
-	POPQ	R8; \
-	POPQ	R8; \
-	POPQ	R9; \
-	POPQ	R10; \
-	POPQ	R11; \
-	POPQ	R12; \
-	POPQ	R13;
-#define PUSHREGS \
-	PUSHQ	R8; \
-	PUSHQ	R10; \
-	PUSHQ	R9; \
-	PUSHQ	TOS;
-#define POPREGS \
-	POPQ	TOS; \
-	POPQ	R9; \
-	POPQ	R10; \
-	POPQ	R8;
-
-#define FF_TO_C_0 \
-	PUSHREGS; \
-	MOVQ DX, ffsp<>(SB); \
-	MOVQ csp<>(SB), DX; \
-	POPREGS;
-
-#define FF_TO_C_1 \
-	MOVQ TOS, BX; \
-	POPQ TOS; /* drop TOS from the parameter stack */ \
-	FF_TO_C_0 \
-	MOVQ BX, R8; /* 1st argument in R8 == RARG */
-
-/* ( 1st_parameter 2nd_parameter -- ) */
-#define FF_TO_C_2 /* for calling a c function with 2 parameters */ \
-	MOVQ TOS, CX; \
-	POPQ TOS; \
-	FF_TO_C_1 \
-	MOVQ CX, 8(DX) \
-
-/* ( 1st_parameter 2nd_parameter 3rd_parameter -- ) */
-#define FF_TO_C_3 /* for calling a c function with 3 parameters */ \
-	MOVQ TOS, DX; \
-	POPQ TOS; \
-	FF_TO_C_2 \
-	MOVQ DX, 16(DX) \
-
-/* no arguments when calling ff from C, for now */
-#define C_TO_FF \
-	PUSHREGS; \
-	MOVQ DX, csp<>(SB); \
-	MOVQ ffsp<>(SB), DX; \
-	POPREGS;
-
-TEXT	ffmain(SB), 1, $-4		/* _main(SB), 1, $-4 without the libc */
-	/* The last dictionary entry address is stored in dtop.
-	 * The location of dtop is stored in the variable dp.
-	 * To get the location of dtop, get the value in the parameter field
-	 * (link + name(1+2) + code field address = 24 bytes) of the dp
-	 * dictionary entry.
-	 */
-	MOVQ $FFEND, PSP	/* setting up stack */
-	/*
-	 * dtop address is stored in the parameter field address(24-32 bytes) of mventry_dp
-	 */
-	MOVQ mventry_dp+24(SB), SI	/* now, SI = dtop address */
-	MOVQ (SI), TOS	/* TOS = *CX = $LAST = boot word address (defined last, stored at dtop) */
-				/* if 6a allows multiple symbols per address, then 
-					the above 3 instructions would have been
-					MOVQ (($mventry_dp+24(SB))), TOS */
-	/*
-	 * Could do this instead of the calculations below
-	 * LEAQ 24(TOS), IP
-	 */
-	ADDQ $16, TOS	/* TOS += link (8 bytes) + len (1 byte) + minimum for align to 8 bytes */
-	XORQ CX, CX
-	MOVB 8(SI), CL	/* CL = length of boot name */
-	ADDQ CX, TOS		/* TOS += len */
-	ANDQ $~7, TOS	/* TOS = address of boot's code - 8 bytes */
-	LEAQ 8(TOS), IP	/* IP = L257 = start of boot code = has docol address there
-					 * skipping over docol as we do not need to save the IP
-					 */
-
-/* lodsl could make this simpler. But, this is more comprehensible
-	why not JMP* (W)?
- */
-#define NEXT	MOVQ (IP), W; /* W = Address next to the DOCOL of boot */ \
-		ADDQ $8, IP; /* move IP further = DOCOL address + 16 */ \
-		MOVQ (W), TOS; /* TOS = code field address of the 1st instruction after DOCOL of boot */ \
-		JMP* TOS; /* Start executing that code field address */
-/*
-Address   0     8    16
-aword : docol  40   ...
-Address   40    48
-bword : docol   72
-Address   72    80
-cword : docol  ....
-at docol address, some assembly instruction
-Assume IP = 8
- */
-#define NEXT	MOVQ (IP), W;	/* W = 40, contents of address in IP, some word's code field address */ \
-		MOVQ (W), TOS;	/* TOS = docol, Get the address in the address in IP = code field address */ \
-		ADDQ $8, IP; 	/* move IP further, IP = 16 */ \
-		JMP* TOS; /* Start executing at docol address, JMP* = jump to a non-relative address */
-
-#define PUSH(r)	SUBQ $8, PSP; \
-			MOVQ r, (PSP)
-#define POP(r)	MOVQ (PSP), r; \
-			ADDQ $8, PSP
-
-	NEXT
-
-TEXT	reset(SB), 1, $-4
-	MOVQ $FFSTART, RSP
-	NEXT
-
-TEXT	clear(SB), 1, $-4
-	MOVQ $FFEND, PSP
-	NEXT
-
-TEXT	colon(SB), 1, $-4
-	MOVQ IP,(RSP)
-	ADDQ $8, RSP
-	LEAQ 8(W), IP
-	NEXT
-
-TEXT	exitcolon(SB), 1, $-4
-	SUBQ $8, RSP
-	MOVQ (RSP), IP
-	NEXT
-
-TEXT	dodoes(SB), 1, $-4	/* ( -- a ) */
-	MOVQ IP,(RSP)
-	ADDQ $8,RSP
-	MOVQ 8(W),IP
-	PUSH(TOS)
-	LEAQ 16(W), TOS
-	NEXT
-
-TEXT	jump(SB), 1, $-4	/* ( -- ) */
-	MOVQ (IP),IP
-	NEXT
-
-/* ( f -- ) cjump address
-	if true, skip the address and continue
-	else, go to the address */
-TEXT	cjump(SB), 1, $-4	/* ( f -- ) */
-	MOVQ (IP), TOS	/* get the next address */
-	ADDQ $8, IP	/* move esi beyond that */
-	TESTQ TOS, TOS
-	JNZ .l1		/* if true, move along */
-	MOVQ TOS, IP	/* if false, go to the above address */
-.l1:
-	POP(TOS
-	NEXT
-
-/* TODO change to allow only fetches from a certain memory range */
-TEXT	fetch(SB), 1, $-4	/* ( a -- n) */
-	MOVQ (TOS), TOS
-	NEXT
-
-/* TODO change to allow stores to a certain memory range only */
-TEXT	store(SB), 1, $-4	/* ( n a -- ) */
-	POP(CX)
-	MOVQ CX, (TOS)
-	POP(TOS)
-	NEXT
-
-TEXT	cfetch(SB), 1, $-4	/* ( a -- c ) */
-	XORQ CX, CX
-	MOVB (TOS), CL
-	POP(TOS)
-	NEXT
-
-TEXT	cstore(SB), 1, $-4	/* ( c a -- ) */
-	POP(CX)
-	MOVB CL, (TOS)
-	POP(TOS)
-	NEXT
-
-TEXT	terminate(SB), 1, $-4	/* ( n -- ) */
-	XORQ CX, CX
-	TESTQ TOS, TOS
-	JZ .l2
-	MOVQ $failtext(SB), TOS
-.l2:
-	/* PUSHQ CX */
-	/* SUBQ $8, PSP */	/* dummy retaddr */
-	MOVQ CX, a0+0(FP)	/* address of exit status? status = nil? */
-	MOVQ $8, RARG	/* EXITS */
-	SYSCALL		/* TODO syscall for exit */
-
-TEXT	testfsopen(SB), 1, $-4
-	PUSHQ SI	/* for some reason, the syscall is changing IP and W */
-	PUSHQ BP
-	PUSHQ $0	/* OREAD */
-	PUSHQ $name(SB)
-	PUSHQ $0	/* dummy retaddr */
-	MOVQ $14, RARG	/* open */
-	SYSCALL
-	ADDQ $24, PSP
-	POPQ RSP
-	POPQ IP
-	NEXT
-	NOP
-	NOP
-	NOP
-	NOP
-	NOP
-
-#include "bindings.s"
-
-TEXT	mmap(SB), 1, $-4	/* ( a1 -- a2 ) */
-	MOVQ $-1, TOS	/* unimplemented */
-
-TEXT	variable(SB), 1, $-4	/* ( -- a ) */
-	PUSH(TOS)
-	LEAQ 8(W), TOS
-	NEXT
-
-TEXT	constant(SB), 1, $-4	/* ( -- n ) */
-	PUSH(TOS)
-	MOVQ 8(W), TOS
-	NEXT
-
-TEXT	literal(SB), 1, $-4	/* ( -- n ) */
-	PUSH(TOS)
-	MOVQ (IP), TOS
-	ADDQ $8, IP
-	NEXT
-
-TEXT	sliteral(SB), 1, $-4	/* ( -- a n ) */
-	PUSH(TOS)
-	XORQ TOS,TOS
-	MOVB (IP), BL
-	INCQ IP
-	PUSH(IP)
-	ADDQ TOS, IP
-	ADDQ $7, IP
-	ANDQ $~7, IP
-	NEXT
-
-/* puts the top 2 entries of the data stack in the return stack */
-TEXT	doinit(SB), 1, $-4	/* ( hi lo -- ) */
-	MOVQ TOS, (RSP)
-	POP(TOS)
-	MOVQ TOS, 8(RSP)
-	POP(TOS)
-	ADDQ $16, RSP
-	NEXT
-
-/* not sure if this works, needs testing to follow https://github.com/mark4th/x64
-	check the notes
-	return stack would have
-		current index
-		end index
-	(R lo hi -- )
-	increment lo
-	when hi > lo, go to the address next to doloop
- */
-TEXT	doloop(SB), 1, $-4
-	INCQ -16(RSP)
-doloop1:
-	MOVQ -16(RSP), CX
-	CMPQ CX, -8(RSP)
-	JGE .l4
-	MOVQ (IP), IP
-	NEXT
-.l4:
-	SUBQ $16, RSP
-	ADDQ $8, IP
-	NEXT
-
-TEXT	doploop(SB), 1, $-4	/* ( n -- ) */
-	ADDQ TOS, -16(RSP)
-	POP(TOS)
-	JMP doloop1
-
-TEXT	rfetch(SB), 1, $-4	/* ( -- n ) */
-	PUSH(TOS)
-	MOVQ -8(RSP), TOS
-	NEXT
-
-TEXT	rpush(SB), 1, $-4	/* ( n -- ) */
-	MOVQ TOS,(RSP)
-	POP(TOS)
-	ADDQ $8,RSP
-	NEXT
-
-TEXT	rpop(SB), 1, $-4	/* ( -- n ) */
-	PUSH(TOS)
-	SUBQ $8, RSP
-	MOVQ (RSP), TOS
-	NEXT
-
-TEXT	i(SB), 1, $-4	/* ( -- n ) */
-	PUSH(TOS)
-	MOVQ -16(RSP), TOS
-	NEXT
-
-TEXT	j(SB), 1, $-4	/* ( -- n ) */
-	PUSH(TOS)
-	MOVQ -32(RSP), TOS
-	NEXT
-
-TEXT	plus(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	POP(CX)
-	ADDQ CX, TOS
-	NEXT  
-
-TEXT	minus(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	MOVQ TOS, CX
-	POP(TOS)
-	SUBQ CX, TOS
-	NEXT
-
-TEXT	multiply(SB), 1, $-4	/* ( n1 n2 -- n1*n2 ) */
-	POP(CX)
-	IMULQ CX,TOS
-	NEXT
-
-TEXT	slashmod(SB), 1, $-4	/* ( n1 n2 -- remainder quotient ) n1/n2 */
-	MOVQ (PSP), CX /* CX = n1 */
-	PUSHQ DX /* DX == PSP, store DX and AX as they are used by CDQ and IDIV */
-	PUSHQ AX
-	XORQ DX, DX /* DX = 0 */
-	MOVQ CX, AX /* AX = n1 */
-	CDQ 		/* RAX -> RDX:RAX sign extension */
-	IDIVQ TOS	/* RDX:RAX / TOS => Quotient in RAX, Remainder in RDX */
-	MOVQ DX, CX	/* CX = remainder */
-	MOVQ AX, TOS /* TOS = quotient */
-	POPQ AX
-	POPQ DX
-	MOVQ CX, (PSP) /* -- remainder quotient */
-	NEXT
-
-TEXT	uslashmod(SB), 1, $-4	/* ( u1 u2 -- uremainder uquotient ) */
-	MOVQ (PSP), CX /* CX = n1 */
-	PUSHQ DX /* DX == PSP, store DX and AX as they are used by CDQ and IDIV */
-	PUSHQ AX
-	XORQ DX, DX /* DX = 0 */
-	MOVQ CX, AX /* AX = n1 */
-	IDIVQ TOS	/* RDX:RAX / TOS => Quotient in RAX, Remainder in RDX */
-	MOVQ DX, CX	/* CX = remainder */
-	MOVQ AX, TOS /* TOS = quotient */
-	POPQ AX
-	POPQ DX
-	MOVQ CX, (PSP) /* -- uremainder uquotient */
-	NEXT
-
-	MOVQ TOS, TOS
-	MOVQ (PSP), TOS
-	XORQ PSP, PSP
-	DIVQ TOS
-	MOVQ PSP, (PSP)
-	NEXT
-
-TEXT	binand(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	ANDQ (PSP), TOS
-	ADDQ $8, PSP
-	NEXT
-
-TEXT	binor(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	ORQ (PSP), TOS
-	ADDQ $8, PSP
-	NEXT
-
-TEXT	binxor(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	XORQ (PSP), TOS
-	ADDQ $8, PSP
-	NEXT
-
-TEXT	xswap(SB), 1, $-4	/* ( x y -- y x ) */
-	XCHGQ TOS, (PSP)
-	NEXT
-
-TEXT	drop(SB), 1, $-4	/* ( x -- ) */
-	POP(TOS)
-	NEXT
-
-TEXT	dup(SB), 1, $-4	/* ( x -- x x ) */
-	PUSH(TOS)
-	NEXT
-
-TEXT	over(SB), 1, $-4	/* ( x y -- x y x ) */
-	PUSH(TOS)
-	MOVQ 8(PSP), TOS
-	NEXT
-
-TEXT	equal(SB), 1, $-4	/* ( x y -- f ) */
-	POP(CX)
-	CMPQ CX, TOS
-	JEQ .true
-	XORQ TOS, TOS
-	NEXT
-TEXT	true(SB), 1, $-4
-.true:
-	MOVQ $-1, TOS
-	NEXT
-	
-TEXT	greater(SB), 1, $-4	/* ( x y -- f ) */
-	POP(CX)
-	CMPQ CX, TOS
-	JGT .true
-	XORQ TOS, TOS
-	NEXT
-
-/* if x < y then y - x > 0, no sign flag
-	intel manual says destination operand - source operand
-	but, 9front assemblers seem to work differently
-	compare x and y == CMP x, y
- */
-TEXT	less(SB), 1, $-4	/* ( x y -- f ) */
-	POP(CX)
-	CMPQ CX, TOS
-	JLT .true
-	XORQ TOS, TOS
-	NEXT
-
-TEXT	stackptr(SB), 1, $-4	/* ( -- a ) does not include TOS! */
-	PUSH(TOS)
-	MOVQ PSP, TOS
-	NEXT
-
-TEXT	lshift(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	MOVQ TOS, CX
-	POP(TOS)
-	SHLQ CL, TOS
-	NEXT
-	
-TEXT	rshift(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	MOVQ TOS, CX
-	POP(TOS)
-	SHRQ CL, TOS
-	NEXT
-
-TEXT	rshifta(SB), 1, $-4	/* ( n1 n2 -- n ) */
-	MOVQ TOS, CX
-	POP(TOS)
-	SARQ CL, TOS
-	NEXT
-
-TEXT	execute(SB), 1, $-4	/* ( ... a -- ... ) */
-	MOVQ TOS, W
-	POP(TOS)
-	MOVQ (W), CX
-	JMP CX
-
-TEXT	deferred(SB), 1, $-4
-	MOVQ 8(W), W
-	MOVQ (W), CX
-	JMP CX
-
-TEXT	unloop(SB), 1, $-4
-	SUBQ $16, RSP
-	NEXT
-
-TEXT	cmove(SB), 1, $-4	/* ( a1 a2 n -- ) */
-	MOVQ TOS, CX
-	POP(W)
-	MOVQ IP, CX
-	POP(IP)
-	REP; MOVSB
-	MOVQ CX, IP
-	POP(TOS)
-	NEXT
-
-TEXT	cmoveb(SB), 1, $-4	/* ( a1 a2 n -- ) */
-	MOVQ TOS, CX
-	POP(W)
-	DECQ TOS
-	ADDQ TOS, W
-	MOVQ IP, CX
-	POP(IP)
-	ADDQ TOS, IP
-	STD
-	REP; MOVSB
-	CLD
-	MOVQ CX, IP
-	POP(TOS)
-	NEXT
-
-TEXT	cas(SB), 1, $-4	/* ( a old new -- f ) */
-	MOVQ TOS, DI	/* new */
-	POP(TOS)	/* old */
-	POP(SI)	/* addr */
-	LOCK; CMPXCHGQ DI, (SI)
-	JE .true
-	XORQ TOS, TOS
-	/* pause -- no equivalent in 6a ? */
-	NEXT
-
-TEXT	ffend(SB), 1, $-4
-
-#include "words.s"
-
-DATA	failtext(SB)/6, $"error\z"
-GLOBL	failtext(SB), $6
-
-DATA	name(SB)/8, $"/tmp/tes"
-DATA	name+8(SB)/6, $"t.txt\z"
-GLOBL	name(SB), $14
-
-DATA	errstrbuffer(SB)/1, $0
-GLOBL	errstrbuffer(SB), $128	/* matches ERRMTOS of libc.h */
-
-DATA	tibuffer(SB)/1, $0
-GLOBL	tibuffer(SB), $1024
-DATA	wordbuffer(SB)/1, $0
-GLOBL	wordbuffer(SB), $256
-/* TODO there should not be a heap limit, get rid of this */
-/*
-DATA	heap(SB)/1, $0
-GLOBL	heap(SB), $HEIP_IPZE
-*/
-
-
-DATA	dtop(SB)/8, LAST
-GLOBL	dtop(SB), $8
-/* 0's until heap allocated */
-DATA	htop(SB)/8, $0
-GLOBL	htop(SB), $8
-DATA	heapend(SB)/8, $0
-GLOBL	heapend(SB), $8
-
-GLOBL	ffsp<>(SB), $8
-GLOBL	csp<>(SB), $8
-
-	END
--- /dev/null
+++ b/os/pc64/forth.s
@@ -1,0 +1,618 @@
+#include "mem.h"
+
+/*
+
+The bigger goal is to replace the dis vm with forth
+forth outputs to stdout.
+But, the input needs to be fixed.
+make this into a devforth like device that reads commands and outputs the result.
+replace variable with value (as in open firmware), to avoid exposing addresses
+
+ forth kernel, amd64 9front variant
+
+ Register usage:
+
+	Original usage
+ TOS: AX top of stack register
+ SP:  SP parameter stack pointer, grows towards lower memory (downwards)
+ RP:  BP (= RARG) return stack pointer, grows towards higher memory (upwards)
+ AP:  SI address pointer
+ W:   DI work register (holds CFA)
+ 	BX, CX, DX, R8-R15 temporary registers
+
+plan9 assembler puts the first argument in BP (RARG), return value in AX.
+
+	Changed to
+ Leaving AX, SP, BP (RARG) alone to not mess with the C environment
+
+ TOS: BX top of stack register
+ PSP: DX parameter stack pointer, grows towards lower memory (downwards)
+ RSP: R8 return stack pointer, grows towards higher memory (upwards)
+ IP:  R9 instruction pointer
+ W:   R10 work register (holds CFA)
+	CX, SI, DI, R11-R15 temporary registers
+
+coding standard
+: <name> (S input-stack --- output-stack) (R --- )
+	(G descriptive glossary text)
+	f1 f2  ( interim stack picture) \ programmers explanatory comment
+		.. fn ;
+
+Memory map:
+Return stack 4096 bytes at FFSTART
+	|
+	|
+	v (grows downwards)
+Others 4096 bytes
+	system variables
+	word buffer
+	tib, text input buffer
+Parameter stack 4096 bytes at FFEND-4096
+	^ (grows upwards)
+	|
+	|
+SSTACK_END = FFEND
+*/
+
+#define SSTACK_SIZE 4096
+#define RSTACK_SIZE 4096
+#define	LAST $centry_c_boot(SB) /* last defined word, should generate this */
+#define	SSTACK_END FFEND
+
+/* putting this above the asm code as the v_dp define is needed by _main */
+/*	m_ for primitive/macro word cfa
+	mc_ for primtive/macro word constants
+	c_ for colon word cfa
+	ci_ for colon immediate word cfa
+	v_ for colon variable word cfa
+ */
+#include "primitives.s"
+#include "bindings.s"
+
+#define PUSHALL \
+	PUSHQ	R13; \
+	PUSHQ	R12; \
+	PUSHQ	R11; \
+	PUSHQ	R10; \
+	PUSHQ	R9; \
+	PUSHQ	R8; \
+	PUSHQ	R8; \
+	PUSHQ	R10; \
+	PUSHQ	R9; \
+	PUSHQ	DX; \
+	PUSHQ	CX; \
+	PUSHQ	BX; \
+	PUSHQ	TOS;
+#define POPALL \
+	POPQ	TOS; \
+	POPQ	BX; \
+	POPQ	CX; \
+	POPQ	DX; \
+	POPQ	R9; \
+	POPQ	R10; \
+	POPQ	R8; \
+	POPQ	R8; \
+	POPQ	R9; \
+	POPQ	R10; \
+	POPQ	R11; \
+	POPQ	R12; \
+	POPQ	R13;
+#define PUSHREGS \
+	PUSHQ	R8; \
+	PUSHQ	R10; \
+	PUSHQ	R9; \
+	PUSHQ	TOS;
+#define POPREGS \
+	POPQ	TOS; \
+	POPQ	R9; \
+	POPQ	R10; \
+	POPQ	R8;
+
+#define FF_TO_C_0 \
+	PUSHREGS; \
+	MOVQ DX, forthsp<>(SB); \
+	MOVQ csp<>(SB), DX; \
+	POPREGS;
+
+#define FF_TO_C_1 \
+	MOVQ TOS, BX; \
+	POPQ TOS; /* drop TOS from the parameter stack */ \
+	FF_TO_C_0 \
+	MOVQ BX, R8; /* 1st argument in R8 == RARG */
+
+/* ( 1st_parameter 2nd_parameter -- ) */
+#define FF_TO_C_2 /* for calling a c function with 2 parameters */ \
+	MOVQ TOS, CX; \
+	POPQ TOS; \
+	FF_TO_C_1 \
+	MOVQ CX, 8(DX) \
+
+/* ( 1st_parameter 2nd_parameter 3rd_parameter -- ) */
+#define FF_TO_C_3 /* for calling a c function with 3 parameters */ \
+	MOVQ TOS, DX; \
+	POPQ TOS; \
+	FF_TO_C_2 \
+	MOVQ DX, 16(DX) \
+
+/* no arguments when calling ff from C, for now */
+#define C_TO_FF \
+	PUSHREGS; \
+	MOVQ DX, csp<>(SB); \
+	MOVQ ffsp<>(SB), DX; \
+	POPREGS;
+
+TEXT	forthmain(SB), 1, $-4		/* _main(SB), 1, $-4 without the libc */
+	/* The last dictionary entry address is stored in dtop.
+	 * The location of dtop is stored in the variable dp.
+	 * To get the location of dtop, get the value in the parameter field
+	 * (link + name(1+2) + code field address = 24 bytes) of the dp
+	 * dictionary entry.
+	 */
+	MOVQ $FFEND, PSP	/* setting up stack */
+	/*
+	 * dtop address is stored in the parameter field address(24-32 bytes) of mventry_dp
+	 */
+	MOVQ mventry_dp+24(SB), SI	/* now, SI = dtop address */
+	MOVQ (SI), TOS	/* TOS = *CX = $LAST = boot word address (defined last, stored at dtop) */
+				/* if 6a allows multiple symbols per address, then 
+					the above 3 instructions would have been
+					MOVQ (($mventry_dp+24(SB))), TOS */
+	/*
+	 * Could do this instead of the calculations below
+	 * LEAQ 24(TOS), IP
+	 */
+	ADDQ $16, TOS	/* TOS += link (8 bytes) + len (1 byte) + minimum for align to 8 bytes */
+	XORQ CX, CX
+	MOVB 8(SI), CL	/* CL = length of boot name */
+	ADDQ CX, TOS		/* TOS += len */
+	ANDQ $~7, TOS	/* TOS = address of boot's code - 8 bytes */
+	LEAQ 8(TOS), IP	/* IP = L257 = start of boot code = has docol address there
+					 * skipping over docol as we do not need to save the IP
+					 */
+
+/* lodsl could make this simpler. But, this is more comprehensible
+	why not JMP* (W)?
+ */
+#define NEXT	MOVQ (IP), W; /* W = Address next to the DOCOL of boot */ \
+		ADDQ $8, IP; /* move IP further = DOCOL address + 16 */ \
+		MOVQ (W), TOS; /* TOS = code field address of the 1st instruction after DOCOL of boot */ \
+		JMP* TOS; /* Start executing that code field address */
+/*
+Address   0     8    16
+aword : docol  40   ...
+Address   40    48
+bword : docol   72
+Address   72    80
+cword : docol  ....
+at docol address, some assembly instruction
+Assume IP = 8
+ */
+#define NEXT	MOVQ (IP), W;	/* W = 40, contents of address in IP, some word's code field address */ \
+		MOVQ (W), TOS;	/* TOS = docol, Get the address in the address in IP = code field address */ \
+		ADDQ $8, IP; 	/* move IP further, IP = 16 */ \
+		JMP* TOS; /* Start executing at docol address, JMP* = jump to a non-relative address */
+
+#define PUSH(r)	SUBQ $8, PSP; \
+			MOVQ r, (PSP)
+#define POP(r)	MOVQ (PSP), r; \
+			ADDQ $8, PSP
+
+	NEXT
+
+TEXT	reset(SB), 1, $-4
+	MOVQ $FFSTART, RSP
+	NEXT
+
+TEXT	clear(SB), 1, $-4
+	MOVQ $FFEND, PSP
+	NEXT
+
+TEXT	colon(SB), 1, $-4
+	MOVQ IP,(RSP)
+	ADDQ $8, RSP
+	LEAQ 8(W), IP
+	NEXT
+
+TEXT	exitcolon(SB), 1, $-4
+	SUBQ $8, RSP
+	MOVQ (RSP), IP
+	NEXT
+
+TEXT	dodoes(SB), 1, $-4	/* ( -- a ) */
+	MOVQ IP,(RSP)
+	ADDQ $8,RSP
+	MOVQ 8(W),IP
+	PUSH(TOS)
+	LEAQ 16(W), TOS
+	NEXT
+
+TEXT	jump(SB), 1, $-4	/* ( -- ) */
+	MOVQ (IP),IP
+	NEXT
+
+/* ( f -- ) cjump address
+	if true, skip the address and continue
+	else, go to the address */
+TEXT	cjump(SB), 1, $-4	/* ( f -- ) */
+	MOVQ (IP), TOS	/* get the next address */
+	ADDQ $8, IP	/* move esi beyond that */
+	TESTQ TOS, TOS
+	JNZ .l1		/* if true, move along */
+	MOVQ TOS, IP	/* if false, go to the above address */
+.l1:
+	POP(TOS
+	NEXT
+
+/* TODO change to allow only fetches from a certain memory range */
+TEXT	fetch(SB), 1, $-4	/* ( a -- n) */
+	MOVQ (TOS), TOS
+	NEXT
+
+/* TODO change to allow stores to a certain memory range only */
+TEXT	store(SB), 1, $-4	/* ( n a -- ) */
+	POP(CX)
+	MOVQ CX, (TOS)
+	POP(TOS)
+	NEXT
+
+TEXT	cfetch(SB), 1, $-4	/* ( a -- c ) */
+	XORQ CX, CX
+	MOVB (TOS), CL
+	POP(TOS)
+	NEXT
+
+TEXT	cstore(SB), 1, $-4	/* ( c a -- ) */
+	POP(CX)
+	MOVB CL, (TOS)
+	POP(TOS)
+	NEXT
+
+TEXT	terminate(SB), 1, $-4	/* ( n -- ) */
+	XORQ CX, CX
+	TESTQ TOS, TOS
+	JZ .l2
+	MOVQ $failtext(SB), TOS
+.l2:
+	/* PUSHQ CX */
+	/* SUBQ $8, PSP */	/* dummy retaddr */
+	MOVQ CX, a0+0(FP)	/* address of exit status? status = nil? */
+	MOVQ $8, RARG	/* EXITS */
+	SYSCALL		/* TODO syscall for exit */
+
+TEXT	testfsopen(SB), 1, $-4
+	PUSHQ SI	/* for some reason, the syscall is changing IP and W */
+	PUSHQ BP
+	PUSHQ $0	/* OREAD */
+	PUSHQ $name(SB)
+	PUSHQ $0	/* dummy retaddr */
+	MOVQ $14, RARG	/* open */
+	SYSCALL
+	ADDQ $24, PSP
+	POPQ RSP
+	POPQ IP
+	NEXT
+	NOP
+	NOP
+	NOP
+	NOP
+	NOP
+
+#include "bindings.s"
+
+TEXT	mmap(SB), 1, $-4	/* ( a1 -- a2 ) */
+	MOVQ $-1, TOS	/* unimplemented */
+
+TEXT	variable(SB), 1, $-4	/* ( -- a ) */
+	PUSH(TOS)
+	LEAQ 8(W), TOS
+	NEXT
+
+TEXT	constant(SB), 1, $-4	/* ( -- n ) */
+	PUSH(TOS)
+	MOVQ 8(W), TOS
+	NEXT
+
+TEXT	literal(SB), 1, $-4	/* ( -- n ) */
+	PUSH(TOS)
+	MOVQ (IP), TOS
+	ADDQ $8, IP
+	NEXT
+
+TEXT	sliteral(SB), 1, $-4	/* ( -- a n ) */
+	PUSH(TOS)
+	XORQ TOS,TOS
+	MOVB (IP), BL
+	INCQ IP
+	PUSH(IP)
+	ADDQ TOS, IP
+	ADDQ $7, IP
+	ANDQ $~7, IP
+	NEXT
+
+/* puts the top 2 entries of the data stack in the return stack */
+TEXT	doinit(SB), 1, $-4	/* ( hi lo -- ) */
+	MOVQ TOS, (RSP)
+	POP(TOS)
+	MOVQ TOS, 8(RSP)
+	POP(TOS)
+	ADDQ $16, RSP
+	NEXT
+
+/* not sure if this works, needs testing to follow https://github.com/mark4th/x64
+	check the notes
+	return stack would have
+		current index
+		end index
+	(R lo hi -- )
+	increment lo
+	when hi > lo, go to the address next to doloop
+ */
+TEXT	doloop(SB), 1, $-4
+	INCQ -16(RSP)
+doloop1:
+	MOVQ -16(RSP), CX
+	CMPQ CX, -8(RSP)
+	JGE .l4
+	MOVQ (IP), IP
+	NEXT
+.l4:
+	SUBQ $16, RSP
+	ADDQ $8, IP
+	NEXT
+
+TEXT	doploop(SB), 1, $-4	/* ( n -- ) */
+	ADDQ TOS, -16(RSP)
+	POP(TOS)
+	JMP doloop1
+
+TEXT	rfetch(SB), 1, $-4	/* ( -- n ) */
+	PUSH(TOS)
+	MOVQ -8(RSP), TOS
+	NEXT
+
+TEXT	rpush(SB), 1, $-4	/* ( n -- ) */
+	MOVQ TOS,(RSP)
+	POP(TOS)
+	ADDQ $8,RSP
+	NEXT
+
+TEXT	rpop(SB), 1, $-4	/* ( -- n ) */
+	PUSH(TOS)
+	SUBQ $8, RSP
+	MOVQ (RSP), TOS
+	NEXT
+
+TEXT	i(SB), 1, $-4	/* ( -- n ) */
+	PUSH(TOS)
+	MOVQ -16(RSP), TOS
+	NEXT
+
+TEXT	j(SB), 1, $-4	/* ( -- n ) */
+	PUSH(TOS)
+	MOVQ -32(RSP), TOS
+	NEXT
+
+TEXT	plus(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	POP(CX)
+	ADDQ CX, TOS
+	NEXT  
+
+TEXT	minus(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	MOVQ TOS, CX
+	POP(TOS)
+	SUBQ CX, TOS
+	NEXT
+
+TEXT	multiply(SB), 1, $-4	/* ( n1 n2 -- n1*n2 ) */
+	POP(CX)
+	IMULQ CX,TOS
+	NEXT
+
+TEXT	slashmod(SB), 1, $-4	/* ( n1 n2 -- remainder quotient ) n1/n2 */
+	MOVQ (PSP), CX /* CX = n1 */
+	PUSHQ DX /* DX == PSP, store DX and AX as they are used by CDQ and IDIV */
+	PUSHQ AX
+	XORQ DX, DX /* DX = 0 */
+	MOVQ CX, AX /* AX = n1 */
+	CDQ 		/* RAX -> RDX:RAX sign extension */
+	IDIVQ TOS	/* RDX:RAX / TOS => Quotient in RAX, Remainder in RDX */
+	MOVQ DX, CX	/* CX = remainder */
+	MOVQ AX, TOS /* TOS = quotient */
+	POPQ AX
+	POPQ DX
+	MOVQ CX, (PSP) /* -- remainder quotient */
+	NEXT
+
+TEXT	uslashmod(SB), 1, $-4	/* ( u1 u2 -- uremainder uquotient ) */
+	MOVQ (PSP), CX /* CX = n1 */
+	PUSHQ DX /* DX == PSP, store DX and AX as they are used by CDQ and IDIV */
+	PUSHQ AX
+	XORQ DX, DX /* DX = 0 */
+	MOVQ CX, AX /* AX = n1 */
+	IDIVQ TOS	/* RDX:RAX / TOS => Quotient in RAX, Remainder in RDX */
+	MOVQ DX, CX	/* CX = remainder */
+	MOVQ AX, TOS /* TOS = quotient */
+	POPQ AX
+	POPQ DX
+	MOVQ CX, (PSP) /* -- uremainder uquotient */
+	NEXT
+
+	MOVQ TOS, TOS
+	MOVQ (PSP), TOS
+	XORQ PSP, PSP
+	DIVQ TOS
+	MOVQ PSP, (PSP)
+	NEXT
+
+TEXT	binand(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	ANDQ (PSP), TOS
+	ADDQ $8, PSP
+	NEXT
+
+TEXT	binor(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	ORQ (PSP), TOS
+	ADDQ $8, PSP
+	NEXT
+
+TEXT	binxor(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	XORQ (PSP), TOS
+	ADDQ $8, PSP
+	NEXT
+
+TEXT	xswap(SB), 1, $-4	/* ( x y -- y x ) */
+	XCHGQ TOS, (PSP)
+	NEXT
+
+TEXT	drop(SB), 1, $-4	/* ( x -- ) */
+	POP(TOS)
+	NEXT
+
+TEXT	dup(SB), 1, $-4	/* ( x -- x x ) */
+	PUSH(TOS)
+	NEXT
+
+TEXT	over(SB), 1, $-4	/* ( x y -- x y x ) */
+	PUSH(TOS)
+	MOVQ 8(PSP), TOS
+	NEXT
+
+TEXT	equal(SB), 1, $-4	/* ( x y -- f ) */
+	POP(CX)
+	CMPQ CX, TOS
+	JEQ .true
+	XORQ TOS, TOS
+	NEXT
+TEXT	true(SB), 1, $-4
+.true:
+	MOVQ $-1, TOS
+	NEXT
+	
+TEXT	greater(SB), 1, $-4	/* ( x y -- f ) */
+	POP(CX)
+	CMPQ CX, TOS
+	JGT .true
+	XORQ TOS, TOS
+	NEXT
+
+/* if x < y then y - x > 0, no sign flag
+	intel manual says destination operand - source operand
+	but, 9front assemblers seem to work differently
+	compare x and y == CMP x, y
+ */
+TEXT	less(SB), 1, $-4	/* ( x y -- f ) */
+	POP(CX)
+	CMPQ CX, TOS
+	JLT .true
+	XORQ TOS, TOS
+	NEXT
+
+TEXT	stackptr(SB), 1, $-4	/* ( -- a ) does not include TOS! */
+	PUSH(TOS)
+	MOVQ PSP, TOS
+	NEXT
+
+TEXT	lshift(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	MOVQ TOS, CX
+	POP(TOS)
+	SHLQ CL, TOS
+	NEXT
+	
+TEXT	rshift(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	MOVQ TOS, CX
+	POP(TOS)
+	SHRQ CL, TOS
+	NEXT
+
+TEXT	rshifta(SB), 1, $-4	/* ( n1 n2 -- n ) */
+	MOVQ TOS, CX
+	POP(TOS)
+	SARQ CL, TOS
+	NEXT
+
+TEXT	execute(SB), 1, $-4	/* ( ... a -- ... ) */
+	MOVQ TOS, W
+	POP(TOS)
+	MOVQ (W), CX
+	JMP CX
+
+TEXT	deferred(SB), 1, $-4
+	MOVQ 8(W), W
+	MOVQ (W), CX
+	JMP CX
+
+TEXT	unloop(SB), 1, $-4
+	SUBQ $16, RSP
+	NEXT
+
+TEXT	cmove(SB), 1, $-4	/* ( a1 a2 n -- ) */
+	MOVQ TOS, CX
+	POP(W)
+	MOVQ IP, CX
+	POP(IP)
+	REP; MOVSB
+	MOVQ CX, IP
+	POP(TOS)
+	NEXT
+
+TEXT	cmoveb(SB), 1, $-4	/* ( a1 a2 n -- ) */
+	MOVQ TOS, CX
+	POP(W)
+	DECQ TOS
+	ADDQ TOS, W
+	MOVQ IP, CX
+	POP(IP)
+	ADDQ TOS, IP
+	STD
+	REP; MOVSB
+	CLD
+	MOVQ CX, IP
+	POP(TOS)
+	NEXT
+
+TEXT	cas(SB), 1, $-4	/* ( a old new -- f ) */
+	MOVQ TOS, DI	/* new */
+	POP(TOS)	/* old */
+	POP(SI)	/* addr */
+	LOCK; CMPXCHGQ DI, (SI)
+	JE .true
+	XORQ TOS, TOS
+	/* pause -- no equivalent in 6a ? */
+	NEXT
+
+TEXT	forthend(SB), 1, $-4
+
+#include "words.s"
+
+DATA	failtext(SB)/6, $"error\z"
+GLOBL	failtext(SB), $6
+
+DATA	name(SB)/8, $"/tmp/tes"
+DATA	name+8(SB)/6, $"t.txt\z"
+GLOBL	name(SB), $14
+
+DATA	errstrbuffer(SB)/1, $0
+GLOBL	errstrbuffer(SB), $128	/* matches ERRMTOS of libc.h */
+
+DATA	tibuffer(SB)/1, $0
+GLOBL	tibuffer(SB), $1024
+DATA	wordbuffer(SB)/1, $0
+GLOBL	wordbuffer(SB), $256
+/* TODO there should not be a heap limit, get rid of this */
+/*
+DATA	heap(SB)/1, $0
+GLOBL	heap(SB), $HEIP_IPZE
+*/
+
+
+DATA	dtop(SB)/8, LAST
+GLOBL	dtop(SB), $8
+/* 0's until heap allocated */
+DATA	htop(SB)/8, $0
+GLOBL	htop(SB), $8
+DATA	heapend(SB)/8, $0
+GLOBL	heapend(SB), $8
+
+GLOBL	forthsp<>(SB), $8
+GLOBL	csp<>(SB), $8
+
+	END
--- a/os/pc64/primitives-nasm.s
+++ b/os/pc64/primitives-nasm.s
@@ -5,11 +5,11 @@
   MENTRY "c!", cstore, 2
   MENTRY "testfsopen", testfsopen, 10
   MENTRY "errstr", errstr, 6
-  MENTRY "fsread", fsread, 6
-  MENTRY "fswrite", fswrite, 7
-  MENTRY "fsseek", fsseek, 6
-  MENTRY "fsopen", fsopen, 6
-  MENTRY "fsclose", fsclose, 7
+  MENTRY "read", read, 4
+  MENTRY "write", write, 5
+  MENTRY "seek", seek, 4
+  MENTRY "open", open, 4
+  MENTRY "close", close, 5
   MENTRY "mmap", mmap, 4
   MENTRY "halt", terminate, 4
   MENTRY "clear", clear, 5
@@ -63,4 +63,3 @@
   MVENTRY "_end", textend, ffend, 4
   MENTRY "cas", cas, 3
   MENTRY "(deferred)", deferred, 10
-  MENTRY "ffprint", ffprint, 7
--- /dev/null
+++ b/os/pc64/rebootcode.s
@@ -1,0 +1,123 @@
+#include "mem.h"
+
+MODE $64
+
+/*
+ * Turn off MMU, then memmove the new kernel to its correct location
+ * in physical memory.  Then jumps the to start of the kernel.
+ */
+
+TEXT	main(SB), 1, $-4
+	MOVL	RARG, DI		/* destination */
+	MOVL	p2+8(FP), SI		/* source */
+	MOVL	n+16(FP), BX		/* byte count */
+
+	/* load zero length idt */
+	MOVL	$_idtptr64p<>(SB), AX
+	MOVL	(AX), IDTR
+
+	/* load temporary gdt */
+	MOVL	$_gdtptr64p<>(SB), AX
+	MOVL	(AX), GDTR
+
+	/* load CS with 32bit code segment */
+	PUSHQ	$SELECTOR(3, SELGDT, 0)
+	PUSHQ	$_warp32<>(SB)
+	RETFQ
+
+MODE $32
+
+TEXT	_warp32<>(SB), 1, $-4
+
+	/* load 32bit data segments */
+	MOVL	$SELECTOR(2, SELGDT, 0), AX
+	MOVW	AX, DS
+	MOVW	AX, ES
+	MOVW	AX, FS
+	MOVW	AX, GS
+	MOVW	AX, SS
+
+	/* turn off paging */
+	MOVL	CR0, AX
+	ANDL	$0x7fffffff, AX		/* ~(PG) */
+	MOVL	AX, CR0
+
+	MOVL	$0, AX
+	MOVL	AX, CR3
+
+	/* disable long mode */
+	MOVL	$0xc0000080, CX		/* Extended Feature Enable */
+	RDMSR
+	ANDL	$0xfffffeff, AX		/* Long Mode Disable */
+	WRMSR
+
+	/* diable pae */
+	MOVL	CR4, AX
+	ANDL	$0xffffff5f, AX		/* ~(PAE|PGE) */
+	MOVL	AX, CR4
+
+	MOVL	BX, CX			/* byte count */
+	MOVL	DI, AX			/* save entry point */
+	MOVL	AX, SP			/* move stack below entry */
+
+	/* park cpu for zero entry point */
+	ORL	AX, AX
+	JZ	_idle
+
+
+/*
+ * the source and destination may overlap.
+ * determine whether to copy forward or backwards
+ */
+	CMPL	SI, DI
+	JGT	_forward
+	MOVL	SI, DX
+	ADDL	CX, DX
+	CMPL	DX, DI
+	JGT	_back
+
+_forward:
+	CLD
+	REP;	MOVSB
+
+_startkernel:
+	/* jump to entry point */
+	JMP*	AX
+
+_back:
+	ADDL	CX, DI
+	ADDL	CX, SI
+	SUBL	$1, DI
+	SUBL	$1, SI
+	STD
+	REP;	MOVSB
+	JMP	_startkernel
+
+_idle:
+	HLT
+	JMP	_idle
+
+TEXT _gdt<>(SB), 1, $-4
+	/* null descriptor */
+	LONG	$0
+	LONG	$0
+
+	/* (KESEG) 64 bit long mode exec segment */
+	LONG	$(0xFFFF)
+	LONG	$(SEGL|SEGG|SEGP|(0xF<<16)|SEGPL(0)|SEGEXEC|SEGR)
+
+	/* 32 bit data segment descriptor for 4 gigabytes (PL 0) */
+	LONG	$(0xFFFF)
+	LONG	$(SEGG|SEGB|(0xF<<16)|SEGP|SEGPL(0)|SEGDATA|SEGW)
+
+	/* 32 bit exec segment descriptor for 4 gigabytes (PL 0) */
+	LONG	$(0xFFFF)
+	LONG	$(SEGG|SEGD|(0xF<<16)|SEGP|SEGPL(0)|SEGEXEC|SEGR)
+
+TEXT _gdtptr64p<>(SB), 1, $-4
+	WORD	$(4*8-1)
+	QUAD	$_gdt<>(SB)
+
+TEXT _idtptr64p<>(SB), 1, $-4
+	WORD	$0
+	QUAD	$0
--- a/os/pc64/words-nasm.s
+++ b/os/pc64/words-nasm.s
@@ -292,12 +292,18 @@
  dd m_cstore		; variable iobuf has character
  dd v_iobuf			; variable iobuf address
  dd m_literal
- dd 1				; ( iobuf 1 -- )
- dd m_ffprint		; calls c screenput()
+ dd 1
+ dd swap			; ( iobuf 1 --  1 iobuf )
+ dd m_literal
+ dd 1				; stdout
+ dd write			; ( 1 iobuf 1 --  )
  dd m_exitcolon
 
  CENTRY `type`, c_type, 4	; ( addr n -- ) 
- dd m_ffprint
+ dd swap			; ( addr n --  n addr )
+ dd m_literal
+ dd 1				; stdout
+ dd write			; ( n addr 1 --  )
  dd m_exitcolon
 
  CENTRY `cr`, c_cr, 2