WORD, the dictionary words and the code generator run underneath whatever the user has on the stacks, which are ten and nine deep (D-2). Measured, they used five to seven data cells of their own, leaving a line about three. Each now keeps what it works on in its file's scratch cells and has at most three cells on the data stack; , calls nothing; and the longest chains of calls are shorter. The public words of core.v4, input.v4 and dict.v4 get dictionary headers. NUMBER is split so the interpreter can have a flag instead of NODE-ERROR. The host-node tests share one memory map, host_map.h. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
335 lines
17 KiB
C
335 lines
17 KiB
C
/* test_host_codegen.c -- the code generator, executed on the host node.
|
|
*
|
|
* capsule/codegen.v4 packs opcodes, literals and branches into instruction
|
|
* words at HERE; it is what the compiling words will be built on. It is the
|
|
* third thing in this tree that does that job -- after the slot packer in C
|
|
* (asm.h) and the text assembler on top of it (text.h) -- and the first that
|
|
* is itself v4 code. So the test is a comparison: the same programme is laid
|
|
* down twice, once by the text assembler on one node and once by the code
|
|
* generator running on another, at the same address, and the two memories
|
|
* must be the same word for word. Then what the generator made is run.
|
|
*/
|
|
#include "v4/text.h"
|
|
#include "v4/testcode.h"
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
static int failures = 0, checks = 0;
|
|
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
|
|
|
|
#define CANARY ((v4_cell)0x0C0FFEE5)
|
|
#define MAXU ((v4_ucell)~(v4_ucell)0)
|
|
|
|
#define DICT_W ((v4_cell)8192) /* dictionary space: words 8192 .. 12287 */
|
|
#define DICT_END_W ((v4_cell)12288)
|
|
#include "host_map.h"
|
|
|
|
static v4_node n, ref; /* the generator's node; the text assembler's */
|
|
static v4_exec_state es;
|
|
static v4_heat h;
|
|
static v4_text tx, rtx; /* the capsule on n; the reference programme on ref */
|
|
|
|
static const char *const mnemonic[V4_OPCODE_COUNT] = {
|
|
";", "ex", "jump", "call", "unext", "next", "if", "-if", "@p", "@+", "@b", "@", "!p", "!+", "!b", "!",
|
|
"+*", "2*", "2/", "inv", "+", "and", "xor", "drop", "dup", "pop", "over", "a", "nop", "push", "b!", "a!" };
|
|
|
|
static v4_cell W(const char *name)
|
|
{
|
|
v4_cell w = v4_text_word(&tx, name);
|
|
if (w < 0) { failures++; printf("FAIL: no word %s\n", name); }
|
|
return w;
|
|
}
|
|
static int run(const char *name, unsigned argc, v4_cell a, v4_cell b)
|
|
{
|
|
v4_dstack_reset(&n.ds);
|
|
v4_rstack_reset(&n.rs);
|
|
v4_exec_reset(&es);
|
|
v4_heat_reset(&h);
|
|
v4_dstack_push(&n.ds, CANARY);
|
|
if (argc > 0) v4_dstack_push(&n.ds, a);
|
|
if (argc > 1) v4_dstack_push(&n.ds, b);
|
|
return v4_test_call(&n, &es, &h, W(name), 100000) > 0;
|
|
}
|
|
static v4_cell pop(void) { return v4_dstack_pop(&n.ds); }
|
|
static int clean(void) { return pop() == CANARY; }
|
|
/* a word that takes its arguments and leaves nothing */
|
|
static int did(const char *name, unsigned argc, v4_cell a, v4_cell b) { return run(name, argc, a, b) && clean(); }
|
|
/* a word that leaves one cell */
|
|
static v4_cell get(const char *name, unsigned argc, v4_cell a, v4_cell b)
|
|
{
|
|
v4_cell r;
|
|
if (!run(name, argc, a, b)) return (v4_cell)0x0BADBAD;
|
|
r = pop();
|
|
return clean() ? r : (v4_cell)0x0BADBAD;
|
|
}
|
|
|
|
/* ---- a programme, described once and laid down twice --------------------- */
|
|
|
|
enum { K_OP, K_LIT, K_LABEL, K_BACK, K_FWD, K_HERE }; /* K_HERE resolves a K_FWD */
|
|
typedef struct { int kind; unsigned op; v4_cell value; int label; } item;
|
|
|
|
#define MAX_ITEMS 400
|
|
static item prog[MAX_ITEMS];
|
|
static unsigned nprog;
|
|
|
|
static void p_op(unsigned op) { prog[nprog].kind = K_OP; prog[nprog].op = op; nprog++; }
|
|
static void p_lit(v4_cell v) { prog[nprog].kind = K_LIT; prog[nprog].value = v; nprog++; }
|
|
static void p_label(int l) { prog[nprog].kind = K_LABEL; prog[nprog].label = l; nprog++; }
|
|
static void p_back(unsigned op, int l) { prog[nprog].kind = K_BACK; prog[nprog].op = op; prog[nprog].label = l; nprog++; }
|
|
static void p_fwd(unsigned op, int l) { prog[nprog].kind = K_FWD; prog[nprog].op = op; prog[nprog].label = l; nprog++; }
|
|
static void p_here(int l) { prog[nprog].kind = K_HERE; prog[nprog].label = l; nprog++; }
|
|
|
|
/* By the text assembler, on `ref`, at the dictionary's first word. */
|
|
static int lay_by_text(void)
|
|
{
|
|
static char src[MAX_ITEMS * 40];
|
|
size_t at = 0;
|
|
unsigned i;
|
|
at += (size_t)sprintf(src + at, ": PROG ");
|
|
for (i = 0; i < nprog; i++) {
|
|
const item *it = &prog[i];
|
|
switch (it->kind) {
|
|
case K_OP: at += (size_t)sprintf(src + at, "%s ", mnemonic[it->op]); break;
|
|
#if V4_CELL_BITS == 32
|
|
case K_LIT: at += (size_t)sprintf(src + at, "%ld ", (long)it->value); break;
|
|
#else
|
|
case K_LIT: at += (size_t)sprintf(src + at, "%lld ", (long long)it->value); break;
|
|
#endif
|
|
case K_LABEL: at += (size_t)sprintf(src + at, "B%d: ", it->label); break;
|
|
case K_BACK: at += (size_t)sprintf(src + at, "%s B%d ", mnemonic[it->op], it->label); break;
|
|
case K_FWD: at += (size_t)sprintf(src + at, "%s F%d ", mnemonic[it->op], it->label); break;
|
|
case K_HERE: at += (size_t)sprintf(src + at, "F%d: ", it->label); break;
|
|
}
|
|
}
|
|
v4_node_reset(&ref);
|
|
v4_text_begin(&rtx, &ref, DICT_W);
|
|
return v4_text_assemble(&rtx, src) && v4_text_finish(&rtx);
|
|
}
|
|
|
|
/* By the code generator, on `n`: one call to a word of codegen.v4 for each
|
|
* item, as the compiling words will make them. */
|
|
static int lay_by_generator(void)
|
|
{
|
|
static v4_cell back[64], fwd[64];
|
|
unsigned i;
|
|
for (v4_cell k = DICT_W; k < DICT_END_W; k++) n.mem[k] = 0;
|
|
n.mem[DP] = DICT_W * 4;
|
|
n.mem[NODE_ERROR] = 0;
|
|
if (!did("(CG-RESET)", 0, 0, 0)) return 0;
|
|
for (i = 0; i < nprog; i++) {
|
|
const item *it = &prog[i];
|
|
switch (it->kind) {
|
|
case K_OP: if (!did("(OP,)", 1, (v4_cell)it->op, 0)) return 0; break;
|
|
case K_LIT: if (!did("(LIT,)", 1, it->value, 0)) return 0; break;
|
|
case K_LABEL: back[it->label] = get("(LABEL)", 0, 0, 0); break;
|
|
case K_BACK: if (!did("(BRANCH,)", 2, back[it->label], (v4_cell)it->op)) return 0; break;
|
|
case K_FWD: fwd[it->label] = get("(BRANCH>)", 1, (v4_cell)it->op, 0); break;
|
|
case K_HERE: { v4_cell here = get("(LABEL)", 0, 0, 0); if (!did("(RESOLVE)", 2, here, fwd[it->label])) return 0; } break;
|
|
}
|
|
}
|
|
return did("(FLUSH)", 0, 0, 0) && n.mem[NODE_ERROR] == 0;
|
|
}
|
|
|
|
/* Lay the programme down both ways; 1 if the two are the same. */
|
|
static int same(const char *what)
|
|
{
|
|
v4_cell end_ref, end_gen, k;
|
|
if (!lay_by_text()) { printf(" %s: the text assembler refused it: %s\n", what, v4_text_error(&rtx)); return 0; }
|
|
if (!lay_by_generator()) { printf(" %s: the generator did not finish\n", what); return 0; }
|
|
end_ref = v4_text_here(&rtx);
|
|
end_gen = (n.mem[DP] + 3) / 4;
|
|
if (end_ref != end_gen) { printf(" %s: lengths differ: %ld by text, %ld by the generator\n", what, (long)(end_ref - DICT_W), (long)(end_gen - DICT_W)); return 0; }
|
|
for (k = DICT_W; k < end_ref + 8; k++)
|
|
if (n.mem[k] != ref.mem[k]) {
|
|
printf(" %s: word %ld differs: %llx by text, %llx by the generator\n", what, (long)(k - DICT_W),
|
|
(unsigned long long)(v4_ucell)ref.mem[k], (unsigned long long)(v4_ucell)n.mem[k]);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint32_t rng = 0x7F4A7C15u;
|
|
static uint32_t rnd(void) { rng ^= rng << 13; rng ^= rng >> 17; rng ^= rng << 5; return rng; }
|
|
|
|
static const unsigned plain_ops[] = { /* every opcode but the branches and @p */
|
|
0, 1, 4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 };
|
|
static const unsigned branch_ops[] = { 2, 3, 5, 6, 7 };
|
|
static const v4_cell lits[] = { 0, 1, -1, 2, 255, 65536, -65536, 12345678, (v4_cell)V4_MSB, (v4_cell)(V4_MSB - 1u), (v4_cell)(MAXU / 3u) };
|
|
|
|
/* Run the programme the generator laid down, as a word. */
|
|
static int exec_prog(unsigned argc, v4_cell a, v4_cell b)
|
|
{
|
|
v4_dstack_reset(&n.ds);
|
|
v4_rstack_reset(&n.rs);
|
|
v4_exec_reset(&es);
|
|
v4_dstack_push(&n.ds, CANARY);
|
|
if (argc > 0) v4_dstack_push(&n.ds, a);
|
|
if (argc > 1) v4_dstack_push(&n.ds, b);
|
|
return v4_test_call(&n, &es, &h, DICT_W, 100000) > 0;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
unsigned i, t, bslots = 0;
|
|
|
|
printf("v4 host code generator tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
|
|
|
|
for (i = 0; i < 4; i++) if ((v4_ucell)v4_iword_slot_mask(i) >= (v4_ucell)(V4_NODE_WORDS - 1u)) bslots = i + 1;
|
|
CHECK(bslots == 3, "at this node size a branch may sit in slots 0 .. 2: %u", bslots);
|
|
|
|
{
|
|
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4" };
|
|
CHECK(host_load(&tx, &n, files, 4), "the capsule assembles");
|
|
}
|
|
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
|
|
CHECK(v4_text_here(&tx) < DICT_W, "code stays below the dictionary space");
|
|
printf(" code: %ld words\n", (long)v4_text_here(&tx) - 16);
|
|
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
|
|
|
|
/* ---- the arithmetic it rests on ---- */
|
|
for (i = 0; i <= 31; i++)
|
|
CHECK(get("(LSHIFT)", 2, 1, (v4_cell)i) == (v4_cell)((v4_ucell)1 << i) && get("(LSHIFT)", 2, 5, (v4_cell)i) == (v4_cell)((v4_ucell)5 << i),
|
|
"(LSHIFT) by %u", i);
|
|
for (i = 0; i < 6; i++) {
|
|
CHECK(get("(LOWBIT)", 1, (v4_cell)i, 0) == (v4_cell)V4_SLOT_LOW_BIT(i), "slot %u's lowest bit is %u", i, V4_SLOT_LOW_BIT(i));
|
|
CHECK(get("(MASK)", 1, (v4_cell)i, 0) == (v4_cell)v4_iword_slot_mask(i), "slot %u's address mask", i);
|
|
}
|
|
|
|
/* ---- one thing at a time ---- */
|
|
nprog = 0;
|
|
CHECK(same("nothing"), "nothing lays nothing");
|
|
CHECK((n.mem[DP] + 3) / 4 == DICT_W, "and HERE does not move");
|
|
|
|
for (i = 0; i < sizeof plain_ops / sizeof plain_ops[0]; i++) {
|
|
nprog = 0; p_op(plain_ops[i]);
|
|
CHECK(same(mnemonic[plain_ops[i]]), "the opcode %s alone", mnemonic[plain_ops[i]]);
|
|
nprog = 0; p_op(28); p_op(28); p_op(plain_ops[i]); p_op(24);
|
|
CHECK(same(mnemonic[plain_ops[i]]), "the opcode %s in slot 2, something after it", mnemonic[plain_ops[i]]);
|
|
}
|
|
for (t = 1; t <= 14; t++) {
|
|
nprog = 0; for (i = 0; i < t; i++) p_op(24);
|
|
CHECK(same("dups"), "%u opcodes: a word every six", t);
|
|
}
|
|
for (i = 0; i < sizeof lits / sizeof lits[0]; i++) {
|
|
nprog = 0; p_lit(lits[i]); p_op(0);
|
|
CHECK(same("a literal"), "the literal [%u]", i);
|
|
}
|
|
for (t = 1; t <= 14; t++) {
|
|
nprog = 0; for (i = 0; i < t; i++) p_lit((v4_cell)(100 + i));
|
|
CHECK(same("literals"), "%u literals in a row: each word's follow it in order", t);
|
|
}
|
|
for (t = 0; t <= 6; t++) { /* a literal in each slot, then more code */
|
|
nprog = 0; for (i = 0; i < t; i++) p_op(24);
|
|
p_lit(77); p_op(20); p_lit(-88); p_op(0);
|
|
CHECK(same("mixed"), "a literal after %u opcodes", t);
|
|
}
|
|
|
|
/* a branch after 0 .. 6 opcodes, backward and forward, each of the five */
|
|
for (i = 0; i < 5; i++)
|
|
for (t = 0; t <= 6; t++) {
|
|
unsigned k;
|
|
nprog = 0; p_label(0); p_op(28); p_op(28); p_op(28); p_op(28); p_op(28); p_op(28);
|
|
for (k = 0; k < t; k++) p_op(24);
|
|
p_back(branch_ops[i], 0); p_op(23); p_op(0);
|
|
CHECK(same("back"), "%s backward after %u opcodes", mnemonic[branch_ops[i]], t);
|
|
nprog = 0;
|
|
for (k = 0; k < t; k++) p_op(24);
|
|
p_fwd(branch_ops[i], 0); p_op(23); p_op(23); p_op(0); p_here(0); p_op(0);
|
|
CHECK(same("forward"), "%s forward after %u opcodes", mnemonic[branch_ops[i]], t);
|
|
nprog = 0; /* with literals owed by the branch's word */
|
|
p_lit(5); p_lit(6);
|
|
for (k = 0; k < t; k++) p_op(24);
|
|
p_fwd(branch_ops[i], 0); p_lit(7); p_op(0); p_here(0); p_op(0);
|
|
CHECK(same("forward with literals"), "%s forward after two literals and %u opcodes", mnemonic[branch_ops[i]], t);
|
|
}
|
|
|
|
/* a label after 0 .. 6 opcodes closes the word */
|
|
for (t = 0; t <= 6; t++) {
|
|
nprog = 0; for (i = 0; i < t; i++) p_op(24);
|
|
p_label(0); p_op(23); p_back(2, 0);
|
|
CHECK(same("label"), "a label after %u opcodes", t);
|
|
}
|
|
|
|
/* ---- random programmes ---- */
|
|
for (t = 0; t < 600; t++) {
|
|
unsigned len = 5 + rnd() % 120, nback = 0, nfwd = 0, open[8], nopen = 0;
|
|
nprog = 0;
|
|
for (i = 0; i < len && nprog < MAX_ITEMS - 20; i++) {
|
|
unsigned r = rnd() % 100;
|
|
if (r < 55) p_op(plain_ops[rnd() % (sizeof plain_ops / sizeof plain_ops[0])]);
|
|
else if (r < 75) p_lit(rnd() % 3 ? (v4_cell)(int32_t)rnd() : lits[rnd() % (sizeof lits / sizeof lits[0])]);
|
|
else if (r < 82 && nback < 40) p_label((int)nback++);
|
|
else if (r < 90 && nback > 0) p_back(branch_ops[rnd() % 5], (int)(rnd() % nback));
|
|
else if (r < 95 && nopen < 8 && nfwd < 40) { open[nopen++] = nfwd; p_fwd(branch_ops[rnd() % 5], (int)nfwd++); }
|
|
else if (nopen > 0) { unsigned k = rnd() % nopen; p_here((int)open[k]); open[k] = open[--nopen]; }
|
|
}
|
|
while (nopen > 0) p_here((int)open[--nopen]);
|
|
CHECK(same("random"), "random programme %u of %u items", t, nprog);
|
|
}
|
|
|
|
/* ---- what it lays down runs ---- */
|
|
|
|
/* ( a b -- b a ) over push push drop pop pop ; */
|
|
nprog = 0; p_op(26); p_op(29); p_op(29); p_op(23); p_op(25); p_op(25); p_op(0);
|
|
CHECK(same("SWAP") && exec_prog(2, 3, 4) && pop() == 3 && pop() == 4 && clean(), "SWAP, generated, swaps");
|
|
|
|
/* ( n -- n*8+5 ) 2 push L: 2* next L 5 + ; a backward branch and a literal */
|
|
nprog = 0; p_lit(2); p_op(29); p_label(0); p_op(17); p_back(5, 0); p_lit(5); p_op(20); p_op(0);
|
|
CHECK(same("loop") && exec_prog(1, 3, 0) && pop() == 29 && clean(), "a counted loop, generated, counts");
|
|
|
|
/* ( n -- 10 | 20 ) if Z drop 10 ; Z: drop 20 ; a forward branch */
|
|
nprog = 0; p_fwd(6, 0); p_op(23); p_lit(10); p_op(0); p_here(0); p_op(23); p_lit(20); p_op(0);
|
|
CHECK(same("if") && exec_prog(1, 7, 0) && pop() == 10 && clean() && exec_prog(1, 0, 0) && pop() == 20 && clean(), "a conditional, generated, chooses");
|
|
|
|
/* a call to a word of the capsule, and a jump to one: ( c baddr -- c' ) dup push C! pop jump C@ */
|
|
CHECK(lay_by_generator() == 1, "reset");
|
|
nprog = 0;
|
|
for (v4_cell k = DICT_W; k < DICT_W + 16; k++) n.mem[k] = 0;
|
|
n.mem[DP] = DICT_W * 4;
|
|
CHECK(did("(CG-RESET)", 0, 0, 0) && did("(OP,)", 1, 24, 0) && did("(OP,)", 1, 29, 0)
|
|
&& did("(CALL,)", 1, W("C!"), 0) && did("(OP,)", 1, 25, 0) && did("(JUMP,)", 1, W("C@"), 0) && did("(FLUSH)", 0, 0, 0),
|
|
"a call and a jump to capsule words are generated");
|
|
for (i = 0; i < 8; i++)
|
|
CHECK(exec_prog(2, (v4_cell)(0x500 + 'A' + i), PAD_W * 4 + (v4_cell)i) && pop() == (v4_cell)('A' + i) && clean(), "and they run [%u]", i);
|
|
/* dup push call | pop jump : the call is in slot 2 of the first word, the jump in slot 1 of the second */
|
|
CHECK(v4_iword_op((v4_iword)(v4_ucell)n.mem[DICT_W], 2) == V4_OP_CALL
|
|
&& v4_iword_target((v4_iword)(v4_ucell)n.mem[DICT_W], 2) == (v4_iword)W("C!"), "(CALL,) lays a call to its address");
|
|
CHECK(v4_iword_op((v4_iword)(v4_ucell)n.mem[DICT_W + 1], 1) == V4_OP_JUMP
|
|
&& v4_iword_target((v4_iword)(v4_ucell)n.mem[DICT_W + 1], 1) == (v4_iword)W("C@"), "(JUMP,) lays a jump to its address");
|
|
|
|
/* (RESOLVE) writes only the address bits of its slot: a target with
|
|
* higher bits set leaves the opcodes of the word as they were */
|
|
for (i = 0; i < 3; i++) {
|
|
v4_cell r, before;
|
|
unsigned k;
|
|
n.mem[DP] = DICT_W * 4;
|
|
CHECK(did("(CG-RESET)", 0, 0, 0), "reset");
|
|
for (k = 0; k < i; k++) CHECK(did("(OP,)", 1, 24, 0), "dup");
|
|
r = get("(BRANCH>)", 1, 6, 0);
|
|
before = n.mem[DICT_W];
|
|
CHECK(r == DICT_W * 8 + (v4_cell)i, "the reference names the word and slot %u", i);
|
|
CHECK(did("(RESOLVE)", 2, (v4_cell)-1, r), "resolved to all ones");
|
|
CHECK((v4_ucell)n.mem[DICT_W] == ((v4_ucell)before | (v4_ucell)v4_iword_slot_mask(i)),
|
|
"only the %u address bits of slot %u are written", V4_SLOT_LOW_BIT(i), i);
|
|
CHECK(did("(RESOLVE)", 2, 0, r) && n.mem[DICT_W] == before, "and resolving again to 0 clears them, and nothing else");
|
|
}
|
|
|
|
/* (CG-RESET) forgets a word half built */
|
|
n.mem[DP] = DICT_W * 4;
|
|
CHECK(did("(OP,)", 1, 24, 0) && did("(LIT,)", 1, 9, 0) && did("(CG-RESET)", 0, 0, 0) && did("(FLUSH)", 0, 0, 0)
|
|
&& n.mem[DP] == DICT_W * 4, "(CG-RESET) forgets a half-built word");
|
|
|
|
/* a full dictionary: nothing is written past it, and NODE-ERROR is set */
|
|
n.mem[DP] = (DICT_END_W - 1) * 4;
|
|
n.mem[DICT_END_W] = 0x5EED;
|
|
n.mem[NODE_ERROR] = 0;
|
|
CHECK(did("(LIT,)", 1, 1, 0) && did("(LIT,)", 1, 2, 0) && did("(FLUSH)", 0, 0, 0)
|
|
&& n.mem[NODE_ERROR] != 0 && n.mem[DICT_END_W] == 0x5EED, "code that does not fit sets NODE-ERROR and writes nothing past the end");
|
|
|
|
CHECK(v4_node_guards_intact(&n) && v4_node_guards_intact(&ref), "guards intact");
|
|
|
|
printf(" %d checks, %d failures\n", checks, failures);
|
|
return failures ? 1 : 0;
|
|
}
|