10 Pass(
"IRInliner", debug),
14 void IRInliner::process(
const std::vector<IR::Block>& pages,
const std::vector<std::string>& symbols,
const std::vector<ValTableElem>& values,
const IR::label_t last_label)
25 for (
const auto& block : pages)
31 for (std::size_t i = 0, end = block.data.size(); i < end; ++i)
33 const auto& entity = block.data[i];
35 std::optional<uint16_t> maybe_id;
36 std::size_t argc = std::numeric_limits<std::size_t>::max();
39 if (entity.inst() == CALL_SYMBOL || entity.inst() == CALL_SYMBOL_BY_INDEX)
41 maybe_id = entity.relatedResourceId();
42 argc = entity.secondaryArg();
44 else if (entity.inst() == CALL)
46 maybe_id = entity.relatedResourceId();
47 argc = entity.primaryArg();
51 if (
const auto maybe_block =
blockToInlineInCall(kind, pages, maybe_id, block, argc); maybe_block.has_value())
53 const IR::Block& inlinee = pages[maybe_block->addr];
58 assert(block.data[i + 1].kind() ==
IR::Kind::Label &&
"Expected a label right after the CALL instruction! The AST lowerer messed up somewhere");
60 const IR::label_t return_label = block.data[i + 1].label();
61 const std::size_t removed = std::erase_if(new_block.
data, [return_label](
const IR::Entity& e) ->
bool {
62 return e.kind() == IR::Kind::Goto && e.inst() == PUSH_RETURN_ADDRESS && e.label() == return_label;
66 throw std::runtime_error(fmt::format(
"No PUSH_RETURN_ADDRESS L{} instruction removed, even though one was expected", return_label));
72 new_block.
data.emplace_back(entity);
75 m_ir.emplace_back(new_block);
88 const std::size_t candidate_inst_count = candidate.instructionCount(),
89 source_inst_count = source.instructionCount();
91 if (candidate.metadata.is_closure ||
92 candidate.metadata.is_recursive ||
93 candidate.metadata.is_mutating_args ||
94 candidate.metadata.argument_count != argc ||
96 std::cmp_greater_equal(candidate_inst_count + source_inst_count,
MaxValue16Bits))
99 return candidate.metadata.is_simple && candidate_inst_count < 24;
104 const std::vector<IR::Block>& pages,
105 const std::optional<uint16_t> maybe_id,
107 const std::size_t argc)
const noexcept
109 if (!maybe_id.has_value())
112 const uint16_t
id = maybe_id.value();
113 std::optional<BlockInfo> maybe_block = findBlockBy(kind,
id);
114 if (!maybe_block.has_value())
119 if (kind == CallKind::Symbol && m_symbols_data.contains(
id) && m_symbols_data.at(
id).declarations_count != 1)
122 const std::size_t block_addr = maybe_block->addr;
123 if (canBeInlined(pages[block_addr], current, argc))
139 if (block.
data.size() < 6 || (block.
data.size() - 4) % 2 != 0)
143 std::size_t store_count = 0;
144 std::size_t load_count = 0;
146 std::optional<IR::Entity> call_builtin;
148 for (
const auto& entity : block.
data)
153 if (expected != inst)
155 if (expected == STORE)
156 expected = PUSH_RETURN_ADDRESS;
157 else if (expected == PUSH_RETURN_ADDRESS)
158 expected = LOAD_FAST_BY_INDEX;
159 else if (expected == LOAD_FAST_BY_INDEX)
160 expected = CALL_BUILTIN;
161 else if (expected == CALL_BUILTIN)
163 else if (expected == NOP)
167 if (expected == inst)
169 if (expected == STORE)
171 else if (expected == LOAD_FAST_BY_INDEX)
173 else if (expected == CALL_BUILTIN)
175 call_builtin = entity;
176 if (entity.secondaryArg() != store_count)
179 else if (expected == PUSH_RETURN_ADDRESS)
180 label = entity.label();
182 else if (is_label && label != entity.label())
186 if (store_count == load_count)
198 "Inlining call to '{}' ({} from '{}') inside '{}' @ {}, from '{}'",
201 inlinee.
data.front().filename(),
204 destination.
data.front().filename());
208 m_logger.
info(
" -> builtin proxy with args ({}, {})", inst->primaryArg(), inst->secondaryArg());
210 .emplace_back(CALL_BUILTIN_WITHOUT_RETURN_ADDRESS, inst->primaryArg(), inst->secondaryArg())
211 .setSourceLocation(inst->filename(), inst->sourceLine());
217 destination.
data.emplace_back(CREATE_SCOPE);
223 std::unordered_map<IR::label_t, IR::label_t> old_to_new_label;
227 if (entity.
inst() == RET)
233 if (
auto it = old_to_new_label.find(entity.
label()); it != old_to_new_label.end())
241 destination.
data.emplace_back(labelled_entity);
243 else if (entity.
inst() == LOAD_FAST_BY_INDEX)
247 else if (entity.
inst() == CALL_SYMBOL_BY_INDEX)
252 destination.
data.emplace_back(entity);
255 destination.
data.emplace_back(POP_SCOPE, 1);
260 for (std::size_t i = 0, end = pages.size(); i < end; ++i)
262 const std::string& name = pages[i].debugName();
268 assert(it_val !=
m_values.end() &&
"Could not find a constant referencing the current page!");
270 const auto it_sym = std::ranges::find_if(
m_symbols, [&name](
const std::string& sym) ->
bool {
278 .declarations_count = 0,
284 .constant_id =
static_cast<long>(std::distance(
m_values.begin(), it_val)),
286 .name = pages[i].debugName(),
289 : std::make_optional(std::distance(
m_symbols.begin(), it_sym)) });
297 switch (entity.
inst())
300 case CALL_SYMBOL: [[fallthrough]];
301 case LOAD_FAST: [[fallthrough]];
304 it->second.use_count++;
308 case CALL_SYMBOL_BY_INDEX: [[fallthrough]];
309 case LOAD_FAST_BY_INDEX:
313 it->second.use_count++;
318 case STORE: [[fallthrough]];
319 case STORE_REF: [[fallthrough]];
322 it->second.declarations_count++;
334 const auto it = std::ranges::find_if(
336 [
id, kind](
const BlockInfo& info) ->
bool {
339 case CallKind::Symbol:
342 case CallKind::Constant:
348 if (it != m_funcs.end())
std::vector< IR::Block > m_ir
void extractPagesMetadata(const std::vector< IR::Block > &pages)
Extract metadata from the IR entities pages, to have a name, constant id, and potentially symbol id p...
IRInliner(unsigned debug)
Create a new IRInliner.
IR::label_t m_current_label
static bool canBeInlined(const IR::Block &candidate, const IR::Block &source, std::size_t argc) noexcept
Check if a block can be inlined in another one.
static std::optional< IR::Entity > isBuiltinProxy(const IR::Block &block)
Check if an IR block represents a builtin proxy, and return its CALL instruction if it is.
void process(const std::vector< IR::Block > &pages, const std::vector< std::string > &symbols, const std::vector< ValTableElem > &values, IR::label_t last_label)
Attempt to inline IR blocks to avoid function calls when possible.
std::optional< BlockInfo > findBlockBy(CallKind kind, uint16_t id) const noexcept
Search for a block by one of its IDs.
std::optional< BlockInfo > blockToInlineInCall(CallKind kind, const std::vector< IR::Block > &pages, std::optional< uint16_t > maybe_id, const IR::Block ¤t, std::size_t argc) const noexcept
See if a block can be inlined in the current call site.
std::vector< ValTableElem > m_values
void inlineBlock(const IR::Block &inlinee, IR::Block &destination)
Perform the inlining.
std::vector< BlockInfo > m_funcs
std::vector< std::string > m_symbols
const std::vector< IR::Block > & intermediateRepresentation() const noexcept
Return the IR blocks (one per scope)
std::unordered_map< long, SymbolData > m_symbols_data
uint16_t secondaryArg() const
Return the second argument of the IR Entity.
label_t label() const
Return the label of the IR Entity.
bool hasLabel() const
Check if the Entity has a label attached.
Instruction inst() const
Return the underlying instruction of the IR Entity.
std::size_t sourceLine() const
void replaceLabel(label_t replacement)
uint16_t primaryArg() const
Return the primary argument of the IR Entity (can be 0 if the argument isn't used)
const std::string & filename() const
std::optional< uint16_t > relatedResourceId() const
Return the related constant/symbol id an IR Entity refers to (only populated for LOAD_FAST_BY_INDEX,...
void info(const char *fmt, Args &&... args)
Write an info level log using fmtlib.
void traceStart(std::string &&trace_name)
An interface to describe compiler passes.
constexpr std::string_view AnonymousBlockName
Instruction
The different bytecodes are stored here.
constexpr uint16_t MaxValue16Bits
std::optional< std::size_t > symbol_id
Block of IR entities, with attached metadata.
static Block InitWithMetadata(const Block &source)
Create a new empty IR::Block with the same metadata as the source block.
std::string metadataRepr() const
struct Ark::internal::IR::Block::Metadata metadata
std::string debugName() const
A Compiler Value class helper to handle multiple types.
std::variant< double, std::string, std::size_t > value