The host node starts in its ROM (entry 12), and the boot feeds it the system's source through the console: capsules/v4/sys/map-64.f18, the fourteen .f18 blobs and end.f18, each found, hash-checked and signature-checked as any capsule, then EOT. The ROM assembles the system and starts it at (IDLE); the host reads the image record (v4_image_from_node) for what only the assembled system knows. A ROM error is reported with its blob, line and meaning, then PARITY:FAIL. Deleted: v4/tools/mkimage.c, capsules/v4/nucleus-64.f18, v4/capsule/*.v4, v4/tools/v4tof18.py. The .f18 files are the source now. - text.c speaks the rest of the ROM's language (set, here, store, start; v4_text_value, v4_text_start), so the oracle reads the same source: test_host_rom_nucleus compares the ROM against text.c on the .f18 files, word for word, image record included. Host tests load the .f18 files. - mkmap image writes v4_map_image (the map's description of the system); hosted and the kernel link the ROM and the map instead of v4_image_64.c. - Hera (Captain Bob's ruling, option 1): births stream the system the ROM assembled, as assembled code; hera.4th is unchanged. test_host_unit's host builds the system with the ROM. - New parity lines: PARITY:V4_F18 per blob, PARITY:V4_ROM words=2049 rom_hash=0xa6c792b5821010b7 instructions=60725216. Acceptance: amd64, aarch64, riscv64 boot to ok> with PARITY:V4_POST tests=538 pass=538 fail=0, POST: PASSED, and PARITY:V4_SYSTEM word_count=317 dict_hash=0xe5a57f3eb72ff94a on all three -- the same hash as the prebuilt nucleus gave. make -C v4 test test-32 hosted-check: all pass. Logs: logs/20261015-141648, -141938, -142350. Startup cost under QEMU TCG, the ROM's assembly (last V4_F18 line to V4_ROM): amd64 26.7 s of 132 s to ok>; aarch64 41.0 s of 195 s; riscv64 20.5 s of 101 s. Hosted native: +0.85 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
187 lines
8.5 KiB
C
187 lines
8.5 KiB
C
/* text.h -- assemble definitions written as text onto a node.
|
|
*
|
|
* Until now every DECOMPOSITION.md definition was retyped by hand in the
|
|
* tests as calls to the slot packer (asm.h), one opcode at a time. This
|
|
* reads the definitions as they are written in the document instead:
|
|
*
|
|
* : EMIT ( c -- ) CONSOLE-TX b! !b ;
|
|
* : SPACE 32 jump EMIT
|
|
*
|
|
* and lays them down through the same slot packer, so the words it produces
|
|
* are the ones the hand-written calls would have produced. It is test
|
|
* support, like asm.h: it is not the compiler capsule (JUSTIFICATION.md
|
|
* section 9), which is itself v4 code and will be assembled by this.
|
|
*
|
|
* THE NOTATION. Tokens are separated by white space. Names are case
|
|
* sensitive; the 32 opcode mnemonics are lower case.
|
|
*
|
|
* \ ... comment to the end of the line
|
|
* ( ... ) comment to the closing parenthesis
|
|
* : NAME start the word NAME at the next instruction word. A
|
|
* word ends where the next one starts; `;` is the return
|
|
* opcode and a word may hold several, or none (it then
|
|
* runs on into the next word).
|
|
* NAME call the word NAME. It may be defined later in the
|
|
* text, or in a later call to v4_text_assemble.
|
|
* 123 -5 $FF a literal: `@p` and its value. Decimal, or hex after $.
|
|
* It must fit a cell, as a signed or an unsigned number.
|
|
* CONST a constant given by v4_text_constant: a literal.
|
|
* CONST+N the constant plus the number N: one literal. (Only when
|
|
* the whole token is not itself a name.)
|
|
* &NAME the address of the word NAME, which must already be
|
|
* defined: a literal.
|
|
* ; + dup a! ... an opcode. `@p` cannot be written; write the number.
|
|
* jump X call X if X -if X next X
|
|
* a branch to X: a label of this word if it has one of
|
|
* that name, before or after the branch; otherwise a word.
|
|
* LABEL: a label, at the next instruction word. Labels belong
|
|
* to the word they are in and may be used before they
|
|
* are defined.
|
|
* FOR ... NEXT `push`, then the body, then `next` back to the body.
|
|
* FOR ... UNEXT the same with `unext`: the body and the `unext` must
|
|
* fit one instruction word and hold no literal.
|
|
* macro NAME ... endmacro
|
|
* NAME stands for the tokens between, wherever it is
|
|
* written afterwards (the document's "in line"). A macro
|
|
* may use other macros; it may not define a word, a label
|
|
* or a macro.
|
|
*
|
|
* constant NAME VALUE
|
|
* NAME is the number VALUE, as v4_text_constant gives it.
|
|
*
|
|
* #, VALUE one cell of data at the next word address (the word
|
|
* being built is ended first). VALUE is a number, a
|
|
* constant, CONST+N, or &NAME of a word already defined.
|
|
* ", TOKEN TOKEN, whatever its characters, as a counted string: its
|
|
* count byte, then its characters, four to a cell, low
|
|
* byte first, zero-padded to a whole cell.
|
|
*
|
|
* set NAME VALUE an assembler variable: like a constant, but it may be
|
|
* set again (the newest value is its value). In code it
|
|
* is a literal.
|
|
* here as a VALUE: the next word address, the word being built
|
|
* ended first. Not something to assemble on its own.
|
|
* store ADDR VALUE ADDR, a VALUE that is a word of RAM, is given VALUE: the
|
|
* state the system is switched on in.
|
|
* start NAME the end of the text: every branch must have its target,
|
|
* and NAME, a word, is where the system starts
|
|
* (v4_text_start). Nothing may follow.
|
|
* These four, constant, #, and ", are the ROM's language
|
|
* (docs/v4.0.0/MACHINE.md): the ROM assembles the same
|
|
* text the same way.
|
|
*
|
|
* header NAME a dictionary entry for NAME (capsules/v4/sys/dict.f18), laid down
|
|
* in front of the word that the next `:` starts, and
|
|
* linked to the header before it. NAME is taken as it
|
|
* stands -- it may be ( or \ or + -- and is at most 31
|
|
* characters. Between it and the `:` may come the flags
|
|
* immediate, data, inline and compile-only. The word after
|
|
* `:` is the assembler's name for the code, as always, and
|
|
* need not be the same: header + inline : PLUS + ;
|
|
*
|
|
* A name is looked up in this order: opcode, macro, number, constant, label
|
|
* (for a branch target), word. So an opcode's name cannot be redefined, and
|
|
* the FORTH words that share one (`@`, `!`, `+`, ...) are written as their
|
|
* opcode sequences.
|
|
*
|
|
* BRANCH PLACEMENT. A branch's address field is what is left of its word, so
|
|
* it reaches less the later its slot. The slot packer refuses a branch that
|
|
* cannot reach its target; this never asks it to: a branch is put in a slot
|
|
* only if that slot reaches the whole node (at 1024 words, every legal slot),
|
|
* and otherwise starts a new word.
|
|
*
|
|
* ERRORS. The first error stops the assembly and is kept: v4_text_error
|
|
* gives a message with the line number, and every later call does nothing.
|
|
*/
|
|
#ifndef V4_TEXT_H
|
|
#define V4_TEXT_H
|
|
|
|
#include "v4/asm.h"
|
|
|
|
#define V4_TEXT_MAX_SYMBOLS 2048u /* words, constants and macros */
|
|
#define V4_TEXT_MAX_LABELS 64u /* in one word */
|
|
#define V4_TEXT_MAX_REFS 1024u /* branches whose target is not known yet */
|
|
#define V4_TEXT_POOL 65536u /* characters of names and macro text */
|
|
#define V4_TEXT_MAX_TOKEN 63u
|
|
#define V4_TEXT_MAX_DEPTH 8u /* macros inside macros; FOR inside FOR */
|
|
|
|
typedef struct {
|
|
unsigned name; /* offset into pool */
|
|
unsigned char kind;
|
|
v4_cell value; /* address, constant, or macro text offset */
|
|
} v4_text_symbol;
|
|
|
|
typedef struct {
|
|
unsigned name;
|
|
v4_asm_ref ref;
|
|
unsigned line;
|
|
int local; /* may still turn out to be a label of this word */
|
|
} v4_text_ref;
|
|
|
|
typedef struct {
|
|
v4_asm as;
|
|
|
|
v4_text_symbol sym[V4_TEXT_MAX_SYMBOLS];
|
|
unsigned nsym;
|
|
|
|
struct { unsigned name; v4_cell addr; } label[V4_TEXT_MAX_LABELS];
|
|
unsigned nlabel;
|
|
|
|
v4_text_ref ref[V4_TEXT_MAX_REFS];
|
|
unsigned nref;
|
|
|
|
v4_cell for_addr[V4_TEXT_MAX_DEPTH];
|
|
unsigned nfor;
|
|
|
|
char pool[V4_TEXT_POOL];
|
|
unsigned pool_used;
|
|
|
|
v4_cell latest; /* xt of the newest header, 0 if none */
|
|
v4_cell start; /* the word `start` named, -1 if none yet */
|
|
int header_pending; /* a header waits for its `:` */
|
|
|
|
unsigned line;
|
|
int failed;
|
|
char message[224];
|
|
} v4_text;
|
|
|
|
/* Start assembling at word address `origin` of node `n`. */
|
|
void v4_text_begin(v4_text *t, v4_node *n, v4_cell origin);
|
|
|
|
/* Give NAME a value; written in the text it is a literal. */
|
|
void v4_text_constant(v4_text *t, const char *name, v4_cell value);
|
|
|
|
/* Assemble `source`, a NUL-terminated string. May be called several times;
|
|
* words, constants and macros carry over, labels do not. Returns 1 if there
|
|
* has been no error. */
|
|
int v4_text_assemble(v4_text *t, const char *source);
|
|
|
|
/* The same for the text in the file at `path`. Its errors name the file. */
|
|
int v4_text_assemble_file(v4_text *t, const char *path);
|
|
|
|
/* Finish: every branch must have found its target. Returns 1 if there has
|
|
* been no error. Assembling may continue afterwards. */
|
|
int v4_text_finish(v4_text *t);
|
|
|
|
/* The address of the word NAME, or -1 if there is none. */
|
|
v4_cell v4_text_word(const v4_text *t, const char *name);
|
|
|
|
/* The xt of the newest `header`, 0 if there has been none: what the
|
|
* dictionary's LATEST starts as. */
|
|
v4_cell v4_text_latest(const v4_text *t);
|
|
|
|
/* The value of the constant or set variable NAME: 1 and *value, or 0 if
|
|
* NAME is neither. */
|
|
int v4_text_value(const v4_text *t, const char *name, v4_cell *value);
|
|
|
|
/* The word `start` named, or -1 if the text has had no start. */
|
|
v4_cell v4_text_start(const v4_text *t);
|
|
|
|
/* The next free word address. */
|
|
v4_cell v4_text_here(v4_text *t);
|
|
|
|
/* "" if there has been no error, else what and where. */
|
|
const char *v4_text_error(const v4_text *t);
|
|
|
|
#endif /* V4_TEXT_H */
|