# encoding: utf-8 # frozen_string_literal: true module Carbon module Tacky # A pseudo function. This is used for definitions in order to better # serialize them. However, each instruction uses a {Concrete::Type} # instead of the LLVM type that it corresponds to, allowing expansion # only upon generation. # # @api semiprivate class Function # A mutable counter for giving out instruction ids. This makes it so # that _all_ instruction ids are unique for every function. # # @api private class Counter # The value. # @return [::Numeric] attr_reader :value # Initialize the counter. # # @param value [::Numeric] The initial value of the counter. def initialize(value = 0) @value = value end # Increments the counter by one, and returns the new value. # # @return [::Numeric] def increment @value += 1 end end # The basic blocks of the function. The first block is expected to be # the entry block; the rest can be in any order. # # @return [] attr_reader :blocks # The instruction id counter. # # @return [Counter] attr_reader :counter # Creates a function with the given parameters and blocks. The # parameters are required; the blocks should not be given. # # @param parameters [] The parameters that are passed # to the function. # @param blocks [] Should not be used. def initialize(parameters, blocks = []) @blocks = blocks @parameters = parameters @counter = Counter.new params freeze build(&Proc.new) if block_given? end # Creates a new {Tacky::Block} with the given name, adding it to the # block list, and returns it. # # @param name [::String] The name of the new block. # @return [Tacky::Block] The new block. def add(name = "") block = Block.new(self, name) @blocks << block block end # Finds the block with the given name, if it exists. Note that multiple # blocks can have the same name and still be disparate. # # @param name [::String] The name of the block to find. # @return [Tacky::Block] If the block can be found. # @return [nil] Otherwise. def find(name) @blocks.find { |b| b.name == name } end # Returns a set of dependencies that the function depends on. The set # is built by asking the constituant blocks for their dependencies, # and merging them all into one set. If no blocks exist, or there are # no dependencies, an empty set is returned. # # @api private # @return [Set] The dependencies of the function. def dependencies @blocks.map(&:dependencies).inject(Set.new, :merge) end # Creates the function for LLVM. This has three main steps: first, mark # the function parameters with names. Second, create the function blocks # for instructional use. Third, call the constituant blocks for them # to build themselves. # # @api private # @param function [::LLVM::Function] The LLVM function. # @param build [Concrete::Build] The build. This should contain all # relevant information for building this function. # @param generics [{::String => Concrete::Type}] The generics that are # being applied to the function. # @return [void] def call(function, build, generics) context = Context.new(build, generics) mark_function_params(context, function) @blocks.each do |block| context.blocks[block] = function.basic_blocks.append(block.name) end.each { |block| block.call(context) } end # The parameters of the function. These are enclosed with a # {Tacky::Parameter} to allow the name of said parameters to be set. # The parameters are given an index and a type. # # @return [Tacky::Parameter] def params @params ||= @parameters.each_with_index.map do |type, i| Tacky::Parameter.new(i, type) end end private def mark_function_params(context, function) params.zip(function.params).each do |(param, arg)| arg.name = param.name context.params << arg end end end end end