ADR-005: DRY in XS via XSUB ALIAS
Status
Accepted
Context
Params::Util's XS layer historically implemented each function as a standalone XSUB, even when functions share nearly identical logic. For example, _INSTANCE calls ->isa while _INSTANCEDOES calls ->DOES and _INSTANCECAN calls ->can -- the surrounding code (magic handling, blessed check, stack manipulation, return convention) is identical.
Duplicating 40+ lines of XS per variant violates DRY and makes maintenance error-prone. The XSUB ALIAS mechanism allows multiple Perl-visible function names to share one C entry point, distinguished only by the ix variable.
A concern raised during review of List::MoreUtils and Scalar::List::Utils is that ALIAS forces conditionals into a shared code path. For functions that loop over lists (e.g. uniq/uniqstr/ uniqint), a branch per iteration can hurt performance. However, Params::Util predicates perform a single check per call -- the dominant cost is always the Perl method dispatch (call_method), and an array-index lookup for the method name is negligible in comparison.
Decision
Consolidate the XS implementation into ALIAS groups where the only difference is the method name passed to call_method or the validation logic selected by ix:
- Group 1: String validators
-
_IDENTIFIERand_CLASS-- differ only in the pattern matched against the string (identifier vs. class name with optional::separators). - Group 2: Numeric validators
-
_POSINTand_NONNEGINT-- differ only in whether zero is accepted. - Group 3: Class dispatchers
-
_CLASSISA,_CLASSDOES,_CLASSCAN,_SUBCLASS-- class name validation followed bycall_methodwith method name from a static table._SUBCLASSadds one inequality check. - Group 4: Instance dispatchers
-
_INSTANCE,_INSTANCEDOES,_INSTANCECAN--sv_isobjectfollowed bycall_methodwith method name from a static table. - Group 5: Invocant dispatchers
-
_INVOCANT,_INVOCANTCAN-- blessed-or-class check, optionally followed bycall_method("can").
Functions deliberately excluded from XS acceleration:
_SET/_SET0-
These loop over an array calling
_INSTANCEper element. The per-element_INSTANCEis already XS-accelerated; the loop overhead in Perl is minimal. Callers needing high-performance list filtering should use "any" in List::Util or "all" in List::MoreUtils instead. _HANDLE-
Too many special cases (globs, tied handles, multiple
isachecks). Rarely performance-critical. _DRIVER-
Dominated by
requirecost; XS around the dispatch adds no value.
Consequences
Each ALIAS group is maintained as a single block of C; bug fixes and improvements apply to all aliased functions simultaneously.
Adding a new variant (e.g. a future
_CLASSDOES_ALL) requires only a new ALIAS line and a table entry, not duplicating the entire XSUB.The existing
_INSTANCEXSUB is refactored into the Group 4 ALIAS block, which is a non-functional change -- existing tests cover the behaviour.No measurable performance regression from ALIAS dispatch: one static array index per call vs. hundreds of cycles for
call_method.