RFC: First class bindings, GlobalRef unification, TLS improvements #48843
Keno
started this conversation in
RFC: features for discussion
Replies: 1 comment
-
Clever design! |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
This is a design overview that combines a few threads that have been discussed recently, including the GlobalRef-Binding unification (briefly touched upon in #46729 and probably something we want to do in #40399) and potential improvements to TLS variables (e.g. mentioned in #46259 (comment)).
Proposal
Summary
Currently, in julia, there are essentially two kinds of variables that julia knows about: local variables and global variables [1]. We also have support for task-local variables, which have a different mechanism and go through a dictionary (so need special syntax, and don't have type inference support etc.). The gist of the current proposal is to extend syntax support for non-local variables to bindings with storage semantics other than all-thread, global accesses. The running example here is of course task local storage, but other storage classes are imaginable (OS-specific thread local storage, storage shared among some particular task group, etc.). The basic idea is to retain the two basic kinds of variables for lowering purposes, but add an extra layer of indirection for global variables that allows user-extensible specification of the storage location.
Details
The
getbinding
generic methodConsider
Currently we lower this to:
The easiest way to thing of this proposal (though there's some special implementation concerns discussed below) is that we introduce an extra generic function in this lowering:
conceptually, users are allowed to create new kinds of
GlobalRef
like things with customgetbinding
behavior (such as TLS).Binding-GlobalRef unification
Currently
Module
contains a global table of non-first-classjl_binding_t
objects:The proposal here is to make bindings first class and extensible. The rough object hierarchy may look something like:
In this system, modules would keep a first class
IdDict{Symbol, Binding}
that is used for symbol lookup, but the actual storage location for globals would be up to the user implementation.IR space and bootstrap considerations
There's two problems with the
getbinding
method as proposed above.First, what would be the scope resolution of
getbinding
? It can't be a global since it's part of the implementation. We could of course hack around that, but I think a more elegant solution would be to fold its functionality into functionality of(::Binding)(...)
. In particular the zero argument method(::Binding)()
would read the binding (i.e. be the equivalent of getbinding above) and the 1-argument method(::Binding)(@nospecialize(val))
would write the binding. Sample implementations of the above may look like:For IR size, I think, we probably want at least the 0-arg version to be implicit, i.e. a bare object that is a subtype of
Binding
in the IR would implicitly be called (might seem a bit weird, but it's not unusual, e.g. think of the effects of a bareSymbol
in IR). This would slightly pessimize the lowering of a global assignment (requiring an extra quote node), but I think that's ok. Global assignments are much rarer than global reads.Special considerations for lowering
Lowering (as well as inference where appropriate) would be allowed to resolve
GlobalRef
s to the appropriate binding kind. This can be seen as a sort of special-case super early inlining/constant folding. We already do something similar now when we finding theGlobalRef
object associated to a particularjl_binding_t
. For performance, we may want similar optimizations in inference, but I think that should be just fine. We already do a few of these heuristically required special cases.Types for TLS
One of the particular advantages worth pointing out here is that this would give us properly typed and namespaced TLS. Currently, our TLS implementation is neither. Different packages using the same TLS names will step on each other, and inference has no capability to infer types for TLS. I do also want to teaches the optimizer to optimize TLS more, though that is somewhat orthogonal to this proposal (though I think it gives better justification for it, since right now TLS just happens to be "some IdDict that
Task
carries around"). It also clarifies the exact semantics for TLS. They're essentially the same as for globals, except with a different storage location.Implication for uniqueness of names
One of the constraints of this design worth pointing out is that globals and TLS variables would share the same name space. I think this is fine and maybe even desirable, but it's worth pointing out specifically. Another implication is that it's not possible to know whether a particular access is task local or not.
Foo.bar
could be an implicitly task-local. Of course the same is true in many other languages also, but our user base has gotten used to the globalness of these accesses, so this might be surprising behavior.End user interface
This proposal does not prescribe a particular end-user interface. The new functionality would be accessed using macros,
along the lines of:
Naturally, the end-user interface for current globals is unchanged (though the exact way the builtins work might be re-arranged).
[1] Well, and type parameters, but they mostly behave like local variables and should probably more so - I thought we had an issue for this somewhere, but I can't find it.
Beta Was this translation helpful? Give feedback.
All reactions