This RFC proposes to add a mechanism for inner references to symbols outside of a symbol table.
Background
Currently, it is impossible to reference from within a symbol table symbols that are outside that symbol table. From the symbol table documentation:
// This `func.func` operation defines a symbol named `symbol`.
func.func @symbol()
// Here we define a nested symbol table. References within this operation will
// not resolve to any symbols defined above.
module {
// Error. We resolve references with respect to the closest parent operation
// that defines a symbol table, so this reference can't be resolved.
"foo.user"() {uses = [@symbol]} : () -> ()
}
However, it is often desirable to define scopes and nesting of symbols while still being able to navigate the parent scope. Many programming languages offer this feature in their item path references, which are hard to model in MLIR using symbols.
The issue has already been encountered in upstream MLIR, for example in IRDL:
irdl.dialect @some_dialect { // symbol table
irdl.type @foo
}
// From outside the dialect definition, some_dialect.foo can easily
// be referenced.
%foo_type_from_outside = irdl.base @some_dialect::@foo
irdl.dialect @other_dialect { // symbol table
irdl.type @bar
irdl.operation @baz {
// Referencing a type defined in the same dialect is possible.
%bar_type = irdl.base @bar
// Referencing another dialect is however impossible.
%foo_type = irdl.base @??? // How to reference some_dialect.foo here?
// ...
}
}
Proposal
We propose a way for a symbol reference to target symbol tables that are the parent of the current symbol table. This is achieved by prefixing symbol references with a super
-like construct, for which the proposed syntax is @.super
. Symbol tables would no longer isolate inner symbol references to themselves.
A general-purpose super
-like referencing construct has the unfortunate consequence of creating multiple paths to reference the same symbol. Indeed, symbol reference @foo::@bar
would now be equivalent to @foo::@.super::@foo::@bar
. To limit this effect and keep implementation simple, we propose that @.super
may only be used at the beginning of a symbol reference. While @.super::@foo::@bar
and @bar
are still equivalent in a symbol table called @foo
, the implementation does not need backward lookups. It may be interesting to nonetheless offer infrastructure to canonicalize symbols at a later time, as passes like CSE may not be able to prove equality between symbols otherwise.
The syntax for a symbol reference would now be:
symbol-part ::= `@` suffix-id | string-literal
symbol-ref-id ::= `@.super::`* symbol-part (`::` symbol-part)*
Consider the following example:
module {
module @foo {
func.func @nested()
}
module @bar {
"symbol_user.user"() {uses = [@.super::@foo::@nested]} : () -> ()
}
}
The user operation here references the @nested
function from the @foo
module.
The resolution algorithm is unchanged, except that for each @.super::
prefix to the reference, the selected symbol table for resolution is one level higher in the parent hierarchy.
It may be important, for example for multithreading purposes, to ensure symbol references cannot escape certain scopes. This has so far been achieved by implicitly adding this property to the SymbolTable
trait. We propose to decouple this by introducing an IsolatedSymbols
trait. When going up the parent hierarchy to find which symbol table to start from, if an operation implementing IsolatedSymbols
is encountered, an appropriate error is raised, informing that a symbol has attempted to cross the symbol isolation boundary. This not only allows to have symbol tables that do not isolate symbols, but it also enables operations that are not symbol tables to act as boundaries.
In the scope of this RFC, IsolatedSymbols
still allows accessing inner symbols from outside, in order to mimmick current symbol resolution behavior. It is not clear if it is useful to introduce visibility modifiers to let users disallow this.
Implementation
The implementation will be carried out at once, where:
- The
SymbolRefAttr
attribute will be updated to support prefix@.super
. - The
IsolatedSymbols
trait will be created and manually added to all upstream operations with theSymbolTable
trait to avoid breaking any previous assumption. - The symbol resolution algorithm will be updated to skip as many symbol tables as required by the references, checking for the presence of
IsolatedSymbols
operations.
Existing upstream users of SymbolTable
that desire removing the isolation currently provided may drop the IsolatedSymbols
trait afterwards.
Alternatives
This RFC makes symbol isolation explicitly opt-in via the IsolatedSymbols
trait. However, while this is taken care of manually upstream, this may create inconvenience to downstream users as they may depend on the isolation in some way. The opposite approach could be considered, where operations with the SymbolTable
trait could implement a TransparentSymbolTable
trait that allows going to the parent via @.super
. However, we believe that isolation being opt-in is a more natural default.
We would like to hear from users:
- Does your use of symbol tables depend on the provided isolation?
- Do you know of a better way to communicate the changes to downstream users if we keep the opt-in isolation approach?
Thank you for your attention.