liquid-c 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/liquid.yml +24 -2
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +14 -0
  5. data/Gemfile +14 -5
  6. data/README.md +29 -5
  7. data/Rakefile +13 -62
  8. data/ext/liquid_c/block.c +488 -60
  9. data/ext/liquid_c/block.h +28 -2
  10. data/ext/liquid_c/c_buffer.c +42 -0
  11. data/ext/liquid_c/c_buffer.h +76 -0
  12. data/ext/liquid_c/context.c +233 -0
  13. data/ext/liquid_c/context.h +70 -0
  14. data/ext/liquid_c/document_body.c +89 -0
  15. data/ext/liquid_c/document_body.h +59 -0
  16. data/ext/liquid_c/expression.c +116 -0
  17. data/ext/liquid_c/expression.h +24 -0
  18. data/ext/liquid_c/extconf.rb +19 -9
  19. data/ext/liquid_c/intutil.h +22 -0
  20. data/ext/liquid_c/lexer.c +6 -2
  21. data/ext/liquid_c/lexer.h +18 -3
  22. data/ext/liquid_c/liquid.c +76 -6
  23. data/ext/liquid_c/liquid.h +24 -1
  24. data/ext/liquid_c/parse_context.c +76 -0
  25. data/ext/liquid_c/parse_context.h +13 -0
  26. data/ext/liquid_c/parser.c +141 -65
  27. data/ext/liquid_c/parser.h +4 -2
  28. data/ext/liquid_c/raw.c +110 -0
  29. data/ext/liquid_c/raw.h +6 -0
  30. data/ext/liquid_c/resource_limits.c +279 -0
  31. data/ext/liquid_c/resource_limits.h +23 -0
  32. data/ext/liquid_c/stringutil.h +44 -0
  33. data/ext/liquid_c/tokenizer.c +149 -35
  34. data/ext/liquid_c/tokenizer.h +20 -9
  35. data/ext/liquid_c/usage.c +18 -0
  36. data/ext/liquid_c/usage.h +9 -0
  37. data/ext/liquid_c/variable.c +196 -20
  38. data/ext/liquid_c/variable.h +18 -1
  39. data/ext/liquid_c/variable_lookup.c +44 -0
  40. data/ext/liquid_c/variable_lookup.h +8 -0
  41. data/ext/liquid_c/vm.c +588 -0
  42. data/ext/liquid_c/vm.h +25 -0
  43. data/ext/liquid_c/vm_assembler.c +491 -0
  44. data/ext/liquid_c/vm_assembler.h +240 -0
  45. data/ext/liquid_c/vm_assembler_pool.c +97 -0
  46. data/ext/liquid_c/vm_assembler_pool.h +27 -0
  47. data/lib/liquid/c/compile_ext.rb +44 -0
  48. data/lib/liquid/c/version.rb +3 -1
  49. data/lib/liquid/c.rb +225 -46
  50. data/liquid-c.gemspec +16 -10
  51. data/performance/c_profile.rb +23 -0
  52. data/performance.rb +6 -4
  53. data/rakelib/compile.rake +15 -0
  54. data/rakelib/integration_test.rake +43 -0
  55. data/rakelib/performance.rake +43 -0
  56. data/rakelib/rubocop.rake +6 -0
  57. data/rakelib/unit_test.rake +14 -0
  58. data/test/integration_test.rb +11 -0
  59. data/test/liquid_test_helper.rb +21 -0
  60. data/test/test_helper.rb +14 -2
  61. data/test/unit/block_test.rb +130 -0
  62. data/test/unit/context_test.rb +83 -0
  63. data/test/unit/expression_test.rb +186 -0
  64. data/test/unit/gc_stress_test.rb +28 -0
  65. data/test/unit/raw_test.rb +19 -0
  66. data/test/unit/resource_limits_test.rb +50 -0
  67. data/test/unit/tokenizer_test.rb +90 -20
  68. data/test/unit/variable_test.rb +212 -60
  69. metadata +59 -11
  70. data/test/liquid_test.rb +0 -11
@@ -0,0 +1,240 @@
1
+ #ifndef VM_ASSEMBLER_H
2
+ #define VM_ASSEMBLER_H
3
+
4
+ #include <assert.h>
5
+ #include "liquid.h"
6
+ #include "c_buffer.h"
7
+ #include "intutil.h"
8
+
9
+ enum opcode {
10
+ OP_LEAVE = 0,
11
+ OP_WRITE_RAW_W = 1,
12
+ OP_WRITE_NODE = 2,
13
+ OP_POP_WRITE,
14
+ OP_WRITE_RAW_SKIP,
15
+ OP_PUSH_CONST,
16
+ OP_PUSH_NIL,
17
+ OP_PUSH_TRUE,
18
+ OP_PUSH_FALSE,
19
+ OP_PUSH_INT8,
20
+ OP_PUSH_INT16,
21
+ OP_FIND_STATIC_VAR,
22
+ OP_FIND_VAR,
23
+ OP_LOOKUP_CONST_KEY,
24
+ OP_LOOKUP_KEY,
25
+ OP_LOOKUP_COMMAND,
26
+ OP_NEW_INT_RANGE,
27
+ OP_HASH_NEW, // rb_hash_new & rb_hash_bulk_insert
28
+ OP_FILTER,
29
+ OP_BUILTIN_FILTER,
30
+ OP_RENDER_VARIABLE_RESCUE, // setup state to rescue variable rendering
31
+ OP_WRITE_RAW,
32
+ OP_JUMP_FWD_W,
33
+ OP_JUMP_FWD,
34
+ };
35
+
36
+ typedef struct {
37
+ const char *name;
38
+ VALUE sym;
39
+ } filter_desc_t;
40
+
41
+ extern filter_desc_t builtin_filters[];
42
+
43
+ typedef struct vm_assembler {
44
+ c_buffer_t instructions;
45
+ c_buffer_t constants;
46
+ st_table *constants_table;
47
+ size_t max_stack_size;
48
+ size_t stack_size;
49
+ size_t protected_stack_size;
50
+ bool parsing; // prevent executing when incomplete or extending when complete
51
+ } vm_assembler_t;
52
+
53
+ void liquid_define_vm_assembler(void);
54
+ void vm_assembler_init(vm_assembler_t *code);
55
+ void vm_assembler_reset(vm_assembler_t *code);
56
+ void vm_assembler_free(vm_assembler_t *code);
57
+ void vm_assembler_gc_mark(vm_assembler_t *code);
58
+ VALUE vm_assembler_disassemble(const uint8_t *start_ip, const uint8_t *end_ip, const VALUE *constants);
59
+ void vm_assembler_concat(vm_assembler_t *dest, vm_assembler_t *src);
60
+ void vm_assembler_require_stack_args(vm_assembler_t *code, unsigned int count);
61
+
62
+ void vm_assembler_add_write_raw(vm_assembler_t *code, const char *string, size_t size);
63
+ void vm_assembler_add_write_node(vm_assembler_t *code, VALUE node);
64
+ void vm_assembler_add_push_fixnum(vm_assembler_t *code, VALUE num);
65
+ void vm_assembler_add_push_literal(vm_assembler_t *code, VALUE literal);
66
+ void vm_assembler_add_filter(vm_assembler_t *code, VALUE filter_name, size_t arg_count);
67
+
68
+ void vm_assembler_add_evaluate_expression_from_ruby(vm_assembler_t *code, VALUE code_obj, VALUE expression);
69
+ void vm_assembler_add_find_variable_from_ruby(vm_assembler_t *code, VALUE code_obj, VALUE expression);
70
+ void vm_assembler_add_lookup_command_from_ruby(vm_assembler_t *code, VALUE command);
71
+ void vm_assembler_add_lookup_key_from_ruby(vm_assembler_t *code, VALUE code_obj, VALUE expression);
72
+ void vm_assembler_add_new_int_range_from_ruby(vm_assembler_t *code);
73
+ void vm_assembler_add_hash_new_from_ruby(vm_assembler_t *code, VALUE hash_size_obj);
74
+ void vm_assembler_add_filter_from_ruby(vm_assembler_t *code, VALUE filter_name, VALUE arg_count_obj);
75
+
76
+ bool vm_assembler_opcode_has_constant(uint8_t ip);
77
+
78
+ static inline size_t vm_assembler_alloc_memsize(const vm_assembler_t *code)
79
+ {
80
+ return c_buffer_capacity(&code->instructions) + c_buffer_capacity(&code->constants) + sizeof(st_table);
81
+ }
82
+
83
+ static inline void vm_assembler_write_opcode(vm_assembler_t *code, enum opcode op)
84
+ {
85
+ c_buffer_write_byte(&code->instructions, op);
86
+ }
87
+
88
+ static inline uint16_t vm_assembler_write_ruby_constant(vm_assembler_t *code, VALUE constant)
89
+ {
90
+ st_table *constants_table = code->constants_table;
91
+ st_data_t index_value;
92
+
93
+ if (st_lookup(constants_table, constant, &index_value)) {
94
+ return (uint16_t)index_value;
95
+ } else {
96
+ uint16_t index = c_buffer_size(&code->constants) / sizeof(VALUE);
97
+ st_insert(constants_table, constant, index);
98
+ c_buffer_write(&code->constants, &constant, sizeof(VALUE));
99
+ return index;
100
+ }
101
+ }
102
+
103
+ static inline void vm_assembler_increment_stack_size(vm_assembler_t *code, size_t amount)
104
+ {
105
+ code->stack_size += amount;
106
+ if (code->stack_size > code->max_stack_size)
107
+ code->max_stack_size = code->stack_size;
108
+ }
109
+
110
+ static inline void vm_assembler_reserve_stack_size(vm_assembler_t *code, size_t amount)
111
+ {
112
+ vm_assembler_increment_stack_size(code, amount);
113
+ code->stack_size -= amount;
114
+ }
115
+
116
+ static inline void vm_assembler_add_op_with_constant(vm_assembler_t *code, VALUE constant, uint8_t opcode)
117
+ {
118
+ uint16_t index = vm_assembler_write_ruby_constant(code, constant);
119
+ uint8_t *instructions = c_buffer_extend_for_write(&code->instructions, 3);
120
+ instructions[0] = opcode;
121
+ instructions[1] = index >> 8;
122
+ instructions[2] = (uint8_t)index;
123
+ }
124
+
125
+ static inline void vm_assembler_add_leave(vm_assembler_t *code)
126
+ {
127
+ vm_assembler_write_opcode(code, OP_LEAVE);
128
+ code->parsing = false;
129
+ }
130
+
131
+ static inline void vm_assembler_remove_leave(vm_assembler_t *code)
132
+ {
133
+ code->parsing = true;
134
+ code->instructions.data_end--;
135
+ assert(*code->instructions.data_end == OP_LEAVE);
136
+ }
137
+
138
+ static inline void vm_assembler_add_pop_write(vm_assembler_t *code)
139
+ {
140
+ code->stack_size -= 1;
141
+ vm_assembler_write_opcode(code, OP_POP_WRITE);
142
+ }
143
+
144
+ static inline void vm_assembler_add_hash_new(vm_assembler_t *code, size_t hash_size)
145
+ {
146
+ if (hash_size > 255)
147
+ rb_enc_raise(utf8_encoding, cLiquidSyntaxError, "Hash literal has too many keys");
148
+ code->stack_size -= hash_size * 2;
149
+ code->stack_size++;
150
+ uint8_t *instructions = c_buffer_extend_for_write(&code->instructions, 2);
151
+ instructions[0] = OP_HASH_NEW;
152
+ instructions[1] = hash_size;
153
+ }
154
+
155
+
156
+ static inline void vm_assembler_add_push_nil(vm_assembler_t *code)
157
+ {
158
+ vm_assembler_increment_stack_size(code, 1);
159
+ vm_assembler_write_opcode(code, OP_PUSH_NIL);
160
+ }
161
+
162
+ static inline void vm_assembler_add_push_true(vm_assembler_t *code)
163
+ {
164
+ vm_assembler_increment_stack_size(code, 1);
165
+ vm_assembler_write_opcode(code, OP_PUSH_TRUE);
166
+ }
167
+
168
+ static inline void vm_assembler_add_push_false(vm_assembler_t *code)
169
+ {
170
+ vm_assembler_increment_stack_size(code, 1);
171
+ vm_assembler_write_opcode(code, OP_PUSH_FALSE);
172
+ }
173
+
174
+ static inline void vm_assembler_add_push_int8(vm_assembler_t *code, int8_t value)
175
+ {
176
+ vm_assembler_increment_stack_size(code, 1);
177
+ uint8_t *instructions = c_buffer_extend_for_write(&code->instructions, 2);
178
+ instructions[0] = OP_PUSH_INT8;
179
+ instructions[1] = value;
180
+ }
181
+
182
+ static inline void vm_assembler_add_push_int16(vm_assembler_t *code, int16_t value)
183
+ {
184
+ vm_assembler_increment_stack_size(code, 1);
185
+ uint8_t *instructions = c_buffer_extend_for_write(&code->instructions, 3);
186
+ instructions[0] = OP_PUSH_INT16;
187
+ instructions[1] = value >> 8;
188
+ instructions[2] = (uint8_t)value;
189
+ }
190
+
191
+ static inline void vm_assembler_add_push_const(vm_assembler_t *code, VALUE constant)
192
+ {
193
+ vm_assembler_increment_stack_size(code, 1);
194
+ vm_assembler_add_op_with_constant(code, constant, OP_PUSH_CONST);
195
+ }
196
+
197
+ static inline void vm_assembler_add_find_static_variable(vm_assembler_t *code, VALUE key)
198
+ {
199
+ vm_assembler_increment_stack_size(code, 1);
200
+ vm_assembler_add_op_with_constant(code, key, OP_FIND_STATIC_VAR);
201
+ }
202
+
203
+ static inline void vm_assembler_add_find_variable(vm_assembler_t *code)
204
+ {
205
+ // pop 1, push 1
206
+ vm_assembler_write_opcode(code, OP_FIND_VAR);
207
+ }
208
+
209
+ static inline void vm_assembler_add_lookup_const_key(vm_assembler_t *code, VALUE key)
210
+ {
211
+ vm_assembler_reserve_stack_size(code, 1); // push 1, pop 2, push 1
212
+ vm_assembler_add_op_with_constant(code, key, OP_LOOKUP_CONST_KEY);
213
+ }
214
+
215
+ static inline void vm_assembler_add_lookup_key(vm_assembler_t *code)
216
+ {
217
+ code->stack_size--; // pop 2, push 1
218
+ vm_assembler_write_opcode(code, OP_LOOKUP_KEY);
219
+ }
220
+
221
+ static inline void vm_assembler_add_lookup_command(vm_assembler_t *code, VALUE command)
222
+ {
223
+ vm_assembler_reserve_stack_size(code, 1); // push 1, pop 2, push 1
224
+ vm_assembler_add_op_with_constant(code, command, OP_LOOKUP_COMMAND);
225
+ }
226
+
227
+ static inline void vm_assembler_add_new_int_range(vm_assembler_t *code)
228
+ {
229
+ code->stack_size--; // pop 2, push 1
230
+ vm_assembler_write_opcode(code, OP_NEW_INT_RANGE);
231
+ }
232
+
233
+ static inline void vm_assembler_add_render_variable_rescue(vm_assembler_t *code, size_t node_line_number)
234
+ {
235
+ uint8_t *instructions = c_buffer_extend_for_write(&code->instructions, 4);
236
+ instructions[0] = OP_RENDER_VARIABLE_RESCUE;
237
+ uint24_to_bytes((unsigned int)node_line_number, &instructions[1]);
238
+ }
239
+
240
+ #endif
@@ -0,0 +1,97 @@
1
+ #include "vm_assembler_pool.h"
2
+ #include "parse_context.h"
3
+
4
+ static VALUE cLiquidCVMAssemblerPool;
5
+
6
+ void vm_assembler_pool_gc_mark(vm_assembler_pool_t *pool)
7
+ {
8
+ rb_gc_mark(pool->self);
9
+ }
10
+
11
+ static void vm_assembler_pool_free(void *ptr)
12
+ {
13
+ vm_assembler_pool_t *pool = ptr;
14
+
15
+ vm_assembler_element_t *element = pool->freelist;
16
+ while (element) {
17
+ vm_assembler_free(&element->vm_assembler);
18
+ vm_assembler_element_t *next = element->next;
19
+ xfree(element);
20
+ element = next;
21
+ }
22
+
23
+ xfree(pool);
24
+ }
25
+
26
+ static size_t vm_assembler_pool_memsize(const void *ptr)
27
+ {
28
+ const vm_assembler_pool_t *pool = ptr;
29
+
30
+ size_t elements_size = 0;
31
+ vm_assembler_element_t *element = pool->freelist;
32
+ while (element) {
33
+ elements_size += sizeof(vm_assembler_element_t) + vm_assembler_alloc_memsize(&element->vm_assembler);
34
+ element = element->next;
35
+ }
36
+
37
+ return sizeof(vm_assembler_pool_t) + elements_size;
38
+ }
39
+
40
+ const rb_data_type_t vm_assembler_pool_data_type = {
41
+ "liquid_vm_assembler_pool",
42
+ { NULL, vm_assembler_pool_free, vm_assembler_pool_memsize, },
43
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
44
+ };
45
+
46
+ VALUE vm_assembler_pool_new(void)
47
+ {
48
+ vm_assembler_pool_t *pool;
49
+ VALUE obj = TypedData_Make_Struct(cLiquidCVMAssemblerPool, vm_assembler_pool_t, &vm_assembler_pool_data_type, pool);
50
+
51
+ pool->self = obj;
52
+ pool->freelist = NULL;
53
+
54
+ return obj;
55
+ }
56
+
57
+ vm_assembler_t *vm_assembler_pool_alloc_assembler(vm_assembler_pool_t *pool)
58
+ {
59
+ vm_assembler_element_t *element;
60
+ if (!pool->freelist) {
61
+ element = xmalloc(sizeof(vm_assembler_element_t));
62
+ element->next = NULL;
63
+ vm_assembler_init(&element->vm_assembler);
64
+ } else {
65
+ element = pool->freelist;
66
+ pool->freelist = element->next;
67
+ }
68
+
69
+ return &element->vm_assembler;
70
+ }
71
+
72
+ static vm_assembler_element_t *get_element_from_assembler(vm_assembler_t *assembler)
73
+ {
74
+ return (vm_assembler_element_t *)((char *)assembler - offsetof(vm_assembler_element_t, vm_assembler));
75
+ }
76
+
77
+ void vm_assembler_pool_free_assembler(vm_assembler_t *assembler)
78
+ {
79
+ vm_assembler_element_t *element = get_element_from_assembler(assembler);
80
+ vm_assembler_free(&element->vm_assembler);
81
+ xfree(element);
82
+ }
83
+
84
+ void vm_assembler_pool_recycle_assembler(vm_assembler_pool_t *pool, vm_assembler_t *assembler)
85
+ {
86
+ vm_assembler_element_t *element = get_element_from_assembler(assembler);
87
+ vm_assembler_reset(&element->vm_assembler);
88
+ element->next = pool->freelist;
89
+ pool->freelist = element;
90
+ }
91
+
92
+ void liquid_define_vm_assembler_pool(void)
93
+ {
94
+ cLiquidCVMAssemblerPool = rb_define_class_under(mLiquidC, "VMAssemblerPool", rb_cObject);
95
+ rb_global_variable(&cLiquidCVMAssemblerPool);
96
+ rb_undef_alloc_func(cLiquidCVMAssemblerPool);
97
+ }
@@ -0,0 +1,27 @@
1
+ #ifndef LIQUID_VM_ASSEMBLER_POOL_H
2
+ #define LIQUID_VM_ASSEMBLER_POOL_H
3
+
4
+ #include "liquid.h"
5
+ #include "vm_assembler.h"
6
+
7
+ typedef struct vm_assembler_element {
8
+ struct vm_assembler_element *next;
9
+ vm_assembler_t vm_assembler;
10
+ } vm_assembler_element_t;
11
+
12
+ typedef struct vm_assembler_pool {
13
+ VALUE self;
14
+ vm_assembler_element_t *freelist;
15
+ } vm_assembler_pool_t;
16
+
17
+ extern const rb_data_type_t vm_assembler_pool_data_type;
18
+ #define VMAssemblerPool_Get_Struct(obj, sval) TypedData_Get_Struct(obj, vm_assembler_pool_t, &vm_assembler_pool_data_type, sval)
19
+
20
+ void liquid_define_vm_assembler_pool(void);
21
+ void vm_assembler_pool_gc_mark(vm_assembler_pool_t *pool);
22
+ VALUE vm_assembler_pool_new(void);
23
+ vm_assembler_t *vm_assembler_pool_alloc_assembler(vm_assembler_pool_t *pool);
24
+ void vm_assembler_pool_free_assembler(vm_assembler_t *assembler);
25
+ void vm_assembler_pool_recycle_assembler(vm_assembler_pool_t *pool, vm_assembler_t *assembler);
26
+
27
+ #endif
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ Liquid::Variable.class_eval do
4
+ def compile_evaluate(code)
5
+ code.add_evaluate_expression(@name)
6
+ filters.each do |filter_name, filter_args, keyword_args|
7
+ filter_args.each do |arg|
8
+ code.add_evaluate_expression(arg)
9
+ end
10
+ num_args = filter_args.size
11
+ if keyword_args
12
+ keyword_args.each do |key, value|
13
+ code.add_evaluate_expression(key)
14
+ code.add_evaluate_expression(value)
15
+ end
16
+ num_args += 1
17
+ code.add_hash_new(keyword_args.size)
18
+ end
19
+ code.add_filter(filter_name, num_args)
20
+ end
21
+ end
22
+ end
23
+
24
+ Liquid::VariableLookup.class_eval do
25
+ def compile_evaluate(code)
26
+ code.add_find_variable(name)
27
+ lookups.each_with_index do |lookup, i|
28
+ is_command = @command_flags & (1 << i) != 0
29
+ if is_command
30
+ code.add_lookup_command(lookup)
31
+ else
32
+ code.add_lookup_key(lookup)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ Liquid::RangeLookup.class_eval do
39
+ def compile_evaluate(code)
40
+ code.add_evaluate_expression(@start_obj)
41
+ code.add_evaluate_expression(@end_obj)
42
+ code.add_new_int_range
43
+ end
44
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  module C
3
- VERSION = "4.0.1"
5
+ VERSION = "4.1.0"
4
6
  end
5
7
  end