A TCC program is a versioned artifact. Language frontends emit this format. The engine validates an artifact before executing it.
Source text is not an input to the engine.
An artifact has an envelope and a program.
| Field | Description |
|---|---|
artifact_hash |
Identity recorded by continuations produced from this artifact |
frontend_id |
Frontend that produced the artifact |
frontend_version |
Version of that frontend |
language_semantics_version |
Language rules assumed by the instructions |
engine_format_version |
Envelope and instruction encoding. Current value: 1 |
required_engine_features |
Engine features the program uses |
required_host_capabilities |
Host capabilities the program uses |
runtime_modules |
Optional helper modules required at runtime |
program |
Functions, entry point, instructions, and source spans |
An engine must reject an artifact that requires an engine feature or host capability it does not implement. Ignoring a requirement is not valid execution.
A program contains one or more functions and an entry function id.
Each function has:
- a unique function id
- a name
- parameter and local counts.
param_countis not greater thanlocal_count. Slots0..param_count-1are the program parameters, in source order - an instruction sequence
- a source-span array of the same length as the instruction sequence (
nullspans are allowed)
Jump and try targets must be in range for that function. Call targets must name a function that exists in the program. The entry function must exist. program.entry is that function's id. The function's name is source text for diagnostics. Execution identity is the id.
Format version 1 defines the following instructions.
Control:
NopJump { target }JumpIfTrue { target }JumpIfFalse { target }Call { func, argc }Return
Locals and constants:
LoadLocal { local }StoreLocal { local }LoadConst { value }PopNewObjectSetProp { key }GetProp { key }NewArrayArrayPushArrayIndex { index }— copy one element of the array on top of the stack, leaving the array in placeStrictEqStrictNeqLt/Le/Gt/GeNot
Durable operations:
Effect.has_input: falseencodes as{"op":"Effect"}; a missing field decodes as false. That omission is the encoding of false.has_input: trueencodes as{"op":"Effect","has_input":true}and pops one value under the key, including{}. False pops only the key, and the host input is{}. A non-booleanhas_inputis invalid. TypeScript and Python emit{"op":"Effect"}. Rust always emits{"op":"Effect","has_input":true}SleepWaitForEventInvoke. Optionalarg_count. Omitted or zero pops only the program name. A positive count pops that many values underneath the name, in reverse, and restores source order. Encode omitsarg_countwhen it is zero; a missing field decodes as zeroFork { count, join_pc }/JoinAll/JoinAny— requiredurable.concurrent_group;countis 1 through 32.JoinAnyisPromise.race. See concurrency.md
Exceptions:
ThrowPushTry { catch, finally }PopTry
Constants are undefined, null, boolean, IEEE-754 binary64 number, and string. Finite numeric constants, including -0, are JSON numbers. Non-finite constants use "NaN", "Infinity", or "-Infinity" on the same number tag. Runtime values may also be objects (NewObject / SetProp / GetProp) and arrays (NewArray / ArrayPush). In a continuation those collections are heap refs, so aliasing and cycles are representable. TypeScript === / !== on collections is reference equality. Python == / != on collections is structural. Numeric compare requires two numbers. These are not a universal language value model. Identity does not cross a host boundary: effect results, event payloads, and invoke arguments are inlined.
Instruction meaning is bound to language_semantics_version. Sharing an encoding does not make language rules identical.
The caller supplies an ordered argument vector. The invoked program's language_semantics_version determines how that vector binds to its declared parameters. Invoke consumes exactly arg_count values under the program name (arg_count omitted means zero). Parameter slots are written on a fresh start only. Resume does not bind them again.
A frontend that needs different numeric types, truthiness, exceptions, or object identity must introduce new instructions or runtime helpers and declare a new required engine feature. Existing artifacts must not change meaning when the format is extended.
Validation must run before execution. It checks:
engine_format_version- non-empty
artifact_hash - non-empty
frontend_id(ids are not allowlisted) language_semantics_versionin{ ts.subset.v1, py.subset.v1, rust.subset.v1 }- required engine features and host capabilities against the implementing engine and host
- non-empty program
- unique function ids
- entry function present
- jump targets in range
LoadLocal/StoreLocalindices< local_count- span array length equal to instruction count
- call targets present
- size caps: at most 1024 functions, 100000 instructions per function, 4096 locals, and 1048576 bytes per const string or property key
Resume also checks that the continuation engine_format_version and language_semantics_version match the artifact, each frame’s locals.len() equals that function’s local_count, and pc is in range. Unknown opcodes fail at decode (IrError::InvalidEncoding), not as a panic. OOB locals fail validate; they are not UnknownInstruction at runtime.
In this repository, validation is tcc_ir::validate. tcc_ir::encode_artifact / decode_artifact serialize the envelope as JSON. artifact_hash is SHA-256 of the canonical JSON of the artifact with an empty artifact_hash field, hex-encoded. The frontend computes the hash; the engine validates the envelope and does not re-hash.
Format version 1 is shared by ts.subset.v1, py.subset.v1, and rust.subset.v1. Control, locals, constants, objects, arrays, comparisons, exceptions, and durable-operation instructions are defined. Call is encoded in the format and is not executed. Supported source languages are TypeScript subset, Python subset, and Rust subset. First examples: TypeScript, Python, Rust.