ast
無料Manual AST construction with FunctionBuilder for kernels and callables without DSL sugar.
日本語の概要は準備中です。原文の説明を表示しています。
XIR transformation pass authoring under src/xir/passes/.
インストールする前に、エージェントに与えられる指示の中身を確認できます。
This skill captures hard-won knowledge from implementing the CFG normalization pipeline. Read before touching anything under src/xir/passes/ or include/luisa/xir/passes/.
include/luisa/xir/passes/<name>.hsrc/xir/passes/<name>.cppsrc/xir/CMakeLists.txt (look for the passes/ block, ~line 80-90; alphabetical).src/tests/unit/xir/test_xir_pass_<name>.cpp, registered in src/tests/CMakeLists.txt (look for test_xir_pass_* block).Every pass exposes a <Name>Info POD with counters plus two entry points:
struct FooPassInfo {
size_t did_something_count = 0u;
};
[[nodiscard]] LUISA_XIR_API FooPassInfo foo_pass_run_on_function(Function *function) noexcept;
[[nodiscard]] LUISA_XIR_API FooPassInfo foo_pass_run_on_module(Module *module, PassReport *report = nullptr) noexcept;
The function-level entry point should accept Function * (so it also works on external declarations) and use function->definition() to obtain a FunctionDefinition * before touching basic blocks. The module entry point iterates module->function_list() and dispatches by function->definition():
FooPassInfo foo_pass_run_on_module(Module *module, PassReport *report) noexcept {
FooPassInfo info;
for (auto *func : module->function_list()) {
if (auto def = func->definition()) {
info = foo_pass_run_on_function(func); // or operate directly on def
}
}
if (report != nullptr) {
report->set("did_something", info.did_something_count);
}
return info;
}
Some passes (e.g., sroa_pass_run_on_module, algebraic_simplify_pass_run_on_module) also take an options struct before PassReport *. Always consult the header for the exact signature.
Most module-level passes can write statistics into a PassReport:
PassReport report;
auto info = dce_pass_run_on_module(&m, &report);
for (auto &e : report.entries()) {
// e.key, e.value
}
For end-to-end pipelines, prefer the canned pipelines in pass_pipeline.h:
auto pipeline = create_basic_optimization_pipeline({.enable_fast_math = false});
auto stats = pipeline.run(&m);
stats.log("my-pipeline");
Custom pipelines can be built with PassPipeline::add (single run) and PassPipeline::add_fixed_point (fixed-point sub-pipeline).
// namespace foo trailers after namespace closing braces.// given / when / then) is useful in tests when it clarifies the fixture and expected transform.module->function_list() // iterable (NOT `functions()`)
func->is_definition() // true for kernel/callable, false for external
auto def = func->definition(); // returns FunctionDefinition* (nullptr for external)
if (auto def = func->definition()) { ... } // preferred pattern; never cast_or_null
def->body_block() // entry block (NEVER remove)
def->create_basic_block() // orphan block
def->basic_blocks() // ManagedIntrusiveList<BasicBlock>
def->traverse_basic_blocks(visitor) // walks only reachable blocks from body_block
block->instructions() // ManagedIntrusiveList<Instruction>
block->instructions().empty() // always false if terminator present
block->instructions().front() // first inst
block->is_terminated() // true when the last instruction is a terminator
block->terminator() // last inst (may be nullptr if malformed/unterminated)
block->traverse_instructions(visitor)
block->traverse_predecessors(exclude_self, visit) // visits via use list
block->traverse_successors(exclude_self, visit) // visits terminator's target operands
block->remove_self() // returns ManagedPtr<BasicBlock>; detaches from func block list
if (auto v = inst->condition(); v->isa<Constant>()) {
auto c = static_cast<Constant*>(v);
bool b = c->as<bool>(); // checks size; safe for bool
}
condition() is the getter on ConditionalBranchInst / IfInst. For other instruction kinds use the appropriate value getter (value(), operand(i), etc.).
XIR does not have cast_or_null<> or LLVM-style cast<>. Use:
if (v->isa<SomeType>()) {
auto s = static_cast<SomeType*>(v);
...
}
For instruction-tag switch: inst->derived_instruction_tag() returns DerivedInstructionTag::*.
| Terminator | Header | Key API |
|---|---|---|
BranchInst (br) | instructions/branch.h | target_block(), set_target_block(BasicBlock*) |
ConditionalBranchInst (cond_br) | instructions/branch.h | Getters: condition(), true_block(), false_block(). Setters: set_true_target / set_false_target (asymmetric naming — getter says block, setter says target) |
SwitchInst | instructions/switch.h | value(), default_block(), case_count(), case_value(i), case_block(i), set_case_block(i, bb), set_default_block(bb), add_case(v, bb) |
ReturnInst | instructions/return.h | value() |
UnreachableInst | instructions/unreachable.h | none |
RasterDiscardInst | instructions/raster_discard.h | none |
IfInst (structured) | instructions/if.h | condition(), true_block(), false_block(), merge_block() |
LoopInst (structured) | instructions/loop.h | prepare_block(), body_block(), update_block(), merge_block(). No condition() getter. AST lowering normally creates prepare: cond_br(cond, body, merge), but restructure_cfg may create an internally exiting natural loop with prepare: br(body). Both retain distinct prepare/body/update/merge roles. Setters: set_prepare_block, set_body_block, set_update_block. Creators: create_prepare_block(overwrite=false), create_body_block(...), create_update_block(...). |
SimpleLoopInst (structured) | instructions/loop.h | body_block(), merge_block() |
BreakInst (structured) | instructions/break.h | target_block() |
ContinueInst (structured) | instructions/continue.h | target_block() |
RayQueryLoopInst (structured) | instructions/ray_query.h | dispatch_block(), merge_block() |
RayQueryDispatchInst | instructions/ray_query.h | query_object(), on_surface_candidate_block(), on_procedural_candidate_block() (parent is RayQueryLoopInst) |
After Pipeline B destructure_cfg, only the unstructured terminators + SwitchInst + ReturnInst + UnreachableInst + RasterDiscardInst remain.
XIRBuilder b;
b.set_insertion_point(block); // or block->instructions().front() etc.
b.br(target) // BranchInst
b.cond_br(cond, true_target, false_target)
b.if_(cond) // returns IfInst*; populate sub-blocks via if->true_block() etc.
b.loop() // LoopInst*; fill prepare/body/update
b.simple_loop() // SimpleLoopInst*; fill body
b.ray_query_loop() // 0 args; query object only passed to ray_query_dispatch
b.ray_query_dispatch(query_value) // inside dispatch_block
b.call(type, op, operands) // typed call (read ops)
b.call(op, operands) // void call (write ops, e.g., RQ PROCEED)
b.return_(value)
b.unreachable_()
b.break_(target) // structured
b.continue_(target) // structured
For RQ primitive ops (include/luisa/xir/op.h ~line 170-187):
RayQueryObjectReadOp::IS_TERMINATED, IS_TRIANGLE_CANDIDATE, IS_PROCEDURAL_CANDIDATE, ...RayQueryObjectWriteOp::PROCEED, COMMIT_TRIANGLE, COMMIT_PROCEDURAL, TERMINATEYou cannot reliably mutate the instruction list while iterating it. Pattern from lower_break_continue.cpp:
luisa::vector<IfInst*> to_lower;
def->traverse_basic_blocks([&](BasicBlock *bb) {
if (auto t = bb->terminator(); t && t->isa<IfInst>()) {
to_lower.push_back(static_cast<IfInst*>(t));
}
});
for (auto if_inst : to_lower) {
auto bb = if_inst->parent_block();
auto true_b = if_inst->true_block();
auto false_b = if_inst->false_block();
auto cond = if_inst->condition();
if_inst->remove_self();
XIRBuilder b; b.set_insertion_point(bb);
b.cond_br(cond, true_b, false_b);
}
For passes that grow the worklist (e.g., RayQueryLoop → new LoopInst → re-process), wrap in a fixed-point loop:
bool changed = true;
while (changed) {
changed = false;
luisa::vector<...> worklist;
def->traverse_basic_blocks(...);
if (!worklist.empty()) { changed = true; rewrite(); }
}
To redirect every reference to block from in a terminator to point at to:
auto retarget = [&](Instruction *term, BasicBlock *from, BasicBlock *to) {
switch (term->derived_instruction_tag()) {
case DerivedInstructionTag::BRANCH: {
auto br = static_cast<BranchInst*>(term);
if (br->target_block() == from) br->set_target_block(to);
break;
}
case DerivedInstructionTag::CONDITIONAL_BRANCH: {
auto cb = static_cast<ConditionalBranchInst*>(term);
if (cb->true_target() == from) cb->set_true_target(to);
if (cb->false_target() == from) cb->set_false_target(to);
break;
}
case DerivedInstructionTag::SWITCH: {
auto sw = static_cast<SwitchInst*>(term);
if (sw->default_block() == from) sw->set_default_block(to);
for (size_t i = 0; i < sw->case_count(); ++i) {
if (sw->case_block(i) == from) sw->set_case_block(i, to);
}
break;
}
default: break;
}
};
def->traverse_basic_blocks(...) already walks only reachable blocks from body_block(). To remove unreachable blocks:
luisa::unordered_set<BasicBlock*> reachable;
def->traverse_basic_blocks([&](BasicBlock *bb) { reachable.insert(bb); });
luisa::vector<BasicBlock*> dead;
for (auto bb : def->basic_blocks()) {
if (!reachable.contains(bb)) dead.push_back(bb);
}
for (auto bb : dead) bb->remove_self();
Always preserve def->body_block() — never remove it even if it looks empty.
doctest?)XIR unit tests live in src/tests/unit/xir/. Check existing test_xir_pass_*.cpp for framework; they use the project's chosen harness (was boost::ut last checked, see /test skill).
Key test fixtures:
Module m;
auto *k = m.create_kernel(); // KernelFunction*
auto body = k->create_body_block(); // entry BB
// or:
auto *c = m.create_callable(Type::of<float>()); // CallableFunction*
auto def = static_cast<FunctionDefinition*>(k); // both kernel/callable are FunctionDefinitions
XIRBuilder b;
b.set_insertion_point(body);
// build IR ...
b.return_void();
auto info = my_pass_run_on_function(def);
Reachability gotcha: traverse_basic_blocks only visits blocks reachable from body_block. If you build orphan blocks for a test, you must wire them up via br/cond_br from body_block or the pass will see nothing. Trick: m.create_constant_one(Type::of<bool>()) + cond_br(true_const, target, other) to force reachability.
Master plan: src/xir/passes/CFG_NORMALIZATION_PLAN.md.
| Pass | Status | File |
|---|---|---|
Pipeline A lower_break_continue | ✅ done (12 tests) | lower_break_continue.{h,cpp} |
Pipeline A lower_ray_query_loop | ✅ existing (lowers to RayQueryPipelineInst — NOT reusable for Pipeline B) | lower_ray_query_loop.{h,cpp} |
Pipeline A lower_ray_query_loop_to_loop | ✅ done (lowers to structured LoopInst + nested IfInst dispatch) | lower_ray_query_loop_to_loop.{h,cpp} |
Pipeline A early_return_elimination | ✅ done (implemented + unit tests) | early_return_elimination.{h,cpp} |
Pipeline B Pass 1 destructure_cfg | ✅ done (12 tests, 46 asserts) | destructure_cfg.{h,cpp} |
Pipeline B Pass 2 simplify_cfg | ✅ done (8 tests, 22 asserts) | simplify_cfg.{h,cpp} |
Pipeline B Pass 3 restructure_cfg | ✅ done (unit tests) | restructure_cfg.{h,cpp} |
| Structured switch | ✅ SwitchInst is preserved; raw multi-way CFG uses IndexedBranchInst and restructure_cfg reconstructs the merge | switch.{h,cpp}, indexed_branch.{h,cpp} |
convergence_region | ✅ done (region analysis used by restructure_cfg) | convergence_region.{h,cpp} |
early_cse | ✅ done (local common subexpression elimination) | early_cse.{h,cpp} |
pass_pipeline | ✅ done (driver + canned pipelines) | pass_pipeline.{h,cpp} |
| Round-trip Pipeline B test | ✅ verified (path_tracing_cutout PSNR>30) | via test_path_tracing_cutout vk |
Note: src/xir/passes/CFG_NORMALIZATION_PLAN.md is the historical master plan; the table above reflects the current implementation state.
destructure_cfg lowerings (reference)IfInst → cond_br(cond, true, false); merge_block reachable via inner brs.LoopInst → br(prepare).SimpleLoopInst → br(body).BreakInst / ContinueInst → br(target).RayQueryLoopInst → emit LoopInst{prepare→body, body: PROCEED + cond_br cascade on IS_TERMINATED→merge / IS_TRIANGLE_CANDIDATE→on_surface / IS_PROCEDURAL_CANDIDATE→on_procedural / else→update, update→prepare}; rewrite child br dispatch_block → br update_block; remove orphaned RayQueryDispatchInst. New LoopInst destructured on next fixed-point iteration.SwitchInst preserved as-is; recursion handled naturally by traverse_basic_blocks.simplify_cfg opscond_br fold → br.br C terminator; redirect all preds; never remove body_block).body_block, remove rest).folded_constant_cond_br_count, threaded_empty_block_count, merged_straight_line_count, removed_unreachable_block_count.cast_or_null<T>(v) — doesn't exist. Use isa<T> + static_cast.set_true_block / set_false_block on ConditionalBranchInst — wrong names. Asymmetric: getters are true_block() / false_block(), setters are set_true_target / set_false_target.module->functions() — wrong. Use module->function_list().Function * is a definition and casting with static_cast<FunctionDefinition*>(func) — unsafe. Use func->definition(); it returns nullptr for external functions.PassReport *report on module entry points — most passes now take Module *module, PassReport *report = nullptr. Omitting it compiles, but pass pipelines and tests may expect report entries.inst->cond() — does not exist. The condition getter is condition() (on ConditionalBranchInst / IfInst).b.ray_query_loop(query) — wrong; takes 0 args. Pass query to ray_query_dispatch.body_block() — never. Even if empty, it must stay.traverse_basic_blocks will skip them silently.SwitchInst case-block contents structurally — Pipeline B preserves switches; only fold/thread within cases.LoopInst::condition() — does not exist. Inspect the prepare terminator first. AST-canonical loops use cond_br(cond, body, merge), while internally exiting loops recovered by restructure_cfg may use br(body). Only cast to ConditionalBranchInst after checking the tag. There is no set_condition; rewrite the prepare-block terminator instead.PhiInst nodes — splitting/inserting blocks (preheaders, latches, exit stubs) invalidates phi incoming_blocks. Run reg2mem_pass_run_on_module before restructure_cfg_pass_run_on_module so the input is phi-free; assert this as a precondition.ReturnInst, UnreachableInst, RasterDiscardInst in different blocks) yield wrong/null ipostdoms for blocks whose successors reach different sinks. Add a synthetic virtual exit that all sinks point to before running the iterative ipostdom algorithm.restructure_cfg and SSA recovery in a structured backend pipeline — generic cleanup belongs in the raw/destructured CFG interval, before restructuring. DCE preserves a constant-false canonical LoopInst::prepare_block() conditional branch, but that safeguard does not authorize post-restructure cleanup: other structured role arms can still be folded or erased. For native SPIR-V, use the backend's targeted inactive-role payload cleanup, then run mem2reg immediately to recover SSA. A generic pipeline that truly needs later cleanup must first lower or otherwise protect every structured role and reverify the resulting boundary.IfInst/SwitchInst merge during recursive CFG traversal — lowered break/continue edges may bypass the local merge and escape the enclosing region. Carry the immutable outer boundary alongside the current local merge and stop at either.backward_marker_block -> backward_block, replace the AutodiffScopeInst with parent -> entry, and only then call reg2mem_pass_repair_cross_block_rvalue_uses_on_function. The narrow repair snapshots branch-local primal rvalues used by mirrored backward control flow without lowering unrelated Phi nodes. It deliberately ignores Phi edge operands and lvalue definitions; downstream final mem2reg must consume the typed CROSS_BLOCK spills before codegen.PhiInst may legally use itself as the incoming value on a backedge to preserve the previous iteration's value. SPIR-V represents this directly with a self-referencing OpPhi. fix_self_referential repairs malformed aggregate INSERT cycles and must leave legal Phi self-references alone.RestructureCFGInfo::succeeded() before using the result.OpCopyMemory on OpTypeRayQueryKHR in SPIR-V emission — forbidden since Rev 15. Instead, remap _value_map[store->variable()] = val so subsequent loads resolve to the source variable directly.src/xir/passes/CFG_NORMALIZATION_PLAN.md as a task tracker — it is the historical design doc and contains unchecked items that are already implemented (e.g., early_return_elimination, restructure_cfg). Use the table above and the actual headers/sources as the source of truth.Optimization passes (GVN, DCE, SCCP) must respect memory effects. Instructions fall into three categories:
| Tag | Examples |
|---|---|
ARITHMETIC | all ops — no memory side effects |
CAST | all cast ops |
GEP | pointer arithmetic only, no dereference |
RESOURCE_QUERY | buffer_size, texture_size — read-only metadata |
CLOCK | hardware timer read — treated as a memory read (non-deterministic, not safe to value-number or reorder across loop iterations) |
| Tag | Examples |
|---|---|
LOAD | local alloca/GEP load |
RESOURCE_READ | buffer_read, texture_read, byte_buffer_read |
RAY_QUERY_OBJECT_READ | IS_TERMINATED, COMMITTED_HIT, etc. — reads mutable per-thread ray query state that changes after PROCEED/COMMIT/TERMINATE |
| Tag | Examples |
|---|---|
STORE | local alloca/GEP store |
RESOURCE_WRITE | buffer_write, texture_write, byte_buffer_write |
CALL (to definitions) | may have arbitrary side effects |
ATOMIC | read-modify-write |
PRINT | observable side effect |
ASSERT / ASSUME | control flow / UB |
AUTODIFF_INTRINSIC (non-GRADIENT) | tape manipulation |
GVN: only value-number pure instructions + RESOURCE_QUERY. RESOURCE_READ and LOAD require memory dependency analysis (not yet implemented) to prove no intervening write.
DCE: remove instructions with use_list().empty() ONLY if they are pure or memory-reading. Never remove writes, atomics, calls to definitions, prints, or asserts.
SCCP: only fold ARITHMETIC on constant operands. Branch elimination is safe (replaces cond_br with br) but must call term->remove_self() BEFORE builder.set_insertion_point(block) — otherwise the builder targets the tail sentinel and asserts.
Code motion: pure instructions can be hoisted/sunk freely. Reads can be hoisted past other reads but not past writes to the same resource. Writes cannot be reordered with respect to other accesses to the same resource.
is_safe_to_remove (used by GVN/DCE cleanup): checks use_list().empty() + instruction tag whitelist. Current whitelist: PHI, ALLOCA, LOAD, GEP, ARITHMETIC, CAST, CLOCK, RAY_QUERY_OBJECT_READ, RESOURCE_QUERY, RESOURCE_READ, AUTODIFF_INTRINSIC(GRADIENT).
Use get_memory_info() from helpers.h:
#include "helpers.h" // from src/xir/passes/
auto info = get_memory_info(inst);
info.is_pure() // no memory effects, not volatile
info.reads_memory() // LOCAL or GLOBAL read
info.writes_memory() // LOCAL or GLOBAL write
info.is_removable_if_unused() // safe to DCE (no writes, not volatile)
info.is_safe_to_value_number() // safe for GVN/CSE (pure only)
info.scope // NONE, LOCAL, GLOBAL
info.effects // NONE, READ, WRITE, READ_WRITE
info.is_volatile // barriers, prints, asserts — never remove/reorder
MemoryScope::LOCAL = alloca/load/store (function-private memory).
MemoryScope::SHARED = workgroup-shared memory (thread_group barriers/ops).
MemoryScope::GLOBAL = buffers, textures, atomics.
Two instructions with different scopes cannot alias. Two LOCAL instructions alias only if they trace to the same alloca (use trace_pointer_base_local_alloca_inst). SHARED memory is visible to all threads in a workgroup — never reorder across barriers.
Create the build directory with the project's bootstrap script if it does not exist:
python bootstrap.py cmake -f cuda -c -o cmake-build-release
Then use the CMake build directory:
# Build XIR library only (fast iteration)
cmake --build cmake-build-release --target luisa-compute-xir -j
# Build one specific pass test
cmake --build cmake-build-release --target test_xir_pass_destructure_cfg -j
# Run the test binary directly
cmake-build-release/bin/test_xir_pass_destructure_cfg
# Or run all XIR pass tests via ctest
ctest --test-dir cmake-build-release -R xir_pass --output-on-failure
Build dir convention: cmake-build-release. On CI you may see build-cmake-verify; the commands above work there too if you substitute the directory name.
When debugging or implementing an XIR pass, the LLVM project has similar passes for reference. Below is the mapping from XIR pass file to the closest LLVM implementation(s).
| XIR Pass | LLVM Equivalent(s) | Notes |
|---|---|---|
aggregate_field_bitmask | — | XIR-specific aggregate field bit-range analysis. |
algebraic_simplify | llvm/lib/Transforms/InstCombine/InstCombineAndOrXor.cpp (and siblings) | Peephole algebraic simplifications; also see AggressiveInstCombine. |
alias_analysis | llvm/lib/Analysis/BasicAliasAnalysis.cpp | Basic and type-based alias analysis. |
autodiff | — | XIR-specific autodiff pass. Reverse mode closes the generated CFG with a narrow cross-block-rvalue reg2mem repair so the pass returns dominance-valid XIR. |
call_graph | llvm/lib/Analysis/CallGraph.cpp | Call-graph construction and SCC passes. |
const_fold | llvm/lib/Analysis/ConstantFolding.cpp | Constant folding of instructions and intrinsics. |
convergence_region | — | XIR-specific convergence-region / region-of-interest analysis used by restructure_cfg. |
cvp | llvm/lib/Transforms/Scalar/CorrelatedValuePropagation.cpp | Correlated value propagation (range/branch info). |
dce | llvm/lib/Transforms/Scalar/DCE.cpp | Standard dead-code elimination. Also see ADCE.cpp, BDCE.cpp, GlobalDCE.cpp. |
dead_arg_elim | llvm/lib/Transforms/IPO/DeadArgumentElimination.cpp | Remove unused arguments from internal functions. |
dead_store_elimination | llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp | Memory-write DSE (FastDSE / ClassicDSE). |
destructure_cfg | llvm/lib/Transforms/Scalar/StructurizeCFG.cpp | XIR: structured → unstructured. LLVM StructurizeCFG does the inverse (unstructured → structured). Also see FixIrreducible.cpp. |
div_rem_pairs | llvm/lib/Transforms/Scalar/DivRemPairs.cpp | Combine div/rem into a single instruction. |
dom_tree | llvm/lib/IR/Dominators.cpp | Dominator tree construction. |
early_cse | llvm/lib/Transforms/Scalar/EarlyCSE.cpp | Local common subexpression elimination. |
early_return_elimination | llvm/lib/Transforms/Utils/UnifyFunctionExitNodes.cpp | XIR-specific early-return elimination for structured CFG. |
fix_self_referential | — | XIR-specific fix for self-referential INSERT operands after buggy optimizations. |
gvn | llvm/lib/Transforms/Scalar/GVN.cpp / NewGVN.cpp | Global value numbering and redundant-load elimination. |
if_conversion | llvm/lib/CodeGen/IfConversion.cpp | Machine-level if-conversion (predication). |
indvar_simplify | llvm/lib/Transforms/Scalar/IndVarSimplify.cpp | Canonicalize and simplify induction variables. |
inline | llvm/lib/Transforms/IPO/Inliner.cpp | Call-site inlining heuristics and implementation. Also see InlineFunction.cpp. |
lex_scope_analysis | — | XIR-specific lexical-scope analysis. |
licm | llvm/lib/Transforms/Scalar/LICM.cpp | Loop-invariant code motion. |
local_load_elimination | llvm/lib/Transforms/Utils/Local.cpp | Local redundant-load elimination (part of GVN/local CSE). |
local_store_forward | llvm/lib/Transforms/Utils/Local.cpp | Store-to-load forwarding (part of local memory opts). |
loop_rotation | llvm/lib/Transforms/Scalar/LoopRotation.cpp | Rotate loops to place test at bottom. |
loop_fusion | llvm/lib/Transforms/Scalar/LoopFuse.cpp | Fuse adjacent loops with compatible bounds. |
loop_vectorization | llvm/lib/Transforms/Vectorize/LoopVectorize.cpp | Vectorize innermost loops (widening). |
slp_vectorization | llvm/lib/Transforms/Vectorize/SLPVectorizer.cpp | Superword-level parallelism (horizontal SIMD). |
| Generic loop unroll | llvm/lib/Transforms/Utils/LoopUnroll.cpp | No generic XIR pass: it was removed because its structured-CFG correctness contract was not established. Autodiff's private bounded semantic expansion and SPIRV-Tools unrolling are separate mechanisms. |
lower_break_continue | — | XIR-specific lowering of structured BreakInst/ContinueInst. |
lower_ray_query_loop | — | XIR-specific ray-query pipeline lowering. |
lower_ray_query_loop_to_loop | — | XIR-specific ray-query → structured LoopInst lowering. |
| Generic lower switch | llvm/lib/Transforms/Utils/LowerSwitch.cpp | No generic XIR pass: SwitchInst is a first-class structured terminator. destructure_cfg alone maps it to raw IndexedBranchInst, and restructure_cfg rebuilds the switch merge. |
mem2reg | llvm/lib/Transforms/Utils/Mem2Reg.cpp | Promote memory to registers (alloca → SSA). Also see PromoteMemToReg.h. |
outline | llvm/lib/Transforms/IPO/IROutliner.cpp | Outlining similar instruction sequences. |
pass_pipeline | — | XIR pass pipeline driver. |
phi_cleanup | llvm/lib/Transforms/Utils/BasicBlockUtils.cpp | Trivial/dead PHI removal (DeleteDeadPHIs, simplifyPHINode). |
pointer_usage | — | Stub / XIR-specific pointer-usage tracking. |
post_dom_tree | llvm/lib/Analysis/PostDominators.cpp | Post-dominator tree construction. |
promote_ref_arg | llvm/lib/Transforms/IPO/ArgumentPromotion.cpp | XIR-specific promotion of reference arguments to value arguments. |
reassociate | llvm/lib/Transforms/Scalar/Reassociate.cpp | Reassociate expressions for CSE/constant folding. |
reg2mem | llvm/lib/Transforms/Scalar/Reg2Mem.cpp | Demote SSA to memory (inverse of mem2reg). |
restructure_cfg | llvm/lib/Transforms/Scalar/StructurizeCFG.cpp | XIR: unstructured → structured. LLVM StructurizeCFG is the closest analog. Also see FixIrreducible.cpp. |
scalar_evolution | llvm/lib/Analysis/ScalarEvolution.cpp | Analyze evolution of scalar expressions in loops. |
scalarizer | llvm/lib/Transforms/Scalar/Scalarizer.cpp | Break vector ops into scalar components. |
sccp | llvm/lib/Transforms/Scalar/SCCP.cpp | Sparse conditional constant propagation. |
simplify_cfg | llvm/lib/Transforms/Utils/SimplifyCFG.cpp | CFG simplification (fold branches, remove empty blocks, etc.). |
simplify_libcalls | llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp | Fold library calls to simpler forms. |
sroa | llvm/lib/Transforms/Scalar/SROA.cpp | Scalar replacement of aggregates (alloca splitting). |
trace_gep | llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp | GEP chaining / index folding. General GEP combining lives in InstCombine. |
transpose_gep | llvm/lib/Transforms/Scalar/SeparateConstOffsetFromGEP.cpp | Reorder / split GEP indices. |
uniformity_analysis | llvm/lib/Analysis/UniformityAnalysis.cpp | Divergence / uniformity analysis (GPU-focused). |
unused_callable_removal | llvm/lib/Transforms/IPO/GlobalDCE.cpp | Remove unreferenced internal functions. Also see Internalize.cpp. |
When an XIR pass produces incorrect IR:
SROA splits allocas, how StructurizeCFG builds regions).src/xir/passes/<name>.cpp. The XIR passes are often simplified versions of the LLVM algorithms.まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
Manual AST construction with FunctionBuilder for kernels and callables without DSL sugar.
日本語の概要は準備中です。原文の説明を表示しています。
Backend plugin architecture, DeviceInterface, dynamic loading, and command encoding.
日本語の概要は準備中です。原文の説明を表示しています。
CMake build options, custom functions, and backend patterns for LuisaCompute.
日本語の概要は準備中です。原文の説明を表示しています。
C++ naming, formatting, static analysis, and RTTI rules for LuisaCompute.
日本語の概要は準備中です。原文の説明を表示しています。
Debug crashes and test failures via stack-traces, host/device logging, and DSL buffer inspection.
日本語の概要は準備中です。原文の説明を表示しています。
Show uncommitted changes and commit history via git. Use when: (1) inspecting what changed in a file before committing, (2) checking diff of tracked/new/deleted files, (3) reviewing a specific commit's changes, (4) listing files changed in a commit.
日本語の概要は準備中です。原文の説明を表示しています。