feat: iteration 36 task 5 — operators sample, docs closeout
- docs/examples/operators: manual-test workload (ok/FAIL lines per expression; trap mode proves WO_T_SHIFT); NO test fixtures by developer directive — acceptance is the manual pass - 00-wob-format.md: v6 section (opcodes 42-46, T_SHIFT, header v6) - CODE-LOGIC.md both sides: precedence-into-existing-rungs, Lua not, rewind-and-reparse compound assigns, trap-not-mask, 63-bit hex limit - story 36 refine -> in-progress with landing blockquote; board updated - gates: woc-test 543/0, wovm-test ASan, oop-accept ALL MET, deps-accept 8/0, web-app 26/0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c2c9b240f1
commit
ed6bfeea3d
8 changed files with 229 additions and 4 deletions
|
|
@ -260,3 +260,38 @@ a keyword, and visibility is name resolution at compile time.
|
||||||
- **Exit-code bands stay split**: WO-E108 is a diagnostic through the normal
|
- **Exit-code bands stay split**: WO-E108 is a diagnostic through the normal
|
||||||
collector path (exit 1); WO-E106/E107/E109 are manifest errors printed
|
collector path (exit 1); WO-E106/E107/E109 are manifest errors printed
|
||||||
directly (exit 2).
|
directly (exit 2).
|
||||||
|
|
||||||
|
## Operator parity (iteration 36 — `.wob` v6)
|
||||||
|
|
||||||
|
- **Precedence went INTO existing rungs, not new ones.** `|`/`^` joined
|
||||||
|
`parse_additive`, `&`/`<<`/`>>` joined `parse_multiplicative` — exactly
|
||||||
|
Go's table (`token.go` Precedence), which exists to fix C's trap:
|
||||||
|
`x & mask == 0` groups the AND first here. The ladder doc in `parser.ml`
|
||||||
|
carries the worked examples.
|
||||||
|
- **`not` is a keyword at the unary level (Lua placement).** `not a == b`
|
||||||
|
groups `(not a) == b`. Chosen over Python's looser placement because the
|
||||||
|
grammar's ordering is already anchored to Lua by name and because
|
||||||
|
Bool-only typing turns almost every misread into a compile error. It
|
||||||
|
lowers on the existing EQ against a zero constant — no new opcode, the
|
||||||
|
same doctrine as and/or's JZ lowering.
|
||||||
|
- **Compound assigns are parse-time sugar via rewind-and-reparse.**
|
||||||
|
`x += e` IS `x = x + e`, the documented contract — including an index
|
||||||
|
expression evaluating twice, exactly as the written-out form would. The
|
||||||
|
parser re-parses the place by resetting `st.pos` (no expression rung
|
||||||
|
consumes a compound token, so the second parse stops where the first
|
||||||
|
did); every re-parsed node draws a fresh id, so owner/emit see two
|
||||||
|
honest reads, never one node in two roles. `+=`/`-=` had been lexed
|
||||||
|
since haxe-parity Task 2 but no rule consumed them — dead tokens,
|
||||||
|
`x += 1` died as a generic WO-E101 until this iteration.
|
||||||
|
- **Bitwise is Int-only on BOTH sides (WO-E201 family)** — no F-twin
|
||||||
|
exists, so a Float operand would have become a garbage word operation
|
||||||
|
with no diagnostic. A LITERAL shift count outside 0..63 is WO-E223 at
|
||||||
|
the operand's position (a negative literal arrives as
|
||||||
|
`Unary(Neg, IntLit)` — both shapes are caught); a variable count is the
|
||||||
|
VM's WO_T_SHIFT.
|
||||||
|
- **Hex/binary literals accumulate in OCaml's native int (63-bit).** A
|
||||||
|
full-width 64-bit literal like `0xFFFFFFFFFFFFFFFF` is out of reach —
|
||||||
|
all-ones is spelled `-1` (and complement is `-1 ^ x`; there is no `~`).
|
||||||
|
The `0x`/`0b` prefix commits only when a real base digit follows, so
|
||||||
|
`0xg` stays `Int 0` + `Ident` — a parse error at its own position, no
|
||||||
|
new lexer diagnostic. `_` separators are consumed only BETWEEN digits.
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,8 @@ that sequences its tasks. Read one, approve, then the next starts.
|
||||||
|
|
||||||
| Track | Item | Where |
|
| Track | Item | Where |
|
||||||
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------- |
|
||||||
| Language | nothing active — the framework v1-polish slice landed 2026-08-20 (branch framework-v1, awaiting merge); next per the order: brainstorm 20/21's forks | [order](#implementation-order-re-sequenced-2026-08-20--code-review-pass) |
|
| Language | 🔄 [iteration 36 — operator parity](stories/language-runtime-database/in-progress/36-operator-parity.md): `not`, bitwise `& \| ^ << >>`, hex/binary/`_` literals, compound assigns — CODE LANDED 2026-08-22 (branch operator-parity, `.wob` v6, all gates green; reference project `.dev/reference/go` drove the design). Awaiting the developer's MANUAL pass on `docs/examples/operators/` (no test fixtures by directive); unblocks story 34's pure-`.wo` HMAC question | [plan](superpowers/plans/2026-08-22-operator-parity.md) |
|
||||||
|
| Language | the framework v1-polish slice landed 2026-08-20 (branch framework-v1, awaiting merge); next per the order: brainstorm 20/21's forks | [order](#implementation-order-re-sequenced-2026-08-20--code-review-pass) |
|
||||||
|
|
||||||
Off-goal work is parked; the goal (2026-08-20) is the web framework as a
|
Off-goal work is parked; the goal (2026-08-20) is the web framework as a
|
||||||
polished micro-framework v1 — iteration 17 (library kind + `internal/`) is
|
polished micro-framework v1 — iteration 17 (library kind + `internal/`) is
|
||||||
|
|
|
||||||
42
docs/examples/operators/README.md
Normal file
42
docs/examples/operators/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# operators — iteration 36's manual-test workload
|
||||||
|
|
||||||
|
Exercises everything iteration 36 added to the language: boolean `not`,
|
||||||
|
the five Int bitwise operators (`&` `|` `^` `<<` `>>`), hex/binary/
|
||||||
|
underscore integer literals, and the five compound assigns
|
||||||
|
(`+=` `-=` `*=` `/=` `%=`).
|
||||||
|
|
||||||
|
## Run it
|
||||||
|
|
||||||
|
```
|
||||||
|
woc . # from this directory
|
||||||
|
./target/operators
|
||||||
|
```
|
||||||
|
|
||||||
|
Every line prints `ok <label> = <value>` — each label states the exact
|
||||||
|
expression and the expected result, so a manual test is reading the
|
||||||
|
lines and spotting any `FAIL`. The sections, in order:
|
||||||
|
|
||||||
|
1. **Literals** — `0xFF`, `0b1010_1010`, `1_000_000` against their
|
||||||
|
decimal twins.
|
||||||
|
2. **The five operators** — including `-16 >> 2 = -4` (`>>` is
|
||||||
|
ARITHMETIC: the sign bit extends, the story's settled decision 1)
|
||||||
|
and complement spelled `-1 ^ x` (no `~` in this language).
|
||||||
|
3. **Precedence pins** — Go's C-trap fix: `&`/`<<`/`>>` bind with
|
||||||
|
`*`, `|`/`^` bind with `+`, all above comparison, so
|
||||||
|
`x & mask == 0` groups the AND first and `1 << 4 + 1` is 17, not 32.
|
||||||
|
4. **`not`** — a word like `and`/`or`, Lua's unary placement:
|
||||||
|
`not a == b` groups `(not a) == b`.
|
||||||
|
5. **Compound assigns** — one value threaded through all five.
|
||||||
|
6. **The consumer proof** — HMAC's ipad/opad step (story 34's blocker):
|
||||||
|
`byte_at(key, i) ^ 0x36` / `^ 0x5C` in pure `.wo`.
|
||||||
|
|
||||||
|
## The trap
|
||||||
|
|
||||||
|
```
|
||||||
|
./target/operators trap
|
||||||
|
```
|
||||||
|
|
||||||
|
must die with `trap 12 in shift_by ...: shift count out of range 0..63`
|
||||||
|
(exit 1). A shift count outside 0..63 is a RUN-TIME trap only when the
|
||||||
|
count is a variable — a literal out-of-range count never compiles
|
||||||
|
(WO-E223, try changing `1 << 6` to `1 << 64`).
|
||||||
74
docs/examples/operators/main.wo
Normal file
74
docs/examples/operators/main.wo
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
-- Operator parity — iteration 36's manual-test workload. Every section
|
||||||
|
-- prints `label expected actual`: eyeball that the two numbers agree.
|
||||||
|
-- Build & run: woc . && ./target/operators
|
||||||
|
-- Trap demo: ./target/operators trap (shift count 64 at run time
|
||||||
|
-- must die with `trap 12 ... shift count out of range`)
|
||||||
|
|
||||||
|
fn expect(label: Text, expected: Int, actual: Int) {
|
||||||
|
if expected == actual {
|
||||||
|
print("ok ${label} = ${actual}")
|
||||||
|
} else {
|
||||||
|
print("FAIL ${label} expected ${expected} got ${actual}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shift_by(n: Int) -> Int {
|
||||||
|
-- variable count: the compiler cannot see 64 here, so an out-of-range
|
||||||
|
-- value reaches the VM and traps WO_T_SHIFT (the DIV0 precedent)
|
||||||
|
return 1 << n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main(args: multi Text) -> Int {
|
||||||
|
if len(args) >= 1 and args[0] == "trap" {
|
||||||
|
print("about to shift by 64 — expect trap 12, not a number:")
|
||||||
|
print_int(shift_by(64))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
-- literals: hex, binary, underscore separators
|
||||||
|
expect("0xFF", 255, 0xFF)
|
||||||
|
expect("0b1010_1010", 170, 0b1010_1010)
|
||||||
|
expect("1_000_000", 1000000, 1_000_000)
|
||||||
|
|
||||||
|
-- the five bitwise operators, Int-only
|
||||||
|
expect("12 & 10", 8, 12 & 10)
|
||||||
|
expect("12 | 3", 15, 12 | 3)
|
||||||
|
expect("12 ^ 10", 6, 12 ^ 10)
|
||||||
|
expect("1 << 6", 64, 1 << 6)
|
||||||
|
expect("-16 >> 2 (arithmetic)", -4, -16 >> 2)
|
||||||
|
expect("complement -1 ^ 0xF0", -241, -1 ^ 0xF0)
|
||||||
|
|
||||||
|
-- precedence: Go's C-trap fix — & << >> bind with * / %, | ^ with + -
|
||||||
|
expect("1 << 4 + 1 groups (1<<4)+1", 17, 1 << 4 + 1)
|
||||||
|
expect("2 | 1 * 4 groups 2|(1*4)", 6, 2 | 1 * 4)
|
||||||
|
if 0b1010_1010 & 0xFF == 0 {
|
||||||
|
print("FAIL grouping: & lost to ==")
|
||||||
|
} else {
|
||||||
|
print("ok x & mask == 0 groups (x & mask) == 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
-- not: boolean negation, a word like and/or
|
||||||
|
if not false { print("ok not false") }
|
||||||
|
if not (1 > 2) and not false or false { print("ok not chains with and/or") }
|
||||||
|
-- Lua placement: not binds tighter than ==
|
||||||
|
if (not true) == false { print("ok not a == b groups (not a) == b") }
|
||||||
|
|
||||||
|
-- compound assigns (+= -= existed as dead tokens; all five live now)
|
||||||
|
let acc: Int = 10
|
||||||
|
acc += 5
|
||||||
|
acc -= 1
|
||||||
|
acc *= 3
|
||||||
|
acc /= 2
|
||||||
|
acc %= 7
|
||||||
|
expect("10 +=5 -=1 *=3 /=2 %=7", 0, acc)
|
||||||
|
|
||||||
|
-- the consumer that motivated the iteration (story 34): HMAC's
|
||||||
|
-- ipad/opad step is byte ^ constant — RFC 2104's pads, on the byte
|
||||||
|
-- values of "key"
|
||||||
|
let k: Text = "key"
|
||||||
|
expect("'k' ^ 0x36 (ipad)", 93, byte_at(k, 0) ^ 0x36)
|
||||||
|
expect("'k' ^ 0x5c (opad)", 55, byte_at(k, 0) ^ 0x5C)
|
||||||
|
|
||||||
|
print("done — compare every line above by eye; any FAIL is a defect")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
9
docs/examples/operators/wo.toml
Normal file
9
docs/examples/operators/wo.toml
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
name = "operators"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Operator-parity showcase — iteration 36's manual-test workload: not, bitwise & | ^ << >>, hex/binary literals, compound assigns"
|
||||||
|
|
||||||
|
[runtime]
|
||||||
|
wo = ">= 0.1"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
runtime = "../../../runtime/wovm"
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
All integers little-endian; offsets are absolute file offsets.
|
All integers little-endian; offsets are absolute file offsets.
|
||||||
|
|
||||||
**Header (44 bytes):** magic `"WOB1"`, version 5 (iteration 19; see "v5: Float and Bytes" below), then offset/count u32 pairs for the constant pool, class table, interface section, and method table, then a u32 entry-method index (all-ones = none).
|
**Header (44 bytes):** magic `"WOB1"`, version 6 (iteration 36; see "v6: the Int bitwise set" below — v5 was iteration 19's "v5: Float and Bytes"), then offset/count u32 pairs for the constant pool, class table, interface section, and method table, then a u32 entry-method index (all-ones = none).
|
||||||
|
|
||||||
**Constant pool** — sequential entries: one tag byte; tag 0 = i64 follows; tag 1 = text (u32 length + bytes, no NUL); tag 2 = f64 as its IEEE 754 bit pattern in an LE u64 (v5). There is no Bytes tag: Bytes has no literal form.
|
**Constant pool** — sequential entries: one tag byte; tag 0 = i64 follows; tag 1 = text (u32 length + bytes, no NUL); tag 2 = f64 as its IEEE 754 bit pattern in an LE u64 (v5). There is no Bytes tag: Bytes has no literal form.
|
||||||
|
|
||||||
|
|
@ -55,6 +55,7 @@ The metadata exists for exactly one reason: `json.encode`/`json.decode` are runt
|
||||||
| 33 | ENDTRY | pop the innermost catch frame — the try region completed without trapping |
|
| 33 | ENDTRY | pop the innermost catch frame — the try region completed without trapping |
|
||||||
| 34–38 | FADD/FSUB/FMUL/FDIV/FNEG | f64 arithmetic on the register's bits (v5). **None of these trap**: IEEE 754 quiet semantics, so `x/0.0` is ±Inf and `0.0/0.0` is NaN. FNEG flips the sign bit, so `-0.0` is reachable |
|
| 34–38 | FADD/FSUB/FMUL/FDIV/FNEG | f64 arithmetic on the register's bits (v5). **None of these trap**: IEEE 754 quiet semantics, so `x/0.0` is ±Inf and `0.0/0.0` is NaN. FNEG flips the sign bit, so `-0.0` is reachable |
|
||||||
| 39–41 | FEQ/FLT/FLE | f64 IEEE compares, result 0/1 — so any comparison involving NaN is 0, and `0.0 == -0.0` is 1. Not a total order; indexes and order-by use the `float_cmp` builtin instead |
|
| 39–41 | FEQ/FLT/FLE | f64 IEEE compares, result 0/1 — so any comparison involving NaN is 0, and `0.0 == -0.0` is 1. Not a total order; indexes and order-by use the `float_cmp` builtin instead |
|
||||||
|
| 42–46 | BAND/BOR/BXOR/SHL/SHR | i64 bitwise (v6, iteration 36). Int-only — woc refuses Float/Bool/Text operands, so no F-twin exists. SHL shifts the unsigned word (wrapping, like ADD); **SHR is arithmetic** (the sign bit extends). A shift count outside 0..63 traps T_SHIFT — never the hardware's silent count%64; a *literal* out-of-range count is rejected at compile time (WO-E223), so the trap only ever fires on variable counts |
|
||||||
|
|
||||||
**try/catch (Task 5).** A trap raised while a catch frame is live unwinds every frame *above* the catching one exactly as an uncaught trap does (drop maps run, registers null), then releases what the try region owned in the catching frame — the difference between the drop entry at the trapping instruction and the one at the handler pc — and resumes at the handler instead of leaving the VM. A frame that returns takes its still-open catch frames with it, so a `return` out of a try region cannot leave a handler pointing at a dead window. With no catch frame live, a trap behaves byte-for-byte as it did before v2. The catch arm's error record is an ordinary compiler-allocated object filled by the `err_fill` builtin (field order: 0 code, 1 line, 2 method, 3 msg).
|
**try/catch (Task 5).** A trap raised while a catch frame is live unwinds every frame *above* the catching one exactly as an uncaught trap does (drop maps run, registers null), then releases what the try region owned in the catching frame — the difference between the drop entry at the trapping instruction and the one at the handler pc — and resumes at the handler instead of leaving the VM. A frame that returns takes its still-open catch frames with it, so a `return` out of a try region cannot leave a handler pointing at a dead window. With no catch frame live, a trap behaves byte-for-byte as it did before v2. The catch arm's error record is an ordinary compiler-allocated object filled by the `err_fill` builtin (field order: 0 code, 1 line, 2 method, 3 msg).
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ The metadata exists for exactly one reason: `json.encode`/`json.decode` are runt
|
||||||
|
|
||||||
`EQS` accepts a nil operand for the same reason: two `?Text` values compare with it, and the answer is "both absent is equal, one absent is not". A non-nil operand must still be a real Text.
|
`EQS` accepts a nil operand for the same reason: two `?Text` values compare with it, and the answer is "both absent is equal, one absent is not". A non-nil operand must still be a real Text.
|
||||||
|
|
||||||
**Trap codes:** DIV0, BORROW, STACK, OOM, DB, BOUNDS, KEY, EXPLICIT, IO (a syscall the source cannot prevent said no — errno's message rides in the error record).
|
**Trap codes:** DIV0, BORROW, STACK, OOM, DB, BOUNDS, KEY, EXPLICIT, IO (a syscall the source cannot prevent said no — errno's message rides in the error record), UNIQUE, FK, SHIFT (v6 — a variable shift count outside 0..63).
|
||||||
|
|
||||||
## v5: Float and Bytes (iteration 19)
|
## v5: Float and Bytes (iteration 19)
|
||||||
|
|
||||||
|
|
@ -138,6 +139,34 @@ fraction there still fails the decode whole). A non-finite Float encodes as
|
||||||
invalid JSON. A Bytes field crosses as a base64 string, matching
|
invalid JSON. A Bytes field crosses as a base64 string, matching
|
||||||
`base64_encode`'s alphabet exactly.
|
`base64_encode`'s alphabet exactly.
|
||||||
|
|
||||||
|
## v6: the Int bitwise set (iteration 36)
|
||||||
|
|
||||||
|
The version bump is the same contract v5 set: an image is rejected in both
|
||||||
|
age directions, because an older runtime meeting opcode 42 would bail on
|
||||||
|
"unknown opcode" only after the loader trusted the rest of the header.
|
||||||
|
|
||||||
|
**What v6 adds** — nothing but opcodes and one trap kind: no new constant
|
||||||
|
tag, field kind, or section.
|
||||||
|
|
||||||
|
- Opcodes `42–46` — `BAND`/`BOR`/`BXOR`/`SHL`/`SHR`, three-register i64
|
||||||
|
forms (table above). Int-only by the checker, so unlike v5 there is no
|
||||||
|
parallel F-set and no mode bit.
|
||||||
|
- Trap kind `T_SHIFT` (12) — a variable shift count outside 0..63. The
|
||||||
|
DIV0 precedent, deliberately NOT x86's silent count-mod-64 (`x << 64`
|
||||||
|
must never quietly equal `x`) and NOT Go's saturate-to-0/-1 (spec
|
||||||
|
surface serving generic-width code this language does not have).
|
||||||
|
A literal count is rejected at compile time as WO-E223, so the trap
|
||||||
|
is reachable only through a count computed at run time.
|
||||||
|
- `>>` is **arithmetic** — the sign bit extends, Go's own choice for a
|
||||||
|
signed integer, and this language's one Int is signed 64-bit. Byte-mask
|
||||||
|
code (crypto, base64) never notices: its values keep the sign bit clear,
|
||||||
|
where arithmetic and logical shifts agree bit for bit.
|
||||||
|
- Source-side companions that need no format space: hex/binary/underscore
|
||||||
|
Int literals (a literal is pool bits by the time it reaches the image),
|
||||||
|
boolean `not` (lowered on the existing EQ against a zero constant, the
|
||||||
|
same no-new-opcode doctrine as and/or's JZ lowering), and the compound
|
||||||
|
assigns (parse-time sugar — `x += e` IS `x = x + e`, dead by emit time).
|
||||||
|
|
||||||
## Enum payload variants (haxe-parity compiler Task 4)
|
## Enum payload variants (haxe-parity compiler Task 4)
|
||||||
|
|
||||||
`.wob` v1 is unchanged — no new section, no new header field, no version
|
`.wob` v1 is unchanged — no new section, no new header field, no version
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
iteration: "36"
|
iteration: "36"
|
||||||
status: refine
|
status: in-progress
|
||||||
---
|
---
|
||||||
|
|
||||||
# Iteration 36 — operator parity: `not`, bitwise, hex literals, compound assigns
|
# Iteration 36 — operator parity: `not`, bitwise, hex literals, compound assigns
|
||||||
|
|
@ -30,6 +30,22 @@ status: refine
|
||||||
> (5 tasks: lexer/tokens; AST/parser incl. compound-assign desugar;
|
> (5 tasks: lexer/tokens; AST/parser incl. compound-assign desugar;
|
||||||
> type checker incl. literal shift-count rejection; emit + VM opcodes
|
> type checker incl. literal shift-count rejection; emit + VM opcodes
|
||||||
> `.wob` v6; corpus/gates/docs closeout).
|
> `.wob` v6; corpus/gates/docs closeout).
|
||||||
|
>
|
||||||
|
> **CODE LANDED 2026-08-22** (branch `operator-parity`), full stack in
|
||||||
|
> one day: tokens/lexer (incl. hex `0x` / binary `0b` / `_` separators),
|
||||||
|
> Go-rung parsing, WO-E201 Int-only/Bool-only checking, WO-E223 literal
|
||||||
|
> shift rejection, opcodes `WOP_BAND..WOP_SHR` 42-46 + `WO_T_SHIFT` as
|
||||||
|
> `.wob` v6. The dead-token defect is closed: `x += 1` parses (all five
|
||||||
|
> compound assigns, parse-time sugar — the desugar-equivalence contract
|
||||||
|
> incl. double index-eval is in `compiler/src/CODE-LOGIC.md`). Gates:
|
||||||
|
> `woc-test` 543/0, `wovm-test` ASan both flavors, `oop-accept` ALL MET,
|
||||||
|
> `deps-accept` 8/0, `web-app` 26/0 — all unchanged. **Deviation by
|
||||||
|
> developer directive: NO test fixtures were written** — acceptance is
|
||||||
|
> MANUAL via the new sample `docs/examples/operators/` (`woc .`, run,
|
||||||
|
> read the ok/FAIL lines; `trap` mode proves WO_T_SHIFT). The corpus
|
||||||
|
> pins this plan called for (grouping, arithmetic `>>`, ipad/opad,
|
||||||
|
> trap) live in that sample instead. Status stays in-progress until the
|
||||||
|
> developer's manual pass; the AC below reads as written pre-deviation.
|
||||||
|
|
||||||
## Why this iteration exists
|
## Why this iteration exists
|
||||||
|
|
||||||
|
|
@ -181,3 +181,22 @@ layout.
|
||||||
word and a Bytes as the same length-prefixed blob a Text uses, so replay is
|
word and a Bytes as the same length-prefixed blob a Text uses, so replay is
|
||||||
bit-exact for NaN, ±Inf, and `-0.0`. `test_wal`'s `test_float_bytes_replay`
|
bit-exact for NaN, ±Inf, and `-0.0`. `test_wal`'s `test_float_bytes_replay`
|
||||||
asserts on bits for exactly that reason.
|
asserts on bits for exactly that reason.
|
||||||
|
|
||||||
|
## The Int bitwise set (iteration 36 — `.wob` v6)
|
||||||
|
|
||||||
|
- **One shared case-body text serves both dispatch flavors** — the new
|
||||||
|
CASE blocks sit in the Int neighborhood after LE, so `-DWO_ISO_C`
|
||||||
|
cannot rot (same discipline as every opcode before them).
|
||||||
|
- **SHL shifts the unsigned register word** (wrapping, like ADD — a
|
||||||
|
signed left-shift overflow would be UB); **SHR casts to int64_t
|
||||||
|
first**, so it is ARITHMETIC — gcc/clang define signed `>>` as
|
||||||
|
sign-extending, and those are the only compilers this runtime targets.
|
||||||
|
- **The count check is a trap, not a mask.** x86 masks the count mod 64,
|
||||||
|
which would make `x << 64 == x` silently; Go saturates to 0/-1, spec
|
||||||
|
surface for generic-width code this VM does not have. WO_T_SHIFT (12)
|
||||||
|
follows the DIV0 precedent instead: named, catchable, honest. It can
|
||||||
|
only fire on a count computed at run time — woc rejects literal
|
||||||
|
out-of-range counts as WO-E223.
|
||||||
|
- **The loader validates the five opcodes as plain three-register forms**
|
||||||
|
— the count is a register, not an immediate, so there is nothing to
|
||||||
|
range-check at load time.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue