feat: iteration 36 task 4 — runtime bitwise opcodes, .wob v6

- WOP_BAND..WOP_SHR = 42..46 (wob.h), WOP_MAX 46, WOB_VERSION 6
- trap kind WO_T_SHIFT=12: shift count outside 0..63 traps (DIV0
  precedent, never x86's silent count%64); SHR arithmetic
- vm.c: one shared case-body serves both dispatch flavors; SHL shifts
  the unsigned word (wrapping), SHR casts int64_t (sign extends)
- loader.c + test runner battery + disasm: v6 accepted, new opcodes
  validated three-register, rendered BAND/BOR/BXOR/SHL/SHR
- woc-test 543/0, wovm-test ASan both flavors green, cli_smoke OK

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
shoney.arickathil 2026-08-22 21:38:22 +02:00
parent 41cfe3f53e
commit 5d0e6635f1
6 changed files with 77 additions and 7 deletions

View file

@ -159,6 +159,12 @@ let ins_str (i : int) (pc : int) : string =
| 39 -> Printf.sprintf "FEQ r%d, r%d, r%d" a b c
| 40 -> Printf.sprintf "FLT r%d, r%d, r%d" a b c
| 41 -> Printf.sprintf "FLE r%d, r%d, r%d" a b c
(* iteration 36 (v6): the Int bitwise set, same three-register shape *)
| 42 -> Printf.sprintf "BAND r%d, r%d, r%d" a b c
| 43 -> Printf.sprintf "BOR r%d, r%d, r%d" a b c
| 44 -> Printf.sprintf "BXOR r%d, r%d, r%d" a b c
| 45 -> Printf.sprintf "SHL r%d, r%d, r%d" a b c
| 46 -> Printf.sprintf "SHR r%d, r%d, r%d" a b c
| op -> Printf.sprintf "?OP%d" op
(* ---- the dump ---- *)
@ -173,10 +179,11 @@ let dump (img : string) : string =
let line fmt = Buffer.add_string out (fmt ^ "\n") in
if u32 img 0 <> magic then raise (Bad "bad magic");
let ver = u32 img 4 in
(* iteration 19 bumped the format to v5 (Float constant tag, kinds 6/7,
(* iteration 36 bumped the format to v6 (opcodes 42-46, the Int bitwise
set; iteration 19's v5 added the Float constant tag, kinds 6/7 and
opcodes 34-41). The disassembler tracks the emitter, not a range: an old
image is a different format and reading it as this one would misrender. *)
if ver <> 5 then raise (Bad (Printf.sprintf "unsupported version %d" ver));
if ver <> 6 then raise (Bad (Printf.sprintf "unsupported version %d" ver));
let coff = u32 img 8 and ccnt = u32 img 12 in
let koff = u32 img 16 and kcnt = u32 img 20 in
let ioff = u32 img 24 and icnt = u32 img 28 in

View file

@ -154,7 +154,7 @@ let wob_magic = 0x31424F57 (* "WOB1" read as an LE u32 *)
(* v5 (iteration 19): the Float constant tag, field kinds 6/7, opcodes 34-41,
builtins 70-83. v4 (iteration 7b): RC opcodes retired; gc mask = GC roots *)
let wob_version = 5
let wob_version = 6
let wob_hdr_size = 44
let wob_none = 0xFFFFFFFF

View file

@ -2278,7 +2278,7 @@ let validate_image (img : string) : string list =
let u64 o = if ok 8 o then String.get_int64_le img o else 0L in
let none = 0xFFFFFFFF in
if u32 0 <> 0x31424F57 then fail "bad magic";
if u32 4 <> 5 then fail "unsupported version"; (* v5: iteration 19 *)
if u32 4 <> 6 then fail "unsupported version"; (* v6: iteration 36 *)
let coff = u32 8 and ccnt = u32 12 in
let koff = u32 16 and kcnt = u32 20 in
let ioff = u32 24 and icnt = u32 28 in
@ -2554,7 +2554,9 @@ let validate_image (img : string) : string list =
| 38 ->
rchk pc a;
rchk pc b
| 34 | 35 | 36 | 37 | 39 | 40 | 41 ->
(* iteration 36 (v6): 42-46, the Int bitwise set — same
three-register shape *)
| 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 ->
rchk pc a;
rchk pc b;
rchk pc c

View file

@ -441,6 +441,14 @@ int wo_load_buf(wo_module *m, const uint8_t *buf, size_t len, char *err,
case WOP_FEQ:
case WOP_FLT:
case WOP_FLE:
/* iteration 36 (v6): same three-register shape; the shift
count is range-checked at RUN time (WO_T_SHIFT), not here
— it lives in a register, not an immediate. */
case WOP_BAND:
case WOP_BOR:
case WOP_BXOR:
case WOP_SHL:
case WOP_SHR:
RCHK(A);
RCHK(B);
RCHK(C);

View file

@ -859,6 +859,10 @@ static int vm_run(wo_vm *vm, uint64_t *ret, wo_err *err) {
[WOP_FMUL] = &&L_FMUL, [WOP_FDIV] = &&L_FDIV,
[WOP_FNEG] = &&L_FNEG, [WOP_FEQ] = &&L_FEQ,
[WOP_FLT] = &&L_FLT, [WOP_FLE] = &&L_FLE,
/* iteration 36 (v6): the Int bitwise set */
[WOP_BAND] = &&L_BAND, [WOP_BOR] = &&L_BOR,
[WOP_BXOR] = &&L_BXOR, [WOP_SHL] = &&L_SHL,
[WOP_SHR] = &&L_SHR,
};
#define CASE(name) L_##name
#define NEXT() \
@ -935,6 +939,38 @@ dispatch:
NEXT();
}
/* iteration 36 (v6): Int bitwise. AND/OR/XOR are sign-agnostic on
* the u64 register word. SHL shifts the unsigned word (wrapping,
* like ADD); SHR casts to int64_t first — ARITHMETIC, the sign bit
* extends (gcc/clang define signed >> as arithmetic, the only
* compilers this runtime targets). A count outside 0..63 traps
* WO_T_SHIFT rather than silently masking like the hardware would;
* literal counts were rejected at compile time (WO-E223). */
CASE(BAND) : {
R[wo_ins_a(ins)] = R[wo_ins_b(ins)] & R[wo_ins_c(ins)];
NEXT();
}
CASE(BOR) : {
R[wo_ins_a(ins)] = R[wo_ins_b(ins)] | R[wo_ins_c(ins)];
NEXT();
}
CASE(BXOR) : {
R[wo_ins_a(ins)] = R[wo_ins_b(ins)] ^ R[wo_ins_c(ins)];
NEXT();
}
CASE(SHL) : {
int64_t s = (int64_t)R[wo_ins_c(ins)];
if (s < 0 || s > 63) TRAPF(WO_T_SHIFT, "shift count out of range 0..63");
R[wo_ins_a(ins)] = R[wo_ins_b(ins)] << s;
NEXT();
}
CASE(SHR) : {
int64_t s = (int64_t)R[wo_ins_c(ins)];
if (s < 0 || s > 63) TRAPF(WO_T_SHIFT, "shift count out of range 0..63");
R[wo_ins_a(ins)] = (uint64_t)((int64_t)R[wo_ins_b(ins)] >> s);
NEXT();
}
/* iteration 19: f64 arithmetic. Registers are u64, so each op bitcasts in
* and out (wo_f64/wo_bits — memcpy-based, the only strict-aliasing-clean
* way). Nothing here traps: IEEE 754 quiet semantics are the contract, so

View file

@ -13,7 +13,9 @@
/* ---- file header (44 bytes, absolute offsets) ---- */
#define WOB_MAGIC 0x31424F57u /* "WOB1" read as LE u32 */
#define WOB_VERSION 5u /* v5 (iteration 19): the two missing scalars. New
#define WOB_VERSION 6u /* v6 (iteration 36): opcodes 42-46 (the Int
* bitwise set BAND/BOR/BXOR/SHL/SHR), trap kind WO_T_SHIFT.
* v5 (iteration 19): the two missing scalars. New
* constant tag WOB_K_FLOAT, field kinds WO_K_FLOAT/WO_K_BYTES (WO_K_MAX 5->7),
* opcodes 34-41 (the f64 arithmetic/compare set), builtins 70-82.
* v4 (iteration 7b): opcodes 27-28 (RC_INC/RC_DEC) retired; the drop table's
@ -184,6 +186,10 @@ enum {
/* iteration 9b: deleting a row still referenced by a `ref` traps here
(restrict) — the employee sample's DROP-of-a-department-with-staff */
WO_T_FK = 11,
/* iteration 36: a shift count outside 0..63 at run time — the Int
honesty precedent DIV0 set (trap, never x86's silent count%64).
Literal counts never get here: woc rejects them (WO-E223). */
WO_T_SHIFT = 12,
};
/* ---- opcodes (spec section 5; semantics in the format doc) ---- */
@ -247,8 +253,19 @@ enum {
WOP_FEQ = 39, /* A B C: IEEE equality, so NaN == NaN is 0 */
WOP_FLT = 40, /* IEEE ordered <: any comparison with NaN is 0 */
WOP_FLE = 41,
/* iteration 36 (v6): the Int bitwise set. A B C on i64, Int-only —
* woc's checker refuses Float/Bool/Text operands, so no F-twin
* exists. SHL/SHR trap WO_T_SHIFT when the count register is
* outside 0..63 (never x86's silent count%64; literal counts were
* already rejected at compile time as WO-E223). SHR is ARITHMETIC:
* the sign bit extends, Go's own choice for a signed integer. */
WOP_BAND = 42, /* A B C: r[A] = r[B] & r[C] */
WOP_BOR = 43,
WOP_BXOR = 44,
WOP_SHL = 45, /* A B C: r[A] = r[B] << r[C]; C outside 0..63 traps */
WOP_SHR = 46, /* A B C: arithmetic; C outside 0..63 traps */
};
#define WOP_MAX 41u
#define WOP_MAX 46u
/* ---- builtin ids (WOP_BUILTIN operand C) ---- */
enum {