-
Notifications
You must be signed in to change notification settings - Fork 112
Stable Functions, Stable Objects, and Stable Classes #4789
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
Open
luc-blaeser
wants to merge
109
commits into
master
Choose a base branch
from
luc/stable-functions
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Stable Functions, Stable Objects, and Stable Classes
This is 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 (the future standard mode of Motoko).
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
The closures of stable functions are represented in a portable format with the following clearly defined binding of captured variables:
This offers flexibility for changes of stable closures in new program versions:
On a stable function upgrade, the closure type of the stable function must remain compatible:
Specific aspects apply to generic types used in a 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.
The runtime system reports the fully qualified name of missing stable functions (not only the internal name hash).
Open Aspects
Separate of this PR: Lift the base library to support stable functions.
Later, not now: Recycle slots in the peristent virtual table.