Native code clinic — follow a String to QBE
Run the Native executable trace first. This clinic follows its
String literal and puts call through the TypeRB-authored frontend into two
small QBE products: static data and an exported entry function.
This correspondence assumes the trace’s default downloaded seed. A compiler
supplied through TYPE_RB_NATIVE_COMPILER can represent different source.
Hold source, data, and control together
Section titled “Hold source, data, and control together”def main() puts("hello from TypeRB Native") returnenddata $g4s7 = align 8 { l 24, b 104 101 108 108 111 32 102 114 111 109 32 84 121 112 101 82, b 66 32 78 97 116 105 118 101, b 0 }function $g4f0() {@start call $g4_puts(l $g4s7) ret}
export function w $main(w %argc, l %argv) {@start call $g4f0() ret 0}The number 7 is a token-derived symbol in this snapshot. It is not a stable
public name. Search for the data declaration or g4_puts handoff, not for the
exact $g4s7 spelling in a future revision.
1. The lexer preserves the decoded String
Section titled “1. The lexer preserves the decoded String”The lexer enters gate4_scan_string after seeing ". It collects characters,
decodes supported escapes, and stores a token with kind 3:
def gate4_scan_string(mut state: Gate4Compiler, source: String, mut position: Array<Integer>, line: Integer) # scan characters and supported escapes if closed gate4_push_token(state, 3, text, line) else gate4_diagnostic(state, "TRBN4001", line, "unterminated String literal") endendRead the exact
gate4_scan_string implementation.
Its output is decoded text plus a source line. It has not yet established that
the surrounding call is well typed.
Small TypeRB note: mut marks bindings or parameters that may change.
Array<Integer> and small records are used as explicit compiler storage; they
are implementation mechanisms, not new source-language phases.
2. Parsing accepts a String as a primary expression
Section titled “2. Parsing accepts a String as a primary expression”This seed’s parser validates expression syntax over token storage. A kind-3
token is a complete primary expression, so parsing advances once:
if kind == 3 gate4_advance(position)else # grouped expressions and other primary formsendThe branch lives in
gate4_parse_primary_syntax.
Unlike the reference compiler clinic, this path does not need a separate public
AST node for the lesson. The durable responsibility is still the same: accept
the authored shape before checking its meaning.
3. Checking connects String to puts
Section titled “3. Checking connects String to puts”The checker gives a kind-3 primary the type String:
if kind == 3 gate4_advance(position) return gate4_checked_value("String", 0, "", 0)endSee
gate4_check_primary.
The call checker then treats puts as a one-argument function returning Void
and expects that argument to be String:
if callee.name == "puts" function_index =- 1 result_type = "Void"end# while checking the argumentif callee.name == "puts" argument_expected = "String"endRead the complete
gate4_check_call branch.
Changing the fixture to puts(42) should therefore stop before QBE emission
with a type diagnostic.
4. Emission gives the String a data identity
Section titled “4. Emission gives the String a data identity”Before functions are emitted, gate4_emit_static_strings walks String tokens,
deduplicates equal values when appropriate, and writes a QBE data declaration:
gate4_output_line( output, "data $g4s" + gate4_integer_string(token_index) + " = align 8 { l " + gate4_integer_string(value.size()) + ",",)The
static String emitter
then emits the bytes and a trailing zero. For this fixture, l 24 records the
24-byte content length and the b items spell the UTF-8-compatible ASCII text.
When the expression emitter sees the same String token, it returns the matching
global symbol as a checked String value:
if kind == 3 gate4_advance(position) return gate4_emitted_value("$g4s" + gate4_integer_string(canonical), "String", 0, "", 0, "")endSee
gate4_emit_primary.
The data producer and expression consumer agree on one canonical symbol.
5. puts and main make control visible
Section titled “5. puts and main make control visible”The direct-call emitter turns the checked puts call into:
if callee.name == "puts" gate4_output_line(output, "\tcall $g4_puts(" + arguments + ")") return gate4_emitted_value("", "Void", 0, "", 0, "")endThat branch is in
gate4_emit_direct_call.
The runtime’s
$g4_puts function
loads the length, advances eight bytes to the content, writes it, and writes a
newline.
Finally, the ordinary application entry finds the source main, calls its
emitted function, and returns status zero:
gate4_output_line(output, "export function w $main(w %argc, l %argv) {")gate4_output_line(output, "@start")main_index := gate4_find_function_in_module(state, state.entry_module[0], "main")gate4_output_line(output, "\tcall $g4f" + gate4_integer_string(main_index) + "()")gate4_output_line(output, "\tret 0")Read the exact
main emission.
The QBE needed for this page
Section titled “The QBE needed for this page”You only need six pieces of notation:
| QBE spelling | Meaning here |
|---|---|
data $g4s7 |
a global data definition; $ names a global symbol |
l |
a 64-bit long value, used here for lengths and pointers |
w |
a 32-bit word value, used for the process status and argc |
b |
individual bytes in static data |
%argc |
a function-local temporary; % names local values |
@start, call, ret |
a basic-block label, a call, and a return |
Learn more QBE only when a code path introduces another operation. This keeps the backend notation attached to the TypeRB value whose behavior you already understand.
Run focused evidence in a current checkout
Section titled “Run focused evidence in a current checkout”The clinic explains the immutable seed source. For a new Native change, use the commands owned by your current checkout:
trb check --config compiler/gate4/trbconfig.jsoncTYPE_RB_NATIVE_ROOT="$PWD" trb test --config compiler/gate4/trbconfig.jsoncThen run the contributor-guide trace to observe the fixed seed boundary again. Do not treat agreement with this older seed as proof that a current bootstrap chain or compatibility claim passes.
Check yourself
Section titled “Check yourself”- Which stage first says the literal has type
String? - Why do the static-data emitter and expression emitter need the same canonical symbol?
- What additional claim would require more than the QBE emitted by this trace?
Check your answers
- The checker; the lexer preserves decoded text and the parser accepts its syntax first.
- The function call must refer to the exact data object that contains the literal’s length and bytes.
- Running native code, proving a runtime or ABI change, claiming target portability, or proving self-hosting would require the corresponding QBE, toolchain, conformance, target, or multi-generation evidence.
Continue with the workflow and test matrix and the Native change journey. Use the big map to find the corresponding responsibilities in the newer implementation snapshot.