ikra 0.0.1 → 0.0.2
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/lib/ast/builder.rb +225 -77
- data/lib/ast/host_section_builder.rb +38 -0
- data/lib/ast/interpreter.rb +67 -0
- data/lib/ast/lexical_variables_enumerator.rb +3 -2
- data/lib/ast/nodes.rb +521 -31
- data/lib/ast/printer.rb +116 -18
- data/lib/ast/ssa_generator.rb +192 -0
- data/lib/ast/visitor.rb +235 -21
- data/lib/config/configuration.rb +28 -3
- data/lib/config/os_configuration.rb +62 -9
- data/lib/cpu/cpu_implementation.rb +39 -0
- data/lib/ikra.rb +13 -3
- data/lib/resources/cuda/allocate_device_memory.cpp +5 -0
- data/lib/resources/cuda/allocate_host_memory.cpp +1 -0
- data/lib/resources/cuda/allocate_memcpy_environment_to_device.cpp +11 -0
- data/lib/resources/cuda/ast/assignment.cpp +1 -0
- data/lib/resources/cuda/block_function_head.cpp +7 -1
- data/lib/resources/cuda/entry_point.cpp +47 -0
- data/lib/resources/cuda/env_builder_copy_array.cpp +8 -2
- data/lib/resources/cuda/free_device_memory.cpp +3 -0
- data/lib/resources/cuda/free_memory_for_command.cpp +24 -0
- data/lib/resources/cuda/header.cpp +23 -9
- data/lib/resources/cuda/header_structs.cpp +92 -0
- data/lib/resources/cuda/host_section_block_function_head.cpp +12 -0
- data/lib/resources/cuda/host_section_entry_point.cpp +55 -0
- data/lib/resources/cuda/host_section_free_device_memory.cpp +18 -0
- data/lib/resources/cuda/host_section_launch_parallel_section.cpp +14 -0
- data/lib/resources/cuda/host_section_malloc_memcpy_device_to_host.cpp +10 -0
- data/lib/resources/cuda/kernel.cpp +9 -2
- data/lib/resources/cuda/launch_kernel.cpp +5 -0
- data/lib/resources/cuda/memcpy_device_to_host.cpp +3 -0
- data/lib/resources/cuda/memcpy_device_to_host_expr.cpp +10 -0
- data/lib/resources/cuda/reduce_body.cpp +88 -0
- data/lib/resources/cuda/stencil_array_reconstruction.cpp +2 -0
- data/lib/resources/cuda/stencil_body.cpp +16 -0
- data/lib/resources/cuda/struct_definition.cpp +4 -0
- data/lib/ruby_core/array.rb +34 -0
- data/lib/ruby_core/array_command.rb +313 -0
- data/lib/ruby_core/core.rb +103 -0
- data/lib/ruby_core/interpreter.rb +16 -0
- data/lib/ruby_core/math.rb +32 -0
- data/lib/ruby_core/ruby_integration.rb +256 -0
- data/lib/symbolic/host_section.rb +115 -0
- data/lib/symbolic/input.rb +87 -0
- data/lib/symbolic/input_visitor.rb +68 -0
- data/lib/symbolic/symbolic.rb +793 -117
- data/lib/symbolic/visitor.rb +70 -8
- data/lib/translator/array_command_struct_builder.rb +163 -0
- data/lib/translator/ast_translator.rb +572 -0
- data/lib/translator/block_translator.rb +104 -48
- data/lib/translator/commands/array_combine_command.rb +41 -0
- data/lib/translator/commands/array_identity_command.rb +28 -0
- data/lib/translator/commands/array_index_command.rb +52 -0
- data/lib/translator/commands/array_reduce_command.rb +135 -0
- data/lib/translator/commands/array_stencil_command.rb +129 -0
- data/lib/translator/commands/array_zip_command.rb +30 -0
- data/lib/translator/commands/command_translator.rb +264 -0
- data/lib/translator/cuda_errors.rb +32 -0
- data/lib/translator/environment_builder.rb +263 -0
- data/lib/translator/host_section/array_host_section_command.rb +150 -0
- data/lib/translator/host_section/array_in_host_section_command.rb +41 -0
- data/lib/translator/host_section/ast_translator.rb +14 -0
- data/lib/translator/host_section/parallel_section_invocation_visitor.rb +20 -0
- data/lib/translator/host_section/program_builder.rb +89 -0
- data/lib/translator/input_translator.rb +226 -0
- data/lib/translator/kernel_builder.rb +137 -0
- data/lib/translator/kernel_launcher/for_loop_kernel_launcher.rb +40 -0
- data/lib/translator/kernel_launcher/kernel_launcher.rb +259 -0
- data/lib/translator/kernel_launcher/while_loop_kernel_launcher.rb +38 -0
- data/lib/translator/last_returns_visitor.rb +19 -10
- data/lib/translator/program_builder.rb +197 -0
- data/lib/translator/program_launcher.rb +273 -0
- data/lib/translator/struct_type.rb +55 -0
- data/lib/translator/translator.rb +34 -11
- data/lib/translator/variable_classifier_visitor.rb +56 -0
- data/lib/types/inference/ast_inference.rb +586 -0
- data/lib/types/inference/clear_types_visitor.rb +11 -0
- data/lib/types/inference/command_inference.rb +101 -0
- data/lib/types/inference/input_inference.rb +62 -0
- data/lib/types/{object_tracer.rb → inference/object_tracer.rb} +5 -6
- data/lib/types/inference/ruby_extension.rb +35 -0
- data/lib/types/inference/symbol_table.rb +131 -0
- data/lib/types/types.rb +14 -0
- data/lib/types/types/array_command_type.rb +123 -0
- data/lib/types/types/array_type.rb +137 -0
- data/lib/types/{class_type.rb → types/class_type.rb} +42 -18
- data/lib/types/{primitive_type.rb → types/primitive_type.rb} +20 -7
- data/lib/types/types/ruby_type.rb +88 -0
- data/lib/types/types/struct_type.rb +179 -0
- data/lib/types/types/union_type.rb +239 -0
- metadata +160 -18
- data/lib/ast/method_definition.rb +0 -37
- data/lib/ast/translator.rb +0 -264
- data/lib/resources/cuda/kernel_launcher.cpp +0 -28
- data/lib/scope.rb +0 -166
- data/lib/translator/command_translator.rb +0 -421
- data/lib/translator/local_variables_enumerator.rb +0 -35
- data/lib/translator/method_translator.rb +0 -24
- data/lib/types/array_type.rb +0 -51
- data/lib/types/ruby_extension.rb +0 -67
- data/lib/types/ruby_type.rb +0 -45
- data/lib/types/type_inference.rb +0 -382
- data/lib/types/union_type.rb +0 -155
data/lib/symbolic/visitor.rb
CHANGED
@@ -1,14 +1,26 @@
|
|
1
1
|
module Ikra
|
2
2
|
module Symbolic
|
3
|
-
class
|
3
|
+
class ArrayIndexCommand
|
4
4
|
def accept(visitor)
|
5
|
-
visitor.
|
5
|
+
visitor.visit_array_index_command(self)
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
9
|
-
class
|
9
|
+
class ArrayCombineCommand
|
10
10
|
def accept(visitor)
|
11
|
-
visitor.
|
11
|
+
visitor.visit_array_combine_command(self)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class ArrayReduceCommand
|
16
|
+
def accept(visitor)
|
17
|
+
visitor.visit_array_reduce_command(self)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ArrayStencilCommand
|
22
|
+
def accept(visitor)
|
23
|
+
visitor.visit_array_stencil_command(self)
|
12
24
|
end
|
13
25
|
end
|
14
26
|
|
@@ -24,28 +36,78 @@ module Ikra
|
|
24
36
|
end
|
25
37
|
end
|
26
38
|
|
39
|
+
class ArrayZipCommand
|
40
|
+
def accept(visitor)
|
41
|
+
visitor.visit_array_zip_command(self)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class ArrayHostSectionCommand
|
46
|
+
def accept(visitor)
|
47
|
+
visitor.visit_array_host_section_command(self)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ArrayInHostSectionCommand
|
52
|
+
def accept(visitor)
|
53
|
+
visitor.visit_array_in_host_section_command(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class FixedSizeArrayInHostSectionCommand < ArrayInHostSectionCommand
|
58
|
+
def accept(visitor)
|
59
|
+
visitor.visit_fixed_size_array_in_host_section_command(self)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
27
63
|
class Visitor
|
28
64
|
def visit_array_command(command)
|
65
|
+
for input in command.input
|
66
|
+
if input.command.is_a?(ArrayCommand)
|
67
|
+
input.command.accept(self)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
29
71
|
|
72
|
+
def visit_array_index_command(command)
|
73
|
+
visit_array_command(command)
|
30
74
|
end
|
31
75
|
|
32
|
-
def
|
76
|
+
def visit_array_combine_command(command)
|
33
77
|
visit_array_command(command)
|
34
78
|
end
|
35
79
|
|
36
|
-
def
|
80
|
+
def visit_array_reduce_command(command)
|
81
|
+
visit_array_command(command)
|
82
|
+
end
|
83
|
+
|
84
|
+
def visit_array_stencil_command(command)
|
37
85
|
visit_array_command(command)
|
38
|
-
command.target.accept(self)
|
39
86
|
end
|
40
87
|
|
41
88
|
def visit_array_select_command(command)
|
42
89
|
visit_array_command(command)
|
43
|
-
command.target.accept(self)
|
44
90
|
end
|
45
91
|
|
46
92
|
def visit_array_identity_command(command)
|
47
93
|
visit_array_command(command)
|
48
94
|
end
|
95
|
+
|
96
|
+
def visit_array_in_host_section_command(command)
|
97
|
+
visit_array_command(command)
|
98
|
+
end
|
99
|
+
|
100
|
+
def visit_array_zip_command(command)
|
101
|
+
visit_array_command(command)
|
102
|
+
end
|
103
|
+
|
104
|
+
def visit_array_host_section_command(command)
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
def visit_fixed_size_array_in_host_section_command(command)
|
109
|
+
visit_array_in_host_section_command(command)
|
110
|
+
end
|
49
111
|
end
|
50
112
|
end
|
51
113
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module Ikra
|
2
|
+
module Translator
|
3
|
+
# This class looks for `input_command` in the tree of commands (and inputs) provided
|
4
|
+
# by `relative_command`. It traverses this tree and generates and expression that can
|
5
|
+
# be used to access `input_command` from `relative_command`, which is represented by
|
6
|
+
# an array_command struct in the CUDA code and referenced with `command_expr`.
|
7
|
+
# TODO: This class should be a visitor, but we need to pass additional values (`path`)
|
8
|
+
# along the way.
|
9
|
+
module KernelLaunchArgumentGenerator
|
10
|
+
def self.generate_arg(input_command, relative_command, command_expr)
|
11
|
+
return visit_array_command(input_command, relative_command, command_expr)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.visit_array_command(input_command, command, path)
|
15
|
+
if command.is_a?(Symbolic::ArrayInHostSectionCommand)
|
16
|
+
if command.equal?(input_command)
|
17
|
+
# This should be passed as an argument
|
18
|
+
return "((#{command.base_type.to_c_type} *) #{path}->input_0.content)"
|
19
|
+
else
|
20
|
+
# This is not the one we are looking for
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
else
|
24
|
+
command.input.each_with_index do |input, index|
|
25
|
+
if input.command.is_a?(Symbolic::ArrayCommand)
|
26
|
+
result = visit_array_command(
|
27
|
+
input_command, input.command, "#{path}->input_#{index}")
|
28
|
+
|
29
|
+
if result != nil
|
30
|
+
return "((#{input_command.base_type.to_c_type} *) #{result})"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
return nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ArrayCommandStructBuilder < Symbolic::Visitor
|
41
|
+
def self.struct_name(command)
|
42
|
+
return "array_command_#{command.unique_id}"
|
43
|
+
end
|
44
|
+
|
45
|
+
# This class determines if a `size` instance method should be generated for an
|
46
|
+
# array_command struct type. This is the case iff struct, or its first input, or
|
47
|
+
# the first input of its first input, etc., is an ArrayInHostSectionCommand.
|
48
|
+
# The size of such arrays is in general not known at compile time.
|
49
|
+
class RequireRuntimeSizeChecker < Symbolic::Visitor
|
50
|
+
def self.require_size_function?(command)
|
51
|
+
return command.accept(self.new)
|
52
|
+
end
|
53
|
+
|
54
|
+
def visit_array_reduce_command(command)
|
55
|
+
# Size is always 1
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_array_identity_command(command)
|
60
|
+
# Fully fused, size known at compile time
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_array_in_host_section_command(command)
|
65
|
+
# Cannot be fused, size unknown at compile time
|
66
|
+
return true
|
67
|
+
end
|
68
|
+
|
69
|
+
def visit_fixed_size_array_in_host_section_command(command)
|
70
|
+
# Size is part of the type/command
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
|
74
|
+
def visit_array_command(command)
|
75
|
+
if command.input.size == 0
|
76
|
+
return false
|
77
|
+
else
|
78
|
+
return command.input.first.command.accept(self)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# This class builds a struct containing references to input (depending) commands for
|
84
|
+
# a certain array command. It is a subclass of [Symbolic::Visitor] but does not
|
85
|
+
# traverse the tree. We just take advantage of the double dispatch here.
|
86
|
+
class SingleStructBuilder < Symbolic::Visitor
|
87
|
+
def struct_name(command)
|
88
|
+
return ArrayCommandStructBuilder.struct_name(command)
|
89
|
+
end
|
90
|
+
|
91
|
+
def visit_array_command(command)
|
92
|
+
this_name = struct_name(command)
|
93
|
+
struct_def = "struct #{this_name} {\n"
|
94
|
+
|
95
|
+
# Debug information
|
96
|
+
struct_def = struct_def + " // #{command.class}\n"
|
97
|
+
|
98
|
+
# Generate fields
|
99
|
+
struct_def = struct_def + " #{command.result_type.to_c_type} *result;\n"
|
100
|
+
|
101
|
+
all_params = ["#{command.result_type.to_c_type} *result = NULL"]
|
102
|
+
all_initializers = ["result(result)"]
|
103
|
+
|
104
|
+
command.input.each_with_index do |input, index|
|
105
|
+
if input.command.is_a?(Symbolic::ArrayCommand)
|
106
|
+
struct_def = struct_def + " #{struct_name(input.command)} *input_#{index};\n"
|
107
|
+
all_params.push("#{struct_name(input.command)} *input_#{index} = NULL")
|
108
|
+
all_initializers.push("input_#{index}(input_#{index})")
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# Generate constructor
|
113
|
+
struct_def = struct_def + " __host__ __device__ #{this_name}(#{all_params.join(', ')}) : #{all_initializers.join(', ')} { }\n"
|
114
|
+
|
115
|
+
# Add instance methods
|
116
|
+
if RequireRuntimeSizeChecker.require_size_function?(command)
|
117
|
+
# ArrayIndexCommand does not have any input, as an example. But in this
|
118
|
+
# case, we also do not need the `size` function, because it is a root
|
119
|
+
# command that can be fused.
|
120
|
+
struct_def = struct_def + " int size() { return input_0->size(); }\n"
|
121
|
+
end
|
122
|
+
|
123
|
+
struct_def = struct_def + "};"
|
124
|
+
end
|
125
|
+
|
126
|
+
def visit_array_in_host_section_command(command)
|
127
|
+
this_name = struct_name(command)
|
128
|
+
struct_def = "struct #{this_name} {\n"
|
129
|
+
|
130
|
+
# Debug information
|
131
|
+
struct_def = struct_def + " // #{command.class}\n"
|
132
|
+
|
133
|
+
struct_def = struct_def + " #{command.result_type.to_c_type} *result;\n"
|
134
|
+
struct_def = struct_def + " variable_size_array_t input_0;\n"
|
135
|
+
struct_def = struct_def + " __host__ __device__ #{this_name}(#{command.result_type.to_c_type} *result = NULL, variable_size_array_t input_0 = variable_size_array_t::error_return_value) : result(result), input_0(input_0) { }\n"
|
136
|
+
|
137
|
+
# Add instance methods
|
138
|
+
struct_def = struct_def + " int size() { return input_0.size; }\n"
|
139
|
+
|
140
|
+
return struct_def + "};"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_reader :all_structs
|
145
|
+
|
146
|
+
def initialize
|
147
|
+
@all_structs = []
|
148
|
+
@builder = SingleStructBuilder.new
|
149
|
+
end
|
150
|
+
|
151
|
+
def visit_array_command(command)
|
152
|
+
super
|
153
|
+
@all_structs.push(command.accept(@builder))
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.build_all_structs(command)
|
157
|
+
visitor = self.new
|
158
|
+
command.accept(visitor)
|
159
|
+
return visitor.all_structs
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,572 @@
|
|
1
|
+
require_relative "../ast/nodes.rb"
|
2
|
+
require_relative "../ast/visitor.rb"
|
3
|
+
require_relative "../ruby_core/ruby_integration"
|
4
|
+
|
5
|
+
# Rule: every statement ends with newline
|
6
|
+
# TODO: Add proper exceptions (CompilationError)
|
7
|
+
|
8
|
+
module Ikra
|
9
|
+
module Translator
|
10
|
+
class ASTTranslator
|
11
|
+
class ExpressionTranslator
|
12
|
+
# This class should not inherit from [AST::Visitor]. Otherwise, the dispatch
|
13
|
+
# mechanisum to [StatementTranslator] will not work properly anymore.
|
14
|
+
|
15
|
+
attr_reader :translator
|
16
|
+
|
17
|
+
def initialize(translator)
|
18
|
+
@translator = translator
|
19
|
+
end
|
20
|
+
|
21
|
+
def expression_translator
|
22
|
+
return self
|
23
|
+
end
|
24
|
+
|
25
|
+
def statement_translator
|
26
|
+
return translator.statement_translator
|
27
|
+
end
|
28
|
+
|
29
|
+
def method_missing(symbol, *args)
|
30
|
+
if symbol.to_s.start_with?("visit_")
|
31
|
+
if statement_translator.respond_to?(symbol)
|
32
|
+
return statements_as_expression(
|
33
|
+
statement_translator.send(symbol, *args) + "return NULL;")
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
else
|
38
|
+
return translator.send(symbol, *args)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def visit_behavior_node(node)
|
43
|
+
raise AssertionError.new(
|
44
|
+
"Methods/blocks cannot be translated as an expression")
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit_source_code_expr_node(node)
|
48
|
+
return node.code
|
49
|
+
end
|
50
|
+
|
51
|
+
# TODO: Should never translate a hash node (check `with_index` in host section)
|
52
|
+
def visit_hash_node(node)
|
53
|
+
return ""
|
54
|
+
end
|
55
|
+
|
56
|
+
def visit_const_node(node)
|
57
|
+
raise NotImplementedError.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def visit_lvar_read_node(node)
|
61
|
+
return node.mangled_identifier.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def visit_lvar_write_node(node)
|
65
|
+
if node.value.get_type.is_singleton? and !node.symbol_table[node.identifier].is_singleton?
|
66
|
+
# The assigned value is singleton, but the variable is not
|
67
|
+
singleton_assignment = wrap_in_union_type(
|
68
|
+
node.value.accept(expression_translator),
|
69
|
+
node.value.get_type.singleton_type)
|
70
|
+
return Translator.read_file(file_name: "ast/assignment.cpp",
|
71
|
+
replacements: {
|
72
|
+
"source" => singleton_assignment,
|
73
|
+
"target" => node.mangled_identifier.to_s})
|
74
|
+
else
|
75
|
+
return Translator.read_file(file_name: "ast/assignment.cpp",
|
76
|
+
replacements: {
|
77
|
+
"source" => node.value.accept(expression_translator),
|
78
|
+
"target" => node.mangled_identifier.to_s})
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def visit_ivar_read_node(node)
|
83
|
+
array_identifier = node.enclosing_class.ruby_class.to_ikra_type.inst_var_array_name(identifier)
|
84
|
+
return "#{Constants::ENV_IDENTIFIER}->#{array_identifier}[#{Constants::SELF_IDENTIFIER}]"
|
85
|
+
end
|
86
|
+
|
87
|
+
def visit_array_node(node)
|
88
|
+
elements = node.values.map do |value|
|
89
|
+
value.accept(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
return "(new #{node.get_type.to_c_type[0..-2]} {#{elements.join(', ')}})"
|
93
|
+
end
|
94
|
+
|
95
|
+
def visit_int_node(node)
|
96
|
+
return node.value.to_s
|
97
|
+
end
|
98
|
+
|
99
|
+
def visit_nil_node(node)
|
100
|
+
return "0"
|
101
|
+
end
|
102
|
+
|
103
|
+
def visit_float_node(node)
|
104
|
+
return node.value.to_s
|
105
|
+
end
|
106
|
+
|
107
|
+
def visit_bool_node(node)
|
108
|
+
return node.value.to_s
|
109
|
+
end
|
110
|
+
|
111
|
+
def visit_if_node(node)
|
112
|
+
# Make every branch return
|
113
|
+
node.accept(LastStatementReturnsVisitor.new)
|
114
|
+
|
115
|
+
# Wrap in StatementExpression
|
116
|
+
return statements_as_expression(node.accept(statement_translator))
|
117
|
+
end
|
118
|
+
|
119
|
+
def visit_ternary_node(node)
|
120
|
+
return "((#{node.condition.accept(expression_translator)}) ? (#{node.true_val.accept(expression_translator)}) : (#{node.false_val.accept(expression_translator)}))"
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def visit_begin_node(node)
|
125
|
+
if node.body_stmts.size == 0
|
126
|
+
raise AssertionError.new("Empty BeginNode cannot be an expression")
|
127
|
+
elsif node.body_stmts.size == 1
|
128
|
+
# Preserve brackets
|
129
|
+
return "(#{node.body_stmts.first.accept(self)})"
|
130
|
+
else
|
131
|
+
# Wrap in lambda
|
132
|
+
# Do not worry about scope of varibles, they will all be declared at the
|
133
|
+
# beginning of the function
|
134
|
+
node.accept(LastStatementReturnsVisitor.new)
|
135
|
+
return statements_as_expression(node.accept(statement_translator))
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Builds a synthetic [AST::SourceCodeExprNode] with a type and a translation.
|
140
|
+
def build_synthetic_code_node(code, type)
|
141
|
+
node = AST::SourceCodeExprNode.new(code: code)
|
142
|
+
node.merge_union_type(type.to_union_type)
|
143
|
+
return node
|
144
|
+
end
|
145
|
+
|
146
|
+
def generate_polymorphic_switch(node, &block)
|
147
|
+
poly_id = temp_identifier_id
|
148
|
+
node_identifer = "_polytemp_expr_#{poly_id}"
|
149
|
+
header = "#{define_assign_variable(node_identifer, node)}\nswitch (#{node_identifer}.class_id)\n"
|
150
|
+
case_statements = []
|
151
|
+
|
152
|
+
if node.get_type.size == 0
|
153
|
+
raise AssertionError.new("Cannot generate switch for empty UnionType")
|
154
|
+
end
|
155
|
+
|
156
|
+
for type in node.get_type
|
157
|
+
if type == Types::PrimitiveType::Int
|
158
|
+
self_node = build_synthetic_code_node(
|
159
|
+
"#{node_identifer}.value.int_", type)
|
160
|
+
elsif type == Types::PrimitiveType::Float
|
161
|
+
self_node = build_synthetic_code_node(
|
162
|
+
"#{node_identifer}.value.float_", type)
|
163
|
+
elsif type == Types::PrimitiveType::Bool
|
164
|
+
self_node = build_synthetic_code_node(
|
165
|
+
"#{node_identifer}.value.bool_", type)
|
166
|
+
elsif type == Types::PrimitiveType::Nil
|
167
|
+
self_node = build_synthetic_code_node(
|
168
|
+
"#{node_identifer}.value.int_", type)
|
169
|
+
elsif type.is_a?(Symbolic::ArrayCommand)
|
170
|
+
self_node = build_synthetic_code_node(
|
171
|
+
"(#{type.to_c_type}) #{node_identifer}.value.pointer", type)
|
172
|
+
elsif type.is_a?(Types::LocationAwareArrayType)
|
173
|
+
# TODO: Should not use variable_size_array for fixed size arrays
|
174
|
+
self_node = build_synthetic_code_node(
|
175
|
+
"#{node_identifer}.value.variable_size_array", type)
|
176
|
+
else
|
177
|
+
self_node = build_synthetic_code_node(
|
178
|
+
"#{node_identifer}.value.object_id", type)
|
179
|
+
end
|
180
|
+
|
181
|
+
debug_info = "#{type.to_s} (#{type.to_ruby_type.to_s})"
|
182
|
+
|
183
|
+
case_statements.push("case #{type.class_id}: /* #{debug_info} */ #{yield(self_node)} break;")
|
184
|
+
end
|
185
|
+
|
186
|
+
return header + wrap_in_c_block(case_statements.join("\n"))
|
187
|
+
end
|
188
|
+
|
189
|
+
def visit_send_node(node)
|
190
|
+
if node.receiver.get_type.is_singleton?
|
191
|
+
return_type = node.return_type_by_recv_type[
|
192
|
+
node.receiver.get_type.singleton_type]
|
193
|
+
|
194
|
+
invocation = generate_send_for_singleton(
|
195
|
+
node,
|
196
|
+
node.receiver,
|
197
|
+
return_type)
|
198
|
+
|
199
|
+
if return_type.is_singleton? and
|
200
|
+
!node.get_type.is_singleton?
|
201
|
+
|
202
|
+
invocation = wrap_in_union_type(
|
203
|
+
invocation,
|
204
|
+
return_type.singleton_type)
|
205
|
+
end
|
206
|
+
|
207
|
+
return invocation
|
208
|
+
else
|
209
|
+
# Polymorphic case
|
210
|
+
result_identifier = "_polytemp_result_#{temp_identifier_id}"
|
211
|
+
declare_result_var = "#{node.get_type.to_c_type} #{result_identifier};\n"
|
212
|
+
|
213
|
+
case_statements = generate_polymorphic_switch(node.receiver) do |self_node|
|
214
|
+
# The singleton type in the current case
|
215
|
+
type = self_node.get_type.singleton_type
|
216
|
+
|
217
|
+
# The return type (result type) in the current case (could be polym.)
|
218
|
+
return_type = node.return_type_by_recv_type[type]
|
219
|
+
|
220
|
+
# Generate method invocation
|
221
|
+
invocation = generate_send_for_singleton(
|
222
|
+
node,
|
223
|
+
self_node,
|
224
|
+
return_type)
|
225
|
+
|
226
|
+
if return_type.is_singleton? and
|
227
|
+
!node.get_type.is_singleton?
|
228
|
+
# The return value of this particular invocation (singleton type
|
229
|
+
# recv) is singleton, but in general this send can return many
|
230
|
+
# types
|
231
|
+
invocation = wrap_in_union_type(
|
232
|
+
invocation,
|
233
|
+
return_type.singleton_type)
|
234
|
+
end
|
235
|
+
|
236
|
+
"#{result_identifier} = #{invocation};"
|
237
|
+
end
|
238
|
+
|
239
|
+
# TODO: compound statements only work with the GNU C++ compiler
|
240
|
+
return "(" + wrap_in_c_block(
|
241
|
+
declare_result_var +
|
242
|
+
wrap_in_c_block(case_statements) +
|
243
|
+
result_identifier + ";")[0..-2] + ")"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def build_switch_for_args(nodes, accumulator = [], &block)
|
248
|
+
if nodes.size == 0
|
249
|
+
# This was the last argument, we are done with nesting switch
|
250
|
+
# stmts. The accumulator contains all singleton-typed self_nodes.
|
251
|
+
return yield(accumulator)
|
252
|
+
end
|
253
|
+
|
254
|
+
next_node = nodes.first
|
255
|
+
|
256
|
+
if next_node.get_type.is_singleton?
|
257
|
+
# This node has a singleton type. We're done with this one.
|
258
|
+
return build_switch_for_args(nodes.drop(1), accumulator + [next_node], &block)
|
259
|
+
elsif next_node.get_type.size == 0
|
260
|
+
raise "AssertionError: Found empty UnionType"
|
261
|
+
else
|
262
|
+
return generate_polymorphic_switch(next_node) do |sing_node|
|
263
|
+
build_switch_for_args(nodes.drop(1), accumulator + [sing_node], &block)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def generate_send_for_singleton(node, singleton_recv, return_type)
|
269
|
+
recv_type = singleton_recv.get_type.singleton_type
|
270
|
+
|
271
|
+
if RubyIntegration.is_interpreter_only?(recv_type)
|
272
|
+
# No translation necessary
|
273
|
+
return nil
|
274
|
+
elsif RubyIntegration.has_implementation?(recv_type, node.selector)
|
275
|
+
# Some implementations accept only singleton-typed arguments
|
276
|
+
if RubyIntegration.expect_singleton_args?(recv_type, node.selector)
|
277
|
+
# Generate additional switch statements (one per non-sing. arg.).
|
278
|
+
# Go through all possible combinations of types (for arguments).
|
279
|
+
result_identifier = "_polytemp_result_#{temp_identifier_id}"
|
280
|
+
declare_result_var = "#{return_type.to_c_type} #{result_identifier};\n"
|
281
|
+
|
282
|
+
case_stmts = build_switch_for_args(node.arguments) do |all_sing_args|
|
283
|
+
# TODO: Do we really have to redo type inference here?
|
284
|
+
all_sing_arg_types = all_sing_args.map do |arg|
|
285
|
+
arg.get_type
|
286
|
+
end
|
287
|
+
|
288
|
+
this_return_type = RubyIntegration.get_return_type(
|
289
|
+
singleton_recv.get_type.singleton_type,
|
290
|
+
node.selector,
|
291
|
+
*all_sing_arg_types,
|
292
|
+
send_node: node)
|
293
|
+
|
294
|
+
impl = RubyIntegration.get_implementation(
|
295
|
+
singleton_recv,
|
296
|
+
node.selector,
|
297
|
+
all_sing_args,
|
298
|
+
translator,
|
299
|
+
this_return_type)
|
300
|
+
|
301
|
+
if this_return_type.is_singleton? and
|
302
|
+
!return_type.is_singleton?
|
303
|
+
|
304
|
+
impl = wrap_in_union_type(
|
305
|
+
impl,
|
306
|
+
this_return_type.singleton_type)
|
307
|
+
end
|
308
|
+
|
309
|
+
"#{result_identifier} = #{impl};"
|
310
|
+
end
|
311
|
+
|
312
|
+
return "(" + wrap_in_c_block(
|
313
|
+
declare_result_var +
|
314
|
+
wrap_in_c_block(case_stmts) +
|
315
|
+
result_identifier + ";")[0..-2] + ")"
|
316
|
+
else
|
317
|
+
# The easy case: Anything is fine (but might fail in ruby_integration)
|
318
|
+
return RubyIntegration.get_implementation(
|
319
|
+
singleton_recv,
|
320
|
+
node.selector,
|
321
|
+
node.arguments,
|
322
|
+
translator,
|
323
|
+
return_type)
|
324
|
+
end
|
325
|
+
elsif recv_type.is_a?(Types::StructType)
|
326
|
+
first_arg = node.arguments.first
|
327
|
+
|
328
|
+
if first_arg.is_a?(AST::IntLiteralNode)
|
329
|
+
# Reading the struct at a constant position
|
330
|
+
return recv_type.generate_read(
|
331
|
+
singleton_recv.accept(self),
|
332
|
+
node.selector,
|
333
|
+
first_arg.accept(self))
|
334
|
+
else
|
335
|
+
# Reading the struct at a non-constant position
|
336
|
+
id = temp_identifier_id
|
337
|
+
name = "_temp_var_#{id}"
|
338
|
+
first_arg_eval = first_arg.accept(self)
|
339
|
+
|
340
|
+
# Store index in local variable, then generate non-constant access
|
341
|
+
# TODO: Statement expression is potentially inefficient
|
342
|
+
return "({ int #{name} = #{first_arg_eval};\n" +
|
343
|
+
recv_type.generate_non_constant_read(
|
344
|
+
singleton_recv.accept(self),
|
345
|
+
node.selector,
|
346
|
+
name) + "; })"
|
347
|
+
end
|
348
|
+
else
|
349
|
+
args = [Constants::ENV_IDENTIFIER]
|
350
|
+
|
351
|
+
if recv_type.should_generate_self_arg?
|
352
|
+
args.push(singleton_recv.accept(self))
|
353
|
+
else
|
354
|
+
args.push("NULL")
|
355
|
+
end
|
356
|
+
|
357
|
+
args.push(*(node.arguments.map do |arg| arg.accept(self) end))
|
358
|
+
args_string = args.join(", ")
|
359
|
+
|
360
|
+
return "#{node.receiver.get_type.singleton_type.mangled_method_name(node.selector)}(#{args_string})"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def visit_return_node(node)
|
365
|
+
raise AssertionError.new("ReturnNode is never an expression")
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
class StatementTranslator
|
370
|
+
# This class should not inherit from [AST::Visitor]. Otherwise, the dispatch
|
371
|
+
# mechanisum to [ExpressionTranslator] will not work properly anymore.
|
372
|
+
|
373
|
+
attr_reader :translator
|
374
|
+
|
375
|
+
def initialize(translator)
|
376
|
+
@translator = translator
|
377
|
+
end
|
378
|
+
|
379
|
+
def expression_translator
|
380
|
+
return translator.expression_translator
|
381
|
+
end
|
382
|
+
|
383
|
+
def statement_translator
|
384
|
+
return self
|
385
|
+
end
|
386
|
+
|
387
|
+
def method_missing(symbol, *args)
|
388
|
+
if symbol.to_s.start_with?("visit_")
|
389
|
+
if expression_translator.respond_to?(symbol)
|
390
|
+
return expression_translator.send(symbol, *args) + ";\n"
|
391
|
+
else
|
392
|
+
super
|
393
|
+
end
|
394
|
+
else
|
395
|
+
return translator.send(symbol, *args)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
def visit_behavior_node(node)
|
400
|
+
raise AssertionError.new("Methods/blocks cannot be translated as a statement")
|
401
|
+
end
|
402
|
+
|
403
|
+
def visit_root_node(node)
|
404
|
+
return node.single_child.accept(self)
|
405
|
+
end
|
406
|
+
|
407
|
+
def visit_for_node(node)
|
408
|
+
loop_header = "for (#{node.iterator_identifier.to_s} = #{node.range_from.accept(expression_translator)}; #{node.iterator_identifier.to_s} <= #{node.range_to.accept(expression_translator)}; #{node.iterator_identifier.to_s}++)"
|
409
|
+
|
410
|
+
return loop_header +
|
411
|
+
"\n" +
|
412
|
+
node.body_stmts.accept(self) +
|
413
|
+
"#{node.iterator_identifier.to_s}--;\n"
|
414
|
+
end
|
415
|
+
|
416
|
+
def visit_while_node(node)
|
417
|
+
return "while (#{node.condition.accept(expression_translator)})\n#{node.body_stmts.accept(self)}"
|
418
|
+
end
|
419
|
+
|
420
|
+
def visit_while_post_node(node)
|
421
|
+
return "do #{node.body_stmts.accept(self)}while (#{node.condition.accept(expression_translator)});\n"
|
422
|
+
end
|
423
|
+
|
424
|
+
def visit_until_node(node)
|
425
|
+
return "while (#{node.condition.accept(expression_translator)})\n#{node.body_stmts.accept(self)}"
|
426
|
+
end
|
427
|
+
|
428
|
+
def visit_until_post_node(node)
|
429
|
+
return "do #{node.body_stmts.accept(self)}while (#{node.condition.accept(expression_translator)});\n"
|
430
|
+
end
|
431
|
+
|
432
|
+
def visit_break_node(node)
|
433
|
+
return "break;\n"
|
434
|
+
end
|
435
|
+
|
436
|
+
def visit_if_node(node)
|
437
|
+
return "if (#{node.condition.accept(expression_translator)})\n" +
|
438
|
+
node.true_body_stmts.accept(self) +
|
439
|
+
"else\n" +
|
440
|
+
node.false_body_stmts.accept(self)
|
441
|
+
end
|
442
|
+
|
443
|
+
def visit_begin_node(node)
|
444
|
+
if node.body_stmts.size == 0
|
445
|
+
return wrap_in_c_block("")
|
446
|
+
end
|
447
|
+
|
448
|
+
body_translated = node.body_stmts.map do |stmt|
|
449
|
+
stmt.accept(self)
|
450
|
+
end.join("")
|
451
|
+
|
452
|
+
return wrap_in_c_block(body_translated)
|
453
|
+
end
|
454
|
+
|
455
|
+
def visit_return_node(node)
|
456
|
+
return "return #{node.value.accept(expression_translator)};\n"
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
attr_reader :expression_translator
|
461
|
+
|
462
|
+
attr_reader :statement_translator
|
463
|
+
|
464
|
+
def initialize
|
465
|
+
@expression_translator = ExpressionTranslator.new(self)
|
466
|
+
@statement_translator = StatementTranslator.new(self)
|
467
|
+
end
|
468
|
+
|
469
|
+
def statements_as_expression(str)
|
470
|
+
return "[&]#{wrap_in_c_block(str, omit_newl: true)}()"
|
471
|
+
end
|
472
|
+
|
473
|
+
def indent_block(str)
|
474
|
+
return str.split("\n").map do |line| " " + line end.join("\n")
|
475
|
+
end
|
476
|
+
|
477
|
+
def wrap_in_c_block(str, omit_newl: false)
|
478
|
+
result = "{\n" + indent_block(str) + "\n}"
|
479
|
+
|
480
|
+
if omit_newl
|
481
|
+
return result
|
482
|
+
else
|
483
|
+
return result + "\n"
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
@@next_temp_identifier_id = 0
|
488
|
+
def temp_identifier_id
|
489
|
+
@@next_temp_identifier_id += 1
|
490
|
+
@@next_temp_identifier_id
|
491
|
+
end
|
492
|
+
|
493
|
+
# Generates code that assigns the value of a node to a newly-defined variable.
|
494
|
+
def define_assign_variable(name, node)
|
495
|
+
type = node.get_type.to_c_type
|
496
|
+
return "#{type} #{name} = #{node.accept(expression_translator)};"
|
497
|
+
end
|
498
|
+
|
499
|
+
def wrap_in_union_type(str, type)
|
500
|
+
if type == Types::PrimitiveType::Int
|
501
|
+
return "union_t(#{type.class_id}, union_v_t::from_int(#{str}))"
|
502
|
+
elsif type == Types::PrimitiveType::Float
|
503
|
+
return "union_t(#{type.class_id}, union_v_t::from_float(#{str}))"
|
504
|
+
elsif type == Types::PrimitiveType::Bool
|
505
|
+
return "union_t(#{type.class_id}, union_v_t::from_bool(#{str}))"
|
506
|
+
elsif type == Types::PrimitiveType::Nil
|
507
|
+
return "union_t(#{type.class_id}, union_v_t::from_int(#{str}))"
|
508
|
+
elsif type.is_a?(Symbolic::ArrayCommand)
|
509
|
+
return "union_t(#{type.class_id}, union_v_t::from_pointer((void *) #{str}))"
|
510
|
+
elsif type.is_a?(Types::LocationAwareArrayType)
|
511
|
+
# TODO: Should not use variable_size_array for fixed size arrays
|
512
|
+
return "union_t(#{type.class_id}, union_v_t::from_variable_size_array_t(#{str}))"
|
513
|
+
elsif !type.is_a?(Types::UnionType)
|
514
|
+
return "union_t(#{type.class_id}, union_v_t::from_object_id(#{str}))"
|
515
|
+
else
|
516
|
+
raise AssertionError.new("UnionType found but singleton type expected")
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
def self.translate_block(block_def_node)
|
521
|
+
return self.new.translate_block(block_def_node)
|
522
|
+
end
|
523
|
+
|
524
|
+
def translate_block(block_def_node)
|
525
|
+
return block_def_node.body.accept(statement_translator)
|
526
|
+
end
|
527
|
+
|
528
|
+
def self.translate_method(method_def_node)
|
529
|
+
return self.new.translate_method(method_def_node)
|
530
|
+
end
|
531
|
+
|
532
|
+
def generate_method_signature(meth_def_node)
|
533
|
+
method_params = ([
|
534
|
+
"environment_t * #{Constants::ENV_IDENTIFIER}",
|
535
|
+
"#{meth_def_node.parent.get_type.to_c_type} #{Constants::SELF_IDENTIFIER}"] +
|
536
|
+
meth_def_node.parameters_names_and_types.map do |name, type|
|
537
|
+
"#{type.singleton_type.to_c_type} #{name}"
|
538
|
+
end).join(", ")
|
539
|
+
|
540
|
+
signature = "__device__ #{meth_def_node.get_type.singleton_type.to_c_type} #{meth_def_node.parent.get_type.mangled_method_name(meth_def_node.name)}(#{method_params})"
|
541
|
+
|
542
|
+
return signature
|
543
|
+
end
|
544
|
+
|
545
|
+
def translate_method_predecl(meth_def_node)
|
546
|
+
return generate_method_signature(meth_def_node) + ";"
|
547
|
+
end
|
548
|
+
|
549
|
+
def translate_method(meth_def_node)
|
550
|
+
# TODO: merge with BlockTranslator
|
551
|
+
# TODO: load environment variables
|
552
|
+
|
553
|
+
# Declare local variables
|
554
|
+
local_variables_def = ""
|
555
|
+
meth_def_node.local_variables_names_and_types.each do |name, type|
|
556
|
+
local_variables_def += "#{type.to_c_type} #{name};\n"
|
557
|
+
end
|
558
|
+
|
559
|
+
# TODO: There should be a better way to ensure that we don't generate methods
|
560
|
+
# multiple times.
|
561
|
+
mangled_name = meth_def_node.parent.get_type.mangled_method_name(meth_def_node.name)
|
562
|
+
def_label = "def_label_#{mangled_name}"
|
563
|
+
|
564
|
+
return "#ifndef #{def_label}\n#define #{def_label}\n" +
|
565
|
+
generate_method_signature(meth_def_node) +
|
566
|
+
"\n" +
|
567
|
+
wrap_in_c_block(local_variables_def + meth_def_node.body.accept(statement_translator)) +
|
568
|
+
"#endif"
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|