Skip to content

Latest commit

 

History

History
219 lines (168 loc) · 5.1 KB

File metadata and controls

219 lines (168 loc) · 5.1 KB

@error Codegen Report — What Compiles to What

Nanz → Z80 via CY flag. Zero-cost error propagation.


1. Basic: @error(N)

fun safe_div(a: u8, b: u8) -> u8 {
    if b == 0 { @error(1) }
    return a / b
}

Compiles to:

safe_div:                    ; a=A, b=C (PFCCO)
    LD A, C                  ; check b
    AND A                    ; b == 0?
    JR NZ, .ok               ; no → do division
    ; @error(1):
    SCF                      ; CY = 1 (error!)
    RET                      ; return with CY set, A = 1
.ok:
    ; ... 8-bit division ...
    RET                      ; A = result, CY = 0 (from division)

Cost: error path = 2 bytes (SCF + RET). Success path = 0 extra bytes.


2. Propagation: @propagate → RET C

fun compute(a: u8, b: u8) -> u8 {
    var x: u8 = safe_div(a, b)
    @propagate                    // if CY, return immediately
    return x + 1
}

Compiles to:

compute:
    CALL safe_div
    RET C                    ; ← 1 BYTE! if CY=1, return with error
    INC A                    ; x + 1
    RET                      ; success

RET C is the Z80's native error propagation. One byte. One instruction. CY flag and A register pass through the return chain untouched.


3. Bounds checking: safe_sub

fun safe_sub(a: u8, b: u8) -> u8 {
    if a < b { @error(3) }
    return a - b
}

Compiles to:

safe_sub:                    ; a=A, b=C
    CP C                     ; compare a - b
    JR NC, .ok               ; a >= b → safe
    SCF                      ; CY = 1 (error)
    RET                      ; A = 3 (error code)
.ok:
    SUB C                    ; A = a - b
    RET                      ; CY = 0 (from SUB with no borrow)

5 instructions total. The Z80's CP + JR NC pattern is the natural bounds check.


4. Chain: multiple fallible calls

fun chain(a: u8, b: u8) -> u8 {
    var x: u8 = safe_div(a, b)
    @propagate
    var y: u8 = safe_add(x, 5)
    @propagate
    return y
}

Compiles to:

chain:
    CALL safe_div
    RET C                    ; propagate div error
    LD C, 5                  ; second arg for safe_add
    CALL safe_add
    RET C                    ; propagate add error
    RET                      ; success: A = result

6 instructions for a 2-call error chain. Each @propagate = 1 byte. Compare with Rust's ? which generates match + discriminant check + error conversion.


5. Array bounds

fun array_get(idx: u8) -> u8 {
    if idx >= data_len { @error(4) }
    var p: ^u8 = &data
    return p[idx]
}

Compiles to:

array_get:                   ; idx=A
    LD HL, data_len
    LD C, (HL)               ; C = data_len
    CP C                     ; idx >= data_len?
    JR C, .ok                ; idx < len → safe
    SCF                      ; CY = 1 (bounds error)
    RET                      ; A = 4
.ok:
    LD HL, data              ; base address
    LD E, A / LD D, 0        ; offset
    ADD HL, DE               ; HL = &data[idx]
    LD A, (HL)               ; A = data[idx]
    RET                      ; CY = 0 (success)

6. @check — handle error inline

var x: u8 = safe_div(10, 0)
@check                        // if error: propagate + return
// x is valid here

Compiles to:

    CALL safe_div
    JR NC, .ok               ; success → skip handler
    RET                      ; error → return (propagate)
.ok:
    ; A = result, use it

Comparison: Error Handling Costs

Pattern Nanz/Z80 Rust C
Declare fallible func 0 bytes 0 bytes 0 bytes
Return error 2 bytes (SCF+RET) ~4 bytes (mov+ret) varies
Return success 0 extra (OR A implicit) 0 bytes 0 bytes
Check at call site 2 bytes (JR C) ~4 bytes (cmp+jmp) varies
Propagate error 1 byte (RET C) ~6 bytes (match+ret) N/A
Chain 3 calls 3 bytes (3× RET C) ~18 bytes manual

RET C is the key insight. No other architecture has a 1-byte conditional error propagation instruction. The Z80 was designed for this pattern.


Error Code Convention

Code Meaning Used by
0 No error (reserved) CY=0 implies no error
1 Division by zero safe_div
2 Overflow safe_add
3 Underflow safe_sub
4 Bounds error array_get
5-127 Application-defined User functions
128-255 System errors CP/M BDOS, ROM

With enum:

enum MathErr { DivByZero = 1, Overflow = 2, Underflow = 3 }

fun safe_div(a: u8, b: u8) -> u8 {
    if b == 0 { @error(MathErr.DivByZero) }
    return a / b
}

Same codegen. Enum just provides compile-time names.


Implementation

Component What LOC
Parser @error(N), @check, @propagate in parseExpr 25
Z80 codegen SCF/RET, JR NC/RET, RET C as intrinsics 30
Total 55

No HIR changes. No MIR2 changes. No semantic analysis. No new types. Pure metafunction expansion to inline Z80 asm.

Future (Layer 2): ? in function name = compiler-enforced @check after call.