-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[Macros] Support user-defined macros as compiler plugins #61734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
swiftlang/swift-syntax#1022 |
1ae1e0e
to
f154896
Compare
swiftlang/swift-syntax#1022 |
f154896
to
8c9922f
Compare
swiftlang/swift-syntax#1022 |
@swift-ci please test macOS |
swiftlang/swift-syntax#1022 |
8c9922f
to
e864a54
Compare
swiftlang/swift-syntax#1022 |
a745059
to
340b2f7
Compare
swiftlang/swift-syntax#1022 |
85123ee
to
fdce2b7
Compare
fdce2b7
to
1ed0057
Compare
swiftlang/swift-syntax#1022 |
// TODO: Consider using runtime lookup to get all types that conform to the | ||
// macro protocol instead of finding a global `allMacros` property. | ||
std::string name; | ||
// Build a mangling tree for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is... quite clever. I wonder if we could do something simpler by forcing some more structure into the plugins themselves. For example, if allCases
had a @_silgen_name("swift_plugin_allMacros")
on it, then the name would be the same for all plugins and we wouldn't have to do mangling tricks at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't multiple plugins end up with colliding symbols if we used @_silgen_name("swift_plugin_allMacros")
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could definitely enforce some naming pattern, e.g. @_silgen_name("swift_plugin_<module_name>_allMacros")
. But I still believe it's more hassle-free to just let the compiler handle this, even though building a mangling tree isn't pretty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking we'd dlopen
with RTLD_LOCAL
to keep all of the symbols separate, so different plugins could have the same symbol. But your solution might be more hassle-free. Either way, we can change it later; this shouldn't block merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Separately I wonder if RTLD_LOCAL
is a solution to the duplicate symbols problem.
lib/Sema/TypeCheckMacros.cpp
Outdated
|
||
// Built-in macros go through `MacroSystem` in Swift Syntax linked to this | ||
// compiler. | ||
if (isBuiltinMacro) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine for now, but we should seek to unify them by having a single Macro
structure that abstracts over builtin vs. loaded from a plugin so the rest of the compiler doesn't care.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think handling of both of these can be cleaned up a bit, so don't fret over it in this PR.
test/Sema/macro_expansion.swift
Outdated
// FIXME: Swift parser is not enabled on Linux CI yet. | ||
// REQUIRES: OS=macosx | ||
|
||
let _ = #stringify(1.byteSwapped + 2.advanced(by: 10)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing! ;)
894e1e7
to
afc2687
Compare
swiftlang/swift-syntax#1022 |
afc2687
to
845f224
Compare
swiftlang/swift-syntax#1022 |
swiftlang/swift-syntax#1022 |
swiftlang/swift-syntax#1022 |
Allow user-defined macros to be loaded from dynamic libraries and evaluated. - Introduce a _CompilerPluginSupport module installed into the toolchain. Its `_CompilerPlugin` protocol acts as a stable interface between the compiler and user-defined macros. - Introduce a `-load-plugin-library <path>` attribute which allows users to specify dynamic libraries to be loaded into the compiler. A macro library must declare a public top-level computed property `public var allMacros: [Any.Type]` and be compiled to a dynamic library. The compiler will call the getter of this property to obtain and regsiter all macros. Known issues: - We current do not have a way to strip out unnecessary symbols from the plugin dylib, i.e. produce a plugin library that does not contain SwiftSyntax symbols that will collide with the compiler itself. - `MacroExpansionExpr`'s type is hard-coded as `(Int, String)`. It should instead be specified by the macro via protocol requirements such as `signature` and `genericSignature`. We need more protocol requirements in `_CompilerPlugin` to handle this. - `dlopen` is not secure and is only for prototyping use here.
845f224
to
2aef407
Compare
swiftlang/swift-syntax#1022 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of comments that we can address with follow-ups, but this is looking good! Let's merge
// TODO: Consider using runtime lookup to get all types that conform to the | ||
// macro protocol instead of finding a global `allMacros` property. | ||
std::string name; | ||
// Build a mangling tree for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking we'd dlopen
with RTLD_LOCAL
to keep all of the symbols separate, so different plugins could have the same symbol. But your solution might be more hassle-free. Either way, we can change it later; this shouldn't block merging.
@@ -121,7 +121,8 @@ public func getMacroEvaluationContext( | |||
|
|||
|
|||
@_cdecl("swift_ASTGen_evaluateMacro") | |||
public func evaluateMacro( | |||
@usableFromInline | |||
func evaluateMacro( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is a good idea to do in general for our entry points from the C++ world.
|
||
target_link_libraries("${library_name}" | ||
PRIVATE | ||
swiftCore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised we need this, but... okay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not be needed. Another problem is that currently the _CompilerPluginSupport module files aren't installed into the toolchain. I'll merge this and investigate it next.
|
||
// Built-in macros go through `MacroSystem` in Swift Syntax linked to this | ||
// compiler. | ||
if (swift_ASTGen_lookupMacro(macroName.str().c_str())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should track where macros come from (builtin vs. loaded) in the macro itself, which would simplify this code, but that can come later.
ConformanceDescriptor = 0, | ||
// static func _name() -> (UnsafePointer<UInt8>, count: Int8) | ||
Name = 1, | ||
// static func _kind() -> _CompilerPluginKind |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the _CompilerPlugin
protocol is resilient, we don't have to hard-code these. Rather, we could look up the requirement symbols and do the address calculation to find the entries dynamically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking about that, but most of the protocol layout utilities are down in IRGen. Here we could look up a requirement but it doesn't provide the offset info. We could go though all requirements up front and build up an offset table ahead of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The requirement is the offset :). See https://github.com/apple/swift/blob/main/stdlib/public/runtime/Metadata.cpp#L5156-L5157
Make `Macro` refine `_CompilerPlugin` protocol when `_CompilerPluginSupport` exists. (`_CompilerPluginSupport` may not exist when ASTGen or SwiftCompilerSources is built with host tools.) This enables macros to talk to the compiler without an ABI-stable SwiftSyntax. Friend PR: swiftlang/swift#61734
Allow user-defined macros to be loaded from dynamic libraries and evaluated.
_CompilerPlugin
protocol acts as a stable interface between the compiler and user-defined macros.-load-plugin-library <path>
attribute which allows users to specify dynamic libraries to be loaded into the compiler.A macro library must declare a public top-level computed property
public var allMacros: [Any.Type]
and be compiled to a dynamic library. The compiler will call the getter of this property to obtain and register all macros.Known issues:
MacroExpansionExpr
's type is hard-coded as(Int, String)
. It should instead be specified by the macro via protocol requirements such assignature
andgenericSignature
. We need more protocol requirements in_CompilerPlugin
to handle this.dlopen
is not secure and is only for prototyping use here.Friend PR: swiftlang/swift-syntax#1022