Skip to content

Commit

Permalink
Enhanced Orthogonal Persistence (#4193)
Browse files Browse the repository at this point in the history
# Enhanced Orthogonal Persistence
This implements the vision of **enhanced orthogonal persistence** in Motoko that combines:
* **Stable heap**: Persisting the program main memory across canister upgrades.
* **64-bit heap**: Extending the main memory to 64-bit for large-scaled persistence.

As a result, the use of secondary storage (explicit stable memory, dedicated stable data structures, DB-like storage abstractions) will no longer be necessary: Motoko developers can directly work on their normal object-oriented program structures that are automatically persisted and retained across program version changes.

## Activation
Enhanced orthogonal persistence is currently offered for **beta testing** via the compiler flag `--enhanced-orthogonal-persistence`.

To activate enhanced orthogonal persistence under `dfx`, the following command-line argument needs to be specified in `dfx.json`:

```
...
    "type" : "motoko"
    ...
    "args" : "--enhanced-orthogonal-persistence"
...
```

## Advantages
Compared to the existing orthogonal persistence in Motoko, this design offers:
* **Performance**: New program versions directly resume from the existing main memory and have access to the memory-compatible data.
* **Scalability**: The upgrade mechanism scales with larger heaps and in contrast to serialization, does not hit IC instruction limits.

Compared to the explicit use of stable memory, this design improves:
* **Simplicity**: Developers do not need to deal with explicit stable memory.
* **Performance**: No copying to and from the separate stable memory is necessary.

## Design
The enhanced orthogonal persistence is based on the following main properties:
* Extension of the IC to retain main memory on upgrades.
* Supporting 64-bit main memory on the IC.
* A long-term memory layout that is invariant to new compiled program versions.
* A fast memory compatibility check performed on each canister upgrade.
* Incremental garbage collection using a partitioned heap.

### Integration with Classical Persistence
Enhanced orthogonal persistence is offered for **beta testing** via the compiler flag `--enhanced-orthogonal-persistence`.
Classical persistence with 32-bit main memory and Candid stabilization currently remains the default mode.
See `design/PersistenceModes.md` for more information.

### Memory Layout
In a co-design between the compiler and the runtime system, the main memory is arranged in the following structure, invariant of the compiled program version:
* Lower 4MB: Rust call stack.
* Space between 4MB and 4.5MB: Limited reserved space Wasm data segments, only used for the Motoko runtime system.
* Between 4.5MB and 5MB: Persistent metadata.
* Thereafter: Dynamic heap space. Fix start address at 5MB.

### Persistent Metadata
The persistent metadata describes all anchor information for the program to resume after an upgrade. 

More specifically, it comprises:
* A stable heap version that allows evolving the persistent memory layout in the future.
* The stable subset of the main actor, containing all stable variables declared in the main actor.
* A descriptor of the stable static types to check memory compatibility on upgrades.
* The runtime state of the garbage collector, including the dynamic heap metadata and memory statistics.
* A reserve for future metadata extensions.

### Compatibility Check
Upgrades are only permitted if the new program version is compatible with the old version, such that the runtime system guarantees a compatible memory structure.

Compatible changes for immutable types are largely analogous to the allowed Motoko subtype relation, e.g.
* Adding or removing actor fields.
* Removing object fields.
* Adding variant fields.
* `Nat` to `Int`.
* Shared function parameter contravariance and return type covariance.

The existing IDL-subtype functionality is reused with some adjustments to check memory compatibility: The compiler generates the type descriptor, a type table, that is recorded in the persistent metadata. Upon an upgrade, the new type descriptor is compared against the existing type descriptor, and the upgrade only succeeds for compatible changes.

This compatibility check serves as an additional safety measure on top of the DFX Candid subtype check that can be bypassed by users (when ignoring a warning). Moreover, in some aspects, the memory compatibility rules differ to the Candid sub-type check:
* Top-level actor fields (`stable` fields) can change mutability (`let` to `var` and vice-versa).
* Support of variable (MutBox) with type invariance.
* Types cannot be made optional (no insertion of Option).
* Same arity for function parameters and function return types (no removed optional parameters, no additional optional results).
* Records cannot introduce additional optional fields.
* Same arity for tuple types (no insertion of optional items).
* Records and tuples are distinct.

### Garbage Collection
The implementation focuses on the incremental GC and abandons the other GCs because the GCs use different memory layouts. For example, the incremental GC uses a partitioned heap with objects carrying a forwarding pointer.

The incremental GC is chosen because it is designed to scale on large heaps and the stable heap design also aims to increase scalability.

The garbage collection state needs to be persisted and retained across upgrades. This is because the GC may not yet be completed at the time of an upgrade, such that object forwarding is still in use. The heap partition structure is described by a linked list of partition tables that is reachable from the GC state.

The garbage collector uses two kinds of roots:
* Persistent roots: These refer to root objects that need to survive canister upgrades.
* Transient roots: These cover additional roots that are only valid in a specific version of a program and are discarded on an upgrade.

The persistent roots are registered in the persistent metadata and comprise:
* All stable variables of the main actor, only stored during an upgrade.
* The stable type table.

The transient roots are referenced by the Wasm data segments and comprise:
* All canister variables of the current version, including flexible variables.

### Main Actor
On an upgrade, the main actor is recreated and existing stable variables are recovered from the persistent root. The remaining actor variables, the flexible fields as well as new stable variables, are (re)initialized. 

As a result, the GC can collect unreachable flexible objects of previous canister versions. Unused stable variables of former versions can also be reclaimed by the GC.

### No Static Heap
The static heap is abandoned and former static objects need to be allocated in the dynamic heap. This is because these objects may also need to survive upgrades and the persistent main memory cannot accommodate a growing static heap of a new program version in front of the existing dynamic heap. The incremental GC also operates on these objects, meaning that forwarding pointer resolution is also necessary for these objects. 

For memory and runtime efficiency, object pooling is implemented for compile-time-known constant objects (with side-effect-free initialization), i.e. those objects are already created on program initialization/upgrade in the dynamic heap and thereafter the reference to the corresponding prefabricated object is looked up whenever the constant value is needed at runtime.

The runtime system avoids any global Wasm variables for state that needs to be preserved on upgrades. Instead, such global runtime state is stored in the persistent metadata.

### Wasm Data Segments
Only passive Wasm data segments are used by the Motoko compiler and runtime system. In contrast to ordinary active data segments, passive segments can be explicitly loaded to a dynamic address.

This simplifies two aspects: 
* The generated Motoko code can contain arbitrarily large data segments (to the maximum that is supported by the IC). The segments can be loaded to the dynamic heap when needed.
* The IC can simply retain the main memory on an upgrade without needing to patch any active data segments of the new program version to the persistent main memory.

However, more specific handling is required for the Rust-implemented runtime system (RTS): The Rust-generated active data segment of the runtime system is changed to the passive mode and loaded to the expected static address on the program start (canister initialization and upgrade). The location and size of the RTS data segments is therefore limited to a defined reserve of 512 KB, see above. This is acceptable because the RTS only requires a controlled small amount of memory for its data segments, independent of the compiled Motoko program.

### Null Sentinel
As an optimization, the top-level `null` pointer is represented as a constant sentinel value pointing to the last unallocated Wasm page. This allows fast null tests without involving forwarding pointer resolution of potential non-null comparand pointers.

### Memory Capacity
The canister has no upfront knowledge of the maximum allocatable Wasm main memory in 64-bit address space, as there is no IC API call to query the main memory limit. This limit may also be increased in future IC releases.

Therefore, a mechanism is implemented to deal with an unknown and dynamically increasable main memory capacity offered by the IC. This is needed in two cases:

* GC reserve (strict): The runtime system ensures sufficient free space to allow garbage collection at all times, even if the heap is full. For this purpose, the runtime system already pre-allocates the reserve, to be sure that the reserve is available despite the unknown capacity. As an optimization, this pre-allocation is skipped when the memory demand including the GC reserve is below a guaranteed minimum Wasm memory limit of the IC, e.g. 4GB or 6GB.
* GC scheduling (heuristic): The GC schedules at high frequency when memory is becoming scarce. For this purpose, the GC maintains an assumption of the minimum memory limit and probes the supposed limit when the heap size approaches this limit. If the allocation succeeds, the assumed limit is increased. Otherwise, the critical high-frequency GC scheduling rate is activated.

In both cases, the runtime system tries to reduce Wasm memory allocations as much as possible, i.e. not pre-allocating memory for small heap sizes, and not probing an allocation in certain memory ranges by assuming that the IC only offers main memory of a certain granularity, e.g. multiples of 2GB. To save instructions, the critical GC scheduling is only activated when reaching the actual memory limit. Moreover, the mechanism can handle an increased memory capacity at runtime, e.g. when the IC is upgraded to a new release with a higher memory limit.

### Migration Path
When migrating from the old serialization-based stabilization to the new persistent heap, the old data is deserialized one last time from stable memory and then placed in the new persistent heap layout. Once operating on the persistent heap, the system should prevent downgrade attempts to the old serialization-based persistence. 

Assuming that the persistent memory layout needs to be changed in the future, the runtime system supports serialization and deserialization to and from stable memory in a defined data format using graph copy. 

### Graph Copy
The graph copy is an alternative persistence mechanism that will be only used in the rare situation when the persistent memory layout will be changed in the future. Arbitrarily large data can be serialized and deserialized beyond the instruction and working set limit of upgrades: Large data serialization and deserialization is split in multiple messages, running before and/or after the IC upgrade to migrate large heaps. Of course, other messages will be blocked during this process and only the canister owner or the canister controllers are permitted to initiate this process. 

Graph copying needs to be explicitly initiated before an upgrade to new Motoko version that is incompatible to the current enhanced orthogonal persistent layout. For large data, the graph copy needs to be manually completed after the actual upgrade.

```
dfx canister call CANISTER_ID __motoko_stabilize_before_upgrade "()"
dfx deploy CANISTER_ID
dfx canister call CANISTER_ID __motoko_destabilze_after_upgrade "()"
```

More detailed information and instructions on graph copy are contained in `design/GraphCopyStabilization.md`.

### Old Stable Memory
The old stable memory remains equally accessible as secondary (legacy) memory with the new support.

## Current Limitations
* The memory footprint of a program increases with 64 bit as the word size for scalars and pointers are doubled. In turn, in some cases, boxing can be avoided due to larger word size which again reduces memory demand.
* Freeing old object fields: While new program versions can drop object fields, the runtime system should also delete the redundant fields of persistent objects of previous program versions. This could be realized during garbage collection when objects are copied. For this purpose, the runtime system may maintain a set of field hashes in use and consult this table during garbage collection. Another, probably too restrictive solution could be to disallow field removal (subtyping) on object upgrades during the memory compatibility check.
* The floating point display format differs in Wasm64 for special values, e.g. `nan` becomes `NaN`. There is currently no support for hexadecimal floating point text formatting.
* The Wasm profiler (only used for the flamegraphs) is no longer applicable because the underlying `parity-wasm` crate is deprecated before Wasm Memory64 support. It also lacks full support of passive data segments. A re-implementation of the profiler would be needed.
  • Loading branch information
luc-blaeser authored Aug 26, 2024
1 parent 866f3ac commit f0d4437
Show file tree
Hide file tree
Showing 747 changed files with 35,154 additions and 4,627 deletions.
31 changes: 31 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,37 @@

* motoko (`moc`)

* **For beta testing:** Support __enhanced orthogonal persistence__, enabled with new moc flag `--enhanced-orthogonal-persistence` (#4193).

This implements scalable and efficient orthogonal persistence (stable variables) for Motoko:
* The Wasm main memory (heap) is retained on upgrade with new program versions directly picking up this state.
* The Wasm main memory has been extended to 64-bit to scale as large as stable memory in the future.
* The runtime system checks that data changes of new program versions are compatible with the old state.

Implications:
* Upgrades become extremely fast, only depending on the number of types, not on the number of heap objects.
* Upgrades will no longer hit the IC instruction limit, even for maximum heap usage.
* The change to 64-bit increases the memory demand on the heap, in worst case by a factor of two.
* For step-wise release handling, the IC initially only offers a limited capacity of the 64-bit space (e.g. 4GB or 6GB), that will be gradually increased in future to the capacity of stable memory.
* There is moderate performance regression of around 10% for normal execution due to combined related features (precise tagging, change to incremental GC, and handling of compile-time-known data).
* The garbage collector is fixed to incremental GC and cannot be chosen.
* `Float.format(#hex prec, x)` is no longer supported (expected to be very rarely used in practice).
* The debug print format of `NaN` changes (originally `nan`).

To activate enhanced orthogonal persistence under `dfx`, the following command-line argument needs to be specified in `dfx.json`:

```
...
"type" : "motoko"
...
"args" : "--enhanced-orthogonal-persistence"
...
```

For more information, see:
* The Motoko design documentation `design/OrthogonalPersistence.md`
* The Motoko user documentation `doc/md/canister-maintenance//upgrades.md`.

* Candid decoding: impose an upper limit on the number of values decoded or skipped in a single candid payload,
as a linear function, `max_values`, of binary payload size.

Expand Down
8 changes: 5 additions & 3 deletions bin/wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ declare -A envs # list of expected environment variables with paths to products
# Define build products here
real[moc]=src/moc
hint[moc]="make -C $rel_root/src moc"
envs[moc]="MOC_NON_INCREMENTAL_RELEASE_RTS MOC_NON_INCREMENTAL_DEBUG_RTS MOC_INCREMENTAL_RELEASE_RTS MOC_INCREMENTAL_DEBUG_RTS"
envs[moc]="MOC_NON_INCREMENTAL_RELEASE_RTS MOC_NON_INCREMENTAL_DEBUG_RTS MOC_INCREMENTAL_RELEASE_RTS MOC_INCREMENTAL_DEBUG_RTS MOC_EOP_RELEASE_RTS MOC_EOP_DEBUG_RTS"
real[mo-ld]=src/mo-ld
hint[mo-ld]="make -C $rel_root/src mo-ld"
real[mo-doc]=src/mo-doc
Expand All @@ -32,10 +32,12 @@ hint[candid-tests]="make -C $rel_root/src candid-tests"

rts_hint="make -C $rel_root/rts"

real[MOC_NON_INCREMENTAL_RELEASE_RTS]=rts/mo-rts.wasm
real[MOC_NON_INCREMENTAL_DEBUG_RTS]=rts/mo-rts-debug.wasm
real[MOC_NON_INCREMENTAL_RELEASE_RTS]=rts/mo-rts-non-incremental.wasm
real[MOC_NON_INCREMENTAL_DEBUG_RTS]=rts/mo-rts-non-incremental-debug.wasm
real[MOC_INCREMENTAL_RELEASE_RTS]=rts/mo-rts-incremental.wasm
real[MOC_INCREMENTAL_DEBUG_RTS]=rts/mo-rts-incremental-debug.wasm
real[MOC_EOP_RELEASE_RTS]=rts/mo-rts-eop.wasm
real[MOC_EOP_DEBUG_RTS]=rts/mo-rts-eop-debug.wasm

for var in ${envs[moc]}; do
hint[$var]=$rts_hint
Expand Down
94 changes: 61 additions & 33 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ let ic-ref-run =
cp ${ic-hs-pkgs.ic-hs}/bin/ic-ref-run $out/bin
''; in

let
nixos-unstable = import nixpkgs.sources.nixpkgs-unstable {};
in

let haskellPackages = nixpkgs.haskellPackages.override {
overrides = import nix/haskell-packages.nix nixpkgs subpath;
}; in
Expand All @@ -35,6 +39,7 @@ let
wasmtime
rust-bindgen
python3
nixos-unstable.emscripten
] ++ pkgs.lib.optional pkgs.stdenv.isDarwin [
libiconv
];
Expand Down Expand Up @@ -193,7 +198,7 @@ rec {
name = "motoko-rts-deps";
src = subpath ./rts;
sourceRoot = "rts/motoko-rts-tests";
sha256 = "sha256-jN5nx5UNBHlYKnC0kk90h6mWPUNrqPS7Wln2TixbGgA=";
sha256 = "sha256-prLZVOWV3BFb8/nKHyqZw8neJyBu1gs5d0D56DsDV2o=";
copyLockfile = true;
};

Expand Down Expand Up @@ -241,6 +246,7 @@ rec {
"directory" = "$(stripHash ${allDeps})"
__END__
${llvmEnv}
export TOMMATHSRC=${nixpkgs.sources.libtommath}
export MUSLSRC=${nixpkgs.sources.musl-wasi}/libc-top-half/musl
Expand All @@ -255,10 +261,12 @@ rec {

installPhase = ''
mkdir -p $out/rts
cp mo-rts.wasm $out/rts
cp mo-rts-debug.wasm $out/rts
cp mo-rts-non-incremental.wasm $out/rts
cp mo-rts-non-incremental-debug.wasm $out/rts
cp mo-rts-incremental.wasm $out/rts
cp mo-rts-incremental-debug.wasm $out/rts
cp mo-rts-eop.wasm $out/rts
cp mo-rts-eop-debug.wasm $out/rts
'';

# This needs to be self-contained. Remove mention of nix path in debug
Expand All @@ -268,12 +276,17 @@ rec {
-t ${nixpkgs.rustc-nightly} \
-t ${rtsDeps} \
-t ${rustStdDeps} \
$out/rts/mo-rts.wasm $out/rts/mo-rts-debug.wasm
$out/rts/mo-rts-non-incremental.wasm $out/rts/mo-rts-non-incremental-debug.wasm
remove-references-to \
-t ${nixpkgs.rustc-nightly} \
-t ${rtsDeps} \
-t ${rustStdDeps} \
$out/rts/mo-rts-incremental.wasm $out/rts/mo-rts-incremental-debug.wasm
remove-references-to \
-t ${nixpkgs.rustc-nightly} \
-t ${rtsDeps} \
-t ${rustStdDeps} \
$out/rts/mo-rts-eop.wasm $out/rts/mo-rts-eop-debug.wasm
'';

allowedRequisites = [];
Expand Down Expand Up @@ -363,11 +376,6 @@ rec {
(test_subdir dir deps).overrideAttrs {
EXTRA_MOC_ARGS = "--sanity-checks";
};

generational_gc_subdir = dir: deps:
(test_subdir dir deps).overrideAttrs {
EXTRA_MOC_ARGS = "--generational-gc";
};

snty_compacting_gc_subdir = dir: deps:
(test_subdir dir deps).overrideAttrs {
Expand All @@ -384,6 +392,16 @@ rec {
EXTRA_MOC_ARGS = "--sanity-checks --incremental-gc";
};

enhanced_orthogonal_persistence_subdir = dir: deps:
(test_subdir dir deps).overrideAttrs {
EXTRA_MOC_ARGS = "--enhanced-orthogonal-persistence";
};

snty_enhanced_orthogonal_persistence_subdir = dir: deps:
(test_subdir dir deps).overrideAttrs {
EXTRA_MOC_ARGS = "--sanity-checks --enhanced-orthogonal-persistence";
};

perf_subdir = dir: deps:
(test_subdir dir deps).overrideAttrs (args: {
checkPhase = ''
Expand Down Expand Up @@ -442,23 +460,24 @@ rec {
'';
};

profiling-graphs = testDerivation {
src = test_src "perf";
buildInputs =
(with nixpkgs; [ perl wabt wasm-profiler-instrument wasm-profiler-postproc flamegraph-bin ]) ++
[ moc nixpkgs.drun ];
checkPhase = ''
patchShebangs .
type -p moc && moc --version
type -p drun && drun --help
./profile-report.sh
'';
installPhase = ''
mv _profile $out;
mkdir -p $out/nix-support
echo "report flamegraphs $out index.html" >> $out/nix-support/hydra-build-products
'';
};
# wasm-profiler is not compatible with passive data segments and memory64
# profiling-graphs = testDerivation {
# src = test_src "perf";
# buildInputs =
# (with nixpkgs; [ perl wabt wasm-profiler-instrument wasm-profiler-postproc flamegraph-bin ]) ++
# [ moc nixpkgs.drun ];
# checkPhase = ''
# patchShebangs .
# type -p moc && moc --version
# type -p drun && drun --help
# ./profile-report.sh
# '';
# installPhase = ''
# mv _profile $out;
# mkdir -p $out/nix-support
# echo "report flamegraphs $out index.html" >> $out/nix-support/hydra-build-products
# '';
#};


fix_names = builtins.mapAttrs (name: deriv:
Expand Down Expand Up @@ -494,23 +513,33 @@ rec {
in fix_names ({
run = test_subdir "run" [ moc ] ;
run-dbg = snty_subdir "run" [ moc ] ;
run-eop-release = enhanced_orthogonal_persistence_subdir "run" [ moc ];
run-eop-debug = snty_enhanced_orthogonal_persistence_subdir "run" [ moc ];
# ic-ref-run = test_subdir "run-drun" [ moc ic-ref-run ];
drun = test_subdir "run-drun" [ moc nixpkgs.drun ];
drun-dbg = snty_subdir "run-drun" [ moc nixpkgs.drun ];
drun-compacting-gc = snty_compacting_gc_subdir "run-drun" [ moc nixpkgs.drun ] ;
drun-generational-gc = snty_generational_gc_subdir "run-drun" [ moc nixpkgs.drun ] ;
drun-incremental-gc = snty_incremental_gc_subdir "run-drun" [ moc nixpkgs.drun ] ;
drun-eop-release = enhanced_orthogonal_persistence_subdir "run-drun" [ moc nixpkgs.drun ] ;
drun-eop-debug = snty_enhanced_orthogonal_persistence_subdir "run-drun" [ moc nixpkgs.drun ] ;
fail = test_subdir "fail" [ moc ];
fail-eop = enhanced_orthogonal_persistence_subdir "fail" [ moc ];
repl = test_subdir "repl" [ moc ];
repl-eop = enhanced_orthogonal_persistence_subdir "repl" [ moc ];
ld = test_subdir "ld" ([ mo-ld ] ++ ldTestDeps);
ld-eop = enhanced_orthogonal_persistence_subdir "ld" ([ mo-ld ] ++ ldTestDeps);
idl = test_subdir "idl" [ didc ];
mo-idl = test_subdir "mo-idl" [ moc didc ];
mo-idl-eop = enhanced_orthogonal_persistence_subdir "mo-idl" [ moc didc ];
trap = test_subdir "trap" [ moc ];
trap-eop = enhanced_orthogonal_persistence_subdir "trap" [ moc ];
run-deser = test_subdir "run-deser" [ deser ];
perf = perf_subdir "perf" [ moc nixpkgs.drun ];
bench = perf_subdir "bench" [ moc nixpkgs.drun ic-wasm ];
viper = test_subdir "viper" [ moc nixpkgs.which nixpkgs.openjdk nixpkgs.z3_4_12 ];
inherit qc lsp unit candid profiling-graphs coverage;
# TODO: profiling-graph is excluded because the underlying partity_wasm is deprecated and does not support passive data segments and memory64.
inherit qc lsp unit candid coverage;
}) // { recurseForDerivations = true; };

samples = stdenv.mkDerivation {
Expand Down Expand Up @@ -554,7 +583,7 @@ rec {
doInstallCheck = true;
test = ./test + "/test-${n}.js";
installCheckPhase = ''
NODE_PATH=$out/bin node $test
NODE_PATH=$out/bin node --experimental-wasm-memory64 $test
'';
};
in
Expand Down Expand Up @@ -702,15 +731,13 @@ EOF
mkdir -p $out
ln -s ${base-doc} $out/base-doc
ln -s ${docs} $out/docs
ln -s ${tests.profiling-graphs} $out/flamegraphs
ln -s ${tests.coverage} $out/coverage
cd $out;
# generate a simple index.html, listing the entry points
( echo docs/overview-slides.html;
echo docs/html/motoko.html;
echo base-doc/
echo coverage/
echo flamegraphs/ ) | \
echo coverage/ ) | \
tree -H . -l --fromfile -T "Motoko build reports" > index.html
'';

Expand Down Expand Up @@ -779,8 +806,9 @@ EOF
check-grammar
check-error-codes
] ++
builtins.attrValues tests ++
builtins.attrValues js;
builtins.attrValues tests
++ builtins.attrValues js
;
};

viperServer = nixpkgs.fetchurl {
Expand Down
4 changes: 4 additions & 0 deletions design/Custom-Sections.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ let hash : string -> int32 = fun s ->
(Lib.String.explode s)
)
```

Motoko generates an additional `"enhanced-orthogonal-persistence"` private custom section to
mark Motoko Wasm binaries that rely on IC's support to retain the main Wasm memory on an upgrade,
cf. [Orthogonal Persistence](OrthogonalPersistence.md).
8 changes: 8 additions & 0 deletions design/DFX-Interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ used only in very specific cases.
The above metadata is stored in the Wasm module, and is only accessible by the controllers of the canister, unless the
metadata name is specified in the `--public-metadata` flag.

Moreover, the compiler generates a special marker custom section `"enhanced-orthogonal-persistence"` if the new orthogonal
persistence support is enabled, see [Orthogonal Persistence](OrthogonalPersistence.md). This section is always private and
always emited independent of the compiler flags `--public-metadata` or `--public-metadata`.

Checking stable type compatibility
----------------------------------

Expand All @@ -130,6 +134,10 @@ a type safe way without unintentional data loss.

If the check succeeds, nothing will be printed.
If the check fails, the error message will be printed in stderr and the command returns with exit code 1.
The check can also emit warning messages, e.g. if stable variables are dropped.

With [enhanced orthogonal persistence](OrthogonalPersistence.md), the stable compatibility is also integrated in the runtime
system, to atomically guarantee memory compatibility during an upgrade.

Invoking the IDE
----------------
Expand Down
Loading

0 comments on commit f0d4437

Please sign in to comment.