Skip to content

Commit c267dca

Browse files
committed
chore: docs
1 parent 55e3f98 commit c267dca

File tree

8 files changed

+323
-161
lines changed

8 files changed

+323
-161
lines changed

.claude/skills/testing-hashql/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,5 @@ let body = body!(interner, env; fn@0/1 -> Int {
225225

226226
- [compiletest Guide](references/compiletest-guide.md) - Detailed UI test documentation
227227
- [Testing Strategies](references/testing-strategies.md) - Choosing the right approach
228-
- [MIR Builder Guide](references/mir-builder-guide.md) - Programmatic MIR construction for tests
228+
- [MIR Builder Guide](references/mir-builder-guide.md) - `body!` macro for MIR construction in tests
229+
- [MIR Fluent Builder](references/mir-fluent-builder.md) - Programmatic builder API (for advanced cases)

.claude/skills/testing-hashql/references/mir-builder-guide.md

Lines changed: 100 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,9 @@ Ergonomic API for constructing MIR bodies in tests. Use for testing and benchmar
88

99
The `body!` macro does not support all MIR constructs. If you need a feature that is not supported, **do not work around it manually** - instead, stop and request that the feature be added to the macro.
1010

11-
## Quick Start
12-
13-
Two approaches are available:
11+
For advanced cases not supported by the macro, see [mir-fluent-builder.md](mir-fluent-builder.md).
1412

15-
1. **`body!` macro** (preferred for complex CFG) - Declarative, IR-like syntax
16-
2. **Fluent builder API** - Programmatic construction for simple cases
17-
18-
### `body!` Macro (Preferred)
13+
## Quick Start
1914

2015
```rust
2116
use hashql_core::{heap::Heap, r#type::environment::Environment};
@@ -44,27 +39,6 @@ let body = body!(interner, env; fn@0/1 -> Int {
4439
});
4540
```
4641

47-
### Fluent Builder API
48-
49-
```rust
50-
use hashql_core::r#type::{TypeBuilder, environment::Environment};
51-
use hashql_mir::{builder::BodyBuilder, op, scaffold};
52-
53-
scaffold!(heap, interner, builder);
54-
let env = Environment::new(&heap);
55-
56-
let x = builder.local("x", TypeBuilder::synthetic(&env).integer());
57-
let const_1 = builder.const_int(1);
58-
59-
let bb0 = builder.reserve_block([]);
60-
builder
61-
.build_block(bb0)
62-
.assign_place(x, |rv| rv.load(const_1))
63-
.ret(x);
64-
65-
let body = builder.finish(0, TypeBuilder::synthetic(&env).integer());
66-
```
67-
6842
## `body!` Macro Syntax
6943

7044
```text
@@ -83,17 +57,21 @@ body!(interner, env; <source> @ <id> / <arity> -> <return_type> {
8357
| Component | Description | Example |
8458
| --------- | ----------- | ------- |
8559
| `<source>` | Body source type | `fn` (closure) or `thunk` |
86-
| `<id>` | DefId numeric literal | `0`, `1`, `42` |
60+
| `<id>` | DefId (literal or variable) | `0`, `42`, `my_def_id` |
8761
| `<arity>` | Number of function arguments | `0`, `1`, `2` |
8862
| `<return_type>` | Return type | `Int`, `Bool`, `(Int, Bool)` |
8963

64+
The `<id>` can be a numeric literal (`0`, `1`, `42`) or a variable identifier (`callee_id`, `my_def_id`). When using a variable, it must be a `DefId` in scope.
65+
9066
### Types
9167

9268
| Syntax | Description | Example |
9369
| ------ | ----------- | ------- |
9470
| `Int` | Integer type | `Int` |
9571
| `Bool` | Boolean type | `Bool` |
72+
| `Null` | Null type | `Null` |
9673
| `(T1, T2, ...)` | Tuple types | `(Int, Bool, Int)` |
74+
| `(T,)` | Single-element tuple | `(Int,)` |
9775
| `(a: T1, b: T2)` | Struct types | `(a: Int, b: Bool)` |
9876
| `[fn(T1, T2) -> R]` | Closure types | `[fn(Int) -> Int]`, `[fn() -> Bool]` |
9977
| `\|types\| types.custom()` | Custom type expression | `\|t\| t.null()` |
@@ -106,15 +84,15 @@ Declare field projections after `decl` to access struct/tuple fields as places:
10684
@proj <name> = <base>.<field>: <type>, ...;
10785
```
10886

109-
Example:
87+
Supports nested projections:
11088

11189
```rust
112-
let body = body!(interner, env; fn@1/0 -> Int {
113-
decl closure: [fn(Int) -> Int], result: Int;
114-
@proj closure_fn = closure.0: [fn(Int) -> Int], closure_env = closure.1: Int;
90+
let body = body!(interner, env; fn@0/0 -> Int {
91+
decl tup: ((Int, Int), Int), result: Int;
92+
@proj inner = tup.0: (Int, Int), inner_1 = tup.0.1: Int;
11593

11694
bb0() {
117-
result = apply closure_fn, closure_env;
95+
result = load inner_1;
11896
return result;
11997
}
12098
});
@@ -153,113 +131,20 @@ let body = body!(interner, env; fn@1/0 -> Int {
153131

154132
| Syntax | Description |
155133
| ------ | ----------- |
156-
| `x`, `cond` | Place (local variable) |
134+
| `x`, `cond` | Place (local variable or projection) |
157135
| `42`, `-5` | Integer literal (i64) |
158136
| `3.14` | Float literal (f64) |
159137
| `true`, `false` | Boolean literal |
160138
| `()` | Unit |
161139
| `null` | Null |
162-
| `fn() @ def_id` | Function pointer |
140+
| `def_id` | DefId variable (for function pointers) |
163141

164142
### Operators
165143

166144
**Binary** (`bin.<op>`): `==`, `!=`, `<`, `<=`, `>`, `>=`, `&`, `|`, `+`, `-`, `*`, `/`
167145

168146
**Unary** (`un.<op>`): `!`, `neg`
169147

170-
## Fluent Builder Reference
171-
172-
### `scaffold!` Macro
173-
174-
Sets up heap, interner, and builder:
175-
176-
```rust
177-
scaffold!(heap, interner, builder);
178-
let env = Environment::new(&heap); // Also needed for types
179-
```
180-
181-
### `op!` Macro
182-
183-
Creates operators for fluent builder binary/unary operations:
184-
185-
```rust
186-
// Binary: ==, !=, <, <=, >, >=, &, |, +, -, *, /
187-
rv.binary(x, op![==], y)
188-
189-
// Unary: !, neg
190-
rv.unary(op![!], cond)
191-
rv.unary(op![neg], value)
192-
```
193-
194-
### Locals and Types
195-
196-
```rust
197-
let env = Environment::new(&heap);
198-
199-
// Common types
200-
let int_ty = TypeBuilder::synthetic(&env).integer();
201-
let bool_ty = TypeBuilder::synthetic(&env).boolean();
202-
let null_ty = TypeBuilder::synthetic(&env).null();
203-
204-
// Declare locals
205-
let x = builder.local("x", int_ty); // Returns Place<'heap>
206-
```
207-
208-
### Constants
209-
210-
```rust
211-
let const_42 = builder.const_int(42);
212-
let const_true = builder.const_bool(true);
213-
let const_unit = builder.const_unit();
214-
let const_null = builder.const_null();
215-
let const_fn = builder.const_fn(def_id);
216-
```
217-
218-
### Basic Blocks
219-
220-
```rust
221-
// Reserve without parameters
222-
let bb0 = builder.reserve_block([]);
223-
224-
// Reserve with block parameters (for SSA phi-like merging)
225-
let bb1 = builder.reserve_block([x.local, y.local]);
226-
```
227-
228-
### Building Blocks
229-
230-
```rust
231-
builder
232-
.build_block(bb0)
233-
.assign_place(x, |rv| rv.load(const_1))
234-
.assign_place(y, |rv| rv.binary(x, op![==], x))
235-
.storage_live(local)
236-
.storage_dead(local)
237-
.nop()
238-
.ret(result); // Must end with terminator
239-
```
240-
241-
### Terminators
242-
243-
```rust
244-
// Return
245-
builder.build_block(bb).ret(value);
246-
247-
// Goto
248-
builder.build_block(bb0).goto(bb1, []);
249-
builder.build_block(bb0).goto(bb1, [x.into(), y.into()]);
250-
251-
// If-else
252-
builder.build_block(bb0).if_else(cond, bb_then, [], bb_else, []);
253-
254-
// Switch
255-
builder.build_block(bb0).switch(selector, |switch| {
256-
switch.case(0, bb1, []).case(1, bb2, []).otherwise(bb3, [])
257-
});
258-
259-
// Unreachable
260-
builder.build_block(bb).unreachable();
261-
```
262-
263148
## Common Patterns
264149

265150
### Diamond CFG (Branch and Merge)
@@ -327,17 +212,65 @@ let body = body!(interner, env; fn@0/0 -> Null {
327212
});
328213
```
329214

330-
### Function Calls
215+
### Direct Function Calls
216+
217+
Use a `DefId` variable directly:
218+
219+
```rust
220+
let callee_id = DefId::new(1);
221+
222+
let body = body!(interner, env; fn@0/0 -> Int {
223+
decl result: Int;
224+
225+
bb0() {
226+
result = apply callee_id;
227+
return result;
228+
}
229+
});
230+
```
231+
232+
### Indirect Function Calls (via local)
233+
234+
Load a DefId into a local, then apply the local:
235+
236+
```rust
237+
let callee_id = DefId::new(1);
238+
239+
let body = body!(interner, env; fn@0/0 -> Int {
240+
decl func: [fn(Int) -> Int], result: Int;
241+
242+
bb0() {
243+
func = load callee_id;
244+
result = apply func, 1;
245+
return result;
246+
}
247+
});
248+
```
249+
250+
### Multiple Bodies with DefId Variables
251+
252+
When creating multiple bodies that reference each other:
331253

332254
```rust
333-
let body = body!(interner, env; fn@1/0 -> Int {
255+
let callee_id = DefId::new(1);
256+
let caller_id = DefId::new(0);
257+
258+
let caller = body!(interner, env; fn@caller_id/0 -> Int {
334259
decl result: Int;
335260

336261
bb0() {
337-
result = apply fn() @ callee_def_id;
262+
result = apply callee_id;
338263
return result;
339264
}
340265
});
266+
267+
let callee = body!(interner, env; fn@callee_id/0 -> Int {
268+
decl ret: Int;
269+
270+
bb0() {
271+
return ret;
272+
}
273+
});
341274
```
342275

343276
### Struct Aggregate
@@ -380,6 +313,28 @@ let body1 = body!(interner, env; fn@1/0 -> Int {
380313
});
381314
```
382315

316+
### Projections in Terminators
317+
318+
Projected places can be used as operands in terminators:
319+
320+
```rust
321+
let body = body!(interner, env; fn@0/0 -> Int {
322+
decl tup: (Int, Int), result: Int;
323+
@proj tup_0 = tup.0: Int, tup_1 = tup.1: Int;
324+
325+
bb0() {
326+
tup = tuple 1, 2;
327+
if tup_0 then bb1(tup_0) else bb2(tup_1);
328+
},
329+
bb1(result) {
330+
return result;
331+
},
332+
bb2(result) {
333+
return result;
334+
}
335+
});
336+
```
337+
383338
## Test Harness Pattern
384339

385340
Standard pattern used across transform pass tests:
@@ -485,28 +440,23 @@ fn test_case() {
485440
}
486441
```
487442

488-
## RValue Types (Fluent Builder)
489-
490-
| Method | Creates | Example |
491-
| ------ | ------- | ------- |
492-
| `load(operand)` | Copy/move | `rv.load(x)` |
493-
| `binary(l, op, r)` | Binary op | `rv.binary(x, op![+], y)` |
494-
| `unary(op, val)` | Unary op | `rv.unary(op![!], cond)` |
495-
| `tuple([...])` | Tuple | `rv.tuple([x, y, z])` |
496-
| `list([...])` | List | `rv.list([a, b, c])` |
497-
| `struct([...])` | Struct | `rv.r#struct([("x", val)])` |
498-
| `closure(def, env)` | Closure | `rv.closure(def_id, env_place)` |
499-
| `dict([...])` | Dict | `rv.dict([(k, v)])` |
500-
| `apply(fn, args)` | Call | `rv.apply(func, [arg1])` |
501-
| `call(fn)` | Call (no args) | `rv.call(func)` |
502-
| `input(op, name)` | Input | `rv.input(InputOp::Load { required: true }, "x")` |
503-
504443
## Examples in Codebase
505444

506-
Real test examples in `libs/@local/hashql/mir/src/pass/transform/`:
445+
Real test examples in `libs/@local/hashql/mir/src/pass/`:
507446

508-
- `administrative_reduction/tests.rs` - Administrative reduction (uses `body!` macro)
447+
**Transform passes** (`transform/`):
448+
449+
- `administrative_reduction/tests.rs`
509450
- `dse/tests.rs` - Dead Store Elimination
510451
- `ssa_repair/tests.rs` - SSA Repair
511452
- `cfg_simplify/tests.rs` - CFG Simplification
512453
- `dbe/tests.rs` - Dead Block Elimination
454+
- `cp/tests.rs` - Constant Propagation
455+
- `dle/tests.rs` - Dead Local Elimination
456+
- `inst_simplify/tests.rs` - Instruction Simplification
457+
458+
**Analysis passes** (`analysis/`):
459+
460+
- `callgraph/tests.rs` - Call graph analysis
461+
- `data_dependency/tests.rs` - Data dependency analysis
462+
- `dataflow/liveness/tests.rs` - Liveness analysis

0 commit comments

Comments
 (0)