/* test_pictured.c -- pictured numeric output, executed. * * DECOMPOSITION.md 5.8 says only that `<# # #S HOLD SIGN #>` are "standard * pictured-output definitions over UM/MOD and a hold buffer". This file * writes them out, assembles them on a node of their own and runs them on * the golden model against a C reference. * * It is a separate file from test_foundation.c because that file's * hand-assembled words already fill about 900 of a node's 1024 words. The * words these definitions rest on (SWAP, OR, UM/MOD, LSHIFT, RSHIFT, C!) are * therefore assembled again here, opcode for opcode as in test_foundation.c, * where each is tested on its own. * * Behaviour follows v3 (v3/src/word_source/format_words.c) with the standard * stack effects 5.8 rules: digits 0-9 then A-Z; BASE outside 2..36 reads as * 10; SIGN ( n -- ); the buffer holds 63 characters; HOLD of a value outside * 0..255, or into a full buffer, stores nothing and sets NODE-ERROR (D-13). * * The buffer is filled backwards from its end through the pointer HLD, so * `#>` returns the byte address of the first character and the count. */ #include "v4/asm.h" #include "v4/testcode.h" #include #include #include 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) /* The memory map is open (D-4); the test puts the variables at the top of * the node. */ #define NODE_ERROR ((v4_cell)(V4_NODE_WORDS - 2u)) #define HBUF ((v4_cell)(V4_NODE_WORDS - 32u)) /* 16 cells = 64 bytes */ #define HEND ((v4_cell)(HBUF * 4 + 64)) /* byte address past the buffer */ #define HLD ((v4_cell)(V4_NODE_WORDS - 35u)) /* byte address of the first character */ #define BASE ((v4_cell)(V4_NODE_WORDS - 34u)) /* the cell at -33, under the buffer, is left free */ #define HCAP 63 static v4_node n; static v4_exec_state es; static v4_heat h; static v4_asm as; static v4_cell w_swap, w_or, w_ummod, w_lshift, w_rshift, w_cstore, w_base, w_begin, w_hold, w_sign, w_hash, w_hashs, w_end, t_ud, t_signed, t_money, t_hold1; #define O(name) v4_asm_op(&as, V4_OP_##name) #define LIT(v) v4_asm_lit(&as, (v4_cell)(v)) #define CALL(w) v4_asm_branch(&as, V4_OP_CALL, (w)) #define FWD(op) v4_asm_branch_fwd(&as, V4_OP_##op) #define HERE_(r) v4_asm_resolve(&as, (r), v4_asm_label(&as)) #define VGET(a) do { LIT(a); O(BANG_B); O(FETCH_B); } while (0) #define VSET(a) do { LIT(a); O(BANG_B); O(STORE_B); } while (0) #define SWAP_INLINE() do { O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); } while (0) #define NROT_INLINE() do { SWAP_INLINE(); O(PUSH); SWAP_INLINE(); O(RPOP); } while (0) #define SUB_INLINE() do { O(PUSH); O(INV); O(RPOP); O(ADD); O(INV); } while (0) /* The words pictured output rests on, as in test_foundation.c. */ static void build_dependencies(void) { /* : SWAP over push push drop pop pop ; */ w_swap = v4_asm_label(&as); O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); O(SEMI); /* : OR over inv and xor ; */ w_or = v4_asm_label(&as); O(OVER); O(INV); O(AND); O(XOR); O(SEMI); /* : UM/MOD ( ulo uhi ud -- urem uquot ) section 4, call-free */ { v4_cell loop, l_sub, l_setbit, l_nosub; v4_asm_ref r0, r1, r2, r3, r4, r5, r6, to_sub1, to_sub2, to_nosub1, to_nosub2, to_setbit; w_ummod = v4_asm_label(&as); O(BANG_A); LIT(V4_CELL_BITS - 1); O(PUSH); loop = v4_asm_label(&as); r0 = FWD(MINUS_IF); O(TWO_STAR); O(OVER); r1 = FWD(MINUS_IF); O(DROP); LIT(1); O(ADD); r2 = FWD(JUMP); HERE_(r1); O(DROP); HERE_(r2); O(PUSH); O(TWO_STAR); O(RPOP); to_sub1 = FWD(JUMP); HERE_(r0); O(TWO_STAR); O(OVER); r3 = FWD(MINUS_IF); O(DROP); LIT(1); O(ADD); r4 = FWD(JUMP); HERE_(r3); O(DROP); HERE_(r4); O(PUSH); O(TWO_STAR); O(RPOP); O(DUP); O(PUSH_A); O(XOR); r5 = FWD(MINUS_IF); O(DROP); to_nosub1 = FWD(MINUS_IF); to_sub2 = FWD(JUMP); HERE_(r5); O(DROP); O(DUP); O(INV); O(PUSH_A); O(ADD); O(INV); r6 = FWD(MINUS_IF); O(DROP); to_nosub2 = FWD(JUMP); HERE_(r6); O(PUSH); O(DROP); O(RPOP); to_setbit = FWD(JUMP); l_sub = v4_asm_label(&as); O(INV); O(PUSH_A); O(ADD); O(INV); l_setbit = v4_asm_label(&as); O(PUSH); LIT(1); O(ADD); O(RPOP); l_nosub = v4_asm_label(&as); v4_asm_branch(&as, V4_OP_NEXT, loop); O(OVER); O(PUSH); O(PUSH); O(DROP); O(RPOP); O(RPOP); O(SEMI); v4_asm_resolve(&as, to_sub1, l_sub); v4_asm_resolve(&as, to_sub2, l_sub); v4_asm_resolve(&as, to_setbit, l_setbit); v4_asm_resolve(&as, to_nosub1, l_nosub); v4_asm_resolve(&as, to_nosub2, l_nosub); } /* : LSHIFT BEGIN dup WHILE 1- SWAP 2* SWAP REPEAT drop ; * : RSHIFT BEGIN dup WHILE 1- SWAP 2/ MSB inv and SWAP REPEAT drop ; */ { v4_cell l0; v4_asm_ref l1; w_lshift = v4_asm_label(&as); l0 = v4_asm_label(&as); O(DUP); l1 = FWD(IF); O(DROP); LIT(-1); O(ADD); CALL(w_swap); O(TWO_STAR); CALL(w_swap); v4_asm_branch(&as, V4_OP_JUMP, l0); HERE_(l1); O(DROP); O(DROP); O(SEMI); w_rshift = v4_asm_label(&as); l0 = v4_asm_label(&as); O(DUP); l1 = FWD(IF); O(DROP); LIT(-1); O(ADD); CALL(w_swap); O(TWO_SLASH); LIT((v4_cell)V4_MSB); O(INV); O(AND); CALL(w_swap); v4_asm_branch(&as, V4_OP_JUMP, l0); HERE_(l1); O(DROP); O(DROP); O(SEMI); } /* C!, call-free, as in test_foundation.c. * : C! ( c baddr -- ) call-free * dup 2/ 2/ a! 3 and push 255 and pop c' k A: word address * if K0 -1 + if K1 -1 + if K2 * drop 23 FOR 2* UNEXT @ 4278190080 inv and + ! ; byte 3 * K2: drop 15 FOR 2* UNEXT @ -16711681 and + ! ; byte 2 * K1: drop 7 FOR 2* UNEXT @ -65281 and + ! ; byte 1 * K0: drop @ -256 and + ! ; byte 0 * One case per byte position: the byte is shifted up, that byte of the * cell cleared with a constant mask, and the two added. */ { v4_asm_ref k0, k1, k2; w_cstore = v4_asm_label(&as); O(DUP); O(TWO_SLASH); O(TWO_SLASH); O(BANG_A); LIT(3); O(AND); O(PUSH); LIT(255); O(AND); O(RPOP); k0 = v4_asm_branch_fwd(&as, V4_OP_IF); LIT(-1); O(ADD); k1 = v4_asm_branch_fwd(&as, V4_OP_IF); LIT(-1); O(ADD); k2 = v4_asm_branch_fwd(&as, V4_OP_IF); O(DROP); LIT(23); O(PUSH); (void)v4_asm_label(&as); O(TWO_STAR); O(UNEXT); O(FETCH_A); LIT((v4_cell)(v4_ucell)0xFF000000u); O(INV); O(AND); O(ADD); O(STORE_A); O(SEMI); v4_asm_resolve(&as, k2, v4_asm_label(&as)); O(DROP); LIT(15); O(PUSH); (void)v4_asm_label(&as); O(TWO_STAR); O(UNEXT); O(FETCH_A); LIT(-16711681); O(AND); O(ADD); O(STORE_A); O(SEMI); v4_asm_resolve(&as, k1, v4_asm_label(&as)); O(DROP); LIT(7); O(PUSH); (void)v4_asm_label(&as); O(TWO_STAR); O(UNEXT); O(FETCH_A); LIT(-65281); O(AND); O(ADD); O(STORE_A); O(SEMI); v4_asm_resolve(&as, k0, v4_asm_label(&as)); O(DROP); O(FETCH_A); LIT(-256); O(AND); O(ADD); O(STORE_A); O(SEMI); } } /* Pictured output, 5.8. */ static void build_pictured(void) { v4_asm_ref a, b, c; v4_cell l; /* : (BASE) ( -- b ) BASE, or 10 when it is outside 2..36 * BASE b! @b dup -2 + -if L1 drop drop 10 ; * L1: drop dup -37 + -if L2 drop ; * L2: drop drop 10 ; */ w_base = v4_asm_label(&as); VGET(BASE); O(DUP); LIT(-2); O(ADD); a = FWD(MINUS_IF); O(DROP); O(DROP); LIT(10); O(SEMI); HERE_(a); O(DROP); O(DUP); LIT(-37); O(ADD); b = FWD(MINUS_IF); O(DROP); O(SEMI); HERE_(b); O(DROP); O(DROP); LIT(10); O(SEMI); /* : <# ( -- ) HEND HLD b! !b ; */ w_begin = v4_asm_label(&as); LIT(HEND); VSET(HLD); O(SEMI); /* : HOLD ( c -- ) D-13 * dup -256 and if OKC drop jump ERR c outside 0..255 * OKC: drop HLD b! @b -(HEND-62) + -if ROOM drop 63 held already * ERR: drop NODE-ERROR b! -1 !b ; * ROOM: drop HLD b! @b -1 + dup !b jump C! * The last step is a jump, not a call: C! returns to HOLD's caller. */ w_hold = v4_asm_label(&as); O(DUP); LIT(-256); O(AND); a = FWD(IF); O(DROP); b = FWD(JUMP); HERE_(a); O(DROP); VGET(HLD); LIT(-(HEND - 62)); O(ADD); c = FWD(MINUS_IF); O(DROP); HERE_(b); O(DROP); LIT(NODE_ERROR); O(BANG_B); LIT(-1); O(STORE_B); O(SEMI); HERE_(c); O(DROP); VGET(HLD); LIT(-1); O(ADD); O(DUP); O(STORE_B); v4_asm_branch(&as, V4_OP_JUMP, w_cstore); /* : SIGN ( n -- ) -if L1 drop 45 jump HOLD L1: drop ; */ w_sign = v4_asm_label(&as); a = FWD(MINUS_IF); O(DROP); LIT(45); v4_asm_branch(&as, V4_OP_JUMP, w_hold); HERE_(a); O(DROP); O(SEMI); /* : # ( ud1 -- ud2 ) * ud1 / base in two UM/MOD steps, the high cell first and its remainder * leading the low cell; the last remainder is the digit. * 0 (BASE) UM/MOD -ROT qhi lo rem1 * (BASE) UM/MOD -ROT qlo qhi rem * The high quotient waits under the second division on the data stack, * not on the return stack. -ROT in line is SWAP push SWAP pop. * dup -10 + -if L1 drop 48 + jump L2 L1: drop 55 + L2: jump HOLD */ w_hash = v4_asm_label(&as); LIT(0); CALL(w_base); CALL(w_ummod); NROT_INLINE(); CALL(w_base); CALL(w_ummod); NROT_INLINE(); O(DUP); LIT(-10); O(ADD); a = FWD(MINUS_IF); O(DROP); LIT(48); O(ADD); b = FWD(JUMP); HERE_(a); O(DROP); LIT(55); O(ADD); HERE_(b); v4_asm_branch(&as, V4_OP_JUMP, w_hold); /* : #S ( ud -- 0 0 ) L: # over over OR if L1 drop jump L L1: drop ; */ w_hashs = v4_asm_label(&as); l = v4_asm_label(&as); CALL(w_hash); O(OVER); O(OVER); CALL(w_or); a = FWD(IF); O(DROP); v4_asm_branch(&as, V4_OP_JUMP, l); HERE_(a); O(DROP); O(SEMI); /* : #> ( ud -- baddr u ) drop drop HLD b! @b HEND over push inv pop + inv ; */ w_end = v4_asm_label(&as); O(DROP); O(DROP); VGET(HLD); LIT(HEND); O(OVER); SUB_INLINE(); O(SEMI); /* How they are used. */ /* ( ud -- baddr u ) <# #S #> */ t_ud = v4_asm_label(&as); CALL(w_begin); CALL(w_hashs); CALL(w_end); O(SEMI); /* ( n -- baddr u ) dup push ABS 0 <# #S pop SIGN #> as `.` does */ t_signed = v4_asm_label(&as); O(DUP); O(PUSH); a = FWD(MINUS_IF); O(INV); LIT(1); O(ADD); HERE_(a); LIT(0); CALL(w_begin); CALL(w_hashs); O(RPOP); CALL(w_sign); CALL(w_end); O(SEMI); /* ( ud -- baddr u ) <# # # 46 HOLD #S #> two places, a point, the rest */ t_money = v4_asm_label(&as); CALL(w_begin); CALL(w_hash); CALL(w_hash); LIT(46); CALL(w_hold); CALL(w_hashs); CALL(w_end); O(SEMI); /* ( c -- baddr u ) <# HOLD 0 0 #> */ t_hold1 = v4_asm_label(&as); CALL(w_begin); CALL(w_hold); LIT(0); LIT(0); CALL(w_end); O(SEMI); } /* ---- running ---------------------------------------------------------- */ static int call(v4_cell word, 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, word, 4000000) > 0; } /* The string `#>` left: its bytes read out of the node, and the canary * under ( baddr u ). */ static int result_string(char *out, unsigned cap) { v4_cell u = v4_dstack_pop(&n.ds), baddr = v4_dstack_pop(&n.ds); unsigned i; if (v4_dstack_pop(&n.ds) != CANARY) return -1; if (u < 0 || (unsigned)u >= cap) return -1; for (i = 0; i < (unsigned)u; i++) { v4_cell ba = baddr + (v4_cell)i; v4_ucell w = (v4_ucell)v4_node_load(&n, ba >> 2); out[i] = (char)((w >> (8u * (unsigned)(ba & 3))) & 0xFFu); } out[u] = 0; return (int)u; } /* Stack headroom, as in test_foundation.c: run `word` with `dfill` marked * cells under the canary and `rfill` under its return address; the string, * the canary and every marked cell must come back intact. */ static int fits(v4_cell word, unsigned argc, v4_cell a, v4_cell b, const char *want, unsigned dfill, unsigned rfill) { char got[4 * V4_CELL_BITS]; unsigned i; v4_dstack_reset(&n.ds); v4_rstack_reset(&n.rs); v4_exec_reset(&es); for (i = 0; i < dfill; i++) v4_dstack_push(&n.ds, (v4_cell)(0x5A000000 + i)); v4_dstack_push(&n.ds, CANARY); if (argc > 0) v4_dstack_push(&n.ds, a); if (argc > 1) v4_dstack_push(&n.ds, b); for (i = 0; i < rfill; i++) v4_rstack_push(&n.rs, (v4_cell)(0x6B000000 + i)); if (v4_test_call(&n, &es, &h, word, 4000000) <= 0) return 0; if (result_string(got, sizeof got) < 0 || strcmp(got, want) != 0) return 0; for (i = dfill; i-- > 0; ) if (v4_dstack_pop(&n.ds) != (v4_cell)(0x5A000000 + i)) return 0; for (i = rfill; i-- > 0; ) if (v4_rstack_pop(&n.rs) != (v4_cell)(0x6B000000 + i)) return 0; return 1; } static void headroom(const char *name, v4_cell word, unsigned argc, v4_cell a, v4_cell b, const char *want, int *dh, int *rh) { int d, r; for (d = 0; d < V4_DATA_DEPTH; d++) if (!fits(word, argc, a, b, want, (unsigned)d + 1u, 0)) break; for (r = 0; r < V4_RET_DEPTH; r++) if (!fits(word, argc, a, b, want, 0, (unsigned)r + 1u)) break; *dh = d; *rh = r; printf(" %s headroom: data %d below canary, return %d below its return address\n", name, d, r); } /* ---- the C reference ---------------------------------------------------- */ #define LIMBS (2 * V4_CELL_BITS / 16) /* The digits of the unsigned double hi:lo in `base` (2..36), most * significant first, "0" for zero, by short division on 16-bit limbs. */ static unsigned ref_digits(v4_ucell lo, v4_ucell hi, unsigned base, char *out) { uint32_t l[LIMBS]; char tmp[2 * V4_CELL_BITS + 1]; unsigned i, len = 0; for (i = 0; i < LIMBS / 2; i++) { l[i] = (uint32_t)((lo >> (16 * i)) & 0xFFFFu); l[i + LIMBS / 2] = (uint32_t)((hi >> (16 * i)) & 0xFFFFu); } for (;;) { uint32_t rem = 0; int nonzero = 0; for (i = LIMBS; i-- > 0; ) { uint32_t v = (rem << 16) | l[i]; l[i] = v / base; rem = v % base; if (l[i]) nonzero = 1; } tmp[len++] = (char)(rem < 10 ? '0' + rem : 'A' + (rem - 10)); if (!nonzero) break; } for (i = 0; i < len; i++) out[i] = tmp[len - 1 - i]; out[len] = 0; return len; } /* What the buffer holds after the characters of `full` have been held from * its last to its first: the last HCAP of them, and whether any were * dropped (D-13). */ static int ref_truncate(const char *full, char *out) { size_t len = strlen(full); if (len > HCAP) { strcpy(out, full + (len - HCAP)); return 1; } strcpy(out, full); return 0; } static unsigned eff_base(v4_cell b) { return (b < 2 || b > 36) ? 10u : (unsigned)b; } static const v4_cell vec[] = { 0, 1, 2, 9, 10, 35, 36, 255, 12345, -1, -2, -12345, (v4_cell)(V4_MSB - 1u), (v4_cell)V4_MSB, (v4_cell)(MAXU / 3u) }; #define NVEC (sizeof vec / sizeof vec[0]) static const v4_cell bases[] = { 10, 16, 2, 8, 36, 3, 0, 1, 37, -5 }; #define NBASE (sizeof bases / sizeof bases[0]) int main(void) { char got[4 * V4_CELL_BITS], full[4 * V4_CELL_BITS], want[4 * V4_CELL_BITS]; printf("v4 pictured-output tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); v4_node_reset(&n); v4_asm_begin(&as, &n, 16); build_dependencies(); build_pictured(); CHECK(v4_asm_ok(&as), "pictured-output words assemble"); CHECK(v4_asm_label(&as) < HLD, "code stays below the variables"); for (unsigned bi = 0; bi < NBASE; bi++) { unsigned base = eff_base(bases[bi]); /* <# #S #> on unsigned doubles */ for (unsigned i = 0; i < NVEC; i++) for (unsigned j = 0; j < NVEC; j++) { int over; (void)ref_digits((v4_ucell)vec[i], (v4_ucell)vec[j], base, full); over = ref_truncate(full, want); v4_node_store(&n, BASE, bases[bi]); v4_node_store(&n, NODE_ERROR, 0); CHECK(call(t_ud, 2, vec[i], vec[j]) && result_string(got, sizeof got) >= 0 && strcmp(got, want) == 0 && v4_node_load(&n, NODE_ERROR) == (over ? -1 : 0), "<# #S #> base %ld [%u,%u]: got \"%s\" want \"%s\"", (long)bases[bi], i, j, got, want); /* <# # # 46 HOLD #S #> */ { size_t len = strlen(full); char pad[4 * V4_CELL_BITS], pic[4 * V4_CELL_BITS]; if (len < 3) { memset(pad, '0', 3 - len); strcpy(pad + (3 - len), full); } else strcpy(pad, full); len = strlen(pad); memcpy(pic, pad, len - 2); pic[len - 2] = '.'; memcpy(pic + len - 1, pad + len - 2, 2); pic[len + 1] = 0; over = ref_truncate(pic, want); v4_node_store(&n, NODE_ERROR, 0); CHECK(call(t_money, 2, vec[i], vec[j]) && result_string(got, sizeof got) >= 0 && strcmp(got, want) == 0 && v4_node_load(&n, NODE_ERROR) == (over ? -1 : 0), "<# # # . #S #> base %ld [%u,%u]: got \"%s\" want \"%s\"", (long)bases[bi], i, j, got, want); } } /* dup push ABS 0 <# #S pop SIGN #> on signed singles */ for (unsigned i = 0; i < NVEC; i++) { v4_cell v = vec[i]; v4_ucell mag = v < 0 ? (v4_ucell)0 - (v4_ucell)v : (v4_ucell)v; int over; full[0] = '-'; (void)ref_digits(mag, 0, base, full + (v < 0 ? 1 : 0)); over = ref_truncate(full, want); v4_node_store(&n, BASE, bases[bi]); v4_node_store(&n, NODE_ERROR, 0); CHECK(call(t_signed, 1, v, 0) && result_string(got, sizeof got) >= 0 && strcmp(got, want) == 0 && v4_node_load(&n, NODE_ERROR) == (over ? -1 : 0), "signed base %ld [%u]: got \"%s\" want \"%s\"", (long)bases[bi], i, got, want); } } /* HOLD: any byte is kept; anything else is dropped and flagged. */ { static const v4_cell hv[] = { 0, 1, 32, 45, 65, 127, 128, 255, 256, 257, -1, 65536 }; for (unsigned i = 0; i < sizeof hv / sizeof hv[0]; i++) { int ok = hv[i] >= 0 && hv[i] <= 255, len; v4_node_store(&n, NODE_ERROR, 0); CHECK(call(t_hold1, 1, hv[i], 0), "HOLD returns [%u]", i); { v4_cell u = v4_dstack_pop(&n.ds), baddr = v4_dstack_pop(&n.ds); v4_ucell w = (v4_ucell)v4_node_load(&n, (HEND - 1) >> 2); len = (int)u; CHECK(v4_dstack_pop(&n.ds) == CANARY, "HOLD canary [%u]", i); CHECK(len == (ok ? 1 : 0) && baddr == HEND - len, "HOLD count [%u]: %d", i, len); if (ok) CHECK(((w >> (8u * (unsigned)((HEND - 1) & 3))) & 0xFFu) == (v4_ucell)hv[i], "HOLD stores [%u]", i); } CHECK(v4_node_load(&n, NODE_ERROR) == (ok ? 0 : -1), "HOLD NODE-ERROR [%u]", i); } } /* The cell just below the buffer and the variables above it are never * written by a number that overflows (base 2, all ones). */ { v4_node_store(&n, HBUF - 1, (v4_cell)0x5EED5EED); v4_node_store(&n, BASE, 2); v4_node_store(&n, NODE_ERROR, 0); CHECK(call(t_ud, 2, -1, -1) && result_string(got, sizeof got) == HCAP, "overflowing number leaves %d characters", HCAP); CHECK(v4_node_load(&n, NODE_ERROR) == -1, "overflowing number sets NODE-ERROR"); CHECK(v4_node_load(&n, HBUF - 1) == (v4_cell)0x5EED5EED, "cell below the buffer untouched"); CHECK(v4_node_load(&n, BASE) == 2, "BASE untouched"); /* the first byte of the buffer is the one v3 also leaves spare */ CHECK(v4_node_load(&n, HLD) == HEND - HCAP, "HLD stops one byte above the buffer's start"); } /* What the pictures leave their caller (D-2). */ { int dh, rh; v4_node_store(&n, BASE, 10); headroom("<# #S #>", t_ud, 2, 12345, 0, "12345", &dh, &rh); CHECK(dh >= 2 && rh >= 3, "<# #S #> leaves room"); headroom("signed picture", t_signed, 1, -12345, 0, "-12345", &dh, &rh); CHECK(dh >= 2 && rh >= 2, "the signed picture leaves room"); } CHECK(v4_node_guards_intact(&n), "guards intact"); printf(" %d checks, %d failures\n", checks, failures); return failures ? 1 : 0; }