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.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +3 -1
- data/README.md +101 -28
- data/Rakefile +7 -0
- data/lib/rlsl/base_translator/call_parser.rb +13 -2
- data/lib/rlsl/base_translator/code_scanner.rb +39 -1
- data/lib/rlsl/base_translator.rb +24 -2
- data/lib/rlsl/code_generator/math_prelude.rb +103 -10
- data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +52 -12
- data/lib/rlsl/code_generator/template_context.rb +6 -3
- data/lib/rlsl/code_generator/uniform_struct_generator.rb +1 -1
- data/lib/rlsl/code_generator.rb +7 -4
- data/lib/rlsl/compiled_shader.rb +1 -1
- data/lib/rlsl/errors.rb +29 -0
- data/lib/rlsl/glsl/translator.rb +19 -6
- data/lib/rlsl/msl/shader.rb +27 -9
- data/lib/rlsl/msl/translator.rb +25 -4
- data/lib/rlsl/msl/uniform_buffer_packer.rb +11 -7
- data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +59 -18
- data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +22 -9
- data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +18 -14
- data/lib/rlsl/prism/ast_visitor/scope_context.rb +4 -0
- data/lib/rlsl/prism/ast_visitor.rb +78 -20
- data/lib/rlsl/prism/builtins/function_registry.rb +41 -24
- data/lib/rlsl/prism/builtins/operator_rules.rb +24 -0
- data/lib/rlsl/prism/builtins/swizzle_rules.rb +10 -1
- data/lib/rlsl/prism/builtins.rb +8 -0
- data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +51 -8
- data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +42 -12
- data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +20 -4
- data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +7 -1
- data/lib/rlsl/prism/emitters/base_emitter.rb +66 -5
- data/lib/rlsl/prism/emitters/c_emitter.rb +103 -7
- data/lib/rlsl/prism/emitters/glsl_emitter.rb +48 -0
- data/lib/rlsl/prism/emitters/msl_emitter.rb +43 -1
- data/lib/rlsl/prism/emitters/target_emitter.rb +56 -9
- data/lib/rlsl/prism/emitters/wgsl_emitter.rb +173 -18
- data/lib/rlsl/prism/errors.rb +9 -0
- data/lib/rlsl/prism/ir/control_flow.rb +6 -4
- data/lib/rlsl/prism/ir/definitions.rb +17 -2
- data/lib/rlsl/prism/ir/expressions.rb +5 -1
- data/lib/rlsl/prism/ir/node.rb +1 -1
- data/lib/rlsl/prism/ir/traversal.rb +6 -2
- data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
- data/lib/rlsl/prism/node_traversal.rb +41 -0
- data/lib/rlsl/prism/parameter_list.rb +46 -0
- data/lib/rlsl/prism/return_flow_validator.rb +73 -0
- data/lib/rlsl/prism/source_extractor/block_locator.rb +23 -25
- data/lib/rlsl/prism/source_extractor.rb +7 -6
- data/lib/rlsl/prism/source_unit/parser.rb +42 -32
- data/lib/rlsl/prism/source_unit.rb +10 -10
- data/lib/rlsl/prism/target_capability_validator.rb +27 -2
- data/lib/rlsl/prism/transpiler.rb +40 -3
- data/lib/rlsl/prism/type_inference/call_type_resolver.rb +28 -4
- data/lib/rlsl/prism/type_inference/call_validator.rb +5 -1
- data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +45 -19
- data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +88 -7
- data/lib/rlsl/prism/type_inference/definition_inferer.rb +16 -1
- data/lib/rlsl/prism/type_inference/expression_inferer.rb +4 -0
- data/lib/rlsl/prism/type_inference/field_type_resolver.rb +23 -2
- data/lib/rlsl/prism/type_inference/scope_stack.rb +1 -1
- data/lib/rlsl/prism/type_inference/type_shapes.rb +3 -3
- data/lib/rlsl/prism/type_inference.rb +10 -5
- data/lib/rlsl/shader_builder/build_service.rb +34 -7
- data/lib/rlsl/shader_builder/native_extension_compiler.rb +50 -24
- data/lib/rlsl/shader_builder/shader_definition.rb +3 -3
- data/lib/rlsl/shader_builder/source_resolver.rb +19 -1
- data/lib/rlsl/shader_builder.rb +50 -10
- data/lib/rlsl/shader_name.rb +18 -0
- data/lib/rlsl/types/type_spec.rb +3 -3
- data/lib/rlsl/types/value_normalizer.rb +16 -11
- data/lib/rlsl/types.rb +0 -4
- data/lib/rlsl/uniform_context.rb +18 -1
- data/lib/rlsl/version.rb +1 -1
- data/lib/rlsl/wgsl/translator.rb +29 -6
- data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
- data/lib/rlsl.rb +24 -3
- metadata +23 -11
data/lib/rlsl/code_generator.rb
CHANGED
|
@@ -8,18 +8,21 @@ require_relative "code_generator/ruby_wrapper_generator"
|
|
|
8
8
|
|
|
9
9
|
module RLSL
|
|
10
10
|
class CodeGenerator
|
|
11
|
-
def initialize(name, uniforms, helpers_block, fragment_block)
|
|
11
|
+
def initialize(name, uniforms, helpers_block, fragment_block, extension_name: name)
|
|
12
12
|
@context = TemplateContext.new(
|
|
13
13
|
name: name,
|
|
14
14
|
uniforms: uniforms,
|
|
15
15
|
helpers_block: helpers_block,
|
|
16
|
-
fragment_block: fragment_block
|
|
16
|
+
fragment_block: fragment_block,
|
|
17
|
+
extension_name: extension_name
|
|
17
18
|
)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def generate
|
|
21
22
|
<<~C
|
|
22
23
|
#include <ruby.h>
|
|
24
|
+
#include <ruby/thread.h>
|
|
25
|
+
#include <limits.h>
|
|
23
26
|
#include <math.h>
|
|
24
27
|
#include <stdint.h>
|
|
25
28
|
#ifdef __APPLE__
|
|
@@ -45,10 +48,10 @@ module RLSL
|
|
|
45
48
|
|
|
46
49
|
def init_function
|
|
47
50
|
<<~C
|
|
48
|
-
void Init_#{@context.
|
|
51
|
+
void Init_#{@context.extension_name}(void) {
|
|
49
52
|
VALUE mRLSL = rb_define_module("RLSL");
|
|
50
53
|
VALUE mShaders = rb_define_module_under(mRLSL, "CompiledShaders");
|
|
51
|
-
rb_define_module_function(mShaders, "#{@context.
|
|
54
|
+
rb_define_module_function(mShaders, "#{@context.extension_name}_render", shader_#{@context.name}_render, #{@context.render_arity});
|
|
52
55
|
}
|
|
53
56
|
C
|
|
54
57
|
end
|
data/lib/rlsl/compiled_shader.rb
CHANGED
|
@@ -5,7 +5,7 @@ module RLSL
|
|
|
5
5
|
def initialize(name, ext_name, uniforms)
|
|
6
6
|
super(name, uniforms)
|
|
7
7
|
@ext_name = ext_name
|
|
8
|
-
@render_method = RLSL::CompiledShaders.method("#{
|
|
8
|
+
@render_method = RLSL::CompiledShaders.method("#{ext_name}_render")
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def render(buffer, width, height, uniforms = {})
|
data/lib/rlsl/errors.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RLSL
|
|
4
|
+
SourceLocation = Struct.new(:source_name, :line, :column, keyword_init: true) do
|
|
5
|
+
def to_s
|
|
6
|
+
"#{source_name || '(shader source)'}:#{line}:#{column}"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Error < StandardError
|
|
11
|
+
attr_reader :source_location
|
|
12
|
+
|
|
13
|
+
def with_source_location(location)
|
|
14
|
+
@source_location ||= location
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def message
|
|
19
|
+
return super unless source_location
|
|
20
|
+
|
|
21
|
+
"#{super} at #{source_location}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
class InternalError < Error; end
|
|
25
|
+
class ParseError < Error; end
|
|
26
|
+
class TranslationError < Error; end
|
|
27
|
+
class CompilationError < Error; end
|
|
28
|
+
class UniformValueError < ArgumentError; end
|
|
29
|
+
end
|
data/lib/rlsl/glsl/translator.rb
CHANGED
|
@@ -10,12 +10,15 @@ module RLSL
|
|
|
10
10
|
target_vec2: "vec2",
|
|
11
11
|
target_vec3: "vec3",
|
|
12
12
|
target_vec4: "vec4"
|
|
13
|
+
).merge(
|
|
14
|
+
"fmodf" => BaseTranslator.rename_call("mod"),
|
|
15
|
+
"atan2f" => BaseTranslator.rename_call("atan")
|
|
13
16
|
)
|
|
14
17
|
)
|
|
15
18
|
|
|
16
|
-
def initialize(uniforms, helpers_code, fragment_code, version: "450")
|
|
17
|
-
super(uniforms, helpers_code, fragment_code)
|
|
18
|
-
@version = version
|
|
19
|
+
def initialize(uniforms, helpers_code, fragment_code, version: "450", name: nil)
|
|
20
|
+
super(uniforms, helpers_code, fragment_code, name: name)
|
|
21
|
+
@version = validate_version!(version)
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
protected
|
|
@@ -23,6 +26,7 @@ module RLSL
|
|
|
23
26
|
def generate_shader(helpers, fragment)
|
|
24
27
|
<<~GLSL
|
|
25
28
|
#version #{@version}
|
|
29
|
+
#{generated_by_comment("GLSL")}
|
|
26
30
|
|
|
27
31
|
// Uniforms
|
|
28
32
|
#{generate_uniform_declarations}
|
|
@@ -33,8 +37,7 @@ module RLSL
|
|
|
33
37
|
#{helpers}
|
|
34
38
|
|
|
35
39
|
vec3 shader_fragment(vec2 frag_coord, vec2 resolution) {
|
|
36
|
-
|
|
37
|
-
#{fragment}
|
|
40
|
+
#{indent_source(fragment, 4)}
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
layout(local_size_x = 8, local_size_y = 8) in;
|
|
@@ -64,15 +67,25 @@ module RLSL
|
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
def generate_uniform_declarations
|
|
67
|
-
declarations = ["layout(binding = 1) uniform ShaderUniforms {"]
|
|
70
|
+
declarations = ["layout(std140, binding = 1) uniform ShaderUniforms {"]
|
|
68
71
|
declarations.concat(
|
|
69
72
|
uniform_lines(resolution_line: " #{target_vec2_type} resolution;") do |name, glsl_type|
|
|
70
73
|
" #{glsl_type} #{name};"
|
|
71
74
|
end
|
|
72
75
|
)
|
|
73
76
|
declarations << "} u;"
|
|
77
|
+
texture_uniforms.each_with_index do |(name, _type), index|
|
|
78
|
+
declarations << "layout(binding = #{index + 2}) uniform sampler2D #{name};"
|
|
79
|
+
end
|
|
74
80
|
declarations.join("\n")
|
|
75
81
|
end
|
|
82
|
+
|
|
83
|
+
def validate_version!(version)
|
|
84
|
+
normalized = version.to_s
|
|
85
|
+
return normalized if normalized.match?(/\A[1-9]\d{2}(?: (?:core|compatibility|es))?\z/)
|
|
86
|
+
|
|
87
|
+
raise ArgumentError, "Invalid GLSL version: #{version.inspect}"
|
|
88
|
+
end
|
|
76
89
|
end
|
|
77
90
|
end
|
|
78
91
|
end
|
data/lib/rlsl/msl/shader.rb
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
begin
|
|
4
|
-
require "metaco"
|
|
5
|
-
METACO_AVAILABLE = true
|
|
6
|
-
rescue LoadError
|
|
7
|
-
METACO_AVAILABLE = false
|
|
8
|
-
end
|
|
9
|
-
|
|
10
3
|
module RLSL
|
|
11
4
|
module MSL
|
|
5
|
+
begin
|
|
6
|
+
require "metaco"
|
|
7
|
+
METACO_AVAILABLE = true
|
|
8
|
+
rescue LoadError
|
|
9
|
+
METACO_AVAILABLE = false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
12
|
class Shader < RuntimeShader
|
|
13
|
+
COMPILED_HANDLE_CACHE_LIMIT = 64
|
|
13
14
|
attr_reader :name, :msl_source
|
|
14
15
|
|
|
15
16
|
def initialize(name, uniforms, msl_source)
|
|
16
17
|
super(name, uniforms)
|
|
17
18
|
@msl_source = msl_source
|
|
18
19
|
@compiled_handles = {}
|
|
19
|
-
|
|
20
|
+
value_uniform_types = @uniform_types.reject { |_name, type| type == :sampler2D }
|
|
21
|
+
@uniform_buffer_packer = UniformBufferPacker.new(@name, value_uniform_types, value_uniform_types.keys)
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def metal?
|
|
@@ -27,7 +29,7 @@ module RLSL
|
|
|
27
29
|
render_metal(handle, width, height, uniforms)
|
|
28
30
|
end
|
|
29
31
|
|
|
30
|
-
def
|
|
32
|
+
def prepare(handle)
|
|
31
33
|
unless METACO_AVAILABLE
|
|
32
34
|
raise LoadError, "metaco gem is required for Metal rendering. Install it with: gem install metaco"
|
|
33
35
|
end
|
|
@@ -35,10 +37,26 @@ module RLSL
|
|
|
35
37
|
unless @compiled_handles[handle]
|
|
36
38
|
Metaco.compile_compute_shader(handle, @msl_source)
|
|
37
39
|
@compiled_handles[handle] = true
|
|
40
|
+
@compiled_handles.shift while @compiled_handles.length > COMPILED_HANDLE_CACHE_LIMIT
|
|
38
41
|
end
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def render_metal(handle, width, height, uniforms = {}, textures: {})
|
|
46
|
+
prepare(handle)
|
|
39
47
|
|
|
40
48
|
uniform_data = pack_uniforms(uniforms, width, height)
|
|
41
49
|
|
|
50
|
+
sampler_names = @uniform_types.filter_map { |name, type| name if type == :sampler2D }
|
|
51
|
+
sampler_names.each do |name|
|
|
52
|
+
texture = textures[name] || textures[name.to_s]
|
|
53
|
+
raise ArgumentError, "missing texture uniform: #{name}" unless texture
|
|
54
|
+
unless Metaco.respond_to?(:bind_compute_texture)
|
|
55
|
+
raise LoadError, "metaco texture support is required for sampler2D uniforms"
|
|
56
|
+
end
|
|
57
|
+
Metaco.bind_compute_texture(handle, sampler_names.index(name), texture)
|
|
58
|
+
end
|
|
59
|
+
|
|
42
60
|
Metaco.dispatch_compute(handle, uniform_data)
|
|
43
61
|
Metaco.present_compute(handle)
|
|
44
62
|
end
|
data/lib/rlsl/msl/translator.rb
CHANGED
|
@@ -23,6 +23,13 @@ module RLSL
|
|
|
23
23
|
<<~MSL
|
|
24
24
|
#include <metal_stdlib>
|
|
25
25
|
using namespace metal;
|
|
26
|
+
#{generated_by_comment("MSL")}
|
|
27
|
+
|
|
28
|
+
constexpr sampler rlsl_texture_sampler(coord::normalized, address::clamp_to_edge, filter::linear);
|
|
29
|
+
|
|
30
|
+
float rlsl_mod(float x, float y) {
|
|
31
|
+
return x - y * floor(x / y);
|
|
32
|
+
}
|
|
26
33
|
|
|
27
34
|
// Uniform buffer structure
|
|
28
35
|
struct Uniforms {
|
|
@@ -33,22 +40,22 @@ module RLSL
|
|
|
33
40
|
#{helpers}
|
|
34
41
|
|
|
35
42
|
// Fragment shader function
|
|
36
|
-
float3 shader_fragment(float2 frag_coord, float2 resolution, constant Uniforms& u) {
|
|
37
|
-
|
|
38
|
-
#{fragment}
|
|
43
|
+
float3 shader_fragment(float2 frag_coord, float2 resolution, constant Uniforms& u#{fragment_texture_parameters}) {
|
|
44
|
+
#{indent_source(fragment, 4)}
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
// Compute kernel entry point
|
|
42
48
|
kernel void compute_shader(
|
|
43
49
|
texture2d<float, access::write> output [[texture(0)]],
|
|
44
50
|
constant Uniforms& u [[buffer(0)]],
|
|
51
|
+
#{kernel_texture_parameters}
|
|
45
52
|
uint2 gid [[thread_position_in_grid]]
|
|
46
53
|
) {
|
|
47
54
|
float2 resolution = float2(output.get_width(), output.get_height());
|
|
48
55
|
if (gid.x >= uint(resolution.x) || gid.y >= uint(resolution.y)) return;
|
|
49
56
|
|
|
50
57
|
float2 frag_coord = float2(gid.x, resolution.y - 1.0 - float(gid.y));
|
|
51
|
-
float3 color = shader_fragment(frag_coord, resolution, u);
|
|
58
|
+
float3 color = shader_fragment(frag_coord, resolution, u#{fragment_texture_arguments});
|
|
52
59
|
|
|
53
60
|
output.write(float4(clamp(color, 0.0, 1.0), 1.0), gid);
|
|
54
61
|
}
|
|
@@ -67,6 +74,20 @@ module RLSL
|
|
|
67
74
|
end
|
|
68
75
|
fields.join("\n ")
|
|
69
76
|
end
|
|
77
|
+
|
|
78
|
+
def fragment_texture_parameters
|
|
79
|
+
texture_uniforms.keys.map { |name| ", texture2d<float> #{name}" }.join
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def fragment_texture_arguments
|
|
83
|
+
texture_uniforms.keys.map { |name| ", #{name}" }.join
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def kernel_texture_parameters
|
|
87
|
+
texture_uniforms.keys.each_with_index.map do |name, index|
|
|
88
|
+
" texture2d<float, access::sample> #{name} [[texture(#{index + 1})]],"
|
|
89
|
+
end.join("\n")
|
|
90
|
+
end
|
|
70
91
|
end
|
|
71
92
|
end
|
|
72
93
|
end
|
|
@@ -11,7 +11,7 @@ module RLSL
|
|
|
11
11
|
|
|
12
12
|
def pack(width, height, uniforms)
|
|
13
13
|
normalized_uniforms = UniformTypes.normalize_values(@uniform_types, uniforms, shader_name: @shader_name)
|
|
14
|
-
data = [width.to_f, height.to_f].pack("
|
|
14
|
+
data = [width.to_f, height.to_f].pack("e2")
|
|
15
15
|
current_offset = 8
|
|
16
16
|
|
|
17
17
|
@uniform_names.each do |name|
|
|
@@ -22,6 +22,10 @@ module RLSL
|
|
|
22
22
|
current_offset += spec.metal_size
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
if data.bytesize > 256
|
|
26
|
+
raise ArgumentError, "Metal uniform buffer exceeds 256 bytes (#{data.bytesize} bytes)"
|
|
27
|
+
end
|
|
28
|
+
|
|
25
29
|
data.ljust(256, "\x00")
|
|
26
30
|
end
|
|
27
31
|
|
|
@@ -37,11 +41,11 @@ module RLSL
|
|
|
37
41
|
def pack_uniform_value(spec, value)
|
|
38
42
|
case spec.wrapper_kind
|
|
39
43
|
when :float
|
|
40
|
-
[value.to_f].pack("
|
|
44
|
+
[value.to_f].pack("e")
|
|
41
45
|
when :int
|
|
42
|
-
[value.to_i].pack("l")
|
|
46
|
+
[value.to_i].pack("l<")
|
|
43
47
|
when :bool
|
|
44
|
-
[value ? 1 : 0].pack("l")
|
|
48
|
+
[value ? 1 : 0].pack("l<")
|
|
45
49
|
when :vector
|
|
46
50
|
pack_vector_uniform(spec.vector_size, value)
|
|
47
51
|
else
|
|
@@ -54,11 +58,11 @@ module RLSL
|
|
|
54
58
|
|
|
55
59
|
case vector_size
|
|
56
60
|
when 2
|
|
57
|
-
components.pack("
|
|
61
|
+
components.pack("e2")
|
|
58
62
|
when 3
|
|
59
|
-
(components + [0.0]).pack("
|
|
63
|
+
(components + [0.0]).pack("e4")
|
|
60
64
|
when 4
|
|
61
|
-
components.pack("
|
|
65
|
+
components.pack("e4")
|
|
62
66
|
else
|
|
63
67
|
raise ArgumentError, "Unsupported vector size: #{vector_size}"
|
|
64
68
|
end
|
|
@@ -29,58 +29,63 @@ module RLSL
|
|
|
29
29
|
|
|
30
30
|
def visit_if(node)
|
|
31
31
|
condition = visit(node.predicate)
|
|
32
|
+
hoisted_variables = hoist_branch_variables(node)
|
|
32
33
|
then_branch = visit_with_scoped_vars(node.statements)
|
|
33
34
|
else_branch = node.subsequent ? visit_with_scoped_vars(node.subsequent) : nil
|
|
34
35
|
|
|
35
|
-
IR::IfStatement.new(condition, then_branch, else_branch)
|
|
36
|
+
IR::IfStatement.new(condition, then_branch, else_branch, hoisted_variables: hoisted_variables)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
def visit_else(node)
|
|
39
40
|
visit(node.statements)
|
|
40
41
|
end
|
|
41
42
|
|
|
42
|
-
def visit_elsif(node)
|
|
43
|
-
visit_if(node)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def visit_if_node(node)
|
|
47
|
-
visit_if(node)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
43
|
def visit_unless(node)
|
|
51
44
|
condition = IR::UnaryOp.new("!", visit(node.predicate))
|
|
45
|
+
hoisted_variables = hoist_branch_variables(node)
|
|
52
46
|
then_branch = visit_with_scoped_vars(node.statements)
|
|
53
47
|
else_branch = node.else_clause ? visit_with_scoped_vars(node.else_clause) : nil
|
|
54
48
|
|
|
55
|
-
IR::IfStatement.new(condition, then_branch, else_branch)
|
|
49
|
+
IR::IfStatement.new(condition, then_branch, else_branch, hoisted_variables: hoisted_variables)
|
|
56
50
|
end
|
|
57
51
|
|
|
58
52
|
def visit_return(node)
|
|
59
|
-
|
|
53
|
+
arguments = node.arguments&.arguments || []
|
|
54
|
+
if arguments.length > 1
|
|
55
|
+
raise UnsupportedSyntaxError, "Returning multiple values requires an explicitly declared tuple helper"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
expr = arguments.empty? ? nil : normalize_expression(visit(arguments.first))
|
|
60
59
|
IR::Return.new(expr)
|
|
61
60
|
end
|
|
62
61
|
|
|
63
62
|
def visit_range(node)
|
|
64
|
-
[visit(node.left), visit(node.right)]
|
|
63
|
+
[visit(node.left), visit(node.right), node.exclude_end?]
|
|
65
64
|
end
|
|
66
65
|
|
|
67
66
|
def visit_for(node)
|
|
68
67
|
range = visit(node.collection)
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
variable = node.index.name.to_sym
|
|
69
|
+
body = visit_with_scoped_vars(node.statements, params: [variable]) || IR::Block.new
|
|
70
|
+
IR::ForLoop.new(variable, range[0], range[1], body, exclude_end: range[2])
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def visit_call_with_block(node)
|
|
74
|
-
|
|
74
|
+
unless times_loop?(node)
|
|
75
|
+
raise UnsupportedSyntaxError, "Blocks are only supported for Integer#times loops"
|
|
76
|
+
end
|
|
75
77
|
|
|
76
78
|
count = visit(node.receiver)
|
|
77
|
-
|
|
78
|
-
var_name =
|
|
79
|
+
block_params = extract_block_params(node.block)
|
|
80
|
+
var_name = block_params.first || next_implicit_loop_variable
|
|
81
|
+
block = visit_with_scoped_vars(node.block.body, params: [var_name]) || IR::Block.new
|
|
79
82
|
IR::ForLoop.new(var_name, IR::Literal.new(0, :int), count, block)
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
def visit_while(node)
|
|
83
|
-
|
|
86
|
+
condition = visit(node.predicate)
|
|
87
|
+
body = visit_with_scoped_vars(node.statements) || IR::Block.new
|
|
88
|
+
IR::WhileLoop.new(condition, body)
|
|
84
89
|
end
|
|
85
90
|
|
|
86
91
|
def visit_break(_node)
|
|
@@ -90,6 +95,42 @@ module RLSL
|
|
|
90
95
|
def times_loop?(node)
|
|
91
96
|
node.name.to_s == "times" && node.receiver
|
|
92
97
|
end
|
|
98
|
+
|
|
99
|
+
def hoist_branch_variables(node)
|
|
100
|
+
then_names = definitely_assigned_names(node.statements)
|
|
101
|
+
else_node = node.respond_to?(:subsequent) ? node.subsequent : node.else_clause
|
|
102
|
+
else_names = definitely_assigned_names(else_node)
|
|
103
|
+
|
|
104
|
+
(then_names & else_names).each_with_object({}) do |name, variables|
|
|
105
|
+
next if known_variable?(name)
|
|
106
|
+
|
|
107
|
+
declare_variable(name)
|
|
108
|
+
variables[name] = nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def definitely_assigned_names(node)
|
|
113
|
+
return Set.new unless node
|
|
114
|
+
|
|
115
|
+
case node
|
|
116
|
+
when ::Prism::StatementsNode
|
|
117
|
+
node.body.each_with_object(Set.new) do |statement, names|
|
|
118
|
+
names.merge(definitely_assigned_names(statement))
|
|
119
|
+
end
|
|
120
|
+
when ::Prism::LocalVariableWriteNode, ::Prism::LocalVariableOperatorWriteNode
|
|
121
|
+
Set[node.name.to_sym]
|
|
122
|
+
when ::Prism::MultiWriteNode
|
|
123
|
+
Set.new(node.lefts.map { |target| target.name.to_sym })
|
|
124
|
+
when ::Prism::IfNode
|
|
125
|
+
definitely_assigned_names(node.statements) & definitely_assigned_names(node.subsequent)
|
|
126
|
+
when ::Prism::UnlessNode
|
|
127
|
+
definitely_assigned_names(node.statements) & definitely_assigned_names(node.else_clause)
|
|
128
|
+
when ::Prism::ElseNode
|
|
129
|
+
definitely_assigned_names(node.statements)
|
|
130
|
+
else
|
|
131
|
+
Set.new
|
|
132
|
+
end
|
|
133
|
+
end
|
|
93
134
|
end
|
|
94
135
|
end
|
|
95
136
|
end
|
|
@@ -20,9 +20,10 @@ module RLSL
|
|
|
20
20
|
def visit_local_variable_write(node)
|
|
21
21
|
name = node.name.to_sym
|
|
22
22
|
value = normalize_expression(visit(node.value))
|
|
23
|
+
emitted_name = emitted_assignment_name(name)
|
|
23
24
|
|
|
24
25
|
if known_variable?(name)
|
|
25
|
-
IR::Assignment.new(IR::VarRef.new(
|
|
26
|
+
IR::Assignment.new(IR::VarRef.new(emitted_name), value)
|
|
26
27
|
else
|
|
27
28
|
declare_variable(name)
|
|
28
29
|
IR::VarDecl.new(name, value)
|
|
@@ -31,17 +32,23 @@ module RLSL
|
|
|
31
32
|
|
|
32
33
|
def visit_local_variable_operator_write(node)
|
|
33
34
|
name = node.name.to_sym
|
|
34
|
-
operator = node.
|
|
35
|
+
operator = node.binary_operator.to_s
|
|
35
36
|
value = normalize_expression(visit(node.value))
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
unless known_variable?(name)
|
|
39
|
+
raise UnsupportedSyntaxError, "Operator assignment requires an initialized variable: #{name}"
|
|
40
|
+
end
|
|
41
|
+
emitted_name = emitted_assignment_name(name)
|
|
42
|
+
target = IR::VarRef.new(emitted_name)
|
|
43
|
+
expr = IR::BinaryOp.new(operator, IR::VarRef.new(emitted_name), IR::Parenthesized.new(value))
|
|
40
44
|
IR::Assignment.new(target, expr)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
def visit_local_variable_read(node)
|
|
44
|
-
|
|
48
|
+
name = node.name.to_sym
|
|
49
|
+
emitted_name = fragment_parameter_reference?(name) ? emitted_parameter_name(name) : name
|
|
50
|
+
type = infer_param_type(name) if fragment_parameter_reference?(name)
|
|
51
|
+
IR::VarRef.new(emitted_name, type)
|
|
45
52
|
end
|
|
46
53
|
|
|
47
54
|
def visit_def(node)
|
|
@@ -59,13 +66,19 @@ module RLSL
|
|
|
59
66
|
end
|
|
60
67
|
|
|
61
68
|
def visit_multi_write(node)
|
|
69
|
+
if node.rest || !node.rights.empty?
|
|
70
|
+
raise UnsupportedSyntaxError, "Splat multiple assignment is not supported"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
declarations = []
|
|
62
74
|
targets = node.lefts.map do |target|
|
|
63
75
|
name = target.name.to_sym
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
declarations << !known_variable?(name)
|
|
77
|
+
declare_variable(name) if declarations.last
|
|
78
|
+
IR::VarRef.new(emitted_assignment_name(name))
|
|
66
79
|
end
|
|
67
80
|
|
|
68
|
-
IR::MultipleAssignment.new(targets, visit(node.value))
|
|
81
|
+
IR::MultipleAssignment.new(targets, visit(node.value), declarations: declarations)
|
|
69
82
|
end
|
|
70
83
|
|
|
71
84
|
def visit_local_variable_target(node)
|
|
@@ -60,11 +60,13 @@ module RLSL
|
|
|
60
60
|
receiver = normalize_expression(visit(node.receiver)) if node.receiver
|
|
61
61
|
args = node.arguments&.arguments&.map { |arg| normalize_expression(visit(arg)) } || []
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
if parameter_reference_call?(method_name, receiver, args)
|
|
64
|
+
return IR::VarRef.new(emitted_parameter_name(method_name), infer_param_type(method_name))
|
|
65
|
+
end
|
|
66
|
+
return IR::UnaryOp.new("-", receiver) if method_name == "-@" && receiver
|
|
67
|
+
return IR::UnaryOp.new("!", receiver) if method_name == "!" && receiver && args.empty?
|
|
64
68
|
return visit_receiver_call(method_name, receiver) if receiver_without_arguments?(node, receiver, args)
|
|
65
69
|
return IR::BinaryOp.new(method_name, receiver, args.first) if binary_operator_call?(method_name, receiver, args)
|
|
66
|
-
return IR::UnaryOp.new("-", receiver) if method_name == "-@" && receiver
|
|
67
|
-
return IR::UnaryOp.new("!", args.first) if method_name == "!" && args.length == 1
|
|
68
70
|
return IR::ArrayIndex.new(receiver, args.first) if method_name == "[]" && receiver && args.length == 1
|
|
69
71
|
|
|
70
72
|
IR::FuncCall.new(method_name.to_sym, args, receiver)
|
|
@@ -92,12 +94,6 @@ module RLSL
|
|
|
92
94
|
IR::ArrayLiteral.new(elements)
|
|
93
95
|
end
|
|
94
96
|
|
|
95
|
-
def visit_index(node)
|
|
96
|
-
array = normalize_expression(visit(node.receiver))
|
|
97
|
-
index = normalize_expression(visit(node.arguments.arguments.first))
|
|
98
|
-
IR::ArrayIndex.new(array, index)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
97
|
def visit_constant_read(node)
|
|
102
98
|
name = node.name.to_s
|
|
103
99
|
return IR::Constant.new(name.to_sym, :float) if %w[PI TAU].include?(name)
|
|
@@ -114,6 +110,10 @@ module RLSL
|
|
|
114
110
|
end
|
|
115
111
|
path_parts.unshift(current.name.to_s) if current.respond_to?(:name)
|
|
116
112
|
|
|
113
|
+
if path_parts.first == "Math" && %w[PI TAU].include?(path_parts.last) && path_parts.length == 2
|
|
114
|
+
return IR::Constant.new(path_parts.last.to_sym, :float)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
117
|
IR::VarRef.new(path_parts.join("_").to_sym)
|
|
118
118
|
end
|
|
119
119
|
|
|
@@ -126,9 +126,13 @@ module RLSL
|
|
|
126
126
|
|
|
127
127
|
then_expr = extract_if_branch_expr(node.then_branch)
|
|
128
128
|
else_expr = extract_if_branch_expr(node.else_branch)
|
|
129
|
-
|
|
129
|
+
unless then_expr && else_expr
|
|
130
|
+
raise UnsupportedSyntaxError, "Unsupported if-expression: branches must be single expressions"
|
|
131
|
+
end
|
|
130
132
|
|
|
131
|
-
IR::Ternary.new(node.condition, then_expr, else_expr)
|
|
133
|
+
IR::Ternary.new(node.condition, then_expr, else_expr).tap do |ternary|
|
|
134
|
+
ternary.location = node.location
|
|
135
|
+
end
|
|
132
136
|
end
|
|
133
137
|
|
|
134
138
|
def extract_if_branch_expr(branch)
|
|
@@ -144,7 +148,8 @@ module RLSL
|
|
|
144
148
|
end
|
|
145
149
|
|
|
146
150
|
def parameter_reference_call?(method_name, receiver, args)
|
|
147
|
-
!receiver && args.empty? &&
|
|
151
|
+
!receiver && args.empty? &&
|
|
152
|
+
(parameter_reference?(method_name.to_sym) || infer_param_type(method_name.to_sym))
|
|
148
153
|
end
|
|
149
154
|
|
|
150
155
|
def receiver_without_arguments?(node, receiver, args)
|
|
@@ -152,9 +157,8 @@ module RLSL
|
|
|
152
157
|
end
|
|
153
158
|
|
|
154
159
|
def visit_receiver_call(method_name, receiver)
|
|
160
|
+
return IR::FieldAccess.new(receiver, method_name) if receiver.type == :uniforms
|
|
155
161
|
return receiver if method_name == "freeze"
|
|
156
|
-
return IR::FieldAccess.new(receiver, method_name, :float) if Builtins.single_component_field?(method_name)
|
|
157
|
-
return IR::Swizzle.new(receiver, method_name, Builtins.swizzle_type(method_name)) if Builtins.swizzle?(method_name)
|
|
158
162
|
|
|
159
163
|
IR::FieldAccess.new(receiver, method_name)
|
|
160
164
|
end
|
|
@@ -21,6 +21,10 @@ module RLSL
|
|
|
21
21
|
@scopes.reverse_each.any? { |scope| scope.params.include?(name.to_sym) }
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def root_parameter?(name)
|
|
25
|
+
@scopes.rindex { |scope| scope.params.include?(name.to_sym) } == 0
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def declared?(name)
|
|
25
29
|
@scopes.reverse_each.any? { |scope| scope.declared_vars.include?(name.to_sym) }
|
|
26
30
|
end
|