Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start compiling tags into liquid VM code #96

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions ext/liquid_c/block.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
static ID
intern_raise_missing_variable_terminator,
intern_raise_missing_tag_terminator,
intern_is_blank,
intern_parse,
intern_square_brackets,
intern_unknown_tag_in_liquid_tag,
intern_ivar_nodelist;
Expand All @@ -29,6 +27,7 @@ typedef struct tag_markup {

typedef struct parse_context {
tokenizer_t *tokenizer;
VALUE block_body_obj;
VALUE tokenizer_obj;
VALUE ruby_obj;
} parse_context_t;
Expand Down Expand Up @@ -82,8 +81,6 @@ const rb_data_type_t block_body_data_type = {
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
};

#define BlockBody_Get_Struct(obj, sval) TypedData_Get_Struct(obj, block_body_t, &block_body_data_type, sval)

static VALUE block_body_allocate(VALUE klass)
{
block_body_t *body;
Expand Down Expand Up @@ -242,13 +239,14 @@ static tag_markup_t internal_block_body_parse(block_body_t *body, parse_context_
goto loop_break;
}

VALUE new_tag = rb_funcall(tag_class, intern_parse, 4,
tag_name, markup, parse_context->tokenizer_obj, parse_context->ruby_obj);

if (body->as.intermediate.blank && !RTEST(rb_funcall(new_tag, intern_is_blank, 0)))
body->as.intermediate.blank = false;
rb_funcall(tag_class, id_compile, 5, tag_name, markup,
parse_context->tokenizer_obj, parse_context->ruby_obj, parse_context->block_body_obj);
vm_assembler_t *code = body->as.intermediate.code;
size_t unused_stack_items = code->stack_size - code->protected_stack_size;
if (unused_stack_items)
rb_raise(rb_eRuntimeError, "%"PRIsVALUE".compile left %zu unused items on the stack",
tag_class, unused_stack_items);

vm_assembler_add_write_node(body->as.intermediate.code, new_tag);
render_score_increment += 1;
break;
}
Expand All @@ -261,7 +259,9 @@ static tag_markup_t internal_block_body_parse(block_body_t *body, parse_context_
return unknown_tag;
}

static void ensure_intermediate(block_body_t *body)
#define ensure_intermediate block_body_ensure_intermediate

void block_body_ensure_intermediate(block_body_t *body)
{
if (body->compiled) {
rb_raise(rb_eRuntimeError, "Liquid::C::BlockBody is already compiled");
Expand All @@ -280,6 +280,7 @@ static void ensure_intermediate_not_parsing(block_body_t *body)
static VALUE block_body_parse(VALUE self, VALUE tokenizer_obj, VALUE parse_context_obj)
{
parse_context_t parse_context = {
.block_body_obj = self,
.tokenizer_obj = tokenizer_obj,
.ruby_obj = parse_context_obj,
};
Expand Down Expand Up @@ -530,13 +531,19 @@ static VALUE block_body_add_filter(VALUE self, VALUE filter_name, VALUE num_args
return self;
}

static VALUE block_body_add_write_raw(VALUE self, VALUE string)
{
block_body_t *body;
BlockBody_Get_Struct(self, body);
ensure_intermediate(body);
vm_assembler_add_write_raw_from_ruby(body->as.intermediate.code, string);
return self;
}

void liquid_define_block_body()
{
intern_raise_missing_variable_terminator = rb_intern("raise_missing_variable_terminator");
intern_raise_missing_tag_terminator = rb_intern("raise_missing_tag_terminator");
intern_is_blank = rb_intern("blank?");
intern_parse = rb_intern("parse");
intern_square_brackets = rb_intern("[]");
intern_unknown_tag_in_liquid_tag = rb_intern("unknown_tag_in_liquid_tag");
intern_ivar_nodelist = rb_intern("@nodelist");
Expand Down Expand Up @@ -564,6 +571,7 @@ void liquid_define_block_body()

rb_define_method(cLiquidCBlockBody, "add_hash_new", block_body_add_hash_new, 1);
rb_define_method(cLiquidCBlockBody, "add_filter", block_body_add_filter, 2);
rb_define_method(cLiquidCBlockBody, "add_write_raw", block_body_add_write_raw, 1);

rb_global_variable(&variable_placeholder);
}
Expand Down
5 changes: 5 additions & 0 deletions ext/liquid_c/block.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ typedef struct block_body {
} as;
} block_body_t;

extern const rb_data_type_t block_body_data_type;

#define BlockBody_Get_Struct(obj, sval) TypedData_Get_Struct(obj, block_body_t, &block_body_data_type, sval)

void liquid_define_block_body();
void block_body_ensure_intermediate(block_body_t *body);

static inline uint8_t *block_body_instructions_ptr(block_body_header_t *body)
{
Expand Down
6 changes: 6 additions & 0 deletions ext/liquid_c/liquid.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
#include "vm_assembler_pool.h"
#include "vm.h"
#include "usage.h"
#include "tag.h"

ID id_evaluate;
ID id_to_liquid;
ID id_to_s;
ID id_call;
ID id_compile;
ID id_compile_evaluate;
ID id_blank_p;
ID id_ivar_line_number;

VALUE mLiquid, mLiquidC, cLiquidVariable, cLiquidTemplate, cLiquidBlockBody;
Expand All @@ -40,7 +43,9 @@ RUBY_FUNC_EXPORTED void Init_liquid_c(void)
id_to_liquid = rb_intern("to_liquid");
id_to_s = rb_intern("to_s");
id_call = rb_intern("call");
id_compile = rb_intern("compile");
id_compile_evaluate = rb_intern("compile_evaluate");
id_blank_p = rb_intern("blank?");
id_ivar_line_number = rb_intern("@line_number");

utf8_encoding = rb_utf8_encoding();
Expand Down Expand Up @@ -91,5 +96,6 @@ RUBY_FUNC_EXPORTED void Init_liquid_c(void)
liquid_define_vm_assembler();
liquid_define_vm();
liquid_define_usage();
liquid_define_tag();
}

2 changes: 2 additions & 0 deletions ext/liquid_c/liquid.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include <ruby/encoding.h>
#include <stdbool.h>

extern ID id_blank_p;
extern ID id_evaluate;
extern ID id_to_liquid;
extern ID id_to_s;
extern ID id_call;
extern ID id_compile;
extern ID id_compile_evaluate;
extern ID id_ivar_line_number;

Expand Down
72 changes: 72 additions & 0 deletions ext/liquid_c/tag.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include "liquid.h"
#include "vm_assembler.h"
#include "block.h"
#include "tokenizer.h"
#include "variable.h"

static ID id_parse;

static VALUE cLiquidTag;

static VALUE tag_class_compile(VALUE klass, VALUE tag_name, VALUE markup,
VALUE tokenizer_obj, VALUE parse_context_obj, VALUE block_body_obj)
{
block_body_t *body;
BlockBody_Get_Struct(block_body_obj, body);

VALUE new_tag = rb_funcall(klass, id_parse, 4, tag_name, markup,
tokenizer_obj, parse_context_obj);

block_body_ensure_intermediate(body);

if (body->as.intermediate.blank && !RTEST(rb_funcall(new_tag, id_blank_p, 0)))
body->as.intermediate.blank = false;

rb_funcall(new_tag, id_compile, 1, block_body_obj);

return Qnil;
}

static VALUE tag_compile(VALUE self, VALUE block_body_obj)
{
block_body_t *body;
BlockBody_Get_Struct(block_body_obj, body);
block_body_ensure_intermediate(body);
vm_assembler_add_write_node_from_ruby(body->as.intermediate.code, self);
return Qnil;
}

static VALUE echo_class_compile(VALUE klass, VALUE tag_name, VALUE markup,
VALUE tokenizer_obj, VALUE parse_context_obj, VALUE block_body_obj)
{
block_body_t *body;
BlockBody_Get_Struct(block_body_obj, body);
block_body_ensure_intermediate(body);

tokenizer_t *tokenizer;
Tokenizer_Get_Struct(tokenizer_obj, tokenizer);

variable_parse_args_t parse_args = {
.markup = RSTRING_PTR(markup),
.markup_end = RSTRING_PTR(markup) + RSTRING_LEN(markup),
.code = body->as.intermediate.code,
.code_obj = block_body_obj,
.parse_context = parse_context_obj,
};
internal_variable_compile(&parse_args, tokenizer->line_number);
return Qnil;
}

void liquid_define_tag()
{
id_parse = rb_intern("parse");

cLiquidTag = rb_const_get(mLiquid, rb_intern("Tag"));
rb_global_variable(&cLiquidTag);

rb_define_singleton_method(cLiquidTag, "compile", tag_class_compile, 5);
rb_define_method(cLiquidTag, "compile", tag_compile, 1);

VALUE cLiquidEcho = rb_const_get(mLiquid, rb_intern("Echo"));
rb_define_singleton_method(cLiquidEcho, "compile", echo_class_compile, 5);
}
6 changes: 6 additions & 0 deletions ext/liquid_c/tag.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef TAG_H
#define TAG_H

void liquid_define_tag();

#endif
14 changes: 14 additions & 0 deletions ext/liquid_c/vm_assembler.c
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,20 @@ void vm_assembler_add_filter_from_ruby(vm_assembler_t *code, VALUE filter_name,
vm_assembler_add_filter(code, filter_name, arg_count);
}

void vm_assembler_add_write_raw_from_ruby(vm_assembler_t *code, VALUE string)
{
ensure_parsing(code);
Check_Type(string, T_STRING);
check_utf8_encoding(string, "raw string");
vm_assembler_add_write_raw(code, RSTRING_PTR(string), RSTRING_LEN(string));
}

void vm_assembler_add_write_node_from_ruby(vm_assembler_t *code, VALUE node)
{
ensure_parsing(code);
vm_assembler_add_write_node(code, node);
}

void liquid_define_vm_assembler()
{
builtin_filter_table = st_init_numtable_with_size(ARRAY_LENGTH(builtin_filters));
Expand Down
2 changes: 2 additions & 0 deletions ext/liquid_c/vm_assembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ void vm_assembler_add_lookup_key_from_ruby(vm_assembler_t *code, VALUE code_obj,
void vm_assembler_add_new_int_range_from_ruby(vm_assembler_t *code);
void vm_assembler_add_hash_new_from_ruby(vm_assembler_t *code, VALUE hash_size_obj);
void vm_assembler_add_filter_from_ruby(vm_assembler_t *code, VALUE filter_name, VALUE arg_count_obj);
void vm_assembler_add_write_raw_from_ruby(vm_assembler_t *code, VALUE string);
void vm_assembler_add_write_node_from_ruby(vm_assembler_t *code, VALUE node);

static inline size_t vm_assembler_alloc_memsize(const vm_assembler_t *code)
{
Expand Down
23 changes: 23 additions & 0 deletions lib/liquid/c/compile_ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,26 @@ def compile_evaluate(code)
code.add_new_int_range
end
end

Liquid::Tag.class_eval do
# Avoid automatically inheriting compile methods other than the base compile
# method, so that compile methods can be added without breaking subclasses
# that override the render behaviour.
def self.inherited(subclass)
alias_method(:compile, :base_compile)
super
end

alias_method(:base_compile, :compile)
end

Liquid::Comment.class_eval do
def compile(_code)
end
end

Liquid::Raw.class_eval do
def compile(code)
code.add_write_raw(@body)
end
end