diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index d63bc0f2..05cc53ee 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -334,7 +334,7 @@ Section numbers match the v3 primitive reference. | `C@` | CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). Leaves its caller 8 data cells and 7 return entries. | | `C!` | CAP | See below (D-1). Call-free. Executed on the golden model (2026-10-03). | | `FILL` | CAP | `( baddr u c -- )`: the low byte of `c`. See below. Nothing for `u = 0`; for `u < 0` nothing is written and `NODE-ERROR` is set (v3 read a negative count as a huge unsigned one). Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 5 data cells and 5 return entries. Clobbers `A` and, on error, `B`. | -| `MOVE` | CAP | `( src dst u -- )`, bytes: `push over over - -if UP drop pop jump CMOVE> UP: drop pop jump CMOVE` — `CMOVE>` when `src < dst`, else `CMOVE`, so overlapping bytes are read before they are overwritten, as v3's `memmove`. The test is the sign of `src - dst`, in line. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. Leaves its caller 4 data cells and 5 return entries. | +| `MOVE` | CAP | `( addr1 addr2 n -- )` — FORTH-79 (ruled 2026-10-04): `n` cells from `addr1` to `addr2`, the cell at `addr1` first; nothing for `n <= 0`. The addresses are word addresses (D-1). `-if OK drop drop drop ; OK: if DONE push over a! @ over a! ! 1 + push 1 + pop pop -1 + jump OK DONE: drop drop drop ;` v3's `MOVE` moved bytes, in whichever direction did not overwrite what it had yet to read; that is `CMOVE` and `CMOVE>`. Executed on the golden model (2026-10-04) against C, overlapping both ways. Leaves its caller 6 data cells and 6 return entries. Clobbers `A`. | | `ERASE` | CAP | `0 FILL`, as `0 jump FILL`. Executed on the golden model (2026-10-03), including a transcript of the v3 binary. | | `CELLS` | IN | Empty (word-addressed, D-1); v3 answers 24 for `3 CELLS`. Executed on the golden model (2026-10-03). | diff --git a/v4/tests/test_strings.c b/v4/tests/test_strings.c index b0d4ef3b..9bb2331f 100644 --- a/v4/tests/test_strings.c +++ b/v4/tests/test_strings.c @@ -9,8 +9,11 @@ * COUNT ( baddr -- baddr+1 c ) * CMOVE ( src dst u -- ) low byte first * CMOVE> ( src dst u -- ) high byte first - * MOVE ( src dst u -- ) whichever of the two does not overwrite - * bytes it has yet to read + * MOVE ( addr1 addr2 n -- ) FORTH-79 (ruled 2026-10-04): n cells + * from addr1 to addr2, the cell at addr1 + * first; nothing for n <= 0. The addresses + * are word addresses (D-1). v3's MOVE moved + * bytes, either way round, like memmove. * FILL ( baddr u c -- ) the low byte of c * BLANK ERASE ( baddr u -- ) FILL with 32, with 0 * -TRAILING ( baddr u -- baddr u' ) without its trailing spaces @@ -173,15 +176,22 @@ static void build(void) HERE_(b); O(DROP); O(DROP); O(DROP); O(SEMI); - /* : MOVE ( src dst u -- ) - * push over over - -if UP drop pop jump CMOVE> src < dst - * UP: drop pop jump CMOVE - * The sign of src - dst, the subtraction in line. */ + /* : MOVE ( addr1 addr2 n -- ) FORTH-79: cells, low address first + * -if OK drop drop drop ; n < 0 + * OK: if DONE push addr1 addr2 R: n + * over a! @ over a! ! 1 + push 1 + pop pop -1 + jump OK + * DONE: drop drop drop ; */ w_move = v4_asm_label(&as); - O(PUSH); O(OVER); O(OVER); SUB_INLINE(); a = FWD(MINUS_IF); - O(DROP); O(RPOP); JUMP(w_cmoveup); + a = FWD(MINUS_IF); + O(DROP); O(DROP); O(DROP); O(SEMI); HERE_(a); - O(DROP); O(RPOP); JUMP(w_cmove); + l = v4_asm_label(&as); + b = FWD(IF); + O(PUSH); O(OVER); O(BANG_A); O(FETCH_A); O(OVER); O(BANG_A); O(STORE_A); + LIT(1); O(ADD); O(PUSH); LIT(1); O(ADD); O(RPOP); O(RPOP); LIT(-1); O(ADD); + JUMP(l); + HERE_(b); + O(DROP); O(DROP); O(DROP); O(SEMI); /* : FILL ( baddr u c -- ) * -ROT c baddr u @@ -524,12 +534,10 @@ int main(void) * 20 20 20 20 2E 2E 2E 2E 00 00 00 2E 2E 2E 2E 2E * "ABCDEF" B 6 CMOVE B B 2 + 6 CMOVE> B B 1 + 6 CMOVE B 16 DUMP * 41 41 41 41 41 41 41 46 00 00 00 2E 2E 2E 2E 2E - * B 3 + B 5 MOVE B 16 DUMP - * 41 41 41 41 46 41 41 46 00 00 00 2E 2E 2E 2E 2E */ + * (v3 went on to B 3 + B 5 MOVE, a byte move; MOVE here is FORTH-79's and moves cells.) */ { static const unsigned char d1[16] = { 0x20,0x20,0x20,0x20,0x2E,0x2E,0x2E,0x2E,0,0,0,0x2E,0x2E,0x2E,0x2E,0x2E }; static const unsigned char d2[16] = { 0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x46,0,0,0,0x2E,0x2E,0x2E,0x2E,0x2E }; - static const unsigned char d3[16] = { 0x41,0x41,0x41,0x41,0x46,0x41,0x41,0x46,0,0,0,0x2E,0x2E,0x2E,0x2E,0x2E }; const v4_cell B = BUF + 21; #define STEP(word, argc, a, b, c) do { \ CHECK(call(word, argc, a, b, c, 0) && clean() && !err(), "v3 block-copy step returns"); \ @@ -544,8 +552,6 @@ int main(void) STEP(w_cmoveup, 3, B, B + 2, 6); STEP(w_cmove, 3, B, B + 1, 6); CHECK(memcmp(img + 21, d2, 16) == 0, "v3: CMOVE, CMOVE>, CMOVE"); - STEP(w_move, 3, B + 3, B, 5); - CHECK(memcmp(img + 21, d3, 16) == 0, "v3: MOVE"); for (i = 0; i < BUFLEN; i++) if ((i < 21 || i >= 37) && (i < 100 || i >= 106) && img[i] != 0x77) break; CHECK(i == BUFLEN, "nothing outside B was written"); @@ -585,11 +591,16 @@ int main(void) if (t % 2) dst = rnd() % (BUFLEN - 48); else { int d = (int)(rnd() % 17) - 8; dst = (unsigned)((int)src + d < 0 ? 0 : (int)src + d); if (dst > BUFLEN - 48) dst = BUFLEN - 48; } random_img(0); - CHECK(call(which == 0 ? w_cmove : which == 1 ? w_cmoveup : w_move, 3, - BUF + (v4_cell)src, BUF + (v4_cell)dst, (v4_cell)len, 0) && clean(), "move returns [%u]", t); - if (which == 0) for (k = 0; k < len; k++) img[dst + k] = img[src + k]; - else if (which == 1) for (k = len; k-- > 0; ) img[dst + k] = img[src + k]; - else memmove(img + dst, img + src, len); + if (which == 2) { /* MOVE: cells, not bytes */ + len /= 4; src /= 4; dst /= 4; + CHECK(call(w_move, 3, BUFW + (v4_cell)src, BUFW + (v4_cell)dst, (v4_cell)len, 0) && clean(), "MOVE returns [%u]", t); + for (k = 0; k < 4 * len; k++) img[4 * dst + k] = img[4 * src + k]; + } else { + CHECK(call(which == 0 ? w_cmove : w_cmoveup, 3, + BUF + (v4_cell)src, BUF + (v4_cell)dst, (v4_cell)len, 0) && clean(), "move returns [%u]", t); + if (which == 0) for (k = 0; k < len; k++) img[dst + k] = img[src + k]; + else for (k = len; k-- > 0; ) img[dst + k] = img[src + k]; + } CHECK(img_matches() && !err(), "%s [%u] src %u dst %u len %u", which == 0 ? "CMOVE" : which == 1 ? "CMOVE>" : "MOVE", t, src, dst, len); } @@ -601,8 +612,8 @@ int main(void) for (i = 0; i < 3; i++) { CHECK(call(w_cmove, 3, BUF, BUF + 50, neg[i], 0) && clean() && img_matches() && err(), "CMOVE negative count [%u]", i); CHECK(call(w_cmoveup, 3, BUF, BUF + 50, neg[i], 0) && clean() && img_matches() && err(), "CMOVE> negative count [%u]", i); - CHECK(call(w_move, 3, BUF, BUF + 50, neg[i], 0) && clean() && img_matches() && err(), "MOVE negative count, up [%u]", i); - CHECK(call(w_move, 3, BUF + 50, BUF, neg[i], 0) && clean() && img_matches() && err(), "MOVE negative count, down [%u]", i); + CHECK(call(w_move, 3, BUFW, BUFW + 12, neg[i], 0) && clean() && img_matches() && !err(), "MOVE moves nothing for n < 0, up [%u]", i); + CHECK(call(w_move, 3, BUFW + 12, BUFW, neg[i], 0) && clean() && img_matches() && !err(), "MOVE moves nothing for n < 0, down [%u]", i); CHECK(call(w_fill, 3, BUF, neg[i], 65, 0) && clean() && img_matches() && err(), "FILL negative count [%u]", i); CHECK(call(w_blank, 2, BUF, neg[i], 0, 0) && clean() && img_matches() && err(), "BLANK negative count [%u]", i); CHECK(call(w_erase, 2, BUF, neg[i], 0, 0) && clean() && img_matches() && err(), "ERASE negative count [%u]", i); @@ -690,7 +701,7 @@ int main(void) headroom("COUNT", w_count, 1, BUF + 3, 0, 0, 0, 2, 6, 6); headroom("CMOVE", w_cmove, 3, BUF + 3, BUF + 90, 9, 0, 0, 3, 5); headroom("CMOVE>", w_cmoveup, 3, BUF + 3, BUF + 90, 9, 0, 0, 3, 5); - headroom("MOVE", w_move, 3, BUF + 3, BUF + 6, 9, 0, 0, 3, 5); + headroom("MOVE", w_move, 3, BUFW + 1, BUFW + 9, 5, 0, 0, 4, 6); headroom("FILL", w_fill, 3, BUF + 3, 9, 65, 0, 0, 3, 5); headroom("BLANK", w_blank, 2, BUF + 3, 9, 0, 0, 0, 3, 5); headroom("-TRAILING", w_trailing, 2, BUF + 64, 8, 0, 0, 2, 4, 6);