Skip to content

Commit 3fbc3c2

Browse files
authored
Merge pull request #49 from aallan/c6j-effect-handler-compilation
C6j: Effect handler compilation — handle[State<T>] via host imports
2 parents ceb1f4a + d3b8d8d commit 3fbc3c2

9 files changed

Lines changed: 445 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
## [0.0.19] - 2026-02-25
10+
11+
### Added
12+
- **Effect handler compilation** (C6j — closes [#28](https://github.com/aallan/vera/issues/28)): compile `handle[State<T>]` expressions to WASM via host imports
13+
- State handler translation: `handle[State<T>](@T = init) { get/put clauses } in { body }` compiles by initializing state via `state_put_T`, then compiling body with get/put mapped to host imports
14+
- Handler clauses serve as specifications (not compiled) — `resume()` calls describe the default State semantics, validated by type checker
15+
- Effect discharge: pure functions containing `handle[State<T>]` are compilable — state imports registered by scanning function body for handle expressions
16+
- Unsupported handlers (`Exn<E>`, custom effects) cause function to be skipped with warning
17+
- **Reworked `examples/effect_handler.vera`**: removed `safe_parse` (uses String + undefined `parse_int`), added `test_state_init` and `test_put_get` (simple compilable tests)
18+
- **Codegen tests**: 14 new tests — state initialization, put/get, increment pattern, run_counter, let bindings, Bool state, WAT inspection, unsupported handler skip, example file round-trips (691 total, up from 677)
19+
920
## [0.0.18] - 2026-02-25
1021

1122
### Added
@@ -314,7 +325,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
314325
- Grammar: handler body simplified to avoid LALR reduce/reduce conflict
315326
- `pyproject.toml`: corrected build backend, package discovery, PEP 639 compliance
316327

317-
[Unreleased]: https://github.com/aallan/vera/compare/v0.0.17...HEAD
328+
[Unreleased]: https://github.com/aallan/vera/compare/v0.0.19...HEAD
329+
[0.0.19]: https://github.com/aallan/vera/compare/v0.0.18...v0.0.19
330+
[0.0.18]: https://github.com/aallan/vera/compare/v0.0.17...v0.0.18
318331
[0.0.17]: https://github.com/aallan/vera/compare/v0.0.16...v0.0.17
319332
[0.0.16]: https://github.com/aallan/vera/compare/v0.0.15...v0.0.16
320333
[0.0.15]: https://github.com/aallan/vera/compare/v0.0.14...v0.0.15

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ Development follows an **interleaved spiral** — each phase adds a complete com
159159
| C3 | v0.0.5 | **Type checker** — decidable type checking, slot resolution, effect tracking | Done |
160160
| C4 | v0.0.8 | **Contract verifier** — Z3 integration, refinement types, counterexamples | Done |
161161
| C5 | v0.0.9 | **WASM codegen** — compile to WebAssembly, `vera compile` / `vera run` | Done |
162-
| C6 | v0.0.10–0.0.23 | **Codegen completeness** — ADTs, match, closures, effects, generics in WASM | **In progress** (C6a–C6i, C6h done) |
162+
| C6 | v0.0.10–0.0.23 | **Codegen completeness** — ADTs, match, closures, effects, generics in WASM | **In progress** (C6a–C6j done) |
163163
| C7 || **Module system** — cross-file imports, public/private visibility | Planned |
164164
| C8 | v0.1.0 | **End-to-end** — all examples compile and run, spec complete, polish | Planned |
165165

@@ -190,7 +190,7 @@ C6 extends WASM compilation to all language constructs, working through the depe
190190
| Sub-phase | Scope | Closes | Unlocks |
191191
|-----------|-------|--------|---------|
192192
| ~~C6h~~ | ~~Closures — closure conversion, `call_indirect`~~ | ~~[#27](https://github.com/aallan/vera/issues/27)~~ | ~~Done (v0.0.18)~~ |
193-
| C6j | Effect handlers — handle/resume compilation | [#28](https://github.com/aallan/vera/issues/28) | effect_handler.vera |
193+
| ~~C6j~~ | ~~Effect handlers — handle/resume compilation~~ | ~~[#28](https://github.com/aallan/vera/issues/28)~~ | ~~Done (v0.0.19)~~ |
194194

195195
**Collections, runtime, and documentation:**
196196

examples/effect_handler.vera

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
-- Effect handlers — State and Exception patterns
1+
-- Effect handlers — State patterns
22

3-
-- State effect: a counter
3+
-- A counter effect (user-defined effect)
44
effect Counter {
55
op get_count(Unit -> Int);
66
op increment(Unit -> Unit);
77
}
88

9-
-- Use the counter effect
9+
-- Use the counter effect (not compilable — Counter is not a host effect)
1010
fn count_to_three(@Unit -> @Unit)
1111
requires(true)
1212
ensures(true)
@@ -17,7 +17,8 @@ fn count_to_three(@Unit -> @Unit)
1717
Counter.increment(())
1818
}
1919

20-
-- Handle the counter with a State handler
20+
-- Handle State<Int> to implement a counter
21+
-- The handle expression discharges the effect: run_counter is pure
2122
fn run_counter(@Unit -> @Int)
2223
requires(true)
2324
ensures(true)
@@ -35,23 +36,38 @@ fn run_counter(@Unit -> @Int)
3536
}
3637
}
3738

38-
-- Exception effect
39-
effect Exn<E> {
40-
op throw(E -> Unit);
39+
-- Simpler test: read the initial state value
40+
fn test_state_init(@Unit -> @Int)
41+
requires(true)
42+
ensures(true)
43+
effects(pure)
44+
{
45+
handle[State<Int>](@Int = 42) {
46+
get(@Unit) -> { resume(@Int.0) },
47+
put(@Int) -> { resume(()) }
48+
} in {
49+
get(())
50+
}
4151
}
4252

43-
data Option<T> { None, Some(T) }
44-
45-
-- Handle exceptions by returning None on failure
46-
fn safe_parse(@String -> @Option<Int>)
53+
-- Test: put then get
54+
fn test_put_get(@Unit -> @Int)
4755
requires(true)
4856
ensures(true)
4957
effects(pure)
5058
{
51-
handle[Exn<String>] {
52-
throw(@String) -> { None }
59+
handle[State<Int>](@Int = 0) {
60+
get(@Unit) -> { resume(@Int.0) },
61+
put(@Int) -> { resume(()) }
5362
} in {
54-
let @Int = parse_int(@String.0);
55-
Some(@Int.0)
63+
put(99);
64+
get(())
5665
}
5766
}
67+
68+
-- Exception effect (demonstrates non-State handler, not compilable)
69+
effect Exn<E> {
70+
op throw(E -> Unit);
71+
}
72+
73+
data Option<T> { None, Some(T) }

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "vera"
3-
version = "0.0.18"
3+
version = "0.0.19"
44
description = "Vera: a programming language designed for LLMs, with full contracts, algebraic effects, and typed slot references"
55
readme = "README.md"
66
license = "MIT"

spec/11-compilation.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -390,14 +390,49 @@ call_indirect (type $closure_sig_N) ;; indirect call
390390

391391
`apply_fn` is a compiler built-in, not a user-defined function. The checker issues a warning about it being unresolved, but the code generator recognizes it and emits the appropriate `call_indirect` sequence.
392392

393-
## 11.11 Limitations
393+
## 11.11 Effect Handler Compilation
394+
395+
The `handle[Effect<T>]` expression compiles effect handlers to WASM. Currently, `State<T>` handlers are supported via the existing host import mechanism (Section 11.7.2).
396+
397+
### 11.11.1 State Handler Compilation
398+
399+
A `handle[State<T>](@T = init) { clauses } in { body }` expression compiles to:
400+
401+
1. **Initialize state**: compile `init` expression, call `$vera.state_put_T`
402+
2. **Compile body**: with `get`/`put` mapped to `$vera.state_get_T`/`$vera.state_put_T` host imports
403+
3. **Return body result**: the handle expression evaluates to the body's final expression
404+
405+
```wat
406+
;; handle[State<Int>](@Int = 42) { ... } in { put(get(()) + 1); get(()) }
407+
i64.const 42 ;; init expr
408+
call $vera.state_put_Int ;; initialize state
409+
call $vera.state_get_Int ;; get(())
410+
i64.const 1
411+
i64.add
412+
call $vera.state_put_Int ;; put(get(()) + 1)
413+
call $vera.state_get_Int ;; get(()) — body result
414+
```
415+
416+
### 11.11.2 Handler Clauses as Specifications
417+
418+
Handler clauses (e.g. `get(@Unit) -> { resume(@Int.0) }`) describe the handler's operational semantics but are not compiled to WASM. The host runtime already implements the correct `get`/`put` behavior. The `resume` calls in handler clauses serve as specifications validated by the type checker.
419+
420+
### 11.11.3 Effect Discharge
421+
422+
A `handle[State<T>]` expression discharges the `State<T>` effect. This means a function can be declared `effects(pure)` and still use `get`/`put` operations within a handler body. The compiler registers the State<T> host imports by scanning the function body for handle expressions, not just the function's declared effects.
423+
424+
### 11.11.4 Unsupported Handlers
425+
426+
Handler types other than `State<T>` (e.g. `Exn<E>`, custom effects) are not yet compilable. Functions containing unsupported handler types are skipped with a warning.
427+
428+
## 11.12 Limitations
394429

395430
The current compilation model has the following limitations, each tracked as a GitHub issue:
396431

397432
| Limitation | Issue | Notes |
398433
|-----------|-------|-------|
399-
| No effect handler codegen | [#28](https://github.com/aallan/vera/issues/28) | Needs continuation-passing transform |
400434
| No Byte type codegen | [#30](https://github.com/aallan/vera/issues/30) | Needs linear memory byte operations |
401435
| No module-level code generation || Each file compiles independently |
402436
| No garbage collection || Bump allocator only; linear memory is not reclaimed |
403437
| String constants only || No dynamic string construction |
438+
| Only State\<T\> handlers || Exn\<E\> and custom effect handlers not yet compilable |

0 commit comments

Comments
 (0)