Skip to content

Commit

Permalink
Implement Liquid::C::Expression to optimize expression evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
dylanahsmith committed Oct 15, 2020
1 parent aa0b3bf commit 9a12d86
Show file tree
Hide file tree
Showing 22 changed files with 766 additions and 337 deletions.
5 changes: 5 additions & 0 deletions ext/liquid_c/c_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,9 @@ inline void c_buffer_write_ruby_value(c_buffer_t *buffer, VALUE value) {
c_buffer_write(buffer, &value, sizeof(VALUE));
}

inline void c_buffer_concat(c_buffer_t *dest, c_buffer_t *src)
{
c_buffer_write(dest, src->data, c_buffer_size(src));
}

#endif
32 changes: 16 additions & 16 deletions ext/liquid_c/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,32 @@
#include "context.h"
#include "variable_lookup.h"
#include "vm.h"
#include "expression.h"

static VALUE cLiquidVariableLookup, cLiquidUndefinedVariable;
ID id_aset, id_set_context;
static ID id_has_key, id_aref;
static ID id_ivar_scopes, id_ivar_environments, id_ivar_static_environments, id_ivar_strict_variables;

VALUE context_evaluate(VALUE self, VALUE expression)
static VALUE context_evaluate(VALUE self, VALUE expression)
{
// Scalar type stored directly in the VALUE, this is a nearly free check, saving a #respond_to?
// Scalar type stored directly in the VALUE, this needs to be checked anyways to use RB_BUILTIN_TYPE
if (RB_SPECIAL_CONST_P(expression))
return expression;

VALUE klass = RBASIC(expression)->klass;

// Basic types that do not respond to #evaluate
if (klass == rb_cString || klass == rb_cArray || klass == rb_cHash)
return expression;

// Liquid::VariableLookup is by far the most common type after String, call
// the C implementation directly to avoid a Ruby dispatch.
if (klass == cLiquidVariableLookup)
return variable_lookup_evaluate(expression, self);

if (rb_respond_to(expression, id_evaluate))
return rb_funcall(expression, id_evaluate, 1, self);

switch (RB_BUILTIN_TYPE(expression)) {
case T_DATA:
if (RBASIC_CLASS(expression) == cLiquidCExpression)
return internal_expression_evaluate(DATA_PTR(expression), self);
break; // e.g. BigDecimal
case T_OBJECT: // may be Liquid::VariableLookup or Liquid::RangeLookup
{
VALUE result = rb_check_funcall(expression, id_evaluate, 1, &self);
return RB_LIKELY(result != Qundef) ? result : expression;
}
default:
break;
}
return expression;
}

Expand Down
1 change: 0 additions & 1 deletion ext/liquid_c/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#define LIQUID_CONTEXT_H

void init_liquid_context();
VALUE context_evaluate(VALUE self, VALUE expression);
VALUE context_find_variable(VALUE self, VALUE key, VALUE raise_on_not_found);
void context_maybe_raise_undefined_variable(VALUE self, VALUE key);

Expand Down
97 changes: 97 additions & 0 deletions ext/liquid_c/expression.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#include "liquid.h"
#include "vm_assembler.h"
#include "parser.h"
#include "vm.h"
#include "expression.h"

VALUE cLiquidCExpression;

static void expression_mark(void *ptr)
{
expression_t *expression = ptr;
vm_assembler_gc_mark(&expression->code);
}

static void expression_free(void *ptr)
{
expression_t *expression = ptr;
vm_assembler_free(&expression->code);
xfree(expression);
}

static size_t expression_memsize(const void *ptr)
{
const expression_t *expression = ptr;
return sizeof(expression_t) + vm_assembler_alloc_memsize(&expression->code);
}

const rb_data_type_t expression_data_type = {
"liquid_expression",
{ expression_mark, expression_free, expression_memsize, },
NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
};

VALUE expression_new(expression_t **expression_ptr)
{
expression_t *expression;
VALUE obj = TypedData_Make_Struct(cLiquidCExpression, expression_t, &expression_data_type, expression);
*expression_ptr = expression;
vm_assembler_init(&expression->code);
return obj;
}

static VALUE internal_expression_parse(parser_t *p)
{
if (p->cur.type == TOKEN_EOS)
return Qnil;

// Avoid allocating an expression object just to wrap a constant
VALUE const_obj = try_parse_constant_expression(p);
if (const_obj != Qundef)
return const_obj;

expression_t *expression;
VALUE expr_obj = expression_new(&expression);

parse_and_compile_expression(p, &expression->code);
vm_assembler_add_leave(&expression->code);

return expr_obj;
}

static VALUE expression_strict_parse(VALUE klass, VALUE markup)
{
StringValue(markup);
char *start = RSTRING_PTR(markup);

parser_t p;
init_parser(&p, start, start + RSTRING_LEN(markup));
VALUE expr_obj = internal_expression_parse(&p);

if (p.cur.type != TOKEN_EOS)
rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "[:%s] is not a valid expression", symbol_names[p.cur.type]);

return expr_obj;
}

#define Expression_Get_Struct(obj, sval) TypedData_Get_Struct(obj, expression_t, &expression_data_type, sval)

static VALUE expression_evaluate(VALUE self, VALUE context)
{
expression_t *expression;
Expression_Get_Struct(self, expression);
return liquid_vm_evaluate(context, &expression->code);
}

VALUE internal_expression_evaluate(expression_t *expression, VALUE context)
{
return liquid_vm_evaluate(context, &expression->code);
}

void init_liquid_expression()
{
cLiquidCExpression = rb_define_class_under(mLiquidC, "Expression", rb_cObject);
rb_undef_alloc_func(cLiquidCExpression);
rb_define_singleton_method(cLiquidCExpression, "strict_parse", expression_strict_parse, 1);
rb_define_method(cLiquidCExpression, "evaluate", expression_evaluate, 1);
}
19 changes: 19 additions & 0 deletions ext/liquid_c/expression.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#if !defined(LIQUID_EXPRESSION_H)
#define LIQUID_EXPRESSION_H

#include "vm_assembler.h"
#include "parser.h"

extern VALUE cLiquidCExpression;

typedef struct expression {
vm_assembler_t code;
} expression_t;

void init_liquid_expression();

VALUE expression_new(expression_t **expression_ptr);
VALUE internal_expression_evaluate(expression_t *expression, VALUE context);

#endif

13 changes: 12 additions & 1 deletion ext/liquid_c/lexer.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,19 @@ inline static VALUE token_to_rstr(lexer_token_t token) {
return rb_enc_str_new(token.val, token.val_end - token.val, utf8_encoding);
}

inline static VALUE token_check_for_symbol(lexer_token_t token) {
return rb_check_symbol_cstr(token.val, token.val_end - token.val, utf8_encoding);
}

inline static VALUE token_to_rstr_leveraging_existing_symbol(lexer_token_t token) {
VALUE sym = token_check_for_symbol(token);
if (RB_LIKELY(sym != Qnil))
return rb_sym2str(sym);
return token_to_rstr(token);
}

inline static VALUE token_to_rsym(lexer_token_t token) {
VALUE sym = rb_check_symbol_cstr(token.val, token.val_end - token.val, utf8_encoding);
VALUE sym = token_check_for_symbol(token);
if (RB_LIKELY(sym != Qnil))
return sym;
return rb_str_intern(token_to_rstr(token));
Expand Down
4 changes: 4 additions & 0 deletions ext/liquid_c/liquid.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
#include "parser.h"
#include "raw.h"
#include "resource_limits.h"
#include "expression.h"
#include "block.h"
#include "context.h"
#include "variable_lookup.h"
#include "vm.h"

ID id_evaluate;
ID id_to_liquid;
ID id_to_s;
ID id_call;

VALUE mLiquid, mLiquidC, cLiquidVariable, cLiquidTemplate, cLiquidBlockBody;
Expand All @@ -28,6 +30,7 @@ void Init_liquid_c(void)
{
id_evaluate = rb_intern("evaluate");
id_to_liquid = rb_intern("to_liquid");
id_to_s = rb_intern("to_s");
id_call = rb_intern("call");

utf8_encoding = rb_utf8_encoding();
Expand Down Expand Up @@ -61,6 +64,7 @@ void Init_liquid_c(void)
init_liquid_parser();
init_liquid_raw();
init_liquid_resource_limits();
init_liquid_expression();
init_liquid_variable();
init_liquid_block();
init_liquid_context();
Expand Down
1 change: 1 addition & 0 deletions ext/liquid_c/liquid.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

extern ID id_evaluate;
extern ID id_to_liquid;
extern ID id_to_s;
extern ID id_call;

extern VALUE mLiquid, mLiquidC, cLiquidVariable, cLiquidTemplate, cLiquidBlockBody;
Expand Down
Loading

0 comments on commit 9a12d86

Please sign in to comment.