Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Compiler_IR_Model.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
51#ifndef COMPILER_IR_MODEL_H
52#define COMPILER_IR_MODEL_H
53
54#include <limits>
55#include <sstream>
56#include <string>
57#include <utility>
58
59#include <Compiler_HIR_Model.H>
60#include <ah-arena.H>
61#include <ah-errors.H>
62#include <tpl_dynArray.H>
63
64namespace Aleph {
65using Compiler_IR_Value_Id = size_t;
69using Compiler_IR_Block_Id = size_t;
70
72inline constexpr size_t compiler_ir_invalid_id() noexcept
73{
74 return std::numeric_limits<size_t>::max();
75}
76
79{
81 Local,
82 Global
83};
84
87{
89 Load,
90 Store,
91 Unary,
92 Binary,
93 Call,
95};
96
99{
100 None,
101 Jump,
102 Branch,
103 Return,
104 Exit,
106};
107
109inline const char *compiler_ir_slot_kind_name(const Compiler_IR_Slot_Kind kind) noexcept
110{
111 switch (kind)
112 {
114 return "Parameter";
116 return "Local";
118 return "Global";
119 }
120 return "Unknown";
121}
122
125{
126 switch (kind)
127 {
129 return "Constant";
131 return "Load";
133 return "Store";
135 return "Unary";
137 return "Binary";
139 return "Call";
141 return "FunctionRef";
142 }
143 return "Unknown";
144}
145
148{
149 switch (kind)
150 {
152 return "None";
154 return "Jump";
156 return "Branch";
158 return "Return";
160 return "Exit";
162 return "Unreachable";
163 }
164 return "Unknown";
165}
166
176
197
207
224
246
254
257{
259 {
260 void *ptr = nullptr;
261 void (*destroy)(void *) noexcept = nullptr;
262 };
263
266
267public:
269 explicit Compiler_IR_Context(const size_t arena_size = AhArenaAllocator::DEFAULT_SIZE)
270 : arena(arena_size)
271 {}
272
275
277 {
278 reset();
279 }
280
282 template <typename T, typename... Args>
284 {
285 T *ptr = allocate<T>(arena, std::forward<Args>(args)...);
286 ah_runtime_error_unless(ptr != nullptr) << "Compiler_IR_Context: arena allocation failed";
287
288 owned.append({ptr,
289 [](void *raw) noexcept
290 {
291 static_cast<T *>(raw)->~T();
292 }});
293 return ptr;
294 }
295
298 {
299 for (size_t i = owned.size(); i > 0; --i)
300 {
301 auto &[ptr, destroy] = owned.access(i - 1);
302 if (destroy != nullptr and ptr != nullptr)
303 destroy(ptr);
304 }
305 owned.clear();
306 arena.reset();
307 }
308};
309
317
318namespace Compiler_IR_Detail {
319inline std::string block_name(const Compiler_IR_Block_Id id)
320{
321 return "B" + std::to_string(id);
322}
323
325 const Compiler_IR_Block_Id id) noexcept
326{
327 for (size_t i = 0; i < ids.size(); ++i)
328 if (ids.access(i) == id)
329 return true;
330 return false;
331}
332
333inline std::string value_name(const Compiler_IR_Value_Id id)
334{
335 return "v" + std::to_string(id);
336}
337
338inline std::string local_slot_name(const Compiler_IR_Local_Slot_Id id)
339{
340 return "s" + std::to_string(id);
341}
342
344{
345 return "g" + std::to_string(id);
346}
347
348inline std::string function_name(const Compiler_IR_Function_Id id)
349{
350 return "f" + std::to_string(id);
351}
352
353inline void append_indented_text(std::ostream &out, const std::string &text, const size_t indent)
354{
355 const std::string padding(indent, ' ');
356 size_t begin = 0;
357 while (begin < text.size())
358 {
359 const auto end = text.find('\n', begin);
360 if (end == std::string::npos)
361 {
362 out << padding << text.substr(begin) << '\n';
363 return;
364 }
365
366 if (end > begin)
367 out << padding << text.substr(begin, end - begin) << '\n';
368 else
369 out << padding << '\n';
370 begin = end + 1;
371 }
372}
373} // namespace Compiler_IR_Detail
374
377 const Compiler_IR_Module *module = nullptr)
378{
380
381 auto fail = [&report](const std::string &msg)
382 {
383 report.valid = false;
384 report.errors.append(msg);
385 };
386
387 if (function.blocks.is_empty())
388 {
389 fail("IR function '" + function.name + "' has no blocks");
390 return report;
391 }
392
393 if (function.entry_block >= function.blocks.size())
394 fail("IR function '" + function.name + "' has invalid entry block");
395 if (function.exit_block >= function.blocks.size())
396 fail("IR function '" + function.name + "' has invalid exit block");
397 if (not report.valid)
398 return report;
399
400 for (size_t i = 0; i < function.blocks.size(); ++i)
401 {
402 const auto &block = function.blocks.access(i);
403 if (block.id != i)
404 fail("IR function '" + function.name + "' has mismatched block id at "
407 fail("IR block " + Compiler_IR_Detail::block_name(i) + " in function '" + function.name
408 + "' is unterminated");
409
410 for (size_t j = 0; j < block.instructions.size(); ++j)
411 {
412 const auto &inst = block.instructions.access(j);
419 and inst.result_id == 0)
420 fail("IR instruction in " + Compiler_IR_Detail::block_name(i) + " of function '"
421 + function.name + "' must define a result");
422
423 if (inst.local_slot_id != compiler_ir_invalid_id()
424 and inst.local_slot_id >= function.local_slots.size())
425 fail("IR instruction references invalid local slot in function '" + function.name + "'");
426 if (inst.global_slot_id != compiler_ir_invalid_id()
427 and (module == nullptr or inst.global_slot_id >= module->global_slots.size()))
428 fail("IR instruction references invalid global slot in function '" + function.name
429 + "'");
430 if (inst.function_id != compiler_ir_invalid_id()
431 and (module == nullptr or inst.function_id >= module->functions.size()))
432 fail("IR instruction references invalid function id in function '" + function.name
433 + "'");
434 }
435
436 switch (block.terminator.kind)
437 {
439 if (block.terminator.successors.size() != 1)
440 fail("Jump terminator in " + Compiler_IR_Detail::block_name(i)
441 + " must have one successor");
442 break;
444 if (block.terminator.condition_value == 0)
445 fail("Branch terminator in " + Compiler_IR_Detail::block_name(i)
446 + " must reference a condition value");
447 if (block.terminator.successors.size() != 2)
448 fail("Branch terminator in " + Compiler_IR_Detail::block_name(i)
449 + " must have two successors");
450 break;
452 if (block.terminator.return_value == 0)
453 fail("Return terminator in " + Compiler_IR_Detail::block_name(i)
454 + " must reference a return value");
455 if (block.terminator.successors.size() != 1)
456 fail("Return terminator in " + Compiler_IR_Detail::block_name(i)
457 + " must point to the exit block");
458 else if (block.terminator.successors.access(0) != function.exit_block)
459 fail("Return terminator in " + Compiler_IR_Detail::block_name(i)
460 + " must point to exit block "
462 break;
464 if (block.id != function.exit_block)
465 fail("Only the canonical exit block may use Exit in function '" + function.name + "'");
466 if (not block.terminator.successors.is_empty())
467 fail("Exit block " + Compiler_IR_Detail::block_name(i) + " must not have successors");
468 break;
471 break;
472 }
473
474 for (size_t j = 0; j < block.terminator.successors.size(); ++j)
475 {
476 const auto succ = block.terminator.successors.access(j);
477 if (succ >= function.blocks.size())
478 fail("IR block " + Compiler_IR_Detail::block_name(i) + " references invalid successor "
480 }
481 }
482
484 for (size_t i = 0; i < function.blocks.size(); ++i)
485 reachable.append(false);
486
488 worklist.append(function.entry_block);
489 while (not worklist.is_empty())
490 {
491 const auto current = worklist.pop();
492 if (reachable.access(current))
493 continue;
494 reachable.access(current) = true;
495 const auto &block = function.blocks.access(current);
496 for (size_t i = 0; i < block.terminator.successors.size(); ++i)
497 {
498 const auto succ = block.terminator.successors.access(i);
499 if (succ < function.blocks.size() and not reachable.access(succ))
500 worklist.append(succ);
501 }
502 }
503
504 for (size_t i = 0; i < function.blocks.size(); ++i)
505 if (not reachable.access(i))
506 report.warnings.append("IR block " + Compiler_IR_Detail::block_name(i) + " in function '"
507 + function.name + "' is unreachable from entry");
508
509 return report;
510}
511
514{
516
517 auto merge = [&report](const Compiler_IR_Validation_Report &child)
518 {
519 if (not child.valid)
520 report.valid = false;
521 for (size_t i = 0; i < child.errors.size(); ++i)
522 report.errors.append(child.errors.access(i));
523 for (size_t i = 0; i < child.warnings.size(); ++i)
524 report.warnings.append(child.warnings.access(i));
525 };
526
527 for (size_t i = 0; i < module.functions.size(); ++i)
528 merge(validate_ir_function(*module.functions.access(i), &module));
529 if (module.top_level != nullptr)
530 merge(validate_ir_function(*module.top_level, &module));
531 return report;
532}
533
535inline std::string compiler_dump_ir_function(const Compiler_IR_Function *function,
536 const Compiler_IR_Module *module = nullptr,
537 const Compiler_Type_Context *types = nullptr)
538{
539 (void) module;
540 std::ostringstream out;
541 if (function == nullptr)
542 {
543 out << "<null-ir-function>\n";
544 return out.str();
545 }
546
547 out << "IRFunction(" << function->name << ")";
548 if (types != nullptr and function->type_id != 0)
549 out << ": " << types->to_string(function->type_id);
550 out << '\n';
551 out << " Entry: " << Compiler_IR_Detail::block_name(function->entry_block) << '\n';
552 out << " Exit: " << Compiler_IR_Detail::block_name(function->exit_block) << '\n';
553
554 if (not function->local_slots.is_empty())
555 {
556 out << " Slots:\n";
557 for (size_t i = 0; i < function->local_slots.size(); ++i)
558 {
559 const auto &slot = function->local_slots.access(i);
560 out << " " << Compiler_IR_Detail::local_slot_name(slot.id) << " ["
561 << compiler_ir_slot_kind_name(slot.kind) << "] " << slot.name;
562 if (types != nullptr and slot.type_id != 0)
563 out << ": " << types->to_string(slot.type_id);
564 out << '\n';
565 }
566 }
567
568 for (size_t i = 0; i < function->blocks.size(); ++i)
569 {
570 const auto &block = function->blocks.access(i);
571 out << " Block " << Compiler_IR_Detail::block_name(block.id) << " [" << block.label << "]\n";
572
573 if (block.instructions.is_empty())
574 out << " Instructions: <none>\n";
575 else
576 {
577 out << " Instructions:\n";
578 for (size_t j = 0; j < block.instructions.size(); ++j)
579 {
580 const auto &inst = block.instructions.access(j);
581 out << " ";
582 if (inst.result_id != 0)
583 out << Compiler_IR_Detail::value_name(inst.result_id) << " = ";
585
587 {
588 out << ' ';
589 if (inst.global_slot_id != compiler_ir_invalid_id())
591 else
593 }
595 {
596 out << ' ';
597 if (inst.global_slot_id != compiler_ir_invalid_id())
599 else
601 }
603 out << ' ' << Compiler_IR_Detail::function_name(inst.function_id);
606 out << '(' << compiler_operator_name(inst.op) << ')';
608 out << '(' << inst.text << ')';
609
610 if (not inst.operands.is_empty())
611 {
612 out << " [";
613 for (size_t k = 0; k < inst.operands.size(); ++k)
614 {
615 if (k > 0)
616 out << ", ";
617 out << Compiler_IR_Detail::value_name(inst.operands.access(k));
618 }
619 out << ']';
620 }
621
622 if (types != nullptr and inst.type_id != 0)
623 out << " : " << types->to_string(inst.type_id);
624 out << '\n';
625 }
626 }
627
628 out << " Terminator: " << compiler_ir_terminator_kind_name(block.terminator.kind);
633 out << '\n';
634
635 out << " Successors:";
636 if (block.terminator.successors.is_empty())
637 out << " <none>\n";
638 else
639 {
640 for (size_t j = 0; j < block.terminator.successors.size(); ++j)
641 {
642 out << (j == 0 ? " " : ", ");
644 }
645 out << '\n';
646 }
647 }
648
649 return out.str();
650}
651
653inline std::string compiler_dump_ir_module(const Compiler_IR_Module *module,
654 const Compiler_Type_Context *types = nullptr)
655{
656 std::ostringstream out;
657 if (module == nullptr)
658 {
659 out << "<null-ir-module>\n";
660 return out.str();
661 }
662
663 out << "IRModule\n";
664 if (not module->global_slots.is_empty())
665 {
666 out << " Globals:\n";
667 for (size_t i = 0; i < module->global_slots.size(); ++i)
668 {
669 const auto &slot = module->global_slots.access(i);
670 out << " " << Compiler_IR_Detail::global_slot_name(slot.id) << " " << slot.name;
671 if (types != nullptr and slot.type_id != 0)
672 out << ": " << types->to_string(slot.type_id);
673 out << '\n';
674 }
675 }
676
677 for (size_t i = 0; i < module->functions.size(); ++i)
679 out, compiler_dump_ir_function(module->functions.access(i), module, types), 2);
680 if (module->top_level != nullptr)
682 out, compiler_dump_ir_function(module->top_level, module, types), 2);
683 return out.str();
684}
685} // namespace Aleph
686
687#endif
Reusable typed high-level IR model independent from any concrete frontend.
Memory arena for fast bulk allocations.
Exception handling system with formatted messages for Aleph-w.
#define ah_runtime_error_unless(C)
Throws std::runtime_error if condition does NOT hold.
Definition ah-errors.H:255
size_t size_t int32_t * out
Definition ca-c-api.h:120
Arena allocator for fast bump-pointer allocation.
Definition ah-arena.H:122
void reset() noexcept
Reset arena, making all memory available again.
Definition ah-arena.H:256
static constexpr size_t DEFAULT_SIZE
Default arena size (1 MB).
Definition ah-arena.H:133
Arena-backed ownership context for IR nodes.
Compiler_IR_Context(const Compiler_IR_Context &)=delete
T * make(Args &&...args)
Allocates and constructs one IR object.
void reset() noexcept
Destroys all tracked objects and rewinds the arena.
DynArray< Owned_Object > owned
Compiler_IR_Context(const size_t arena_size=AhArenaAllocator::DEFAULT_SIZE)
Constructs an IR context with arena_size bytes.
Compiler_IR_Context & operator=(const Compiler_IR_Context &)=delete
Context owning all compiler type nodes.
size_t size() const noexcept
Return the current dimension of array.
T & access(const size_t i) const noexcept
Fast access without checking allocation and bound_min_clock checking.
T & append()
Allocate a new entry to the end of array.
bool is_empty() const noexcept
Return true if the array is empty.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
std::string local_slot_name(const Compiler_IR_Local_Slot_Id id)
std::string value_name(const Compiler_IR_Value_Id id)
void append_indented_text(std::ostream &out, const std::string &text, const size_t indent)
std::string function_name(const Compiler_IR_Function_Id id)
bool contains_block_id(const DynArray< Compiler_IR_Block_Id > &ids, const Compiler_IR_Block_Id id) noexcept
std::string block_name(const Compiler_IR_Block_Id id)
std::string global_slot_name(const Compiler_IR_Global_Slot_Id id)
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Compiler_IR_Instruction_Kind
Instruction kinds supported by the MVP IR.
const char * compiler_ir_terminator_kind_name(const Compiler_IR_Terminator_Kind kind) noexcept
Stable debug name for one terminator kind.
@ Unreachable
Marks code paths that should never be executed (e.g., after a break).
@ Exit
Sentinel terminator for the canonical exit block.
@ Binary
dst <- op(lhs, rhs)
@ Return
Return one register value.
@ Call
dst <- callee(args...)
@ Branch
pc <- condition ? true_target : false_target
size_t Compiler_IR_Function_Id
@ Function_Ref
Reference to one bytecode function in the current module.
std::string compiler_dump_ir_module(const Compiler_IR_Module *module, const Compiler_Type_Context *types=nullptr)
Dumps all lowered IR in one module deterministically.
const char * compiler_operator_name(const Compiler_Operator_Kind kind) noexcept
Returns a stable debug name for one operator kind.
Compiler_IR_Validation_Report validate_ir_module(const Compiler_IR_Module &module)
Validates all functions in one lowered IR module.
and
Check uniqueness with explicit hash + equality functors.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
size_t Compiler_IR_Value_Id
constexpr size_t compiler_ir_invalid_id() noexcept
Returns the sentinel invalid IR id.
size_t Compiler_Type_Id
size_t Compiler_IR_Block_Id
const char * compiler_ir_slot_kind_name(const Compiler_IR_Slot_Kind kind) noexcept
Stable debug name for one slot kind.
Itor3 merge(Itor1 source1Beg, Itor1 source1End, Itor2 source2Beg, Itor2 source2End, Itor3 destBeg)
Merge two sorted ranges.
Definition ahAlgo.H:1410
size_t Compiler_IR_Global_Slot_Id
Compiler_IR_Validation_Report validate_ir_function(const Compiler_IR_Function &function, const Compiler_IR_Module *module=nullptr)
Validates one lowered IR function structurally.
const char * compiler_ir_instruction_kind_name(const Compiler_IR_Instruction_Kind kind) noexcept
Stable debug name for one instruction kind.
Compiler_IR_Terminator_Kind
Terminator kinds supported by the MVP IR.
Compiler_Operator_Kind
Stable operator kinds shared by reusable compiler layers.
size_t Compiler_IR_Local_Slot_Id
Compiler_IR_Slot_Kind
Storage-space classification for slots.
std::string compiler_dump_ir_function(const Compiler_IR_Function *function, const Compiler_IR_Module *module=nullptr, const Compiler_Type_Context *types=nullptr)
Dumps one lowered IR function deterministically.
One basic block of IR instructions.
std::string label
Deterministic debug label.
Compiler_IR_Terminator terminator
Explicit block terminator.
DynArray< Compiler_IR_Block_Id > predecessors
Predecessor block ids.
DynArray< Compiler_IR_Instruction > instructions
Linear instruction list.
Source_Span span
Aggregate span covered by instructions and terminator.
bool is_terminated() const noexcept
Returns whether the block already has a terminator.
Lowered IR for one function or top-level body.
Compiler_IR_Value_Id next_value_id
Next value id to allocate while lowering.
DynArray< Compiler_IR_Block > blocks
Basic blocks in deterministic id order.
bool is_top_level() const noexcept
Returns whether this IR function represents the top-level body.
std::string name
Debug or source-level name.
Compiler_Type_Id type_id
Full function type, when known.
Compiler_IR_Block_Id exit_block
Canonical exit block.
Compiler_IR_Block_Id entry_block
Canonical entry block.
DynArray< Compiler_IR_Slot > local_slots
Parameter/local slots in stable order.
const Compiler_HIR_Function * source_function
Originating HIR function, or nullptr for top-level.
Source_Span span
Source span of the function/body.
One instruction producing an optional explicit result value.
std::string text
Constant spelling or debug payload.
bool bool_value
Decoded boolean payload for bool constants.
Compiler_IR_Instruction_Kind kind
Instruction category.
Compiler_IR_Value_Id result_id
Produced value id, or 0 for void instructions.
DynArray< Compiler_IR_Value_Id > operands
Input values in deterministic order.
Source_Span span
Source region associated with the instruction.
Compiler_Type_Id type_id
Result type or stored value type.
Compiler_Operator_Kind op
Unary/binary operator kind, when relevant.
Compiler_IR_Local_Slot_Id local_slot_id
Referenced local/parameter slot, when relevant.
Compiler_IR_Function_Id function_id
Referenced function id, when relevant.
Compiler_IR_Global_Slot_Id global_slot_id
Referenced global slot, when relevant.
Lowered IR module with shared global slots and functions.
DynArray< Compiler_IR_Slot > global_slots
Module-wide global slots.
DynArray< Compiler_IR_Function * > functions
Lowered non-top-level functions.
Compiler_IR_Function * top_level
Optional top-level body lowered as a function.
One storage slot used by the IR.
Source_Span span
Declaration span.
std::string name
Debug name of the slot.
Compiler_Type_Id type_id
Declared or inferred slot type.
Compiler_IR_Slot_Kind kind
Slot storage category.
Explicit terminator for one IR basic block.
Compiler_IR_Terminator_Kind kind
Terminator category.
DynArray< Compiler_IR_Block_Id > successors
Successor blocks in deterministic order.
Source_Span span
Source region associated with the terminator.
Compiler_IR_Value_Id condition_value
Branch condition value, if any.
Compiler_IR_Value_Id return_value
Return value, if any.
Structural validation report for one IR module or function.
bool valid
Whether all hard validation checks passed.
DynArray< std::string > warnings
Non-fatal findings such as unreachable blocks.
DynArray< std::string > errors
Hard validation failures.
Compiler_SSA_Block_Id id
Dense SSA block id.
Definition SSA.H:163
DynArray< Compiler_SSA_Instruction > instructions
Linear SSA instruction list.
Definition SSA.H:168
std::string label
Deterministic debug label.
Definition SSA.H:165
Compiler_SSA_Terminator terminator
Explicit block terminator.
Definition SSA.H:169
Compiler_SSA_Value_Id return_value
Return value, when relevant.
Definition SSA.H:156
DynArray< Compiler_SSA_Block_Id > successors
Successors in deterministic order.
Definition SSA.H:157
Compiler_IR_Terminator_Kind kind
Terminator category.
Definition SSA.H:153
Compiler_SSA_Value_Id condition_value
Branch condition, when relevant.
Definition SSA.H:155
Represents a missing value.
Half-open byte range inside a source file.
Definition ah-source.H:100
static int * k
Lazy and scalable dynamic array implementation.