From 179748ac1d5fd202a0326bd4aa19d3a71bf4530f Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Mon, 28 Sep 2020 08:20:08 -0400 Subject: [PATCH 1/4] Move tag compilation to Liquid::Tag.compile to support tag compilation --- ext/liquid_c/block.c | 25 ++++++++++----------- ext/liquid_c/block.h | 5 +++++ ext/liquid_c/liquid.c | 6 +++++ ext/liquid_c/liquid.h | 2 ++ ext/liquid_c/tag.c | 45 +++++++++++++++++++++++++++++++++++++ ext/liquid_c/tag.h | 6 +++++ ext/liquid_c/vm_assembler.c | 6 +++++ ext/liquid_c/vm_assembler.h | 1 + 8 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 ext/liquid_c/tag.c create mode 100644 ext/liquid_c/tag.h diff --git a/ext/liquid_c/block.c b/ext/liquid_c/block.c index c17fa7c2..cec836d9 100644 --- a/ext/liquid_c/block.c +++ b/ext/liquid_c/block.c @@ -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; @@ -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; @@ -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; @@ -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; } @@ -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"); @@ -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, }; @@ -535,8 +536,6 @@ 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"); diff --git a/ext/liquid_c/block.h b/ext/liquid_c/block.h index 40b3f5a0..22f9052e 100644 --- a/ext/liquid_c/block.h +++ b/ext/liquid_c/block.h @@ -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) { diff --git a/ext/liquid_c/liquid.c b/ext/liquid_c/liquid.c index b8d505a8..3e457443 100644 --- a/ext/liquid_c/liquid.c +++ b/ext/liquid_c/liquid.c @@ -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; @@ -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(); @@ -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(); } diff --git a/ext/liquid_c/liquid.h b/ext/liquid_c/liquid.h index 8c9f223b..dbc091d5 100644 --- a/ext/liquid_c/liquid.h +++ b/ext/liquid_c/liquid.h @@ -5,10 +5,12 @@ #include #include +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; diff --git a/ext/liquid_c/tag.c b/ext/liquid_c/tag.c new file mode 100644 index 00000000..4b53f7b2 --- /dev/null +++ b/ext/liquid_c/tag.c @@ -0,0 +1,45 @@ +#include "liquid.h" +#include "vm_assembler.h" +#include "block.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); + vm_assembler_add_write_node_from_ruby(body->as.intermediate.code, self); + 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); +} diff --git a/ext/liquid_c/tag.h b/ext/liquid_c/tag.h new file mode 100644 index 00000000..fd2e454e --- /dev/null +++ b/ext/liquid_c/tag.h @@ -0,0 +1,6 @@ +#ifndef TAG_H +#define TAG_H + +void liquid_define_tag(); + +#endif diff --git a/ext/liquid_c/vm_assembler.c b/ext/liquid_c/vm_assembler.c index 2cf21fe0..2a43cd2c 100644 --- a/ext/liquid_c/vm_assembler.c +++ b/ext/liquid_c/vm_assembler.c @@ -407,6 +407,12 @@ 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_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)); diff --git a/ext/liquid_c/vm_assembler.h b/ext/liquid_c/vm_assembler.h index 1a64cec9..0fd214ce 100644 --- a/ext/liquid_c/vm_assembler.h +++ b/ext/liquid_c/vm_assembler.h @@ -71,6 +71,7 @@ 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_node_from_ruby(vm_assembler_t *code, VALUE node); static inline size_t vm_assembler_alloc_memsize(const vm_assembler_t *code) { From 38207bf9410a7015df558b2bc730b2e7583537e2 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Mon, 28 Sep 2020 09:45:25 -0400 Subject: [PATCH 2/4] Compile `comment` tag from ruby --- lib/liquid/c/compile_ext.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/liquid/c/compile_ext.rb b/lib/liquid/c/compile_ext.rb index f73ce328..c96fd691 100644 --- a/lib/liquid/c/compile_ext.rb +++ b/lib/liquid/c/compile_ext.rb @@ -42,3 +42,8 @@ def compile_evaluate(code) code.add_new_int_range end end + +Liquid::Comment.class_eval do + def compile(_code) + end +end From 8655f0084de24c5e2938e0293f3f53236121eda5 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Mon, 28 Sep 2020 10:01:11 -0400 Subject: [PATCH 3/4] Compile `raw` tag to VM code --- ext/liquid_c/block.c | 8 ++++++++ ext/liquid_c/vm_assembler.c | 8 ++++++++ ext/liquid_c/vm_assembler.h | 1 + lib/liquid/c/compile_ext.rb | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/ext/liquid_c/block.c b/ext/liquid_c/block.c index cec836d9..67481ccc 100644 --- a/ext/liquid_c/block.c +++ b/ext/liquid_c/block.c @@ -531,6 +531,13 @@ 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); + vm_assembler_add_write_raw_from_ruby(&body->code, string); + return self; +} void liquid_define_block_body() { @@ -563,6 +570,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); } diff --git a/ext/liquid_c/vm_assembler.c b/ext/liquid_c/vm_assembler.c index 2a43cd2c..ed903ca4 100644 --- a/ext/liquid_c/vm_assembler.c +++ b/ext/liquid_c/vm_assembler.c @@ -407,6 +407,14 @@ 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); diff --git a/ext/liquid_c/vm_assembler.h b/ext/liquid_c/vm_assembler.h index 0fd214ce..de0fdf05 100644 --- a/ext/liquid_c/vm_assembler.h +++ b/ext/liquid_c/vm_assembler.h @@ -71,6 +71,7 @@ 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) diff --git a/lib/liquid/c/compile_ext.rb b/lib/liquid/c/compile_ext.rb index c96fd691..cd6d0117 100644 --- a/lib/liquid/c/compile_ext.rb +++ b/lib/liquid/c/compile_ext.rb @@ -43,7 +43,25 @@ def compile_evaluate(code) 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 From fcbd69bd67e77b10b825b41c7983ca30c5916a13 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Mon, 28 Sep 2020 16:34:34 -0400 Subject: [PATCH 4/4] Compile echo tag to VM code --- ext/liquid_c/block.c | 3 ++- ext/liquid_c/tag.c | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/ext/liquid_c/block.c b/ext/liquid_c/block.c index 67481ccc..9e02e310 100644 --- a/ext/liquid_c/block.c +++ b/ext/liquid_c/block.c @@ -535,7 +535,8 @@ static VALUE block_body_add_write_raw(VALUE self, VALUE string) { block_body_t *body; BlockBody_Get_Struct(self, body); - vm_assembler_add_write_raw_from_ruby(&body->code, string); + ensure_intermediate(body); + vm_assembler_add_write_raw_from_ruby(body->as.intermediate.code, string); return self; } diff --git a/ext/liquid_c/tag.c b/ext/liquid_c/tag.c index 4b53f7b2..39f2ef73 100644 --- a/ext/liquid_c/tag.c +++ b/ext/liquid_c/tag.c @@ -1,6 +1,8 @@ #include "liquid.h" #include "vm_assembler.h" #include "block.h" +#include "tokenizer.h" +#include "variable.h" static ID id_parse; @@ -29,10 +31,32 @@ 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"); @@ -42,4 +66,7 @@ void liquid_define_tag() 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); }