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,8 @@
1
+ #if !defined(LIQUID_VARIABLE_LOOKUP_H)
2
+ #define LIQUID_VARIABLE_LOOKUP_H
3
+
4
+ void liquid_define_variable_lookup(void);
5
+ VALUE variable_lookup_key(VALUE context, VALUE object, VALUE key, bool is_command);
6
+
7
+ #endif
8
+
data/ext/liquid_c/vm.c ADDED
@@ -0,0 +1,588 @@
1
+ #include <stdint.h>
2
+ #include <assert.h>
3
+
4
+ #include "liquid.h"
5
+ #include "vm.h"
6
+ #include "variable_lookup.h"
7
+ #include "intutil.h"
8
+ #include "document_body.h"
9
+
10
+ ID id_render_node;
11
+ ID id_vm;
12
+
13
+ static VALUE cLiquidCVM;
14
+
15
+ static void vm_mark(void *ptr)
16
+ {
17
+ vm_t *vm = ptr;
18
+
19
+ c_buffer_rb_gc_mark(&vm->stack);
20
+ context_mark(&vm->context);
21
+ }
22
+
23
+ static void vm_free(void *ptr)
24
+ {
25
+ vm_t *vm = ptr;
26
+ c_buffer_free(&vm->stack);
27
+ xfree(vm);
28
+ }
29
+
30
+ static size_t vm_memsize(const void *ptr)
31
+ {
32
+ const vm_t *vm = ptr;
33
+ return sizeof(vm_t) + c_buffer_capacity(&vm->stack);
34
+ }
35
+
36
+ const rb_data_type_t vm_data_type = {
37
+ "liquid_vm",
38
+ { vm_mark, vm_free, vm_memsize, },
39
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
40
+ };
41
+
42
+ static VALUE vm_internal_new(VALUE context)
43
+ {
44
+ vm_t *vm;
45
+ VALUE obj = TypedData_Make_Struct(cLiquidCVM, vm_t, &vm_data_type, vm);
46
+ vm->stack = c_buffer_init();
47
+
48
+ vm->invoking_filter = false;
49
+
50
+ context_internal_init(context, &vm->context);
51
+
52
+ return obj;
53
+ }
54
+
55
+ vm_t *vm_from_context(VALUE context)
56
+ {
57
+ VALUE vm_obj = rb_attr_get(context, id_vm);
58
+ if (vm_obj == Qnil) {
59
+ vm_obj = vm_internal_new(context);
60
+ rb_ivar_set(context, id_vm, vm_obj);
61
+ }
62
+ // instance variable is hidden from ruby so should be safe to unwrap it without type checking
63
+ return DATA_PTR(vm_obj);
64
+ }
65
+
66
+ bool liquid_vm_filtering(VALUE context)
67
+ {
68
+ VALUE vm_obj = rb_attr_get(context, id_vm);
69
+ if (vm_obj == Qnil)
70
+ return false;
71
+ vm_t *vm = DATA_PTR(vm_obj);
72
+ return vm->invoking_filter;
73
+ }
74
+
75
+ static void write_fixnum(VALUE output, VALUE fixnum)
76
+ {
77
+ long long number = RB_NUM2LL(fixnum);
78
+ int write_length = snprintf(NULL, 0, "%lld", number);
79
+ long old_size = RSTRING_LEN(output);
80
+ long new_size = old_size + write_length;
81
+ long capacity = rb_str_capacity(output);
82
+
83
+ if (new_size > capacity) {
84
+ do {
85
+ capacity *= 2;
86
+ } while (new_size > capacity);
87
+ rb_str_resize(output, capacity);
88
+ }
89
+ rb_str_set_len(output, new_size);
90
+
91
+ snprintf(RSTRING_PTR(output) + old_size, write_length + 1, "%lld", number);
92
+ }
93
+
94
+ static VALUE obj_to_s(VALUE obj)
95
+ {
96
+ VALUE str = rb_funcall(obj, id_to_s, 0);
97
+
98
+ if (RB_LIKELY(RB_TYPE_P(str, T_STRING)))
99
+ return str;
100
+
101
+ rb_raise(rb_eTypeError, "%"PRIsVALUE"#to_s returned a non-String convertible value of type %"PRIsVALUE,
102
+ rb_obj_class(obj), rb_obj_class(str));
103
+ }
104
+
105
+ static void write_obj(VALUE output, VALUE obj)
106
+ {
107
+ switch (TYPE(obj)) {
108
+ default:
109
+ obj = obj_to_s(obj);
110
+ // fallthrough
111
+ case T_STRING:
112
+ rb_str_buf_append(output, obj);
113
+ break;
114
+ case T_FIXNUM:
115
+ write_fixnum(output, obj);
116
+ break;
117
+ case T_ARRAY:
118
+ for (long i = 0; i < RARRAY_LEN(obj); i++)
119
+ {
120
+ VALUE item = RARRAY_AREF(obj, i);
121
+
122
+ if (RB_UNLIKELY(RB_TYPE_P(item, T_ARRAY))) {
123
+ // Normally liquid arrays are flat, but for safety and simplicity we
124
+ // leverage ruby's join that detects and raises on a recursion loop
125
+ rb_str_buf_append(output, rb_ary_join(item, Qnil));
126
+ } else {
127
+ write_obj(output, item);
128
+ }
129
+ }
130
+ break;
131
+ case T_NIL:
132
+ break;
133
+ }
134
+ }
135
+
136
+ static inline void vm_stack_push(vm_t *vm, VALUE value)
137
+ {
138
+ VALUE *stack_ptr = (VALUE *)vm->stack.data_end;
139
+ assert(stack_ptr < (VALUE *)vm->stack.capacity_end);
140
+ *stack_ptr++ = value;
141
+ vm->stack.data_end = (uint8_t *)stack_ptr;
142
+ }
143
+
144
+ static inline VALUE vm_stack_pop(vm_t *vm)
145
+ {
146
+ VALUE *stack_ptr = (VALUE *)vm->stack.data_end;
147
+ stack_ptr--;
148
+ assert((VALUE *)vm->stack.data <= stack_ptr);
149
+ vm->stack.data_end = (uint8_t *)stack_ptr;
150
+ return *stack_ptr;
151
+ }
152
+
153
+ static inline VALUE *vm_stack_pop_n_use_in_place(vm_t *vm, size_t n)
154
+ {
155
+ VALUE *stack_ptr = (VALUE *)vm->stack.data_end;
156
+ stack_ptr -= n;
157
+ assert((VALUE *)vm->stack.data <= stack_ptr);
158
+ vm->stack.data_end = (uint8_t *)stack_ptr;
159
+ return stack_ptr;
160
+ }
161
+
162
+ static inline void vm_stack_reserve_for_write(vm_t *vm, size_t num_values)
163
+ {
164
+ c_buffer_reserve_for_write(&vm->stack, num_values * sizeof(VALUE));
165
+ }
166
+
167
+ static VALUE vm_invoke_filter(vm_t *vm, VALUE filter_name, int num_args, VALUE *args)
168
+ {
169
+ bool not_invokable = rb_hash_lookup(vm->context.filter_methods, filter_name) != Qtrue;
170
+ if (RB_UNLIKELY(not_invokable)) {
171
+ if (vm->context.strict_filters) {
172
+ VALUE error_class = rb_const_get(mLiquid, rb_intern("UndefinedFilter"));
173
+ rb_raise(error_class, "undefined filter %"PRIsVALUE, rb_sym2str(filter_name));
174
+ }
175
+ return args[0];
176
+ }
177
+
178
+ vm->invoking_filter = true;
179
+ VALUE result = rb_funcallv(vm->context.strainer, RB_SYM2ID(filter_name), num_args, args);
180
+ vm->invoking_filter = false;
181
+ return rb_funcall(result, id_to_liquid, 0);
182
+ }
183
+
184
+ typedef struct vm_render_until_error_args {
185
+ vm_t *vm;
186
+ const uint8_t *ip; // use for initial address and to save an address for rescuing
187
+ const size_t *const_ptr;
188
+
189
+ /* rendering fields */
190
+ VALUE output;
191
+ const uint8_t *node_line_number;
192
+ } vm_render_until_error_args_t;
193
+
194
+ static VALUE raise_invalid_integer(VALUE unused_arg, VALUE exc)
195
+ {
196
+ rb_raise(cLiquidArgumentError, "invalid integer");
197
+ }
198
+
199
+ // Equivalent to Integer(string) if string is an instance of String
200
+ static VALUE try_string_to_integer(VALUE string)
201
+ {
202
+ return rb_str_to_inum(string, 0, true);
203
+ }
204
+
205
+ static VALUE range_value_to_integer(VALUE value)
206
+ {
207
+ if (RB_INTEGER_TYPE_P(value)) {
208
+ return value;
209
+ } else if (value == Qnil) {
210
+ return INT2FIX(0);
211
+ } else if (RB_TYPE_P(value, T_STRING)) {
212
+ return rb_str_to_inum(value, 0, false); // equivalent to String#to_i
213
+ } else {
214
+ value = obj_to_s(value);
215
+ return rb_rescue2(try_string_to_integer, value, raise_invalid_integer, Qnil, rb_eArgError, (VALUE)0);
216
+ }
217
+ }
218
+
219
+ #ifdef HAVE_RB_HASH_BULK_INSERT
220
+ #define hash_bulk_insert rb_hash_bulk_insert
221
+ #else
222
+ static void hash_bulk_insert(long argc, const VALUE *argv, VALUE hash)
223
+ {
224
+ for (long i = 0; i < argc; i += 2) {
225
+ rb_hash_aset(hash, argv[i], argv[i + 1]);
226
+ }
227
+ }
228
+ #endif
229
+
230
+ // Actually returns a bool resume_rendering value
231
+ static VALUE vm_render_until_error(VALUE uncast_args)
232
+ {
233
+ vm_render_until_error_args_t *args = (void *)uncast_args;
234
+ const VALUE *constants = args->const_ptr;
235
+ const uint8_t *ip = args->ip;
236
+ vm_t *vm = args->vm;
237
+ VALUE output = args->output;
238
+ uint16_t constant_index;
239
+ VALUE constant = Qnil;
240
+ args->ip = NULL; // used by vm_render_rescue, NULL to indicate that it isn't in a rescue block
241
+
242
+ while (true) {
243
+ switch (*ip++) {
244
+ case OP_LEAVE:
245
+ return false;
246
+ case OP_PUSH_NIL:
247
+ vm_stack_push(vm, Qnil);
248
+ break;
249
+ case OP_PUSH_TRUE:
250
+ vm_stack_push(vm, Qtrue);
251
+ break;
252
+ case OP_PUSH_FALSE:
253
+ vm_stack_push(vm, Qfalse);
254
+ break;
255
+ case OP_PUSH_INT8:
256
+ {
257
+ int num = *(int8_t *)ip++; // signed
258
+ vm_stack_push(vm, RB_INT2FIX(num));
259
+ break;
260
+ }
261
+ case OP_PUSH_INT16:
262
+ {
263
+ int num = *(int8_t *)ip++; // big endian encoding, so first byte has sign
264
+ num = (num << 8) | *ip++;
265
+ vm_stack_push(vm, RB_INT2FIX(num));
266
+ break;
267
+ }
268
+ case OP_FIND_STATIC_VAR:
269
+ {
270
+ constant_index = (ip[0] << 8) | ip[1];
271
+ constant = constants[constant_index];
272
+ ip += 2;
273
+ VALUE value = context_find_variable(&vm->context, constant, Qtrue);
274
+ vm_stack_push(vm, value);
275
+ break;
276
+ }
277
+ case OP_FIND_VAR:
278
+ {
279
+ VALUE key = vm_stack_pop(vm);
280
+ VALUE value = context_find_variable(&vm->context, key, Qtrue);
281
+ vm_stack_push(vm, value);
282
+ break;
283
+ }
284
+ case OP_LOOKUP_CONST_KEY:
285
+ case OP_LOOKUP_COMMAND:
286
+ {
287
+ constant_index = (ip[0] << 8) | ip[1];
288
+ constant = constants[constant_index];
289
+ ip += 2;
290
+ vm_stack_push(vm, constant);
291
+ }
292
+ /* fallthrough */
293
+ case OP_LOOKUP_KEY:
294
+ {
295
+ bool is_command = ip[-3] == OP_LOOKUP_COMMAND;
296
+ VALUE key = vm_stack_pop(vm);
297
+ VALUE object = vm_stack_pop(vm);
298
+ VALUE result = variable_lookup_key(vm->context.self, object, key, is_command);
299
+ vm_stack_push(vm, result);
300
+ break;
301
+ }
302
+
303
+ case OP_NEW_INT_RANGE:
304
+ {
305
+ VALUE end = range_value_to_integer(vm_stack_pop(vm));
306
+ VALUE begin = range_value_to_integer(vm_stack_pop(vm));
307
+ bool exclude_end = false;
308
+ vm_stack_push(vm, rb_range_new(begin, end, exclude_end));
309
+ break;
310
+ }
311
+ case OP_HASH_NEW:
312
+ {
313
+ size_t hash_size = *ip++;
314
+ size_t num_keys_and_values = hash_size * 2;
315
+ VALUE hash = rb_hash_new();
316
+ VALUE *args_ptr = vm_stack_pop_n_use_in_place(vm, num_keys_and_values);
317
+ hash_bulk_insert(num_keys_and_values, args_ptr, hash);
318
+ vm_stack_push(vm, hash);
319
+ break;
320
+ }
321
+ case OP_FILTER:
322
+ case OP_BUILTIN_FILTER:
323
+ {
324
+ VALUE filter_name;
325
+ uint8_t num_args;
326
+
327
+ if (ip[-1] == OP_FILTER) {
328
+ constant_index = (ip[0] << 8) | ip[1];
329
+ constant = constants[constant_index];
330
+ filter_name = RARRAY_AREF(constant, 0);
331
+ num_args = RARRAY_AREF(constant, 1);
332
+ ip += 2;
333
+ } else {
334
+ assert(ip[-1] == OP_BUILTIN_FILTER);
335
+ filter_name = builtin_filters[*ip++].sym;
336
+ num_args = *ip++; // includes input argument
337
+ }
338
+
339
+ VALUE *args_ptr = vm_stack_pop_n_use_in_place(vm, num_args);
340
+ VALUE result = vm_invoke_filter(vm, filter_name, num_args, args_ptr);
341
+ vm_stack_push(vm, result);
342
+ break;
343
+ }
344
+
345
+ // Rendering instructions
346
+
347
+ case OP_WRITE_RAW_W:
348
+ case OP_WRITE_RAW:
349
+ {
350
+ const char *text;
351
+ size_t size;
352
+ if (ip[-1] == OP_WRITE_RAW_W) {
353
+ size = bytes_to_uint24(ip);
354
+ text = (const char *)&ip[3];
355
+ ip += 3 + size;
356
+ } else {
357
+ size = *ip;
358
+ text = (const char *)&ip[1];
359
+ ip += 1 + size;
360
+ }
361
+ rb_str_cat(output, text, size);
362
+ resource_limits_increment_write_score(vm->context.resource_limits, output);
363
+ break;
364
+ }
365
+ case OP_JUMP_FWD_W:
366
+ {
367
+ size_t size = bytes_to_uint24(ip);
368
+ ip += 3 + size;
369
+ break;
370
+ }
371
+
372
+ case OP_JUMP_FWD:
373
+ {
374
+ uint8_t size = *ip;
375
+ ip += 1 + size;
376
+ break;
377
+ }
378
+
379
+ case OP_PUSH_CONST:
380
+ {
381
+ constant_index = (ip[0] << 8) | ip[1];
382
+ constant = constants[constant_index];
383
+ ip += 2;
384
+ vm_stack_push(vm, constant);
385
+ break;
386
+ }
387
+
388
+ case OP_WRITE_NODE:
389
+ {
390
+ constant_index = (ip[0] << 8) | ip[1];
391
+ constant = constants[constant_index];
392
+ ip += 2;
393
+ rb_funcall(cLiquidBlockBody, id_render_node, 3, vm->context.self, output, constant);
394
+
395
+ if (RARRAY_LEN(vm->context.interrupts)) {
396
+ return false;
397
+ }
398
+
399
+ resource_limits_increment_write_score(vm->context.resource_limits, output);
400
+ break;
401
+ }
402
+ case OP_RENDER_VARIABLE_RESCUE:
403
+ // Save state used by vm_render_rescue to rescue from a variable rendering exception
404
+ args->node_line_number = ip;
405
+ // vm_render_rescue will iterate from this instruction to the instruction
406
+ // following OP_POP_WRITE_VARIABLE to resume rendering from
407
+ ip += 3;
408
+ args->ip = ip;
409
+ break;
410
+ case OP_POP_WRITE:
411
+ {
412
+ VALUE var_result = vm_stack_pop(vm);
413
+ if (vm->context.global_filter != Qnil)
414
+ var_result = rb_funcall(vm->context.global_filter, id_call, 1, var_result);
415
+ write_obj(output, var_result);
416
+ args->ip = NULL; // mark the end of a rescue block, used by vm_render_rescue
417
+ resource_limits_increment_write_score(vm->context.resource_limits, output);
418
+ break;
419
+ }
420
+
421
+ default:
422
+ rb_bug("invalid opcode: %u", ip[-1]);
423
+ }
424
+ }
425
+ }
426
+
427
+ // Evaluate instructions that avoid using rendering instructions and leave with the result on
428
+ // the top of the stack
429
+ VALUE liquid_vm_evaluate(VALUE context, vm_assembler_t *code)
430
+ {
431
+ vm_t *vm = vm_from_context(context);
432
+ vm_stack_reserve_for_write(vm, code->max_stack_size);
433
+ #ifndef NDEBUG
434
+ size_t old_stack_byte_size = c_buffer_size(&vm->stack);
435
+ #endif
436
+
437
+ vm_render_until_error_args_t args = {
438
+ .vm = vm,
439
+ .const_ptr = (const size_t *)code->constants.data,
440
+ .ip = code->instructions.data
441
+ };
442
+ vm_render_until_error((VALUE)&args);
443
+ VALUE ret = vm_stack_pop(vm);
444
+ assert(old_stack_byte_size == c_buffer_size(&vm->stack));
445
+ return ret;
446
+ }
447
+
448
+ void liquid_vm_next_instruction(const uint8_t **ip_ptr)
449
+ {
450
+ const uint8_t *ip = *ip_ptr;
451
+
452
+ switch (*ip++) {
453
+ case OP_LEAVE:
454
+ case OP_POP_WRITE:
455
+ case OP_PUSH_NIL:
456
+ case OP_PUSH_TRUE:
457
+ case OP_PUSH_FALSE:
458
+ case OP_FIND_VAR:
459
+ case OP_LOOKUP_KEY:
460
+ case OP_NEW_INT_RANGE:
461
+ break;
462
+
463
+ case OP_HASH_NEW:
464
+ case OP_PUSH_INT8:
465
+ ip++;
466
+ break;
467
+
468
+ case OP_BUILTIN_FILTER:
469
+ case OP_PUSH_INT16:
470
+ case OP_PUSH_CONST:
471
+ case OP_WRITE_NODE:
472
+ case OP_FIND_STATIC_VAR:
473
+ case OP_LOOKUP_CONST_KEY:
474
+ case OP_LOOKUP_COMMAND:
475
+ case OP_FILTER:
476
+ ip += 2;
477
+ break;
478
+
479
+ case OP_RENDER_VARIABLE_RESCUE:
480
+ ip += 3;
481
+ break;
482
+
483
+ case OP_WRITE_RAW_W:
484
+ case OP_JUMP_FWD_W:
485
+ {
486
+ size_t size = bytes_to_uint24(ip);
487
+ ip += 3 + size;
488
+ break;
489
+ }
490
+
491
+ case OP_WRITE_RAW:
492
+ case OP_JUMP_FWD:
493
+ {
494
+ uint8_t size = *ip;
495
+ ip += 1 + size;
496
+ break;
497
+ }
498
+
499
+ default:
500
+ rb_bug("invalid opcode: %u", ip[-1]);
501
+ }
502
+ *ip_ptr = ip;
503
+ }
504
+
505
+ VALUE vm_translate_if_filter_argument_error(vm_t *vm, VALUE exception)
506
+ {
507
+ if (vm->invoking_filter) {
508
+ if (rb_obj_is_kind_of(exception, rb_eArgError)) {
509
+ VALUE cLiquidStrainerTemplate = rb_const_get(mLiquid, rb_intern("StrainerTemplate"));
510
+ exception = rb_funcall(cLiquidStrainerTemplate, rb_intern("arg_exc_to_liquid_exc"), 1, exception);
511
+ }
512
+ vm->invoking_filter = false;
513
+ }
514
+ return exception;
515
+ }
516
+
517
+ typedef struct vm_render_rescue_args {
518
+ vm_render_until_error_args_t *render_args;
519
+ size_t old_stack_byte_size;
520
+ } vm_render_rescue_args_t;
521
+
522
+ // Actually returns a bool resume_rendering value
523
+ static VALUE vm_render_rescue(VALUE uncast_args, VALUE exception)
524
+ {
525
+ vm_render_rescue_args_t *args = (void *)uncast_args;
526
+ VALUE blank_tag = Qfalse; // tags are still rendered using Liquid::BlockBody.render_node
527
+ vm_render_until_error_args_t *render_args = args->render_args;
528
+ vm_t *vm = render_args->vm;
529
+
530
+ exception = vm_translate_if_filter_argument_error(vm, exception);
531
+
532
+ const uint8_t *ip = render_args->ip;
533
+ if (!ip)
534
+ rb_exc_raise(exception);
535
+
536
+ // rescue for variable render, where ip is at the start of the render and we need to
537
+ // skip to the end of the variable render to resume rendering if the error is handled
538
+ enum opcode last_op;
539
+ do {
540
+ last_op = *ip;
541
+ liquid_vm_next_instruction(&ip);
542
+ } while (last_op != OP_POP_WRITE);
543
+ render_args->ip = ip;
544
+ // remove temporary stack values from variable evaluation
545
+ vm->stack.data_end = vm->stack.data + args->old_stack_byte_size;
546
+
547
+ assert(render_args->node_line_number);
548
+ unsigned int node_line_number = bytes_to_uint24(render_args->node_line_number);
549
+ VALUE line_number = node_line_number != 0 ? UINT2NUM(node_line_number) : Qnil;
550
+
551
+ rb_funcall(cLiquidBlockBody, rb_intern("c_rescue_render_node"), 5,
552
+ vm->context.self, render_args->output, line_number, exception, blank_tag);
553
+ return true;
554
+ }
555
+
556
+ void liquid_vm_render(block_body_header_t *body, const VALUE *const_ptr, VALUE context, VALUE output)
557
+ {
558
+ vm_t *vm = vm_from_context(context);
559
+
560
+ vm_stack_reserve_for_write(vm, body->max_stack_size);
561
+ resource_limits_increment_render_score(vm->context.resource_limits, body->render_score);
562
+
563
+ vm_render_until_error_args_t render_args = {
564
+ .vm = vm,
565
+ .const_ptr = const_ptr,
566
+ .ip = block_body_instructions_ptr(body),
567
+ .output = output,
568
+ };
569
+ vm_render_rescue_args_t rescue_args = {
570
+ .render_args = &render_args,
571
+ .old_stack_byte_size = c_buffer_size(&vm->stack),
572
+ };
573
+
574
+ while (rb_rescue(vm_render_until_error, (VALUE)&render_args, vm_render_rescue, (VALUE)&rescue_args)) {
575
+ }
576
+ assert(rescue_args.old_stack_byte_size == c_buffer_size(&vm->stack));
577
+ }
578
+
579
+
580
+ void liquid_define_vm(void)
581
+ {
582
+ id_render_node = rb_intern("render_node");
583
+ id_vm = rb_intern("vm");
584
+
585
+ cLiquidCVM = rb_define_class_under(mLiquidC, "VM", rb_cObject);
586
+ rb_undef_alloc_func(cLiquidCVM);
587
+ rb_global_variable(&cLiquidCVM);
588
+ }
data/ext/liquid_c/vm.h ADDED
@@ -0,0 +1,25 @@
1
+ #ifndef VM_H
2
+ #define VM_H
3
+
4
+ #include <ruby.h>
5
+ #include "block.h"
6
+ #include "vm_assembler.h"
7
+ #include "context.h"
8
+
9
+ typedef struct vm {
10
+ c_buffer_t stack;
11
+ bool invoking_filter;
12
+ context_t context;
13
+ } vm_t;
14
+
15
+ void liquid_define_vm(void);
16
+ vm_t *vm_from_context(VALUE context);
17
+ void liquid_vm_render(block_body_header_t *block, const VALUE *const_ptr, VALUE context, VALUE output);
18
+ void liquid_vm_next_instruction(const uint8_t **ip_ptr);
19
+ bool liquid_vm_filtering(VALUE context);
20
+ VALUE liquid_vm_evaluate(VALUE context, vm_assembler_t *code);
21
+
22
+ vm_t *vm_from_context(VALUE context);
23
+ VALUE vm_translate_if_filter_argument_error(vm_t *vm, VALUE exception);
24
+
25
+ #endif