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

Implement Liquid::C::Expression to optimize expression evaluation #60

Merged
merged 2 commits into from
Oct 15, 2020
Merged
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
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require 'bundler/gem_tasks'
require 'rake/extensiontask'
require 'benchmark'

ENV['DEBUG'] = 'true'
ENV['DEBUG'] ||= 'true'
ext_task = Rake::ExtensionTask.new("liquid_c")

# For MacOS, generate debug information that ruby can read
Expand Down
5 changes: 5 additions & 0 deletions ext/liquid_c/c_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ inline void c_buffer_rb_gc_mark(c_buffer_t *buffer)
}
}

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"

peterzhu2118 marked this conversation as resolved.
Show resolved Hide resolved
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

8 changes: 6 additions & 2 deletions ext/liquid_c/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
$CFLAGS << ' -Werror'
end
compiler = RbConfig::MAKEFILE_CONFIG['CC']
if ENV['DEBUG'] == 'true' && compiler =~ /gcc|g\+\+/
$CFLAGS << ' -fbounds-check'
if ENV['DEBUG'] == 'true'
if compiler =~ /gcc|g\+\+/
$CFLAGS << ' -fbounds-check'
end
else
$CFLAGS << ' -DNDEBUG'
end

if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0") # added in 2.7
Expand Down
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