class Ikra::Translator::HostSectionCommandTranslator

Public Class Methods

new(root_command:) click to toggle source
# File lib/translator/host_section/array_host_section_command.rb, line 9
def initialize(root_command:)
    super

    # Use a different program builder
    @program_builder = HostSectionProgramBuilder.new(
        environment_builder: environment_builder, 
        root_command: root_command)
end

Public Instance Methods

start_translation() click to toggle source
# File lib/translator/host_section/array_host_section_command.rb, line 18
def start_translation
    Log.info("HostSectionCommandTranslator: Starting translation...")

    # Trace all objects
    @object_tracer = TypeInference::ObjectTracer.new(root_command)
    all_objects = object_tracer.trace_all

    # Translate the command (might create additional kernels)
    root_command.accept(self)

    # Add SoA arrays to environment
    object_tracer.register_soa_arrays(environment_builder)
end
visit_array_host_section_command(command) click to toggle source
# File lib/translator/host_section/array_host_section_command.rb, line 32
def visit_array_host_section_command(command)
    Log.info("Translating ArrayHostSectionCommand [#{command.unique_id}]")

    super

    # A host section must be a top-level (root) command. It uses a special
    # [HostSectionProgramBuilder].

    block_def_node = command.block_def_node

    # Cannot use the normal `translate_block` method here, this is special!
    # TODO: There's some duplication here with [BlockTranslator]

    # Build hash of parameter name -> type mappings
    block_parameter_types = {}
    command.block_parameter_names.each_with_index do |name, index|
        block_parameter_types[name] = command.section_input[index].ikra_type.to_union_type
    end

    parameter_types_string = "[" + block_parameter_types.map do |id, type| "#{id}: #{type}" end.join(", ") + "]"
    Log.info("Translating block with input types #{parameter_types_string}")

    # Add information to block_def_node
    block_def_node.parameters_names_and_types = block_parameter_types

    # Insert return statements (also done by type inference visitor, but we need
    # it now)
    block_def_node.accept(LastStatementReturnsVisitor.new)

    # Insert synthetic __call__ send nodes for return values
    block_def_node.accept(ParallelSectionInvocationVisitor.new)

    # Concert to SSA form
    AST::SSAGenerator.transform_to_ssa!(block_def_node)

    # Type inference
    type_inference_visitor = TypeInference::Visitor.new
    result_type = type_inference_visitor.process_block(block_def_node)

    for singleton_type in result_type
        if !singleton_type.is_a?(Types::LocationAwareArrayType)
            raise AssertionError.new("Return value of host section must be a LocationAwareArrayType. Found a code path with #{singleton_type}.")
        end
    end

    # C++/CUDA code generation
    ast_translator = HostSectionASTTranslator.new(command_translator: self)

    # Auxiliary methods are instance methods that are called by the host section
    aux_methods = type_inference_visitor.all_methods.map do |method|
        ast_translator.translate_method(method)
    end

    # Build C++ function
    function_translation = ast_translator.translate_block(block_def_node)

    # Declare local variables
    block_def_node.local_variables_names_and_types.each do |name, type|
        function_translation.prepend("#{type.to_c_type} #{name};\n")
    end

    mangled_name = "_host_section_#{command.unique_id}_"
    function_parameters = [
        "#{Constants::ENV_TYPE} *#{Constants::ENV_HOST_IDENTIFIER}",
        "#{Constants::ENV_TYPE} *#{Constants::ENV_DEVICE_IDENTIFIER}",
        "#{Constants::PROGRAM_RESULT_TYPE} *#{Constants::PROGRAM_RESULT_IDENTIFIER}"]

    # Define incoming values (parameters). These must all be array commands for now.
    parameter_def = block_parameter_types.map do |name, type|
        if type.singleton_type.is_a?(Symbolic::ArrayCommand)
            # Should be initialized with new array command struct
            "#{type.singleton_type.to_c_type} #{name} = new #{type.singleton_type.to_c_type[0...-2]}();"
        else
            "#{type.singleton_type.to_c_type} #{name};"
        end
    end.join("\n") + "\n"

    translation_result = Translator.read_file(
        file_name: "host_section_block_function_head.cpp",
        replacements: { 
            "name" => mangled_name, 
            "result_type" => result_type.to_c_type,
            "parameters" => function_parameters.join(", "),
            "body" => Translator.wrap_in_c_block(parameter_def + function_translation)})

    program_builder.host_section_source = translation_result

    # Build function invocation
    args = [
        Constants::ENV_HOST_IDENTIFIER, 
        Constants::ENV_DEVICE_IDENTIFIER,
        Constants::PROGRAM_RESULT_IDENTIFIER]

    # Generate code that transfers data back to host. By creating a synthetic send
    # node here, we can let the compiler generate a switch statement if the type of
    # the return value (array) cannot be determined uniquely at compile time.
    host_section_invocation = AST::SourceCodeExprNode.new(
        code: "#{mangled_name}(#{args.join(", ")})")
    host_section_invocation.merge_union_type(result_type)
    device_to_host_transfer_node = AST::SendNode.new(
        receiver: host_section_invocation,
        selector: :__to_host_array__)

    # Type inference is a prerequisite for code generation
    type_inference_visitor.visit_send_node(device_to_host_transfer_node)

    program_builder.host_result_expression = device_to_host_transfer_node.accept(
        ast_translator.expression_translator)
    program_builder.result_type = device_to_host_transfer_node.get_type

    Log.info("DONE translating ArrayHostSectionCommand [#{command.unique_id}]")

    # This method has no return value (for the moment)
end
visit_array_in_host_section_command(command) click to toggle source
# File lib/translator/host_section/array_in_host_section_command.rb, line 4
def visit_array_in_host_section_command(command)
    Log.info("Translating ArrayInHostSectionCommand [#{command.unique_id}]")

    super

    # This is a root command, determine grid/block dimensions
    kernel_launcher.configure_grid(command.size, block_size: command.block_size)

    array_input_id = "_array_#{self.class.next_unique_id}_"
    kernel_builder.add_additional_parameters("#{command.base_type.to_c_type} *#{array_input_id}")

    # Add placeholder for argument (input array). This should be done here to preserve
    # the order or arguments.
    kernel_launcher.add_additional_arguments(proc do |cmd|
        # `cmd` is a reference to the command being launched (which might be merged
        # with other commands). Based on that information, we can generate an
        # expression that returns the input array.
        arg = Translator::KernelLaunchArgumentGenerator.generate_arg(
            command, cmd, "cmd")

        if arg == nil
            raise AssertionError.new("Argument not found: Trying to launch command #{cmd.unique_id}, looking for result of command #{command.unique_id}")
        end

        arg
    end)

    command_translation = build_command_translation_result(
        result: "#{array_input_id}[_tid_]",
        command: command)

    Log.info("DONE translating ArrayInHostSectionCommand [#{command.unique_id}]")

    return command_translation
end