Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ semantics; it does not define a native-only TypeRB dialect.

Gate 0 implements the experimental boundary in TypeRB: strict decoding of
versioned, data-only bootstrap snapshots, lowering to Native MIR, MIR
verification, deterministic diagnostics, and source-origin preservation. It
does not yet generate or execute machine code and provides no production
runtime, stable ABI, stable artifact format, or compatibility guarantee.
verification, deterministic diagnostics, and source-origin preservation.

Gate 1 will use a small, heap-free corpus with
functions, direct calls, control flow, scalar values, exact TypeRB integer and
failure behavior, simple static-layout values, and observable output.
Gate 1 is active. Its first vertical slice decodes snapshot v2, verifies a
heap-free scalar MIR, emits QBE IL from TypeRB code, and builds a working
`darwin/arm64` executable with pinned QBE 1.3 and the system linker. The current
subset includes functions, direct calls, block parameters, branches, loops,
Boolean, portable Integer, binary64 Float, static UTF-8 output, and deterministic
arithmetic failure. It provides no production runtime, stable ABI, stable
artifact format, or compatibility guarantee. Records and tagged values are
deferred to Gate 2.

## Intended boundary

Expand Down Expand Up @@ -133,8 +136,10 @@ repository.

- [Architecture](docs/architecture.md)
- [Experiment plan](docs/experiment-plan.md)
- [Gate 1 QBE vertical slice](docs/gate-1-qbe.md)
- [Decision 0001: Experimental native toolchain boundary](docs/decisions/0001-experimental-native-toolchain.md)
- [Decision 0002: TypeRB-owned self-hosting](docs/decisions/0002-typerb-owned-self-hosting.md)
- [Decision 0003: Gate 1 QBE and Darwin arm64 profile](docs/decisions/0003-gate-1-qbe-target.md)
- [Contributing](CONTRIBUTING.md)
- [Security](SECURITY.md)

Expand Down
42 changes: 42 additions & 0 deletions docs/decisions/0003-gate-1-qbe-target.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 0003: Use QBE 1.3 and a Disposable Darwin arm64 Profile for Gate 1

## Status

Accepted for Gate 1 only.

## Context

The first executable gate needs to test TypeRB scalar semantics, runtime
behavior, and native toolchain costs before investing in a broad backend or
stable ABI. Building several complete backends would spend effort before the
shared MIR and runtime questions are understood.

The maintainer also rejected adding a public `def main(): Integer` form merely
to simplify the bootstrap. Temporary implementation structure is acceptable,
but the experiment must not leave an unnatural language feature behind.

## Decision

Gate 1 uses QBE 1.3 at release commit
`c0818978acec60ebb6167fade60fb7012cbf20ca`, verified by release archive SHA-256
`d587905d620dc5e1d2bfa7c2cc642b9b837aa89a3188c6e37b53d756cf66e320`.
The only target is QBE `arm64_apple`, behind the internal and disposable
`darwin-arm64-v0` ABI profile.

The TypeRB-authored emitter supplies an exported C `main` wrapper around a
no-argument, `Void` Native MIR entry function. That wrapper is below the
language boundary and does not change TypeRB `main` syntax or semantics.

Gate 1 is restricted to heap-free scalar execution. Static-layout records and
tagged values move to Gate 2. LLVM, Cranelift, direct machine-code emission, C
emission, and additional targets remain out of scope until Gate 1 measurements
identify a concrete reason to test them.

## Consequences

- QBE and the system assembler/linker remain explicit external dependencies.
- The initial ABI and runtime failure surface can be replaced incompatibly.
- A small hand-authored snapshot corpus must execute before a producer is added
to the reference repository.
- Passing this decision's vertical slice justifies Gate 1B integration; it does
not select QBE for production or demonstrate final self-hosted performance.
15 changes: 14 additions & 1 deletion docs/experiment-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,18 +91,31 @@ Scope:
- Boolean, Integer, and Float values;
- exact checked Integer behavior;
- static strings and observable output; and
- simple static-layout records or tagged values.
- one disposable `darwin-arm64-v0` ABI profile through QBE 1.3.

Records and tagged values begin at Gate 2. Gate 1 does not change the TypeRB
`def main()` contract; its no-argument, `Void` MIR entry is an internal
executable convention rather than new language syntax.

Every backend candidate at this gate runs the same differential corpus against
the reference compiler's Go backend. A mismatch is triaged against the TypeRB
specification and accepted conformance behavior rather than automatically
treating either implementation as correct.

The pre-registered Gate 1 continuation criteria require complete differential
correctness and at least one representative-corpus improvement: end-to-end
build time by 20%, steady-state execution time by 10%, or stripped executable
size by 30%. The other primary outcomes should remain within 25% of the
stronger applicable optimized Go baseline. A regression greater than 2x stops
the gate for review. TinyGo is measured only if the unchanged corpus works and
the calibration costs no more than half a working day; it is not a deliverable.

### Gate 2: Portable value model

Scope may expand to:

- records and payload enums;
- static-layout records and tagged values;
- `Result` representation and propagation;
- arrays and dynamic strings;
- closures and captured environments; and
Expand Down
96 changes: 96 additions & 0 deletions docs/gate-1-qbe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Gate 1 QBE Vertical Slice

Gate 1 tests whether a TypeRB-authored, heap-free scalar pipeline can produce a
correct native executable cheaply enough to justify connecting it to the
reference frontend. It is an experimental checkpoint, not a supported TypeRB
target or a measurement of the final self-hosted compiler.

## Current pipeline

```text
snapshot v2 JSON
-> strict TypeRB decoder
-> verified Gate 1 Native MIR
-> TypeRB QBE emitter
-> QBE 1.3 (`arm64_apple`)
-> system assembler and linker
-> Darwin arm64 executable
```

The repository owns the decoder, MIR, verifier, QBE lowering, scalar runtime,
and toolchain driver. QBE and the system C toolchain are external dependencies
and must be counted in build and distribution measurements.

## Pinned backend

- QBE release: `1.3`
- release commit: `c0818978acec60ebb6167fade60fb7012cbf20ca`
- release archive SHA-256:
`d587905d620dc5e1d2bfa7c2cc642b9b837aa89a3188c6e37b53d756cf66e320`
- QBE target: `arm64_apple`
- experimental ABI profile: `darwin-arm64-v0`

The ABI profile is disposable and internal. It may change without an adapter.

## Snapshot v2

Snapshot v2 keeps the Gate 0 envelope and adds `entryFunction`. A function has
`id`, `name`, `parameters`, `result`, `entry`, `origin`, and `blocks`. A block
has `id`, `parameters`, `origin`, `instructions`, and one separate
`terminator`.

Supported scalar types are `Void`, `Boolean`, `Integer`, and `Float`. `Void` is
valid only as a function result. The selected entry function takes no
parameters and returns `Void`. This is an internal executable convention; it
does not add `def main(): Integer` or otherwise change the TypeRB source
contract.

Supported instructions are:

- `boolean_literal`, `integer_literal`, and `float_literal`;
- `integer_binary` with `add`, `subtract`, `multiply`, `divide`, or `remainder`;
- `float_binary` with `add`, `subtract`, `multiply`, or `divide`;
- `integer_compare` and `float_compare` with the six ordered comparison names;
- `boolean_not`;
- `call` with a nullable result and direct function identifier; and
- `write_static` for heap-free UTF-8 output.

Supported terminators are `jump`, `branch`, and `return`. Jump and branch edges
carry typed block arguments. Unknown fields and operations fail explicitly,
and the decoder applies resource limits before code generation.

## Scalar semantics

Portable Integer values remain within `-9007199254740991` through
`9007199254740991`. Arithmetic is checked before an out-of-range result can be
observed. Divide-by-zero, remainder-by-zero, and range failures write a fixed
diagnostic to standard error and exit with status 70 in this temporary runtime.
The exact failure text remains experimental until the source-connected
differential corpus establishes the reference behavior.

Boolean values use QBE words, Integer values use QBE longs, and Float values
use QBE doubles. Non-entry block parameters are lowered through typed stack
slots so the initial emitter does not depend on backend-specific SSA phi
construction. Static UTF-8 output is emitted as QBE data and written without a
heap allocation.

Every MIR origin is emitted as a deterministic QBE comment containing the
source-table index and span. Runtime source traces are outside Gate 1.

## Toolchain workspace

`build_gate1_executable` writes `module.ssa` and `module.s` into a caller-owned
workspace and writes the executable to the requested output path. The caller
owns cleanup. Tests use gate-specific directories under `/tmp`; automation and
benchmark scripts must remove those exact directories after collecting
artifacts.

## Gate 1B deletion condition

The reference snapshot producer is temporary. Remove it when the native
TypeRB-authored frontend produces the same verified MIR corpus, or when the
native experiment is abandoned. It must remain process-based and data-only and
must not expose Go compiler objects as an API.

Records, tagged values, dynamic strings, arrays, allocation, garbage
collection, a stable ABI, and additional targets begin no earlier than Gate 2.
9 changes: 9 additions & 0 deletions fixtures/gate1/invalid/unknown-field.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"format": "type-rb-bootstrap-snapshot",
"version": 2,
"module": "example/main",
"entryFunction": "example/main#main",
"sources": [],
"functions": [],
"unexpected": true
}
8 changes: 8 additions & 0 deletions fixtures/gate1/invalid/unsupported-version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"format": "type-rb-bootstrap-snapshot",
"version": 3,
"module": "example/main",
"entryFunction": "example/main#main",
"sources": [],
"functions": []
}
145 changes: 145 additions & 0 deletions fixtures/gate1/valid/scalar-call.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
{
"format": "type-rb-bootstrap-snapshot",
"version": 2,
"module": "example/main",
"entryFunction": "example/main#main",
"sources": [
{ "id": "main", "path": "src/main.trb" }
],
"functions": [
{
"id": "example/main#add",
"name": "add",
"parameters": [
{
"id": "left",
"type": "Integer",
"origin": { "source": "main", "startLine": 1, "startColumn": 1, "endLine": 1, "endColumn": 20 }
},
{
"id": "right",
"type": "Integer",
"origin": { "source": "main", "startLine": 1, "startColumn": 1, "endLine": 1, "endColumn": 20 }
}
],
"result": "Integer",
"entry": "entry",
"origin": { "source": "main", "startLine": 1, "startColumn": 1, "endLine": 3, "endColumn": 4 },
"blocks": [
{
"id": "entry",
"parameters": [],
"origin": { "source": "main", "startLine": 2, "startColumn": 1, "endLine": 2, "endColumn": 20 },
"instructions": [
{
"op": "integer_binary",
"result": "sum",
"operator": "add",
"left": "left",
"right": "right",
"origin": { "source": "main", "startLine": 2, "startColumn": 9, "endLine": 2, "endColumn": 20 }
}
],
"terminator": {
"op": "return",
"value": "sum",
"origin": { "source": "main", "startLine": 2, "startColumn": 2, "endLine": 2, "endColumn": 20 }
}
}
]
},
{
"id": "example/main#main",
"name": "main",
"parameters": [],
"result": "Void",
"entry": "entry",
"origin": { "source": "main", "startLine": 5, "startColumn": 1, "endLine": 10, "endColumn": 4 },
"blocks": [
{
"id": "entry",
"parameters": [],
"origin": { "source": "main", "startLine": 6, "startColumn": 1, "endLine": 8, "endColumn": 20 },
"instructions": [
{
"op": "integer_literal",
"result": "forty",
"value": 40,
"origin": { "source": "main", "startLine": 6, "startColumn": 2, "endLine": 6, "endColumn": 3 }
},
{
"op": "integer_literal",
"result": "two",
"value": 2,
"origin": { "source": "main", "startLine": 6, "startColumn": 5, "endLine": 6, "endColumn": 5 }
},
{
"op": "call",
"result": "answer",
"function": "example/main#add",
"arguments": ["forty", "two"],
"origin": { "source": "main", "startLine": 6, "startColumn": 7, "endLine": 6, "endColumn": 20 }
},
{
"op": "integer_literal",
"result": "expected",
"value": 42,
"origin": { "source": "main", "startLine": 7, "startColumn": 1, "endLine": 7, "endColumn": 2 }
},
{
"op": "integer_compare",
"result": "matches",
"operator": "equal",
"left": "answer",
"right": "expected",
"origin": { "source": "main", "startLine": 7, "startColumn": 4, "endLine": 7, "endColumn": 20 }
}
],
"terminator": {
"op": "branch",
"condition": "matches",
"whenTrue": "success",
"trueArguments": [],
"whenFalse": "failure",
"falseArguments": [],
"origin": { "source": "main", "startLine": 7, "startColumn": 4, "endLine": 7, "endColumn": 20 }
}
},
{
"id": "success",
"parameters": [],
"origin": { "source": "main", "startLine": 8, "startColumn": 1, "endLine": 8, "endColumn": 7 },
"instructions": [
{
"op": "write_static",
"value": "ok\n",
"origin": { "source": "main", "startLine": 8, "startColumn": 1, "endLine": 8, "endColumn": 7 }
}
],
"terminator": {
"op": "return",
"value": null,
"origin": { "source": "main", "startLine": 8, "startColumn": 1, "endLine": 8, "endColumn": 7 }
}
},
{
"id": "failure",
"parameters": [],
"origin": { "source": "main", "startLine": 9, "startColumn": 1, "endLine": 9, "endColumn": 8 },
"instructions": [
{
"op": "write_static",
"value": "bad\n",
"origin": { "source": "main", "startLine": 9, "startColumn": 1, "endLine": 9, "endColumn": 8 }
}
],
"terminator": {
"op": "return",
"value": null,
"origin": { "source": "main", "startLine": 9, "startColumn": 1, "endLine": 9, "endColumn": 8 }
}
}
]
}
]
}
Loading