Documentation Index
10 min readChapter 28

28. The Deployment Pipeline

The deployment pipeline in the Quorlin compiler toolchain transforms high-level source code written in Quorlin (.ql) into deterministic, binary-encoded bytecode executable on the Kortana Virtual Machine (KVM), along with an Ethereum-compatible JSON Application Binary Interface (ABI).

This pipeline guarantees that invalid contracts are rejected early during semantic analysis, protecting the execution environment from ill-formed instructions while maintaining complete inter-operability with existing Ethereum tooling.

 +-----------------------------------------------------------------------+
 |                         Quorlin Source (.ql)                          |
 +-----------------------------------------------------------------------+
                                     |
                                     v
 +-----------------------------------------------------------------------+
 | 1. Lexical Analysis (Lexer)                                           |
 |    Converts character stream into discrete Tokens.                   |
 +-----------------------------------------------------------------------+
                                     |
                                     v
 +-----------------------------------------------------------------------+
 | 2. Parsing (Parser)                                                   |
 |    Transforms Token stream into an Abstract Syntax Tree (SourceUnit). |
 +-----------------------------------------------------------------------+
                                     |
                                     v
 +-----------------------------------------------------------------------+
 | 3. Semantic Analysis (Analyzer)                                       |
 |    Validates types, scope, builtins, and symbol bindings.             |
 +-----------------------------------------------------------------------+
                                     |
               +---------------------+---------------------+
               |                                           |
               v                                           v
 +---------------------------+               +---------------------------+
 | 4. Code Generation        |               | 5. ABI Emission           |
 |    Generates KVM bytecode |               |    Generates standard     |
 |    & resolves jumps.      |               |    EVM JSON ABI.          |
 +---------------------------+               +---------------------------+
               |                                           |
               v                                           v
 +-----------------------------------------------------------------------+
 | 6. KVM Binary Serialization                                           |
 |    Packs magic bytes, headers, constants, & instructions into payload. |
 +-----------------------------------------------------------------------+
                                     |
                                     v
 +-----------------------------------------------------------------------+
 | 7. On-Chain Deployment & Execution                                    |
 |    State Host instantiates contract & executes constructor.           |
 +-----------------------------------------------------------------------+

1. Overview of Compilation Phases

The compilation process is coordinated by the driver function compile(std::string_view source) defined in quorlin/compiler.cpp. The pipeline executes through distinct, strictly ordered phases. Failure in any phase instantly halts execution, reporting diagnostic errors before subsequent phases are executed.

// quorlin/compiler.cpp execution flow CompilationResult compile(std::string_view source) { CompilationResult result; // 1. Lexical Analysis Lexer lexer{source, result.diagnostics}; std::vector<Token> tokens = lexer.tokenize(); if (result.diagnostics.has_errors()) return result; // 2. Parsing Parser parser{std::move(tokens), result.diagnostics}; SourceUnit unit = parser.parse(); if (result.diagnostics.has_errors()) return result; if (!unit.contract) { result.diagnostics.error({}, "no contract found in this file"); return result; } result.contract_name = unit.contract->name; // 3. Semantic Analysis Analyzer analyzer{result.diagnostics}; const AnalysisResult analysis = analyzer.analyze(unit); if (result.diagnostics.has_errors()) return result; // 4. Code Generation & 5. ABI Generation // ... }

2. Phase 1: Lexical Analysis (Lexer)

The Lexer scans the raw Quorlin string source and breaks it into atomic standard tokens.

Identifier and Keyword Rules

Identifiers must begin with an ASCII letter (a-z, A-Z) or an underscore (_), followed by any combination of ASCII letters, digits (0-9), or underscores. The lexer categorizes reserved keywords, including:

  • Declarations: contract, record, interface, constructor, event
  • Function Modifiers: reads (view/pure functions), writes (state-modifying functions)
  • Control Flow: if, else, emit, indexed

Sample Source Code

Below is a typical Quorlin contract declaration processed by the pipeline:

contract TokenVault { number totalSupply; address owner; map<address, number> balances; event Deposit(address indexed sender, number amount); constructor { owner = caller; totalSupply = 1000; balances[caller] = 1000; } reads number getBalance(address account) { return balances[account]; } writes truth deposit(number amount) { require amount > 0, "invalid amount"; balances[caller] = balances[caller] + amount; totalSupply = totalSupply + amount; emit Deposit(caller, amount); return yes; } }

If the lexer encounters an unclosed string literal or an illegal character sequence, it appends an error to the DiagnosticBag and terminates compilation immediately.


3. Phase 2: Structural Parsing (Parser)

The Parser consumes the stream of Token instances generated by the Lexer and attempts to build an Abstract Syntax Tree (AST) rooted at a SourceUnit.

Source Unit Validation

The parser verifies that:

  1. Syntax grammar conforms to language specifications.
  2. The file contains a valid contract root (unit.contract).
  3. Variable declarations, storage declarations, and methods match required syntactic structures.

During parsing, language-native type identifiers are mapped to internal compiler constructs:

Quorlin KeywordInternal AST Type (Type)Diagnostic Name
numberType::U256number
truthType::Booltruth
addressType::Addressaddress
textType::Texttext
(Implicit)Type::Voidnothing

If structural errors occur (e.g., missing closing braces or malformed event declarations), diagnostic errors are generated, preventing further processing.


4. Phase 3: Semantic Analysis (Analyzer)

Semantic analysis is the main validation gate. As highlighted in quorlin/compiler.cpp:

"The important gate. The code generator does no checking of its own — it trusts that every identifier resolves and every type agrees, exactly as the KVM interpreter trusts module verification. Running it after a failed analysis would not produce a bad diagnostic; it would produce a bad module."

Analysis Tasks (sema.cpp)

  1. Symbol Resolution: Verifies that state variables, local variables, method parameters, and built-in contextual properties (such as caller) are declared in scope.
  2. Type Checking:
    • Ensures arithmetic operations (+, -, *, /, %) and ordering operations (<, <=, >, >=) operate strictly on number (Type::U256).
    • Ensures logical operations (and, or) operate strictly on truth (Type::Bool).
    • Ensures assignments match declared slot types.
  3. Record Field Checking: Validates struct/record field accesses. If a non-existent field is accessed, the analyzer inspects the record schema and presents available choices in diagnostic format (e.g., field `x` does not exist; did you mean `seller`, `price` and `active`?).
  4. Interface and Signature Checking: Validates function mutability annotations (reads vs writes) and verifies adherence to standard interfaces (such as IERC20).

5. Phase 4: Code Generation and Jump Resolution (CodeGenerator)

Once semantic integrity is guaranteed, the CodeGenerator transforms the analyzed AST into KVM machine instructions (kvm::Instruction).

KVM Instruction Formats

Instructions in KVM are represented using standard fixed-width 32-bit register formats:

R-Format: [ Opcode (8b) | Rd (5b) | Rs1 (5b) | Rs2 (5b) | Funct (9b) ]
I-Format: [ Opcode (8b) | Rd (5b) | Rs1 (5b) | Immediate (14b)      ]
J-Format: [ Opcode (8b) | Immediate (24b)                            ]
Plain:    [ Opcode (8b) | Unused (24b)                               ]

Emission Primitives (codegen.cpp)

The code generator relies on explicit builder functions to format instruction words:

// quorlin/codegen.cpp Instruction r_form(Opcode op, uint8_t rd, uint8_t rs1, uint8_t rs2 = 0, uint16_t funct = 0); Instruction i_form(Opcode op, uint8_t rd, uint8_t rs1, uint32_t imm); Instruction j_form(Opcode op, uint32_t imm); Instruction plain(Opcode op);

Jump Label Resolution

Control structures (if/else conditions, loops) emit conditional jump instructions referencing unresolved labels. The code generator maintains a dynamic label fixup table:

size_t CodeGenerator::make_label() { labels_.push_back(SIZE_MAX); // Unplaced label sentinel return labels_.size() - 1; } void CodeGenerator::place(size_t label) { labels_[label] = code_.size(); // Resolve label to current instruction index }

Prior to final module construction, the fixup pass replaces sentinel label markers with resolved instruction address offsets.


6. Phase 5: ABI Export Generation (abi.cpp)

To enable seamless interactions from dApps, wallets, and standard Web3 client libraries (like ethers.js or web3.js), the deployment pipeline automatically generates a JSON ABI.

Type Translation Architecture

While diagnostics and language parsing display Quorlin human-readable syntax, the ABI generator maps all types directly to standard EVM ABI equivalents:

// quorlin/parser.cpp std::string_view abi_type_name(Type type) noexcept { switch (type) { case Type::U256: return "uint256"; case Type::Bool: return "bool"; case Type::Address: return "address"; case Type::Text: return "string"; case Type::Void: return "void"; case Type::Invalid: return "<invalid>"; } return "<unknown>"; }

JSON Construction (abi.cpp)

Function parameters and event fields are formatted using JSON escaping primitives:

std::string parameter_json(std::string_view name, Type type, bool indexed, bool with_indexed) { std::string out = "{\"name\":" + quoted(name) + ",\"type\":" + quoted(abi_type_name(type)) + ",\"internalType\":" + quoted(type_name(type)); if (with_indexed) { out += ",\"indexed\":" + std::string{indexed ? "true" : "false"}; } return out + "}"; }

Example Output: Generated ABI Segment

For the Deposit event and deposit function in our TokenVault contract, the pipeline outputs:

[ { "type": "constructor", "inputs": [] }, { "type": "event", "name": "Deposit", "inputs": [ {"name": "sender", "type": "address", "internalType": "address", "indexed": true}, {"name": "amount", "type": "uint256", "internalType": "number", "indexed": false} ] }, { "type": "function", "name": "deposit", "inputs": [ {"name": "amount", "type": "uint256", "internalType": "number"} ], "outputs": [ {"name": "", "type": "bool", "internalType": "truth"} ], "stateMutability": "nonpayable" } ]

7. Phase 6: KVM Module Binary Layout (kvm/module.cpp)

Once instructions are generated and resolved, the executable program is packed into a binary payload called a KVM Module.

Binary Header Encoding

The module file uses big-endian encoding across all fixed-width binary fields:

Byte OffsetSize (Bytes)Field DescriptionBinary Type
0x004Magic Signature (KVM\0)0x4B 0x56 0x4D 0x00
0x042Version Numberuint16_t (Big-Endian)
0x064Constant Pool Entry Countuint32_t (Big-Endian)
0x0A4Instruction Vector Countuint32_t (Big-Endian)
0x0E4Code Entry Point Indexuint32_t (Big-Endian)
0x12VariableConstant Pool Table32-byte raw words
End ConstsVariableCode Instruction Stream4-byte packed instruction words
+-------------------------------------------------------------------------+
|                              MODULE HEADER                              |
+-------------------------------------------------------------------------+
|  0x00: Magic ("KVM\0")  | 0x04: Version | 0x06: Consts | 0x0A: Instructions |
+-------------------------------------------------------------------------+
|  0x0E: Entry Point      |
+-------------------------+
|                               CONSTANTS                                 |
+-------------------------------------------------------------------------+
|  32-byte constant word 0                                                |
|  32-byte constant word 1                                                |
|  ...                                                                    |
+-------------------------------------------------------------------------+
|                                  CODE                                   |
+-------------------------------------------------------------------------+
|  4-byte encoded instruction 0                                           |
|  4-byte encoded instruction 1                                           |
|  ...                                                                    |
+-------------------------------------------------------------------------+

Low-Level Serialization Logic

The serialization mechanics in kvm/module.cpp write fields sequentially without dynamic padding:

void Module::serialize(Bytes& out) const { // Write 4-byte magic signature "KVM\0" out.push_back('K'); out.push_back('V'); out.push_back('M'); out.push_back('\0'); // Write metadata headers write_u16(out, version); write_u32(out, static_cast<uint32_t>(constants.size())); write_u32(out, static_cast<uint32_t>(instructions.size())); write_u32(out, entry_point); // Write 32-byte constant words for (const auto& word : constants) { const auto bytes = word.to_be_bytes(); out.insert(out.end(), bytes.begin(), bytes.end()); } // Write 4-byte instructions for (const auto& inst : instructions) { write_u32(out, encode_instruction(inst)); } }

8. Phase 7: On-Chain Deployment and State Execution

The compiled KVM Module binary is submitted within a contract creation transaction. On-chain instantiation is executed via kvm::StateHost interacting with Kortana's persistent WorldState trie.

Execution Initialization & Gas Scheduling

  1. Contract Initialization: The KVM interpreter loads the entry point specified in the module header and begins executing constructor logic.
  2. Gas Accounting (kvm/gas.cpp): Every operation deducts gas according to predefined execution tiers:
    • Stop / Return / Revert: kGasZero (0 gas)
    • ALU Operations (Add, Sub, Eq, And, MLoad, MStore): kGasVeryLow
    • Multiplication / Division (Mul, Div, Mod): kGasLow
  3. State Operations (kvm/state_host.cpp): During constructor execution, state writes use the state host wrapper:
Result<uint256_t> StateHost::set_storage(const Address& address, const uint256_t& key, const uint256_t& value) { // Obtains previous value to determine accurate gas pricing (e.g. fresh allocation vs overwrite) KORTANA_TRY_ASSIGN(const uint256_t previous, world_.get_storage(address, key)); KORTANA_TRY(world_.put_storage(address, key, value)); return previous; }

Once constructor execution completes cleanly without exceeding available gas limits or triggering a Revert instruction, the contract byte payload is committed to the node's unified global state trie at its deterministic deployed address.