rlsl 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/CHANGELOG.md +3 -1
  4. data/README.md +101 -28
  5. data/Rakefile +7 -0
  6. data/lib/rlsl/base_translator/call_parser.rb +13 -2
  7. data/lib/rlsl/base_translator/code_scanner.rb +39 -1
  8. data/lib/rlsl/base_translator.rb +24 -2
  9. data/lib/rlsl/code_generator/math_prelude.rb +103 -10
  10. data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +52 -12
  11. data/lib/rlsl/code_generator/template_context.rb +6 -3
  12. data/lib/rlsl/code_generator/uniform_struct_generator.rb +1 -1
  13. data/lib/rlsl/code_generator.rb +7 -4
  14. data/lib/rlsl/compiled_shader.rb +1 -1
  15. data/lib/rlsl/errors.rb +29 -0
  16. data/lib/rlsl/glsl/translator.rb +19 -6
  17. data/lib/rlsl/msl/shader.rb +27 -9
  18. data/lib/rlsl/msl/translator.rb +25 -4
  19. data/lib/rlsl/msl/uniform_buffer_packer.rb +11 -7
  20. data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +59 -18
  21. data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +22 -9
  22. data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +18 -14
  23. data/lib/rlsl/prism/ast_visitor/scope_context.rb +4 -0
  24. data/lib/rlsl/prism/ast_visitor.rb +78 -20
  25. data/lib/rlsl/prism/builtins/function_registry.rb +41 -24
  26. data/lib/rlsl/prism/builtins/operator_rules.rb +24 -0
  27. data/lib/rlsl/prism/builtins/swizzle_rules.rb +10 -1
  28. data/lib/rlsl/prism/builtins.rb +8 -0
  29. data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +51 -8
  30. data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +42 -12
  31. data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +20 -4
  32. data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +7 -1
  33. data/lib/rlsl/prism/emitters/base_emitter.rb +66 -5
  34. data/lib/rlsl/prism/emitters/c_emitter.rb +103 -7
  35. data/lib/rlsl/prism/emitters/glsl_emitter.rb +48 -0
  36. data/lib/rlsl/prism/emitters/msl_emitter.rb +43 -1
  37. data/lib/rlsl/prism/emitters/target_emitter.rb +56 -9
  38. data/lib/rlsl/prism/emitters/wgsl_emitter.rb +173 -18
  39. data/lib/rlsl/prism/errors.rb +9 -0
  40. data/lib/rlsl/prism/ir/control_flow.rb +6 -4
  41. data/lib/rlsl/prism/ir/definitions.rb +17 -2
  42. data/lib/rlsl/prism/ir/expressions.rb +5 -1
  43. data/lib/rlsl/prism/ir/node.rb +1 -1
  44. data/lib/rlsl/prism/ir/traversal.rb +6 -2
  45. data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
  46. data/lib/rlsl/prism/node_traversal.rb +41 -0
  47. data/lib/rlsl/prism/parameter_list.rb +46 -0
  48. data/lib/rlsl/prism/return_flow_validator.rb +73 -0
  49. data/lib/rlsl/prism/source_extractor/block_locator.rb +23 -25
  50. data/lib/rlsl/prism/source_extractor.rb +7 -6
  51. data/lib/rlsl/prism/source_unit/parser.rb +42 -32
  52. data/lib/rlsl/prism/source_unit.rb +10 -10
  53. data/lib/rlsl/prism/target_capability_validator.rb +27 -2
  54. data/lib/rlsl/prism/transpiler.rb +40 -3
  55. data/lib/rlsl/prism/type_inference/call_type_resolver.rb +28 -4
  56. data/lib/rlsl/prism/type_inference/call_validator.rb +5 -1
  57. data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +45 -19
  58. data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +88 -7
  59. data/lib/rlsl/prism/type_inference/definition_inferer.rb +16 -1
  60. data/lib/rlsl/prism/type_inference/expression_inferer.rb +4 -0
  61. data/lib/rlsl/prism/type_inference/field_type_resolver.rb +23 -2
  62. data/lib/rlsl/prism/type_inference/scope_stack.rb +1 -1
  63. data/lib/rlsl/prism/type_inference/type_shapes.rb +3 -3
  64. data/lib/rlsl/prism/type_inference.rb +10 -5
  65. data/lib/rlsl/shader_builder/build_service.rb +34 -7
  66. data/lib/rlsl/shader_builder/native_extension_compiler.rb +50 -24
  67. data/lib/rlsl/shader_builder/shader_definition.rb +3 -3
  68. data/lib/rlsl/shader_builder/source_resolver.rb +19 -1
  69. data/lib/rlsl/shader_builder.rb +50 -10
  70. data/lib/rlsl/shader_name.rb +18 -0
  71. data/lib/rlsl/types/type_spec.rb +3 -3
  72. data/lib/rlsl/types/value_normalizer.rb +16 -11
  73. data/lib/rlsl/types.rb +0 -4
  74. data/lib/rlsl/uniform_context.rb +18 -1
  75. data/lib/rlsl/version.rb +1 -1
  76. data/lib/rlsl/wgsl/translator.rb +29 -6
  77. data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
  78. data/lib/rlsl.rb +24 -3
  79. metadata +23 -11
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
5
+ require_relative "../ir/traversal"
3
6
  require_relative "base_emitter/control_flow_emission"
4
7
  require_relative "base_emitter/expression_emission"
5
8
  require_relative "base_emitter/definition_emission"
@@ -66,14 +69,26 @@ module RLSL
66
69
  def initialize
67
70
  @indent_level = 0
68
71
  @return_context_stack = [false]
72
+ @return_struct_name_stack = []
73
+ @return_type_stack = []
74
+ @temporary_index = 0
75
+ @emit_depth = 0
76
+ @occupied_names = Set.new
69
77
  end
70
78
 
71
79
  def emit(node, needs_return: false)
80
+ @occupied_names = source_names(node) if @emit_depth.zero?
81
+ @emit_depth += 1
72
82
  with_return_context(needs_return) do
73
83
  return node.accept(self) if node.respond_to?(:accept)
74
84
 
75
- raise "Unknown IR node: #{node.class}"
85
+ raise RLSL::InternalError, "Unknown IR node: #{node.class}"
76
86
  end
87
+ rescue RLSL::Error => error
88
+ error.with_source_location(node.location) if node.respond_to?(:location)
89
+ raise
90
+ ensure
91
+ @emit_depth -= 1
77
92
  end
78
93
 
79
94
  protected
@@ -82,13 +97,15 @@ module RLSL
82
97
  type.to_s
83
98
  end
84
99
 
85
- def format_number(value)
100
+ def format_number(value, type: nil)
101
+ return value.to_i.to_s if type == :int || value.is_a?(Integer)
102
+
86
103
  if value.is_a?(Float)
87
104
  formatted = value.to_s
88
105
  formatted += ".0" unless formatted.include?(".")
89
106
  formatted
90
107
  else
91
- "#{value}.0"
108
+ value.to_s
92
109
  end
93
110
  end
94
111
 
@@ -96,19 +113,42 @@ module RLSL
96
113
  " " * @indent_level
97
114
  end
98
115
 
99
- def emit_with_precedence(node, parent_op)
116
+ def emit_with_precedence(node, parent_op, side: :left)
100
117
  code = emit(node)
101
118
  return code unless node.is_a?(IR::BinaryOp)
102
119
 
103
120
  node_prec = PRECEDENCE[node.operator] || 10
104
121
  parent_prec = PRECEDENCE[parent_op] || 10
105
- node_prec < parent_prec ? "(#{code})" : code
122
+ needs_parentheses = node_prec < parent_prec || (side == :right && node_prec == parent_prec)
123
+ needs_parentheses ? "(#{code})" : code
106
124
  end
107
125
 
108
126
  def function_name(name)
109
127
  name.to_s
110
128
  end
111
129
 
130
+ def function_qualifier
131
+ "static inline "
132
+ end
133
+
134
+ def with_return_struct_name(name)
135
+ @return_struct_name_stack << name
136
+ yield
137
+ ensure
138
+ @return_struct_name_stack.pop
139
+ end
140
+
141
+ def with_return_type(type)
142
+ @return_type_stack << type
143
+ yield
144
+ ensure
145
+ @return_type_stack.pop
146
+ end
147
+
148
+ def current_return_type
149
+ @return_type_stack.last
150
+ end
151
+
112
152
  def with_return_context(enabled)
113
153
  @return_context_stack << enabled
114
154
  yield
@@ -119,6 +159,27 @@ module RLSL
119
159
  def return_context?
120
160
  @return_context_stack.last
121
161
  end
162
+
163
+ def next_temporary_name(prefix)
164
+ loop do
165
+ name = :"_rlsl_#{prefix}#{@temporary_index}"
166
+ @temporary_index += 1
167
+ next if @occupied_names.include?(name)
168
+
169
+ @occupied_names.add(name)
170
+ return name
171
+ end
172
+ end
173
+
174
+ def source_names(node)
175
+ IR::Traversal.each(node).filter_map do |current|
176
+ if current.respond_to?(:name)
177
+ current.name.to_sym
178
+ elsif current.is_a?(IR::ForLoop)
179
+ current.variable.to_sym
180
+ end
181
+ end.to_set
182
+ end
122
183
  end
123
184
  end
124
185
  end
@@ -47,10 +47,12 @@ module RLSL
47
47
  abs: "fabsf",
48
48
  floor: "floorf",
49
49
  ceil: "ceilf",
50
+ sign: "sign_f",
51
+ step: "step_f",
50
52
  min: "fminf",
51
53
  max: "fmaxf",
52
54
  fract: "fract",
53
- mod: "fmodf",
55
+ mod: "rlsl_mod",
54
56
  clamp: "clamp_f",
55
57
  mix: "mix_f",
56
58
  smoothstep: "smoothstep",
@@ -70,27 +72,72 @@ module RLSL
70
72
 
71
73
  protected
72
74
 
73
- def format_number(value)
74
- formatted = super(value)
75
+ def format_number(value, type: nil)
76
+ formatted = super(value, type: type)
77
+ return formatted if type == :int || value.is_a?(Integer)
78
+
75
79
  "#{formatted}f"
76
80
  end
77
81
 
78
82
  def emit_profile_func_call(node)
79
83
  name = node.name.to_sym
80
- return emit_named_call("#{node.args.first.type}_#{name}", node.args) if vector_math_call?(name, node)
81
- return emit_named_call("mix_v3", node.args) if vector_mix_call?(name, node)
84
+ return emit_c_vector_constructor(node) if vector_type?(name)
85
+ if name == :atan && node.args.length == 2
86
+ return emit_named_call("atan2f", node.args, expected_types: node.expected_arg_types)
87
+ end
88
+ if %i[distance cross].include?(name) && vector_type?(node.args.first&.type)
89
+ return emit_named_call("#{node.args.first.type}_#{name}", node.args,
90
+ expected_types: node.expected_arg_types)
91
+ end
92
+ if vector_math_call?(name, node)
93
+ return emit_named_call("#{node.args.first.type}_#{name}", node.args,
94
+ expected_types: node.expected_arg_types)
95
+ end
96
+ if vector_mix_call?(name, node)
97
+ return emit_named_call("mix_#{vector_suffix(node.args.first.type)}", node.args,
98
+ expected_types: node.expected_arg_types)
99
+ end
82
100
  end
83
101
 
84
102
  def emit_profile_binary_op(node)
85
- return unless vector_type?(node.left.type) && profile.vector_ops.key?(node.operator)
103
+ return emit_float_modulo(node) if node.operator == "%" && node.type == :float
104
+ return unless profile.vector_ops.key?(node.operator)
105
+
106
+ emit_vector_binary_op(node)
107
+ end
108
+
109
+ def emit_unary_op(node)
110
+ if node.operator == "-" && vector_type?(node.operand.type)
111
+ return emit_named_call("#{node.operand.type}_mul_scalar", [node.operand, IR::Literal.new(-1.0, :float)])
112
+ end
86
113
 
87
- emit_named_call("#{node.left.type}_#{profile.vector_ops[node.operator]}", [node.left, node.right])
114
+ super
88
115
  end
89
116
 
90
117
  def emit_bool_literal(node)
91
118
  node.value ? "1" : "0"
92
119
  end
93
120
 
121
+ def emit_field_access(node)
122
+ return super if node.receiver.type == :uniforms
123
+
124
+ field = node.field.to_s
125
+ if Builtins.single_component_field?(field)
126
+ component = Builtins::SWIZZLE_COMPONENTS.fetch(field)
127
+ return "#{emit(node.receiver)}.#{%w[x y z w].fetch(component)}"
128
+ end
129
+ return super unless Builtins.swizzle?(field)
130
+
131
+ indices = field.each_char.map { |component| Builtins::SWIZZLE_COMPONENTS.fetch(component) }
132
+ "#{node.receiver.type}_swizzle#{indices.length}(#{emit(node.receiver)}, #{indices.join(', ')})"
133
+ end
134
+
135
+ def emit_float_operand(node)
136
+ return emit(node) unless node.type == :int
137
+
138
+ "(float)(#{emit(node)})"
139
+ end
140
+
94
141
  private
95
142
 
96
143
  def vector_math_call?(name, node)
@@ -104,6 +151,55 @@ module RLSL
104
151
  def vector_type?(type)
105
152
  %i[vec2 vec3 vec4].include?(type)
106
153
  end
154
+
155
+ def vector_suffix(type)
156
+ { vec2: "v2", vec3: "v3", vec4: "v4" }.fetch(type)
157
+ end
158
+
159
+ def emit_float_modulo(node)
160
+ "rlsl_mod(#{emit_float_operand(node.left)}, #{emit_float_operand(node.right)})"
161
+ end
162
+
163
+ def emit_vector_binary_op(node)
164
+ left_vector = vector_type?(node.left.type)
165
+ right_vector = vector_type?(node.right.type)
166
+ return unless left_vector || right_vector
167
+
168
+ vector_type = left_vector ? node.left.type : node.right.type
169
+ operation = profile.vector_ops.fetch(node.operator)
170
+
171
+ if left_vector && right_vector
172
+ suffix = %w[* /].include?(node.operator) ? "_components" : ""
173
+ return emit_named_call("#{vector_type}_#{operation}#{suffix}", [node.left, node.right])
174
+ end
175
+
176
+ if left_vector
177
+ return emit_named_call("#{vector_type}_#{operation}_scalar", [node.left, node.right])
178
+ end
179
+
180
+ emit_named_call("#{vector_type}_scalar_#{operation}", [node.left, node.right])
181
+ end
182
+
183
+ def emit_c_vector_constructor(node)
184
+ size = { vec2: 2, vec3: 3, vec4: 4 }.fetch(node.name.to_sym)
185
+ component_count = node.args.sum { |argument| vector_size(argument.type) || 1 }
186
+ component_count = size if node.args.length == 1 && !vector_type?(node.args.first.type)
187
+
188
+ unless component_count == size
189
+ raise TargetCapabilityError,
190
+ "#{node.name} constructor produces #{component_count} components on C, expected #{size}"
191
+ end
192
+
193
+ return "#{node.name}_splat(#{emit(node.args.first)})" if node.args.length == 1 && !vector_type?(node.args.first.type)
194
+ return emit_named_call("#{node.name}_new", node.args) unless node.args.any? { |argument| vector_type?(argument.type) }
195
+
196
+ signature = node.args.map { |argument| vector_type?(argument.type) ? argument.type : :float }.join("_")
197
+ emit_named_call("#{node.name}_from_#{signature}", node.args)
198
+ end
199
+
200
+ def vector_size(type)
201
+ { vec2: 2, vec3: 3, vec4: 4 }[type]
202
+ end
107
203
  end
108
204
  end
109
205
  end
@@ -33,6 +33,54 @@ module RLSL
33
33
  textureLod: "textureLod"
34
34
  }
35
35
  ).freeze
36
+
37
+ protected
38
+
39
+ def function_qualifier
40
+ ""
41
+ end
42
+
43
+ def emit_binary_op(node)
44
+ if node.operator == "%" && (node.left.type != :int || node.right.type != :int)
45
+ return "mod(#{emit_float_operand(node.left)}, #{emit_float_operand(node.right)})"
46
+ end
47
+
48
+ super
49
+ end
50
+
51
+ def emit_result_struct(func_name, types)
52
+ fields = types.each_with_index.map { |type, index| "#{type_name(type)} v#{index};" }.join(" ")
53
+ "struct #{func_name}_result { #{fields} };\n"
54
+ end
55
+
56
+ def emit_field_access(node)
57
+ return node.field.to_s if node.receiver.type == :uniforms && node.type == :sampler2D
58
+
59
+ super
60
+ end
61
+
62
+ def emit_texture_call(name, node)
63
+ return unless profile.texture_functions.key?(name) && node.args.length >= 2
64
+
65
+ args = [node.args[0], node.args[1], node.args[2] || IR::Literal.new(0.0, :float)]
66
+ emit_named_call("textureLod", args, expected_types: node.expected_arg_types)
67
+ end
68
+
69
+ def emit_func_call(node)
70
+ if node.name.to_sym == :atan2
71
+ return emit_named_call("atan", node.args, expected_types: node.expected_arg_types)
72
+ end
73
+
74
+ super
75
+ end
76
+
77
+ def emit_tuple_value(node)
78
+ expected_types = Array(current_return_type)
79
+ elements = node.elements.each_with_index.map do |element, index|
80
+ emit_typed_argument(element, expected_types[index])
81
+ end.join(", ")
82
+ "#{current_return_struct_name}(#{elements})"
83
+ end
36
84
  end
37
85
  end
38
86
  end
@@ -41,7 +41,49 @@ module RLSL
41
41
 
42
42
  texture = emit(node.args[0])
43
43
  uv = emit(node.args[1])
44
- "#{texture}.sample(textureSampler, #{uv})"
44
+ if name == :textureLod
45
+ lod = emit_typed_argument(node.args[2], :float)
46
+ return "#{texture}.sample(rlsl_texture_sampler, #{uv}, level(#{lod}))"
47
+ end
48
+
49
+ "#{texture}.sample(rlsl_texture_sampler, #{uv})"
50
+ end
51
+
52
+ def emit_binary_op(node)
53
+ if node.operator == "%" && node.type == :float
54
+ return "rlsl_mod(#{emit_float_operand(node.left)}, #{emit_float_operand(node.right)})"
55
+ end
56
+
57
+ super
58
+ end
59
+
60
+ def emit_func_call(node)
61
+ if node.name.to_sym == :mod
62
+ args = node.args.map { |argument| emit_float_operand(argument) }
63
+ return "rlsl_mod(#{args.join(', ')})"
64
+ end
65
+ if node.name.to_sym == :atan && node.args.length == 2
66
+ return emit_named_call("atan2", node.args, expected_types: node.expected_arg_types)
67
+ end
68
+
69
+ super
70
+ end
71
+
72
+ def emit_field_access(node)
73
+ return node.field.to_s if node.receiver.type == :uniforms && node.type == :sampler2D
74
+
75
+ code = super
76
+ return "(#{code} != 0)" if node.receiver.type == :uniforms && node.type == :bool
77
+
78
+ code
79
+ end
80
+
81
+ def emit_tuple_value(node)
82
+ expected_types = Array(current_return_type)
83
+ elements = node.elements.each_with_index.map do |element, index|
84
+ emit_typed_argument(element, expected_types[index])
85
+ end.join(", ")
86
+ "#{current_return_struct_name}{#{elements}}"
45
87
  end
46
88
  end
47
89
  end
@@ -19,26 +19,39 @@ module RLSL
19
19
  name = node.name.to_sym
20
20
 
21
21
  constructor_name = profile.vector_constructors[name] || profile.matrix_constructors[name]
22
- return emit_named_call(constructor_name, node.args) if constructor_name
22
+ if constructor_name
23
+ return emit_named_call(constructor_name, node.args, expected_types: Array.new(node.args.length, :float))
24
+ end
23
25
 
24
26
  texture_call = emit_texture_call(name, node)
25
27
  return texture_call if texture_call
26
28
 
27
29
  math_function = profile.math_functions[name]
28
- return emit_named_call(math_function, node.args) if math_function
30
+ return emit_named_call(math_function, node.args, expected_types: node.expected_arg_types) if math_function
29
31
 
30
- emit_named_call(name, node.args, receiver: node.receiver)
32
+ emit_named_call(name, node.args, receiver: node.receiver, expected_types: node.expected_arg_types)
31
33
  end
32
34
 
33
35
  def emit_binary_op(node)
34
36
  resolved_binary_op = emit_profile_binary_op(node)
35
37
  return resolved_binary_op if resolved_binary_op
38
+ return emit_promoted_binary_op(node) if needs_float_promotion?(node)
36
39
 
37
- left = emit_with_precedence(node.left, node.operator)
38
- right = emit_with_precedence(node.right, node.operator)
40
+ left = emit_with_precedence(node.left, node.operator, side: :left)
41
+ right = emit_with_precedence(node.right, node.operator, side: :right)
39
42
  "#{left} #{node.operator} #{right}"
40
43
  end
41
44
 
45
+ def emit_ternary(node)
46
+ then_expr = emit_typed_argument(node.then_expr, node.type)
47
+ else_expr = emit_typed_argument(node.else_expr, node.type)
48
+ "(#{emit(node.condition)} ? #{then_expr} : #{else_expr})"
49
+ end
50
+
51
+ def emit_assignment(node)
52
+ "#{emit(node.target)} = #{emit_typed_argument(node.value, node.target.type)}"
53
+ end
54
+
42
55
  def default_type_name
43
56
  profile.default_type_name
44
57
  end
@@ -46,16 +59,50 @@ module RLSL
46
59
  def emit_texture_call(name, node)
47
60
  return unless profile.texture_functions.key?(name)
48
61
 
49
- emit_named_call(profile.texture_functions[name], node.args)
62
+ emit_named_call(profile.texture_functions[name], node.args, expected_types: node.expected_arg_types)
50
63
  end
51
64
 
52
- def emit_named_call(name, args, receiver: nil)
65
+ def emit_named_call(name, args, receiver: nil, expected_types: [])
53
66
  rendered_args = []
54
- rendered_args << emit(receiver) if receiver
55
- rendered_args.concat(args.map { |arg| emit(arg) })
67
+ rendered_args << emit_typed_argument(receiver, expected_types.first) if receiver
68
+ offset = receiver ? 1 : 0
69
+ rendered_args.concat(args.each_with_index.map do |arg, index|
70
+ emit_typed_argument(arg, expected_types[index + offset])
71
+ end)
56
72
  "#{name}(#{rendered_args.join(', ')})"
57
73
  end
58
74
 
75
+ def emit_typed_argument(node, expected_type)
76
+ return emit_float_operand(node) if expected_type == :float
77
+
78
+ emit(node)
79
+ end
80
+
81
+ def needs_float_promotion?(node)
82
+ types = [node.left.type, node.right.type]
83
+ return true if node.operator == "/" && types.all?(:int)
84
+
85
+ types.include?(:int) && types.any? { |type| type == :float || Builtins.vector_type?(type) || Builtins.matrix_type?(type) }
86
+ end
87
+
88
+ def emit_promoted_binary_op(node)
89
+ left = emit_promoted_operand(node.left, node.operator, :left)
90
+ right = emit_promoted_operand(node.right, node.operator, :right)
91
+ "#{left} #{node.operator} #{right}"
92
+ end
93
+
94
+ def emit_float_operand(node)
95
+ return emit(node) unless node.type == :int
96
+
97
+ "#{type_name(:float)}(#{emit(node)})"
98
+ end
99
+
100
+ def emit_promoted_operand(node, operator, side)
101
+ return emit_float_operand(node) if node.type == :int
102
+
103
+ emit_with_precedence(node, operator, side: side)
104
+ end
105
+
59
106
  def emit_profile_call(node)
60
107
  return unless profile.call_resolver
61
108