Module: Ikra::RubyIntegration
- Defined in:
- lib/ruby_core/core.rb,
lib/ruby_core/math.rb,
lib/ruby_core/array.rb,
lib/ruby_core/interpreter.rb,
lib/ruby_core/array_command.rb,
lib/ruby_core/ruby_integration.rb
Defined Under Namespace
Classes: CycleDetectedError, Implementation, SymbolicCycleFinder
Constant Summary collapse
- TYPE_INT_COERCE_TO_FLOAT =
TODO: Handle non-singleton types
proc do |recv, other| if other.include?(FLOAT_S) FLOAT elsif other.include?(INT_S) INT else # At least one of the types INT_S or FLOAT_S are required raise RuntimeError.new("Operation defined numeric values only (found #{other})") end end
- TYPE_INT_RETURN_INT =
proc do |recv, other| if !other.include?(INT_S) raise RuntimeError.new("Operation defined Int values only (found #{other})") end INT end
- TYPE_NUMERIC_RETURN_BOOL =
proc do |recv, other| if !other.include?(INT_S) && !other.include?(FLOAT_S) raise RuntimeError.new("Expected type Int or Float, found #{other}") end BOOL end
- TYPE_BOOL_RETURN_BOOL =
proc do |recv, other| if !other.include?(BOOL_S) raise RuntimeError.new("Expected type Bool, found #{other}") end BOOL end
- MATH =
Math.singleton_class.to_ikra_type
- ALL_ARRAY_TYPES =
proc do |type| type.is_a?(Types::ArrayType) && !type.is_a?(Types::LocationAwareArrayType) end
- LOCATION_AWARE_ARRAY_TYPE =
proc do |type| # TODO: Maybe there should be an automated transfer to host side here if necessary? type.is_a?(Types::LocationAwareArrayType) end
- LOCATION_AWARE_ARRAY_ACCESS =
proc do |receiver, method_name, args, translator, result_type| recv = receiver.accept(translator.expression_translator) inner_type = receiver.get_type.singleton_type.inner_type.to_c_type index = args[0].accept(translator.expression_translator) "((#{inner_type} *) #{recv}.content)[#{index}]" end
- INNER_TYPE =
proc do |rcvr| rcvr.inner_type end
- INTERPRETER_ONLY_CLS_OBJ =
No need to do type inference or code generation, if a method is called on an on an instance of one of these classes.
[Ikra::Symbolic.singleton_class]
- ALL_ARRAY_COMMAND_TYPES =
proc do |type| type.is_a?(Symbolic::ArrayCommand) end
- PMAP_TYPE =
proc do |rcvr_type, *args_types, send_node:| # TODO: Handle keyword arguments # Ensure that there is no cycle here. "Cycle" means that the same AST send node # was used earlier (i.e., in one of `rcvr_type`'s inputs/dependent computations). # In that case we have to abort type inference here, because it would not terminate. SymbolicCycleFinder.raise_on_cycle(rcvr_type, send_node) more_kw_args = {} if send_node.arguments.size == 1 if !send_node.arguments.first.is_a?(AST::HashNode) raise ArgumentError.new("If an argument is given, it must be a Hash of kwargs.") end # Pass kwargs separately more_kw_args = AST::Interpreter.interpret(send_node.arguments.first) end rcvr_type.pmap( ast: send_node.block_argument, generator_node: send_node, # TODO: Fix binding command_binding: send_node.find_behavior_node.binding, **more_kw_args).to_union_type end
- PZIP_TYPE =
proc do |rcvr_type, *args_types, send_node:| # TODO: Support multiple arguments for `pzip` types = args_types[0].map do |sing_type| raise AssertionError.new("Singleton type expected") if sing_type.is_union_type? rcvr_type.pzip(sing_type, generator_node: send_node).to_union_type end types.reduce(Types::UnionType.new) do |acc, type| acc.(type) end end
- PSTENCIL_TYPE =
proc do |rcvr_type, *args_types, send_node:| # TODO: Handle keyword arguments ruby_args = send_node.arguments.map do |node| AST::Interpreter.interpret(node) end more_kw_args = {} if args_types.size == 3 if !ruby_args.last.is_a?(Hash) raise ArgumentError.new("If 3 arguments are given, the last one must be a Hash of kwargs.") end # Pass kwargs separately more_kw_args = ruby_args.pop end SymbolicCycleFinder.raise_on_cycle(rcvr_type, send_node) rcvr_type.pstencil( *ruby_args, ast: send_node.block_argument, generator_node: send_node, # TODO: Fix binding command_binding: send_node.find_behavior_node.binding, **more_kw_args).to_union_type end
- PREDUCE_TYPE =
proc do |rcvr_type, *args_types, send_node:| # TODO: Handle keyword arguments SymbolicCycleFinder.raise_on_cycle(rcvr_type, send_node) rcvr_type.preduce(ast: send_node.block_argument, generator_node: send_node).to_union_type end
- LAUNCH_KERNEL =
proc do |receiver, method_name, arguments, translator, result_type| # The result type is the symbolically executed result of applying this # parallel section. The result type is an ArrayCommand. array_command = receiver.get_type.singleton_type # Translate command command_translator = translator.command_translator command_translator.push_kernel_launcher result = array_command.accept(command_translator) kernel_launcher = command_translator.pop_kernel_launcher(result) # Prepare kernel launchers for launch of `array_command` command_translator.program_builder.prepare_additional_args_for_launch(array_command) # Generate launch code for all kernels launch_code = command_translator.program_builder.build_kernel_launchers # Always return a device pointer. Only at the very end, we transfer data to the host. result_expr = kernel_launcher.kernel_result_var_name if Translator::ArrayCommandStructBuilder::RequireRuntimeSizeChecker.require_size_function?(array_command) # Size is not statically known, take information from receiver. # TODO: Code depends on template. `cmd` is defined in template. result_size = "cmd->size()" else # Size is known statically result_size = array_command.size.to_s end # Debug information if array_command.generator_node != nil debug_information = array_command.to_s + ": " + array_command.generator_node.to_s else debug_information = array_command.to_s end result = Translator.read_file(file_name: "host_section_launch_parallel_section.cpp", replacements: { "debug_information" => debug_information, "array_command" => receiver.accept(translator.expression_translator), "array_command_type" => array_command.to_c_type, "result_size" => result_size, "kernel_invocation" => launch_code, "kernel_result" => result_expr, "free_memory" => command_translator.program_builder.build_memory_free_except_last}) # Clear kernel launchers. Otherwise, we might launch them again in a later, unrelated # LAUNCH_KERNEL branch. This is because we reuse the same [ProgramBuilder] for an # entire host section. command_translator.program_builder.clear_kernel_launchers # Build all array command structs for this command command_translator.program_builder.add_array_command_struct( *Translator::ArrayCommandStructBuilder.build_all_structs(array_command)) result end
- ARRAY_COMMAND_TO_ARRAY_TYPE =
proc do |rcvr_type, *args_types, send_node:| Types::LocationAwareFixedSizeArrayType.new( rcvr_type.result_type, rcvr_type.dimensions, location: :device).to_union_type end
- SYMBOLICALLY_EXECUTE_KERNEL =
proc do |receiver, method_name, arguments, translator, result_type| if !result_type.is_singleton? raise AssertionError.new("Singleton type expected") end # Build arguments to constructor. First one (result field) is NULL. constructor_args = ["NULL"] # Translate all inputs (receiver, then arguments to parallel section) constructor_args.push(receiver.accept(translator.expression_translator)) for arg in arguments if arg.get_type.is_singleton? && arg.get_type.singleton_type.is_a?(Symbolic::ArrayCommand) # Only ArrayCommands should show up as arguments constructor_args.push(arg.accept(translator.expression_translator)) end end all_args = constructor_args.join(", ") # This is a hack because the type is a pointer type "new #{result_type.singleton_type.to_c_type[0...-2]}(#{all_args})" end
- ALL_LOCATION_AWARE_ARRAY_TYPES =
proc do |type| type.is_a?(Types::LocationAwareArrayType) end
- LOCATION_AWARE_ARRAY_TO_HOST_ARRAY_TYPE =
proc do |rcvr_type, *args_types| # TODO: Should also be able to handle variable variant Types::LocationAwareFixedSizeArrayType.new( rcvr_type.inner_type, rcvr_type.dimensions, location: :host).to_union_type end
- LOCATION_AWARE_ARRAY_CALL_TYPE =
proc do |rcvr_type, *args_types| # Calling `__call__` on an array does not do anything rcvr_type.to_union_type end
- COPY_ARRAY_TO_HOST =
proc do |receiver, method_name, args, translator, result_type| if receiver.get_type.singleton_type.location == :host receiver.accept(translator.expression_translator) else c_type = receiver.get_type.singleton_type.inner_type.to_c_type Translator.read_file(file_name: "memcpy_device_to_host_expr.cpp", replacements: { "type" => c_type, "device_array" => receiver.accept(translator.expression_translator)}) end end
- ARRAY_TYPE_TO_COMMAND_TYPE =
proc do |rcvr_type, *args_types, send_node:| rcvr_type.to_command.to_union_type end
- FREE_MEMORY_FOR_ARRAY_COMMAND =
proc do |receiver, method_name, args, translator, result_type| Translator.read_file(file_name: "free_memory_for_command.cpp", replacements: { "type" => receiver.get_type.to_c_type, "receiver" => receiver.accept(translator.expression_translator)}) end
- INT =
Types::UnionType.create_int
- FLOAT =
Types::UnionType.create_float
- BOOL =
Types::UnionType.create_bool
- INT_S =
INT.singleton_type
- FLOAT_S =
FLOAT.singleton_type
- BOOL_S =
BOOL.singleton_type
- @@impls =
{}
Class Method Summary collapse
- .expect_singleton_args?(rcvr_type, method_name) ⇒ Boolean
-
.get_implementation(receiver, method_name, arguments, translator, result_type) ⇒ Object
Returns the implementation (CUDA source code snippet) for a method with name [method_name] defined on [rcvr_type].
-
.get_return_type(rcvr_type, method_name, *arg_types, send_node: nil) ⇒ Object
Retrieves the return type of a method invocation for receiver type [rcvr_type], selector [method_name], and argument types [arg_types].
- .has_implementation?(rcvr_type, method_name) ⇒ Boolean
- .implement(rcvr_type, method_name, return_type, num_params, impl, pass_self: true, expect_singleton_args: false) ⇒ Object
- .is_interpreter_only?(type) ⇒ Boolean
- .should_pass_self?(rcvr_type, method_name) ⇒ Boolean
Class Method Details
.expect_singleton_args?(rcvr_type, method_name) ⇒ Boolean
68 69 70 |
# File 'lib/ruby_core/ruby_integration.rb', line 68 def self.expect_singleton_args?(rcvr_type, method_name) return find_impl(rcvr_type, method_name).expect_singleton_args end |
.get_implementation(receiver, method_name, arguments, translator, result_type) ⇒ Object
Returns the implementation (CUDA source code snippet) for a method with name
- method_name
-
defined on [rcvr_type].
This method also receives references to the receiver AST node and to AST nodes for arguments. In most cases, these AST nodes are directly translated to source code using ‘translator` (a [Translator::ASTTranslator]). However, if an implementation is given through a block ([Proc]), the implementation might decide to not use the translation (e.g., translation of parallel sections in host sections).
- receiver
-
must have a singleton type.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/ruby_core/ruby_integration.rb', line 82 def self.get_implementation(receiver, method_name, arguments, translator, result_type) impl = find_impl(receiver.get_type.singleton_type, method_name) source = impl.implementation if source.is_a?(Proc) source = source.call(receiver, method_name, arguments, translator, result_type) end sub_code = arguments.map do |arg| arg.accept(translator.expression_translator) end sub_types = arguments.map do |arg| arg.get_type end if impl.pass_self sub_code.insert(0, receiver.accept(translator.expression_translator)) sub_types.insert(0, receiver.get_type) end sub_indices = (0...source.length).find_all do |index| source[index] == "#" end substitutions = {} sub_indices.each do |index| if source[index + 1] == "F" # Insert float arg_index = source[index + 2].to_i if arg_index >= sub_code.size raise ArgumentError.new("Argument missing: Expected at least #{arg_index + 1}, found #{sub_code.size}") end substitutions["\#F#{arg_index}"] = code_argument(FLOAT_S, sub_types[arg_index], sub_code[arg_index]) elsif source[index + 1] == "I" # Insert integer arg_index = source[index + 2].to_i if arg_index >= sub_code.size raise ArgumentError.new("Argument missing: Expected at least #{arg_index + 1}, found #{sub_code.size}") end substitutions["\#I#{arg_index}"] = code_argument(INT_S, sub_types[arg_index], sub_code[arg_index]) elsif source[index + 1] == "B" # Insert integer arg_index = source[index + 2].to_i if arg_index >= sub_code.size raise ArgumentError.new("Argument missing: Expected at least #{arg_index + 1}, found #{sub_code.size}") end substitutions["\#B#{arg_index}"] = code_argument(BOOL_S, sub_types[arg_index], sub_code[arg_index]) elsif source[index + 1] == "N" # Numeric, coerce integer to float arg_index = source[index + 2].to_i if arg_index >= sub_code.size raise ArgumentError.new("Argument missing: Expected at least #{arg_index + 1}, found #{sub_code.size}") end if sub_types[arg_index].include?(FLOAT_S) expected_type = FLOAT_S else expected_type = INT_S end substitutions["\#N#{arg_index}"] = code_argument(expected_type, sub_types[arg_index], sub_code[arg_index]) else arg_index = source[index + 1].to_i if arg_index >= sub_code.size raise ArgumentError.new("Argument missing: Expected at least #{arg_index + 1}, found #{sub_code.size}") end substitutions["\##{arg_index}"] = sub_code[arg_index] end end substitutions.each do |key, value| # Do not use `gsub!` here! source = source.gsub(key, value) end return source end |
.get_return_type(rcvr_type, method_name, *arg_types, send_node: nil) ⇒ Object
Retrieves the return type of a method invocation for receiver type [rcvr_type], selector [method_name], and argument types [arg_types].
In addition, this method accepts an optional parameter [node] containing the send node (abstract syntax tree node). That node is passed to type inference procs. This is required for symbolic execution of array commands inside host sections.
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/ruby_core/ruby_integration.rb', line 170 def self.get_return_type(rcvr_type, method_name, *arg_types, send_node: nil) return_type = find_impl(rcvr_type, method_name).return_type num_params = find_impl(rcvr_type, method_name).num_params if return_type.is_a?(Proc) # Return type depends on argument types if num_params.is_a?(Fixnum) && num_params != arg_types.size raise ArgumentError.new( "#{num_params} arguments expected but #{arg_types.size} given") elsif num_params.is_a?(Range) && !num_params.include?(arg_types.size) raise ArgumentError.new( "#{num_params} arguments expected but #{arg_types.size} given") else if send_node == nil return return_type.call(rcvr_type, *arg_types) else return return_type.call(rcvr_type, *arg_types, send_node: send_node) end end else return return_type end end |
.has_implementation?(rcvr_type, method_name) ⇒ Boolean
60 61 62 |
# File 'lib/ruby_core/ruby_integration.rb', line 60 def self.has_implementation?(rcvr_type, method_name) return find_impl(rcvr_type, method_name) != nil end |
.implement(rcvr_type, method_name, return_type, num_params, impl, pass_self: true, expect_singleton_args: false) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/ruby_core/ruby_integration.rb', line 43 def self.implement( rcvr_type, method_name, return_type, num_params, impl, pass_self: true, expect_singleton_args: false) @@impls[rcvr_type][method_name] = Implementation.new( num_params: num_params, return_type: return_type, implementation: impl, pass_self: pass_self, expect_singleton_args: expect_singleton_args) end |
.is_interpreter_only?(type) ⇒ Boolean
8 9 10 11 12 13 14 |
# File 'lib/ruby_core/interpreter.rb', line 8 def self.is_interpreter_only?(type) if !type.is_a?(Types::ClassType) return false end return INTERPRETER_ONLY_CLS_OBJ.include?(type.cls) end |
.should_pass_self?(rcvr_type, method_name) ⇒ Boolean
64 65 66 |
# File 'lib/ruby_core/ruby_integration.rb', line 64 def self.should_pass_self?(rcvr_type, method_name) return find_impl(rcvr_type, method_name).pass_self end |