Skip to content

Add experimental _hasTargetFeature predicate for CPU/ISA feature query - #92189

Open
hjyamauchi wants to merge 1 commit into
swiftlang:mainfrom
hjyamauchi:has-target-feature-predicate
Open

hjyamauchi wants to merge 1 commit into
swiftlang:mainfrom
hjyamauchi:has-target-feature-predicate

Conversation

@hjyamauchi

Copy link
Copy Markdown
Contributor

This allows conditional compiles (#if...) per the target CPU/ISA features. This is guarded behind an experimental feature flag.

This is the main swift repo part. There's a separate swift-syntax part.

@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test

Comment thread lib/AST/Bridging/ASTContextBridging.cpp Outdated
bool BridgedASTContext_hasTargetFeature(BridgedASTContext cContext,
BridgedStringRef cName) {
auto *loader = cContext.unbridged().getClangModuleLoader();
// Can only be null in unit tests.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to consider a separate testing interface? Doing so allows us to have a reduced branch in the production path. Hopefully we can eliminate the testing interface for release binaries.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean by a testing interface?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "bridging" here is happening between the Swift and C++ portions of the compiler itself. This is not a runtime test.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hjyamauchi the "Can only be null in unit tests" aspects is what I was referring to. Rather than having a bridge for the unit tests, introduce a separate testing entry point for testing.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see. It turned out that this was false, unit tests aren't calling this. I removed the null check.

@hjyamauchi
hjyamauchi requested a review from drexin September 11, 2026 19:34
@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test

@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test

@drexin drexin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending clarification on @compnerd‘s comment, this looks good to me.

Comment thread lib/Parse/ParseIfConfig.cpp Outdated
Comment on lines +644 to +649
return hasAttribute(Ctx.LangOpts, attributeName);
} else if (KindName == "_hasTargetFeature") {
auto featureName = cast<StringLiteralExpr>(Arg)->getValue();
auto *loader = Ctx.getClangModuleLoader();
// Can only be null in unit tests
return loader && loader->hasTargetFeature(featureName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, most of this is dead code nowadays, since we require a host Swift everywhere.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it

}

bool ClangImporter::hasTargetFeature(StringRef featureName) const {
return getTargetInfo().hasFeature(featureName);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about this implementation. Doesn't the @target attribute change what target features are available within a particular function?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but this is orthogonal. This is meant to support the following idiom:

#if arch(particular processor) && _hasTargetFeature(particular feature)
   ... code optimized for particular ISA ...
#else if arch(other processor) && _hasTargetFeature(other feature)
   ... code optimized for some other ISA ...
#else
   ... general code without any ISA assumptions ...
#endif

@hjyamauchi hjyamauchi Sep 11, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and this allows us to choose among different versions at build time. What I'm aiming for here is the combination of @_target and this so that we can call separate code optimized for different ISA features based on the build-time baseline target CPU, something like:

@_target("sve2")
func foo_sve2() { ... }
@_target("sve")
func foo_sve() { ... }
// default
func foo_default() { ... }

#if _hasTargetFeature("sve2")
foo_sve2()
#elseif _hasTargetFeature("sve")
foo_sve()
#else
foo_default()
#endif

where foo_sve2() is taken when it's built with sve2 and foo_sve() is taken when it's built with sve

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, now put that into a function that uses @target:

@target(particular feature)
func f() {
#if arch(particular processor) && _hasTargetFeature(particular feature)
   ... code optimized for particular ISA ...
#else if arch(other processor) && _hasTargetFeature(other feature)
   ... code optimized for some other ISA ...
#else
   ... general code without any ISA assumptions ...
#endif
}

When arch(particular processor) holds, and the target provided on the command line doesn't imply _hasTargetFeature(particular feature), what should I expect this to do?

@tbkka tbkka Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_hasTargetFeature() refers to the current processor architecture being targeted by the current compilation context. This may be affected by compiler command-line options and by surrounding @target() annotations.

In your example here, the presence of @target(particular feature) indicates that this function is being compiled for a processor with that particular feature, so I would expect _hasTargetFeature(particular feature) to evaluate true.

To be honest, this seems like a pointless combination in practice. For an MVP, I'd be happy for _hasTargetFeature() to diagnose when put inside a function with @target() on it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still think #if is the right design here. The whole point is being able to choose different implementations at compile time and that's exactly what #if expresses.

As I said, there's not much point to combining these two. Is it feasible to diagnose? I'd have to think about whether I would be willing to just silently ignore @target() for this purpose. Maybe @stephentyrone has some insight?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some variation of function multi-versioning could conceivably be an alternative: clang supports a feature where you provide overloads of a function and annotate each one with the particular processor/target feature that it should be used for. This should allow us to defer final function selection until IRGen (or even later).

@DougGregor DougGregor Sep 11, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have multi-versioning, do we need this #if _hasTargetFeature?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think #if allows purely compile-time conditional code while multi-versioning involves runtime/link-time checks/indirection. An expected use case is when we are willing to raise baseline target cpu and have multiple builds optimized for each CPU generation. Maybe not be a perfect example but writing code sort of like this would be enabled.

@stephentyrone stephentyrone Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we absolutely need (a good version of) this in the presence of multi-versioning (if anything, it becomes more important, not less). Consider something like a two-register lookup table in SIMD. This is going to get inlined into a multi-versioned function. On arm64, it should become TBL. On baseline x86 it has to be an explicit memory gather. But on x86 with SSSE3 (whether via command line flags, target, or multi-versioning), it should become two PSHUFBs and a blend. On x86 with the right AVX512 extensions, it should be a single instruction. But there's no way to write that in a target-independent manner*. We actually need to have different implementations of the two-register lookup table depending on what features are available in the context we get inlined into.

(*) LLVM has been working on this, so it might be soon, but lots of other things still won't be.

Even if one isn't writing explicit SIMD, I frequently want to use a different algorithm depending on what instructions are available. For instance, in something like Augmented.product from Numerics, we would like to use an FMA-based algorithm when targeting x86 with FMA, but Dekker's hi-lo split algorithm when we do not have FMA available, rather than going through a soft float FMA from the host libm.

@tbkka

tbkka commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

CC: @stephentyrone

@hjyamauchi
hjyamauchi force-pushed the has-target-feature-predicate branch from e0a93ae to 172b428 Compare September 12, 2026 01:16
@hjyamauchi

Copy link
Copy Markdown
Contributor Author

@swift-ci please test

@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test

@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test macOS platform

This allows conditional compiles (#if...) per the target CPU/ISA features. This
is guarded behind an experimental feature flag.

This is the main swift repo part. There's a separate swift-syntax part.
@hjyamauchi
hjyamauchi force-pushed the has-target-feature-predicate branch from 172b428 to c07754b Compare September 14, 2026 16:18
@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Rebased

@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test macOS platform

@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test windows platform

@hjyamauchi

Copy link
Copy Markdown
Contributor Author

Please test with following pull request:
swiftlang/swift-syntax#3436

@swift-ci Please test linux platform

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants