[REFACTOR][IR] Cleanup attrs.h: drop NullValue, AttrsNodeReflAdapter, legacy BaseAttrsNode methods#19607
Merged
Merged
Conversation
…ction Optional<T> is now fully supported throughout the FFI reflection, so NullValue<T>() sentinels are no longer needed; this commit replaces every remaining call site with direct default-construction (T() / Range() / GlobalVar() / DataType::Void()) ahead of removing the NullValue declaration. Also migrates FlipAttrs::axis from Integer (boxed nullable IntImm) to ffi::Optional<int64_t> per the user's directive to prefer Optional<T> over NullValue<Integer>().
…AttrsNode legacy methods BaseAttrsNode has only the reflection-based init path remaining. This commit removes all legacy scaffolding that was needed before the reflection migration: - Remove NullValue<T> template and DataType specialization (all call sites replaced in the previous commit). - Remove InitBySeq (undeclared, dead) and InitByPackedArgs (pure virtual, only invoked on the dead else-branch) from BaseAttrsNode. - Remove DictAttrsNode::InitByPackedArgs override and its definition. - Remove AttrsNodeReflAdapter<DerivedType> template — its sole role was providing the InitByPackedArgs stub that now signals an error. - Simplify AttrsWithDefaultValues to always use the FFI reflection path; broaden the static_assert to accept any ffi::ObjectRef subtype so pass-config classes can use it too. - Trim attrs.h includes: remove reflection/accessor.h, <functional>, <vector> (unused); keep structural_equal.h, structural_hash.h and <unordered_map> as downstream files transitively depend on them. AttrFieldInfo / OpNode::arguments are kept: they are actively read by GetArgStructInfo in op_common.h for argument count validation.
Contributor
There was a problem hiding this comment.
Code Review
This pull request refactors the attribute system by removing the AttrsNodeReflAdapter and the NullValue template, replacing them with BaseAttrsNode or ffi::Object inheritance and default constructors. It also updates the flip operator's axis attribute to be an optional int64_t. However, two critical issues were identified in src/relax/op/tensor/manipulate.cc where attrs->axis.value() is called unconditionally on the newly optional axis field, which will crash if the axis is omitted.
Address Gemini high-priority review on apache#19607: after migrating FlipAttrs::axis to Optional<int64_t>, the existing call sites unconditionally call attrs->axis.value(), which throws when axis is nullopt. Implement the NumPy semantics — axis=None flips every axis — so the optional field has a well-defined meaning instead of being a landmine. - InferStructInfoFlip handles missing axis (shape unchanged). - InferLayoutFlip handles missing axis (layout preserved as-is). - flip lowering generates a per-axis sequence of topi.flip calls when axis is missing (Option A, matches the single-axis TE pattern). - Python wrapper defaults to axis=None. - New tests: test_flip_infer_struct_info_axis_none and test_flip_axis_none (end-to-end execution against np.flip).
…ruff-format Address ruff-format CI failure on apache#19607: the two-line string formed by adjacent string literals exceeds the single-line preferred form ruff-format wants when it fits.
tlopex
approved these changes
May 26, 2026
In commit ff4060f, the NullValue<Var>() default initializer for StorageAccessVisitor::AccessEntry::buffer was replaced with bare `Var buffer;`. Unlike most ObjectRef subclasses, Var has an all-default-arg constructor (Var(name="v", dtype=Int(32))), so `Var()` does NOT produce a null Var — it produces a real Var named "v". This made every default-constructed AccessEntry look like it has a defined buffer, breaking the precondition of the ForNode visitor's range-relaxation step (touched.size() ICHECK). Surfaced as CUDA CI failures in paged-attention KV cache and prefill tests post-PR-19607. Restore the null semantics by using the explicit Var(ObjectPtr<VarNode>(nullptr)) constructor — equivalent to the deleted NullValue<Var>().
Earlier in this PR the field was migrated from Integer to Optional<int64_t> on the rule "Integer fields become Optional<int64_t>". That rule doesn't fit here — relax.flip's axis is a required argument, not a nullable one, and the brief NumPy axis=None compatibility added on top complicates the surface without callers asking for it. Revert the field, signature, and Python wrapper to int64_t. Drop the axis-None legalize branch and the corresponding tests.
tqchen
added a commit
to tqchen/tvm
that referenced
this pull request
May 26, 2026
…s ctor BaseAttrsNode no longer overrides any virtual method after apache#19607; ffi::Object destroys objects through a type-erased deleter captured at make_object time (see tvm-ffi/include/tvm/ffi/memory.h Deleter_::tptr->T::~T()) so the explicit virtual dtor adds nothing. The DictAttrs(Map) constructor is three lines and warrants header placement now that the file is otherwise unchanged.
tqchen
added a commit
to tqchen/tvm
that referenced
this pull request
May 26, 2026
AttrsNodeReflAdapter was the historical 'in between' layer that gave BaseAttrsNode its Base prefix. With the adapter removed in apache#19607 there is no remaining distinction; AttrsNode is the canonical name now used in every comment. The FFI registry key "ir.Attrs" is unchanged, so Python sees no difference.
tqchen
added a commit
to tqchen/tvm
that referenced
this pull request
May 26, 2026
The DictAttrs no-arg constructor already creates an always-defined empty backing (post-apache#19607 inlined ctor), so every existing call site that constructed a DictAttrs already produced a defined object. Switching to TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE makes that property part of the type contract. This removes the wall of explicit copy/move and operator-> declarations on DictAttrs and lets us drop ~15 defensive attrs.defined() checks that could never fire. Python wrappers that previously passed attrs=None for an absent attrs (Function and Function.create_empty) now construct an empty DictAttrs explicitly.
tqchen
added a commit
to tqchen/tvm
that referenced
this pull request
May 26, 2026
…s and move to transform.h After apache#19607 every consumer of AttrsWithDefaultValues is a pass-config class registered via TVM_REGISTER_PASS_CONFIG_OPTION; none are Attrs. Renaming to PassConfigWithDefaults and relocating next to PassContext makes the helper's domain explicit and shrinks attrs.h further.
tqchen
added a commit
to tqchen/tvm
that referenced
this pull request
May 27, 2026
…s ctor BaseAttrsNode no longer overrides any virtual method after apache#19607; ffi::Object destroys objects through a type-erased deleter captured at make_object time (see tvm-ffi/include/tvm/ffi/memory.h Deleter_::tptr->T::~T()) so the explicit virtual dtor adds nothing. The DictAttrs(Map) constructor is three lines and warrants header placement now that the file is otherwise unchanged.
tqchen
added a commit
to tqchen/tvm
that referenced
this pull request
May 27, 2026
The DictAttrs no-arg constructor already creates an always-defined empty backing (post-apache#19607 inlined ctor), so every existing call site that constructed a DictAttrs already produced a defined object. Switching to TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE makes that property part of the type contract. This removes the wall of explicit copy/move and operator-> declarations on DictAttrs and lets us drop ~15 defensive attrs.defined() checks that could never fire. Python wrappers that previously passed attrs=None for an absent attrs (Function and Function.create_empty) now construct an empty DictAttrs explicitly.
tqchen
added a commit
to tqchen/tvm
that referenced
this pull request
May 27, 2026
…s and move to transform.h After apache#19607 every consumer of AttrsWithDefaultValues is a pass-config class registered via TVM_REGISTER_PASS_CONFIG_OPTION; none are Attrs. Renaming to PassConfigWithDefaults and relocating next to PassContext makes the helper's domain explicit and shrinks attrs.h further.
tlopex
pushed a commit
that referenced
this pull request
May 27, 2026
… / phase out AttrFieldInfo (#19615) ## Summary Follow-up to #19607 that continues trimming `attrs.h` and adjacent files. The six commits land independently and each builds clean. - Phase out `OpNode::arguments` and `AttrFieldInfo` — the field stored metadata that no Python tooling, test, or C++ caller (beyond internal sanity checks) read; removing it deletes `AttrFieldInfo` plus ~335 chained `.add_argument(...)` calls. The remaining 12 internal consumers now read `op->num_inputs` and report indexed inputs (`input[i]`). - Drop the (unused) virtual destructor on `BaseAttrsNode` (ffi::Object uses a captured-typed deleter, no virtual dispatch needed) and inline the trivial 3-line `DictAttrs(Map)` constructor into the header. - Rename `BaseAttrsNode` → `AttrsNode`; the `Base` prefix existed only to distinguish from the `AttrsNodeReflAdapter` shim that #19607 removed. The `"ir.Attrs"` FFI registry key is unchanged. - Promote `DictAttrs` to NOTNULLABLE (`TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE` + COW macro). The no-arg `DictAttrs()` constructor already created an empty backing, so every existing call site already produced a defined object; ~15 defensive `attrs.defined()` checks (and a defensive Python `None` fallback in `Function`) are now redundant. - Inline the `WithAttr(DictAttrs, ...)` / `WithAttrs(DictAttrs, ...)` free-function overloads into the TFunc-template wrappers — those overloads had no external callers (no TVM_DLL, no Python binding). - Rename `AttrsWithDefaultValues<T>` → `PassConfigWithDefaults<T>` and move from `attrs.h` to `transform.h`; all 9 consumers are pass-config classes registered via `TVM_REGISTER_PASS_CONFIG_OPTION`. `attrs.h` shrinks from 363 → 262 lines.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
This PR cleans up
include/tvm/ir/attrs.hby removing four deprecated abstractions:NullValue<T>()sentinel helpers (replaced byffi::Optional<T>)AttrsNodeReflAdapter<DerivedType>shim template (Attrs structs now inheritBaseAttrsNodedirectly)BaseAttrsNode::InitBySeq/InitByPackedArgslegacy initialization methodsDictAttrsNode::InitByPackedArgsoverrideIt also migrates 9 pass-config classes from
Attrs/AttrsNodeReflAdaptertoffi::Object, since they are pass configuration objects, not IR attributes.Changes
Commit A — Replace NullValue() call sites (
[REFACTOR][IR] Replace NullValue<T>() call sites with default construction)NullValue<T>()withT(),std::nullopt, orDataType::Void()manipulate.h/manipulate.cc:FlipAttrs::axischanged fromIntegertoffi::Optional<int64_t>Commit B — Drop NullValue, AttrsNodeReflAdapter, legacy BaseAttrsNode methods (
[REFACTOR][IR] Drop NullValue declaration, AttrsNodeReflAdapter, BaseAttrsNode legacy methods)include/tvm/ir/attrs.h: removesNullValue<T>,InitBySeq,InitByPackedArgs,AttrsNodeReflAdapter<T>src/ir/attrs.cc: removesDictAttrsNode::InitByPackedArgsdefinitionAttrsWithDefaultValues<T>()broadened to accept anyffi::ObjectRefsubtype (needed for Commit D)reflection/accessor.h,<functional>,<vector>Commit C — Subclass BaseAttrsNode directly (
[REFACTOR][IR] Subclass BaseAttrsNode directly, drop AttrsNodeReflAdapter)include/tvm/relax/attrs/+include/tvm/target/virtual_device.hstruct FooAttrs : public AttrsNodeReflAdapter<FooAttrs>→struct FooAttrs : public BaseAttrsNodeCommit D — Migrate pass-config classes to ffi::Object (
[REFACTOR] Migrate pass-config classes to subclass ffi::Object)src/s_tir/,src/tirx/,src/relax/backend/contrib/XConfigNode : public ffi::Object(wasAttrsNodeReflAdapter<XConfigNode>)XConfig : public ffi::ObjectRef(wasAttrs)_ir.Attrsto_ffi.ObjectDesign Decisions
AttrFieldInfo/OpNode::argumentskept: Pre-flight check revealedGetArgStructInfo()inop_common.handop_common.ccactively readsop->arguments(names, counts). These were not dead metadata — deleting them would break Relax op argument validation. They are kept as-is.Commit E (trim attrs.h includes) reduced in scope: Removing
structural_equal.h,structural_hash.h, and<unordered_map>fromattrs.hcaused 47 downstream files to fail compilation. Rather than adding explicit includes to 47 files, only clearly-unused includes (reflection/accessor.h,<functional>,<vector>) were removed in Commit B.Testing
-DUSE_CUDA=OFF -DUSE_LLVM=ONtests/python/ir/(93 passed)tests/python/relax/test_analysis.py,test_blockbuilder_core.py,test_op_manipulate.py,test_transform.py(209 passed)tests/python/s_tir/transform/test_s_tir_transform_loop_partition.py,test_s_tir_transform_unify_thread_binding.py(30 passed)tests/python/tirx-transform/test_tir_transform_unroll_loop.py,test_tir_transform_simplify.py,test_tir_transform_remove_no_op.py(108 passed, 6 xfailed)test_s_tir_transform_lower_opaque_block,test_s_tir_transform_compact_buffer_region::TestLetBinding::test_compact,test_tir_transform_vectorize::test_vectorize_llvm_pure_intrin_fail