-
Notifications
You must be signed in to change notification settings - Fork 101
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
Stable Functions, Stable Objects, and Stable Classes #4789
base: master
Are you sure you want to change the base?
Conversation
@@ -68,7 +69,7 @@ and field = {lab : lab; typ : typ; src : src} | |||
and con = kind Cons.t | |||
and kind = | |||
| Def of bind list * typ | |||
| Abs of bind list * typ | |||
| Abs of bind list * typ * int option |
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.
What's the int option
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 need to document this. This is the index of the type parameter according to declaration order in its scope, i.e. starting with generic type parameter of outer scope, then continuing with inner scope. This is used for checking compatibility of stable closures if they refer to values of generic type parameters.
@@ -489,10 +494,9 @@ let close cs t = | |||
let sigma = List.fold_right2 ConEnv.add cs ts ConEnv.empty in | |||
subst sigma t | |||
|
|||
let close_binds cs tbs = | |||
let close_binds cs tbs is_stable = |
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.
is_stable appears unused?
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.
Thanks. A relict...
Stable Functions, Stable Objects, and Stable Classes
This is a proposal to further strenghen orthogonal persistence in Motoko, by also supporting functions, objects, and classes to be automatically persisted, i.e. been used in Motoko's stable variables.
The feature is only supported with enhanced orthogonal persistence.
With this, the only non-persistent (non-stable) values would be lambdas, async handles (aka continuations, futures), and local async function references.
Example
With this, the following program can now be used for orthogonal persistence. For this example, you need a specific base library branch, which contains slight modifications for supporting stable functions.
Stable Functions and Stable Scopes
A stable function is a named non-async local function in a stable scope, only closing over variables of a stable type.
A stable scope is:
Generic type parameters of stable functions and stable classes are bounded to stable types.
A stable function is also a stable type.
Syntactically, function types are prefixed by
stable
to denote a stable function, e.g.stable X -> Y
. Stable functions implicitly have a corresponding stable reference type.A stable function type is a sub-type of a flexible function type with type-compatible signature, i.e.
stable X' -> Y <: X -> Y'
forX' <: X
andY' :< Y
.Upgrades of Stable Functions
Stable functions are upgraded as follows:
All other functions, such as lambdas, named functions in a lambda, async functions, or functions imported from a module without a unique import identifier, are flexible functions.
Stable Closures
On a stable function upgrade, the closure type of the stable function must remain compatible. The runtime system checks on upgrade:
Specific aspects are to be considered for generic types used in the stable closure:
Runtime System Design
Function references are encoded by a function id in the following representation:
A stable function reference that stays invariant across upgrades.
A flexible function reference that is invalidated on upgrade.
Each program version defines a set of named local functions that can be used as stable function references. Each such function obtains a stable function id on program initialization and upgrade. If the stable function was already declared in the previous version, its function id is reused on upgrade. Thereby, the compatibility of the function type and closure type are checked. Otherwise, if it is a new stable function, it obtains a new stable function id, or a recycled id.
The runtime system supports stable functions by two mechanisms:
Persistent virtual table for stable function calls:
The persistent virtual table maps stable function ids to Wasm table indices, for supporting dynamic calls of stable functions. Each entry also stores the hashed name of the stable function to match and rebind the stable function ids to the corresponding functions of the new Wasm binary on a program upgrade. Moreover, each entry also records the type of the closure, referring to the persistent type table. The table survives upgrades and is built and updated by the runtime system. To build and update the persistent virtual table, the compiler provides a stable function map, mapping the hashed name of a potentially stable function to the corresponding Wasm table index, plus its closure type pointing to the new type table. For performance, the stable function map is sorted by the hashed names.
Function literal table for materializing stable function literals:
As the compiler does not yet know the function ids of stable function literals/constants, this table maps a Wasm table index of the current program version to a stable function id. The function literal table is re-built on program initialization and upgrade. When a stable function literal is loaded, it serves for resolving the corresponding function id and thus the stable function reference. The table is discarded on upgrades and (re-)constructed by the runtime system, based on the information of the stable function map.
The runtime system distinguishes between flexible and stable function references by using a different encoding. This is to avoid complicated conversion logic been inserted by the compiler when a stable function reference is assigned to flexible reference, in particular in the presence of sharing (a function reference can be reached by both a stable and flexible function type) and composed types (function references can be deeply nested in a composed value that is assigned).
Compatibility Check
A stable function compatibility check is performed by the runtime system on upgrade.
Flexible function references are represented as negative function ids determining the Wasm table index, specifically
-wasm_table_index - 1
.Garbage Collection
The runtime systems relies on a dedicated garbage collector of stable functions:
Garbage collection is necessary to allow programs to use classes and stable functions in only flexible contexts or not even using imported classes or stable functions. Moreover, it allows programs to drop stable functions and classes, if they are no longer used for persistence.
Open Aspects
Make stable closure more portable, use a record representation for the captured variables.
The runtime system should report the name of missing stable functions in new version. Currently, only the name hash is displayed.
Supporing type-directed GC for stable functions reachable by generic type arguments. Currently, generic type arguments are fully traversed, similar to usual GC marking.
Recycle slots in the peristent virtual table.
Updating documentation, examples etc.
Lift the base library to support stable functions.