Making Liquid-C Ready for Faster Conditional Rendering
Liquid-C, the C-based extension to Shopify’s Liquid template language, has been accelerating parsing performance since it was introduced in 2014. It compiles Liquid templates into bytecode for a custom VM, which then renders them. However, not every Liquid feature has been ported to Liquid-C. Tags like If and For still fall back to being rendered in pure Liquid. To bring those tags into Liquid-C, a refactor of the VM’s instruction model was necessary.
How Liquid-C Compiles Templates
Liquid-C follows the same basic architecture as other bytecode-compiled languages. A template file is parsed into a Document object, then tokenized into Block objects that represent expressions and variables. The Liquid-C extension creates these objects in C and translates each parsed token into a bytecode instruction for its VM.
The tokenizer recognizes six token types:
TOKENIZER_TOKEN_NONETOKEN_INVALIDTOKEN_RAWTOKEN_TAGTOKEN_VARIABLETOKEN_BLANK_LIQUID_TAG_LINE
For raw template text, for example, the Block object emits a write_raw instruction containing the string to output directly.
The VM adds a constants array alongside its generated bytecode. As rendering progresses, an instruction pointer steps through the instruction stream. Each time it reaches an instruction that references a constant, a separate constant pointer is advanced to keep pace.
Removing the Constant Pointer
That paired-pointer design works for straight-line execution, but it breaks down when conditional logic needs to jump forward or backward in the instruction stream. To jump to a different instruction, the VM must also calculate how many constant slots it needs to skip—a costly operation.
To prepare for If and For tags, Liquid-C changes its instruction structure so each instruction stores the index of the constant it needs directly in the constants array. With this refactor, the constant pointer becomes unnecessary.
The refactored structure also enables deduplication. Liquid-C stores constant indices in a hash table, so repeated values are reused rather than duplicated in the constants array. For example, performance/tests/dropify/cart.liquid previously held 157 objects in the VM’s constants array; after the refactor, it stores only 104.
Trade-offs and Next Steps
The refactor does not dramatically speed up theme rendering on its own. In fact, it slightly increases both memory usage and parsing time—the hash table that tracks constant indices is an added cost. There are bigger gains available by implementing conditional tags, but those gains require the ability to freely jump.
With the constant pointer gone, Liquid-C now has a structural basis for GOTO or JMP operations. Those operations will allow the VM to move the instruction pointer forward or backward, which opens the door to compiling If and For tags in C and extending the performance benefits of Liquid-C across more template constructs.
Liquid-C remains an open source project. It still lacks full feature coverage—notably variable processing, template loading, and standard filters are areas for future development. Since Liquid is used by a wide range of projects including Jekyll, reducing reliance on pure-Liquid rendering stands to benefit many downstream users.



