[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.
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