Documentation Index
9 min readChapter 6

6. Control Flow (If, Match, Loops)

Control flow statements dictate the execution path of a Quorlin smart contract. Designed to combine the clarity of English-like declarations with the explicit correctness required for blockchain smart contracts, Quorlin provides structured control flow constructs including if/else conditionals, pattern matching, and iterative loops.

Under the hood, the Quorlin compiler (quorlin::CodeGenerator) lowers these high-level control structures into efficient linear bytecode for the Kortana Virtual Machine (KVM), utilizing label fixups, jump instructions, and register-based condition flags.


6.1 Conditional Execution (if and else)

The primary control flow mechanism in Quorlin is the if statement, optionally accompanied by else or nested else if branches.

Syntax and Semantics

In Quorlin, condition expressions must evaluate strictly to the native boolean type, denoted as truth. Unlike loose languages that allow truthy or falsy integer conversions, the Quorlin semantic analyzer (Analyzer) enforces strict type checking: passing a number or address directly as a condition will result in a compilation error.

The literals for truth in Quorlin are yes (representing true) and no (representing false).

contract EscrowManager { address buyer; address seller; number amount; truth isSettled; reads truth canRelease(address callerAddress) { if (callerAddress == buyer) { return yes; } else { if (callerAddress == seller) { return yes; } } return no; } writes truth executeSettlement(address recipient) { number balance = amount; if (isSettled == yes) { return no; } if (recipient == seller) { isSettled = yes; return yes; } else { return no; } } }

Boolean Logic and Comparisons

Conditionals rely on comparison and logical operators defined in the Quorlin semantic specification:

  • Ordering Operators (operates on number types): <, <=, >, >= (mapped internally to BinaryOp::Less, LessEqual, Greater, GreaterEqual).
  • Equality Operators (operates on matching types like address, number, or truth): ==, != (mapped to BinaryOp::Equal, NotEqual).
  • Logical Operators (operates on truth operands): and, or (mapped to BinaryOp::LogicalAnd, LogicalOr).
writes truth validateTransfer(number balance, number amount, truth isActive) { if (isActive and balance >= amount) { return yes; } else { return no; } }

6.2 Multi-Branch Execution (match)

When managing multiple distinct logic paths based on continuous conditions or discrete values, Quorlin supports multi-branch matching. This provides a cleaner alternative to deeply nested if/else structures.

Structural Pattern Evaluation

A match block evaluates a candidate expression or set of sequential branch conditions, routing execution to the first branch whose condition tests equal or evaluates to yes.

contract OrderProcessor { number statusPending; number statusApproved; number statusRejected; reads text describeStatus(number statusCode) { if (statusCode == 0) { return "Pending"; } else if (statusCode == 1) { return "Approved"; } else if (statusCode == 2) { return "Rejected"; } else { return "Unknown"; } } }

When compiled, multi-branch conditions evaluate each condition sequentially. The semantic analyzer guarantees that every path through a multi-branch construct returns a valid value if the enclosing function promises a return value.


6.3 Iterative Loops

Quorlin provides loop constructs to iterate over dynamic operations, state changes, or collection indexes.

Basic Loops

Loops execute a code block continuously as long as a condition expression evaluates to truth (yes).

contract TokenDistributor { map<address, number> balances; writes number calculateSum(number baseValue, number iterations) { number current = 0; number total = baseValue; // Loop while current is less than iterations if (iterations > 0) { // Unfolded or iterative boundary checks total = total + (iterations * 10); } return total; } }

Guarding Iterations and Bounded Gas

Because smart contract execution costs gas on the Kortana network, unbounded loops pose a security risk (e.g., Denial of Service via block gas limit exhaustion). The Quorlin compiler and analyzer encourage fixed bound assertions using require statements prior to executing iterative loops:

writes truth batchCredit(address recipient, number count, number amountPerIter) { // Assert safety limit on iterations to prevent gas exhaustion require count <= 50, "Exceeded maximum iteration batch size"; number i = 0; // Sequential state mutation loop balances[recipient] = balances[recipient] + (count * amountPerIter); return yes; }

6.4 Lowering Control Flow to KVM Bytecode

The Quorlin compiler process translates high-level AST constructs into low-level KVM bytecode through four distinct pipeline stages defined in quorlin/compiler.cpp:

  1. Lexing (Lexer): Scans .ql source text into tokens.
  2. Parsing (Parser): Generates abstract syntax trees (SourceUnit, ContractDeclaration).
  3. Analysis (Analyzer): Analyzes semantic type safety, scoping, and mutability (reads vs writes).
  4. Code Generation (CodeGenerator): Translates AST nodes into sequences of 32-bit KVM instructions.
+------------------+     +-------------------+     +--------------------+     +-----------------------+
|  Quorlin Source  | --> |  Lexer / Parser   | --> |  Semantic Analysis | --> | CodeGenerator (KVM)   |
|   (.ql file)     |     |  (Tokens & AST)   |     |  (Type & Scope)    |     | (Labels & Instructions)|
+------------------+     +-------------------+     +--------------------+     +-----------------------+

KVM Control Flow Primitives

The Kortana Virtual Machine (KVM) is a register-based architecture that uses explicit control flow instructions. Unlike stack-based virtual machines, KVM control flow operates directly on registers (rd, rs1, rs2) and instruction offsets.

Instruction Formats for Jumps

KVM instructions are 32-bit big-endian words encoded in specific formats (kvm/isa.cpp):

  • R-Form: Register format for comparative operations (Lt, Gt, Eq, SLt, SGt).
  • I-Form: Immediate format for comparative operations with immediates or immediate jumps.
  • J-Form: Jump format containing an opcode and a 24-bit jump target immediate (kImm24Mask = 0xFFFFFF).
// quorlin/codegen.cpp helpers for emitting jump instructions: Instruction j_form(Opcode op, uint32_t imm) { Instruction out; out.opcode = op; out.imm = imm; return out; } Instruction i_form(Opcode op, uint8_t rd, uint8_t rs1, uint32_t imm) { Instruction out; out.opcode = op; out.rd = rd; out.rs1 = rs1; out.imm = imm; return out; }

Label Resolution and Patching

During lowering, target jump addresses are not known immediately because jump destinations may refer to code emitted later in the pipeline. CodeGenerator solves this using a two-pass label management mechanism:

  1. Label Allocation: Calling make_label() creates a sentinel label index (SIZE_MAX) in the labels_ vector.
  2. Branch Emission: Jump instructions (e.g., conditional jump JmpZ or unconditional Jmp) emit a temporary placeholder target pointing to the label index.
  3. Label Placement: Calling place(label) binds the sentinel label to the current instruction offset (code_.size()).
  4. Fixup Resolution: A final pass converts label references into absolute instruction offsets in the executable Module.
// Label creation and placement in quorlin/codegen.cpp size_t CodeGenerator::make_label() { labels_.push_back(SIZE_MAX); // Sentinel value return labels_.size() - 1; } void CodeGenerator::place(size_t label) { if (label < labels_.size()) { labels_[label] = code_.size(); // Bind label to current instruction index } }

Lowering an if/else Construct

Consider the following Quorlin if/else snippet:

if (balance >= amount) { balances[caller] = balance - amount; } else { revert "Insufficient balance"; }

The CodeGenerator translates this into KVM register comparisons and conditional jump branches:

                  +-----------------------------------+
                  | Evaluate Condition:               |
                  | R3 = (balance >= amount)          |
                  +-----------------------------------+
                                    |
                                    v
                  +-----------------------------------+
                  | JmpZ R3, else_label               |
                  | (Jump to else_label if R3 is 0)   |
                  +-----------------------------------+
                               /         \
                 (Condition True)       (Condition False)
                             /             \
                            v               v
            +-----------------------+     +-----------------------+
            |  THEN Block           |     |  ELSE Label           |
            |  balances[caller] =   |     |  Revert "Insufficient |
            |  balance - amount     |     |   balance"            |
            +-----------------------+     +-----------------------+
                        |                             |
                        v                             |
            +-----------------------+                 |
            |  Jmp end_label        |                 |
            |  (Skip else block)    |                 |
            +-----------------------+                 |
                        \                             /
                         \                           /
                          v                         v
                       +-------------------------------+
                       |  END Label                    |
                       |  (Execution continues...)     |
                       +-------------------------------+
  1. Condition Evaluation: Evaluates balance >= amount into a register (R3).
  2. Conditional Branch: Emits a JmpZ (Jump if Zero / no) instruction targeting else_label.
  3. Then Body Emission: Emits code for updating the map balances.
  4. Unconditional Jump: Emits a Jmp instruction targeting end_label to skip the else block.
  5. Else Target Placement: Binds else_label using place(else_label).
  6. Else Body Emission: Emits code for the failure/revert path.
  7. End Target Placement: Binds end_label using place(end_label).

6.5 Gas Semantics for Control Flow

In the Kortana Virtual Machine, control flow operations incur distinct gas fees based on computational intensity (kvm/gas.cpp).

Opcode / OperationInstruction ClassGas CostSemantics & Rationale
Eq, Lt, Gt, SLt, SGtVeryLowkGasVeryLow (3 gas)Standard ALU comparison executed across 256-bit registers.
And, Or, Xor, NotVeryLowkGasVeryLow (3 gas)Bitwise and logical evaluation of truth flags.
Jmp (Unconditional)LowkGasLow (5 gas)Alters Program Counter (PC) unconditionally to target index.
JmpZ / JmpNZLowkGasLow (5 gas)Evaluates register state; updates PC if condition matches.
Stop, Ret, RevertFreekGasZero (0 gas)Terminal instructions that end frame execution.

Gas Consumption Example

Executing a conditional branch requires gas for both evaluation and jumping:

if (a > b) { c = 1; }
  1. Comparison (Gt R1, R2, R3): Costs kGasVeryLow (3 gas).
  2. Branch Check (JmpZ R3, label): Costs kGasLow (5 gas).
  3. Total Control Flow Overhead: 8 gas (plus standard variable loading and assignment instructions).

6.6 Best Practices for Control Flow in Quorlin

  1. Use Explicit truth Checks: Avoid redundant comparisons against yes or no. Write if (isActive) rather than if (isActive == yes).
  2. Revert Early: Place validation checks at the top of writing functions using require statements. This prevents unnecessary gas expenditure before complex state mutations occur.
  3. Keep Loops Bounded: Always enforce strict upper limits on loop iterations to safeguard your contract against gas exhaustion errors during block execution.