ast
무료Manual AST construction with FunctionBuilder for kernels and callables without DSL sugar.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Implement, compose, test, and debug LuisaCompute XIR transformation and analysis passes. Use when modifying files under src/xir/passes or include/luisa/xir/passes, changing CFG or SSA invariants, authoring a PassPipeline, or diagnosing pass-order, reachability, PHI, dominance, aliasing, or memory-effect bugs.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Open the pass's public header, implementation, nearest related pass, and tests
before editing. Treat the header as authoritative: pass entry points are not
uniform. Depending on the pass, an API may be module-only, accept Function *
or FunctionDefinition *, omit PassReport, or return a specialized result.
There is no pass base class and no analysis manager: each pass is a free
function named <name>_pass_run_on_module / <name>_pass_run_on_function
declared in its own header and returning a specialized *Info counter struct,
while PassReport and PassPipeline in
include/luisa/xir/passes/pass_pipeline.h carry the shared report and
composition surface.
Use the common layout without assuming a fixed line number or alphabetical registration:
include/luisa/xir/passes/<pass>.hsrc/xir/passes/<pass>.cppsrc/xir/CMakeLists.txt (CMake lists each pass source
explicitly; xmake globs src/xir/passes/*.cpp from src/runtime/xmake.lua,
so only the CMake list needs a manual entry)src/tests/unit/xir/src/tests/CMakeLists.txt and src/tests/xmake.lua (both
enumerate every test target)Copy the closest correct pass pattern. Aggregate per-function counters rather than overwriting the result on each function.
Resolve a definition before traversing blocks:
if (auto *definition = function->definition()) {
definition->traverse_basic_blocks([](xir::BasicBlock *block) noexcept {
// inspect reachable blocks
});
}
Use these exact APIs:
| Need | API |
|---|---|
| all module functions | module->function_list() |
| all blocks owned by a function | function->basic_blocks() |
| reachable blocks from the body | definition->traverse_basic_blocks(...) |
| function entry | definition->body_block() |
| return operand | return_inst->return_value() |
| conditional targets | true_block(), false_block() |
| retarget a conditional | set_true_target(...), set_false_target(...) |
| branch target | target_block(), set_target_block(...) |
| detach a node | remove_self() |
Guard BasicBlock::terminator() and traverse_successors() with
is_terminated(). terminator() asserts on an unterminated block; it does not
return null as a malformed-IR probe.
Do not use LLVM casting helpers such as cast_or_null. Check isa<T>() and
then use static_cast<T *>.
Structured terminators own or reference regions and merge blocks. Plain CFG
uses BranchInst, ConditionalBranchInst, and the raw multi-way
IndexedBranchInst edges. SwitchInst also carries merge information and
contains_structured_control_flow classifies it as structured; destructuring
converts it to IndexedBranchInst. Inspect DerivedInstructionTag for the
complete current instruction inventory rather than copying an exhaustive list
into a pass.
Apply the relevant lowering order:
lower_ray_query_to_loop to convert ray-query loops into ordinary
structured loop/if constructs, and check its rejection result. The
lower_ray_query_loop_to_loop_* entry points are a deprecated
compatibility alias for these names.destructure_cfg to lower IfInst, SwitchInst, LoopInst,
SimpleLoopInst, BreakInst, and ContinueInst; a structured
SwitchInst becomes the raw-CFG IndexedBranchInst. It also spills
early returns.Do not claim that destructure_cfg lowers RayQueryLoopInst or removes every
specialized terminator: RAY_QUERY_LOOP terminators are counted as errors, and
unterminated owned blocks are reported through leaked_block_count while the
function is rejected unchanged before any mutation.
Use contains_structured_control_flow from src/xir/passes/helpers.h before a
plain-CFG-only mutation.
Follow the inliner's actual contract:
inline_all immediately. Do not
insert cleanup, SSA, or another CFG pass between those two steps. This
enables eligible multi-block call sites.rejected_malformed_call_count as a hard error in backend
normalization; a zero inlined-call count by itself is not a failure.mem2reg right after inline_all to remove
inliner-created argument and return spill slots before SSA optimization; the
Metal4 lowering pipeline inserts hoist-allocas-after-inline between them,
and the CUDA and fallback backends run mem2reg in a separate pre-CFG
pipeline. In pre-autodiff normalization, allow autodiff scopes in the caller,
then use cleanup and reg2mem before restructuring instead.destructure_cfg deliberately preserves ray-query constructs
and other specialized structured operations; reference-argument and other
unsupported uses may also leave allocas after mem2reg.create_value_argument rejects custom
types and promote_ref_arg intentionally excludes them; a backend-owned
handle may have a concrete ABI only after backend lowering.When changing this schedule, add a shape test that checks the call disappears, the inliner does not report a structured skip, eligible generated temporaries are promoted, every reachable block remains terminated, and successor PHI incoming blocks still match real predecessors after block splitting.
After basic optimization, the Metal4 AIR consumer conditionally normalizes autodiff scopes with:
optional outline-ray-query-pipelines (when ray-query pipelines are enabled)
lower_ray_query_to_loop (checked)
-> destructure_cfg (checked)
-> inline_all (immediately adjacent; allow_autodiff_scope_in_caller)
-> post-inline cleanup (one fixed-point iteration)
-> simplify_cfg
-> reg2mem
-> restructure_cfg (checked)
-> verify(no PHIs, unique merge blocks)
-> autodiff
-> reg2mem
-> verify(no PHIs, unique merge blocks)
It then lowers every module with:
optional outline-ray-query-pipelines (when ray-query pipelines are enabled)
lower_ray_query_to_loop (checked)
-> destructure_cfg (checked)
-> inline_all (immediately adjacent)
-> hoist-allocas-after-inline
-> mem2reg
-> SSA optimization
-> unused_callable_removal
-> simplify_cfg
-> verify(require_reachable_blocks = true)
The AIR entry point repeats the reachable-block verification before XIR-to-LLVM
translation. Treat destructure_cfg -> inline_all as the common adjacency and
inline_all -> mem2reg, possibly with an intervening alloca hoist, as the
ordinary-phase adjacency. Read src/backends/metal4/metal_xir_pipeline.cpp
before changing either schedule;
read src/xir/passes/pass_pipeline.cpp for the current contents of factory
pipelines rather than copying their expansion here.
Prefer collect-then-rewrite when a transform removes or replaces nodes during traversal. Mutate in place only when iterator advancement and node lifetime are explicitly controlled by an established local pattern.
For a replacement:
Do not remove blocks with a naive reachable-set recipe on structured CFG.
Merge blocks and owned regions require structurally aware traversal. On plain
CFG, clean incoming PHI entries and detach contained instructions before
deleting a block (src/xir/passes/simplify_cfg.cpp). Never remove
body_block().
compute_dom_tree, discover_natural_loops; the latter requires a plain
CFG).mem2reg only after CFG and alloca placement are valid.reg2mem before a transform that cannot preserve live PHIs, when that
transform's documented precondition requires it.Remember that traverse_basic_blocks visits blocks reachable from
body_block(), while basic_blocks() includes disconnected owned blocks.
Choose intentionally.
Use get_memory_info() from src/xir/passes/helpers.h; do not maintain a
second hard-coded purity whitelist in a new pass.
Check all of the following independently:
alias_analysis_query);Purity alone does not make an instruction safe to hoist or speculate. Integer
division, remainder, and shifts are examples that require additional checks.
Treat CLOCK as a non-deterministic read, not a pure instruction.
Treat these ResourceQueryOp values as stateful ray-query constructors, not
ordinary pure queries:
RAY_TRACING_QUERY_ALLRAY_TRACING_QUERY_ANYRAY_TRACING_QUERY_ALL_MOTION_BLURRAY_TRACING_QUERY_ANY_MOTION_BLUREach creates fresh mutable traversal state. get_memory_info() classifies it
as GLOBAL/READ, non-volatile: it remains removable when unused, but is not
safe to value-number, common with Early CSE/GVN, speculate, or hoist with LICM.
Keep the classification centralized in helpers.cpp, and make optimizers
consult it instead of whitelisting all ResourceQueryInst values.
Use PassPipeline::add adapters that return whether the module changed:
xir::PassPipeline pipeline;
pipeline.add("inline-all", [](xir::Module *module,
xir::PassReport &report) {
auto info = xir::inline_all_pass_run_on_module(module, &report);
return info.inlined_call_count != 0u;
});
auto stats = pipeline.run(module);
Use add_fixed_point only when every child is safe to repeat and every change
predicate is correct. A false negative terminates the group early; a false
positive wastes iterations or masks non-convergence. For a named one-shot
group, use add_sequence instead: unlike a fixed point capped at one
iteration, child changes there do not consume a convergence budget.
Do not infer implementation maturity from registration. Read the header and
source. Region outlining is unimplemented: outline_pass_run_on_module leaves
the IR unchanged and reports "outlining is not implemented. IR was left
unchanged." (include/luisa/xir/passes/outline.h, src/xir/passes/outline.cpp).
The loop transforms that do rewrite (loop_fusion, loop_rotation,
loop_vectorization) are unstructured-CFG-only: structured functions are
rejected without mutation, and they stay out of the factory pipelines in
src/xir/passes/pass_pipeline.cpp.
A pass can preserve valid XIR while making a backend reject it. For the Metal4
AIR path, inspect luisa_compute_metal_codegen_llvm_supported alongside any
change that affects types, special registers, calls, atomics, or resource-use
shape. Every emitter entry calls it before LLVM construction and fails closed on
unsupported XIR: JIT compute including autodiff kernels
(src/backends/metal4/metal_device.cpp), compute and per-stage raster codegen
(codegen_entry, codegen_raster_entry in
src/backends/metal4/metal_air_pipeline.cpp, which also serve the compile-only
raster archive path in src/backends/metal4/metal_raster_ext.cpp), the shared
MetalCodegenLLVMImpl::generate, and the tile path
(src/backends/metal4/tile/metal_tile.cpp). Metal4 has no MSL or legacy-IR
shader fallback. Compute and raster AOT loading consume validated archives and
bypass XIR preflight.
This preflight and its pass schedule live in the independent metal4 backend;
the original metal backend remains the source-MSL compatibility path.
Keep producer and preflight assumptions paired. In particular, texture uses
must reach preflight as direct resource operations after normalization. A
compute AIR module requires exactly one kernel and rejects raster special
registers. A raster AIR module instead requires exactly one
RasterStageFunction whose stage() matches the configured vertex or fragment
program; object ID is valid in both roles, while barycentrics, front-facing, and
discard require the fragment role and base-instance requires the vertex role.
External declarations may remain only when native_include supplies
ABI-compatible LLVM IR/bitcode definitions at link time. Ensure every operand
and result type is checked; constants can otherwise carry an unsupported type
past an instruction-only scan.
Treat raster stage functions as ABI roots:
dead_arg_elim must not remove their arguments, even when a stage body does
not read a slot.unused_callable_removal must seed reachability from both kernels and raster
stages.destructure_cfg -> inline_all immediately adjacent for each separately
translated vertex and fragment module, then run the same SSA cleanup and
reachable-block verification used by compute AIR.RasterStageFunction::stage() through interchange text/bitcode and
debug text; use raster_vertex and raster_fragment interchange kinds.When a pass change affects this boundary, add both:
Use Boost.UT for XIR unit tests. Wire every test block to body_block() when it
must be reached by traversal. Test both the information counters and the IR
invariants that matter:
Build and run focused tests with xmake; the unit XIR pass tests need no device argument:
xmake build test_xir_passes
xmake run test_xir_passes
There is a second, older test location: src/xir/tests/test_<name>.cpp
(dce, mem2reg, debug_printer, ray_query, aggregate_field_bitmasks) is built
as xir_test_<name> by luisa_compute_xir_add_test() only when
-DLUISA_COMPUTE_ENABLE_XIR_TESTS is passed (src/xir/CMakeLists.txt:177-189). That
variable is never declared with option(), so it does not appear in the generated help,
and those targets are plain add_executable — not CTest-registered. Prefer
src/tests/unit/xir/ for new pass tests.
Use CMake/Ninja when the Metal4 integration tests are in scope (they are
registered only in src/tests/CMakeLists.txt):
cmake --build cmake-build-metal4-air --target test_xir_passes -j 8
ctest --test-dir cmake-build-metal4-air -R '^test_xir_passes$' --output-on-failure
ctest --test-dir cmake-build-metal4-air -L unit_xir --output-on-failure
Use the Metal4 backend for an end-to-end AIR test. The dedicated
test_metal_xir_air CTest passes metal4 as its own backend argument, so no
selection environment is needed. Use test_metal_xir_air_raster for
vertex/fragment stage identity, raster-only operations, and render readback;
launch other binaries with backend metal4.
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
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, DSL buffer inspection, and add_debug_checks kernel restrict-mode guards.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
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.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.