diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0e74c6c3c72..dc7b2c2ebbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -107,7 +107,10 @@ jobs: if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && contains(github.event.pull_request.labels.*.name, 'build_artifacts') needs: tests concurrency: ci-${{ github.ref }} - runs-on: ubuntu-latest + strategy: + matrix: + os: [ ubuntu-latest, macos-latest ] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@v23 @@ -123,7 +126,6 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v3 with: - name: moc + name: moc-${{ matrix.os }} path: ${{ env.UPLOAD_PATH }} retention-days: 5 - diff --git a/Changelog.md b/Changelog.md index 6d521c33c7e..bbf2a23bbe8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,10 @@ # Motoko compiler changelog +* motoko (`moc`) + + * Added a new stable `Region` type (#3768) of dynamically allocated, independently growable and + isolated regions of IC stable memory. See documentation. + ## 0.9.8 (2023-08-11) * motoko (`moc`) diff --git a/design/StableRegions.md b/design/StableRegions.md new file mode 100644 index 00000000000..9876946f330 --- /dev/null +++ b/design/StableRegions.md @@ -0,0 +1,333 @@ +# Stable Region API + +See StableMemory.md for context of the current experimental API. + +This document aims to specify the API and memory representations for a generalization +of this API that permits multiple isolated _regions_ of stable memory, where each can be +grown independently. + +The **region manager** is the state and logic to support this generalization. + + +## Role for "stable regions" in Motoko + +The current stable memory module in `base` has been "experimental" for a long time, and requires a more composable API to graduate from this status. + +Stable regions address the problem that today's `ExperimentalStableMemory` module only provides a single, monolithic memory that makes it unsuitable for directly building composable software parts. + +Stable regions permit a new API that supports composable use cases. + +Stable regions also bring Motoko closer to parity with Rust canister development support today, by giving a run-time-system-based analog of a special Rust library for stable data structures that allocates “pages” for them from stable memory in separate, isolated, memory regions. + + +## Design space + +The design space for the page allocator is defined by at least two +tensions: + + 1. fully-stable representation of allocator meta data **versus** fast load/store operations. + + 2. Total scaling capacity **versus** minimum footprint for meta data. + + +**Tension 1** is introduced because we want to avoid relying on the Motoko heap as the "ground truth" about the allocator's state. If this heap is lost, as it is during an upgrade, then a developer may still want to recover all of the regions' data and meta data. + +Tension 1 is resolved by storing the ground truth in stable memory, keeping it in sync with heap structures that permit faster access operations. + +Compared with the Rust version, we store enough extra meta data to permit: + + - Regions whose page blocks are in arbitrary order, not + necessarily in order of smallest to highest address. + + - 2^64-1 Regions max (instead of 255 Regions max). + Due to the limit on blocks, only 2^16-1 can have non-zero page size. + +We want to permit arbitrary page block orders to make a smooth +transition to region reclamation and re-allocation in the near +future, with potential integration into the Motoko GC. The +extra complexity is modest, and seems "worth" the cost. + +We change the maximum region limit because 255 may be too small in +some extreme cases and incompatible with GC. +Instead, we can freely allocate new regions, recycling blocks, but not +Region ids. The id of a Region is invariant and will not change, even with GC. + +We address the question of whether the new limit of 32k regions is +"enough" in the Q&A section (it is, for all practical purposes) + + +**Tension 2** is introduced because we want a design that will continue +to work even when canisters can store more stable data than today (32GB). + +Tension 2 is resolved by making prudent representation choices. + +The representations we choose for regions and region identifiers +permit a scaling to 256GB of stable data while still permitting meta +data to be repeated in both stable and non-stable arenas. These are +the same limits imposed by the Rust implementation, for the same +reasons. See Q&A for more discussion. + + +## Definitions and constants + + - a **page** is 65536 bytes. + - a **page block** is a contiguous sequence of 128 pages (~8MB). + - a **page block index** is a 16 bit, index-based identifier for a page block. + - a **region** is a sequence of (generally non-contiguous) **page blocks**. + - the maximum number of page blocks is 32768. + - the maximum amount of stable memory for all regions is 256GB. + + +## Questions and answers + +### Q: What determines the 8MB non-empty region minimum? + +Mostly, we want to reduce the amount of metadata we need to track, so instead of per-page metadata (lots) we only need per-block metadata (less). +This size means we grow a region by more than one physical page at +a time (in terms of the way that the canister interacts with the +system API, at least). Rather than actually grow by a single page, +the implementation grows by a "page block" (8MB) at a time. + +This choice means that there are 128 pages per page block, and that +the maximum number of regions and region blocks are each relatively +small (32k each). Consequently, they can each be identified with a +2-byte identifier, and we can pre-allocate tables to store certain +relations about them, which is critical. + +### Q: Are 32767 regions enough? + +A: Permitting more than 32k regions may seem theoretically +interesting, but is not practical given other parameters today that +control the minimal footprint of a region (8MB) and dictate the +maximum size of stable memory for a canister today (32GB). With 32k +regions at 8MB each, well over the maximum stable memory size is used +(256GB compared to 32GB, the limit today) + +### Q: When is stable memory becoming less costly? + +Spring 2023. + +### Q: How does the cheaper stable memory access cost compare with ordinary heap memory access cost? + +2-3x slower than ordinary heap memory. + + +## Design details + +### API + +Internal region allocator operations: + + - `initialize` -- called by the RTS, not by the Motoko developer. + +User-facing region allocator operations: + + - `region_new` -- create a dynamic region. + - `region_grow` -- grow region by a specified number of pages. + - `region_load` -- read some data from the region. + - `region_store` -- store some data into the region. + +### FUTURE WORK + +Add a special operation, for testing our design for future GC integration (bonus): + +- `region_release` -- release region and reuse region's page blocks. + +The `_release` operation is *not* part of the user-facing API nor part of the MVP, +but supporting it is important because it means we can transition quickly to an integration +with the ambient Motoko GC if we can support it. + +Another special operation, for disaster recovery: + + - `rebuild` -- not needed unless we need to recreate all Region objects from their stable-memory counterparts. + + +## Internal footprint + +The state of the allocator is stored in a combination of: + + - stable memory fields and tables and + - stable heap memory, in the form of objects of opaque type `Region`. + +The stable memory state is sufficient to reconstitute the stable heap objects +(`rebuild` operation, described in a subsection below). + +That means that even if the stable parts of the heap are lost, the +stable memory state can fully describe the region objects that will be rebuilt when it succeeds. + +### stable memory fields + + - total allocated blocks, `u16`, max value is `32768`. + - total allocated regions, `u64`, max value is 2^64-1 (one region is reserved for "no region" in block-region table). + - The `block` table (fixed size, about 6 pages). + +### representation of values of type `Region` + + - A singleton, heap-allocated object with mutable fields. + - While being heap-allocated, the object is also `stable` (can be stored in a `stable var`, etc). + - `RegionObject { id_lower: u32, id_upper: u32; mut page_count: u32; mut vec_pages: Value }` + - Fields id_lower (lower 32-bits) and id_upper (upper 32-bits) gives the Region's numerical 64-bit (id = (id_upper << 32 | id_lower)). + - Field `page_count` gives the number of pages allocated to the Region. + - Field `vec_pages` points at a heap-allocated `Blob` value, and it works with `page_count` + to represent a growable vector that we call the region's **"access + vector"** (because "blocks vector" sounds a bit strange, and its + used to support O(1) access operations): + - the access vector has `vec_capacity` slots. + - each slot is a `u16`. + - the first `page_count + 127 / 128` slots contain a valid page block ID for the region. + - during an upgrade, the access vectors get serialized and deserialized as data `Blobs`. + + +### region-blocks relation + +The `region-blocks` relation is not materialized into a table in stable memory (doing so with a pre-allocated table would be prohibitively large). + +Instead, this relation is represented in two ways at the same time: + 1. by the set of heap-allocated region objects, and their access vectors. The access vectors provide O(1) store and load support. + 2. by the `block-region` table, which together are sufficient to recreate all of the heap-allocated region objects. + +In ordinary operation, the second feature is not required. In the event of an upgrade failure, however, it could be vital (See `rebuild`). + +### block-region table + + - purpose: + - relate a block ID ("page block ID") to its region (if any), its position (or rank) in that region (see `rebuild`) and its current size in (used) pages (<=128). + All but the last block owned by a region should have all pages 128 allocated. + + - NB: The organization of this table is not useful for efficient + access calculations of load or store (they require a linear + search that would be prohibitively slow). OTOH, this + table is suitable to do a "batch" rebuild of the dynamic heap-allocated vectors + in that table, if need be (see `rebuild`). + + - 32768 entries (statically sized). + - 8 (id) +2 (rank) + 1 (used) = 11 bytes per entry. + - entry type = `BlockRegion { region : u64, position : u16, size: u8 }` + - the location of each entry gives its corresponding block ID. + + +### Overview of `rebuild` + +When upgrades work as expected, stable `Regions` are serialized and deserialized just like other stable data. + +For disaster recovery, we can **also** rebuild all of the region objects from data in stable memory. + +We use the `block-region` tables in stable memory to rebuild the regions' objects: + + - The `block-region` table gives a relative position and region ID for each block ID together with utilized page count. + +Once each regions' vectors have been sized (by a linear scan of block-region, summing block sizes) and allocated, the block-region table says how to fill them, one entry at a time. +Unlike the Rust design, vector entries can be populated out-of-order. + +Currently, we need only recover region 0 (when upgrading). + + +### Special (reserved) regions + + - Region 0 -- Anonymous region, for supporting the legacy API that we have today, which lacks `Region` values. + - Region 1 -- "Reclaimed blocks" region that consists of blocks reclaimed from GC'd regions. + - Regions 2-15 -- Future use by Motoko RTS (TBD). + +### Overview of GC support (future work) + +- Regions are represented (see special subsection) with heap objects that are `stable`, but mutable. +- They have special GC headers to recognize their special structure. +- The block-region table (or a more transient bitmap) keeps track of which blocks are in use as Region heap values are GC'd. + +Blocks can be marked during normal GC, with unmarked blocks returned to a transient free-list. In this design, blocks are recycled during +the lifetime of a single canister version. + +Alternatively, Blocks can be marked only during deserialization after an upgrade, for bespoke, Region-only, GC during upgrades, with unmarked blocks +returned to a free list. +In this design, blocks are only recycled during upgrade from one version to the next, meaning long-lived canisters that create garbage regions will leak +space. + +### Migration from earlier designs into region system + +#### Version overview + +Including this design, there are three possible verions (`0`, `1`, or `2`): + + 0. Stable vars only. + 1. Stable vars *plus* direct access to IC0 API, including `grow`. + This access is exposed via the Experimental Stable Memory API. + 2. This Region system, where direct access still works through region zero. + + +#### Key points + +- Version 0: + - will never be forced to "migrate" to other versions (assuming no stable memory API use). + - will never incur the space overhead of the region system. + +- Migration from 0 to version 1 occurs when: + - Experimental Stable Memory function `grow` is invoked for the first time. + This will not incur the space overhead of the region system. + +- Migration from version 0 or version 1 to version 2 occurs when: + - An initial region is allocated via `Region.new`. + This will incur the space overhead of the region system. + The space overhead is 16 pages (1MB) when migration from version 0, and 128 pages (8MiB) when migrating from version 1. + +#### Compiler flag + +Compiler flag + +``` + --stable-regions +``` + +Affects upgrades only and forces migration directly into version 2 from version 0 or 1. +It is provided for testing purposes and *not* required for use of regions. + +#### Format version details + +The first 32 bits of stable memory record a "marker," which indicates how to determine the "version number" +for Motoko stable memory. This version number is stored either: + - *implicitly*, when the marker is non-zero, and version is `0`. + - *explicitly*, when the marker is zero, and version is stored elsewhere (but currently always `1`). + +Including this design, there are three possible verions (`0`, `1`, or `2`). See preceeding section. + +In the first cases (`0`) and (`1`), we wish to *selectively* migrate into the region system (`2`), with its own internal versioning. + + +#### Opt-in mechanism + +The opt-in mechanism for using version 2 consists of using: + + - dynamically calling `Region.new()` form a canister currently in version 0 or 1; + - staticly specifying compiler flag `--stable-regions`. This is mostly useful for testing. + +Critically, + +1. The use of physical stable memory is pay-as-you-go: canisters that do not use regions do not pay for that priviledge. +2. There is no provision for "downgrading" back to earlier, pre-region systems. + +##### Version 0 migration. + +To migrate from version 0 to version 2, there is nothing additional to do for existing data. + +The region system detects this case by measuring the zero-sized stable memory during its initialization and +starts allocating blocks from address 16*2^16 (1MiB overhead), leaving 10 pages unused for future use. + +##### Version 1 migration. + +Migrating version 1 stable memory renames it as "region 0" in Version 2. + +Critically, to migrate from version 1, we must perserve existing data, but reorganize it slightly. + +In effect, all existing data will retain its logical order as (pre-allocated) region 0. + +To accomodate the meta data of the region system, we move the first block of region 0, physically. + +Then, we reformat the first block of stable memory as the region meta data block. + +The rest of the blocks become part of region 0, with its first block stored at the end of physical memory. + +The region system starts allocating blocks from address 128*2^16 (8MiB overhead), leaving 122 pages unused for future use. + +Since we do not move the remaining blocks of region 0, the first block of memory (excluding meta-data) is unused space. + +This design ensures that an existing canister using very large amounts of experimental stable memory can be migrated with only constant-cost movement +of the first block (128 pages) of memory. diff --git a/doc/md/base/ExperimentalStableMemory.md b/doc/md/base/ExperimentalStableMemory.md index ca86d7e00eb..eee62ef66bf 100644 --- a/doc/md/base/ExperimentalStableMemory.md +++ b/doc/md/base/ExperimentalStableMemory.md @@ -5,6 +5,11 @@ Byte-level access to (virtual) _stable memory_. and may be replaced by safer alternatives in later versions of Motoko. Use at your own risk and discretion. +**DEPRECATION**: Use of `ExperimentalStableMemory` library may be deprecated in future. +Going forward, users should consider using library `Region.mo` to allocate *isolated* regions of memory instead. +Using dedicated regions for different user applications ensures that writing +to one region will not affect the state of another, unrelated region. + This is a lightweight abstraction over IC _stable memory_ and supports persisting raw binary data across Motoko upgrades. Use of this module is fully compatible with Motoko's use of @@ -30,7 +35,7 @@ NB: The IC's actual stable memory size (`ic0.stable_size`) may exceed the page size reported by Motoko function `size()`. This (and the cap on growth) are to accommodate Motoko's stable variables. Applications that plan to use Motoko stable variables sparingly or not at all can -increase `--max-stable-pages` as desired, approaching the IC maximum (initially 8GiB, then 32Gib, currently 48Gib). +increase `--max-stable-pages` as desired, approaching the IC maximum (initially 8GiB, then 32Gib, currently 64Gib). All applications should reserve at least one page for stable variable data, even when no stable variables are used. Usage: diff --git a/doc/md/base/Float.md b/doc/md/base/Float.md index 06b19128493..67abe4e34ce 100644 --- a/doc/md/base/Float.md +++ b/doc/md/base/Float.md @@ -39,6 +39,9 @@ Advice: * For absolute precision, it is recommened to encode the fraction number as a pair of a Nat for the base and a Nat for the exponent (decimal point). +NaN sign: +* The NaN sign is only applied by `abs`, `neg`, and `copySign`. Other operations can have an arbitrary + sign bit for NaN results. ## Type `Float` ``` motoko no-repl @@ -72,7 +75,7 @@ Determines whether the `number` is a `NaN` ("not a number" in the floating point Notes: * Equality test of `NaN` with itself or another number is always `false`. * There exist many internal `NaN` value representations, such as positive and negative NaN, - signalling and quiet nans, each with many different bit representations. + signalling and quiet NaNs, each with many different bit representations. Example: ```motoko @@ -92,7 +95,7 @@ Special cases: ``` abs(+inf) => +inf abs(-inf) => +inf -abs(NaN) => NaN +abs(-NaN) => +NaN abs(-0.0) => 0.0 ``` @@ -787,8 +790,16 @@ func neg(x : Float) : Float Returns the negation of `x`, `-x` . Changes the sign bit for infinity. -Issue: Inconsistent behavior for zero and `NaN`. Probably related to -https://github.com/dfinity/motoko/issues/3646 + +Special cases: +``` +neg(+inf) => -inf +neg(-inf) => +inf +neg(+NaN) => -NaN +neg(-NaN) => +NaN +neg(+0.0) => -0.0 +neg(-0.0) => +0.0 +``` Example: ```motoko diff --git a/doc/md/base/Int.md b/doc/md/base/Int.md index f731a0172eb..80e784f8e84 100644 --- a/doc/md/base/Int.md +++ b/doc/md/base/Int.md @@ -233,6 +233,11 @@ Example: Int.neg(123) // => -123 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + ## Function `add` ``` motoko no-repl func add(x : Int, y : Int) : Int diff --git a/doc/md/base/Int16.md b/doc/md/base/Int16.md index 9e5ec937a76..4ec85bb969f 100644 --- a/doc/md/base/Int16.md +++ b/doc/md/base/Int16.md @@ -1,8 +1,12 @@ # Int16 -16-bit signed integers with checked arithmetic. +Provides utility functions on 16-bit signed integers. -Common 16-bit integer functions. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Int16 "mo:base/Int16"; +``` ## Type `Int16` ``` motoko no-repl @@ -18,6 +22,11 @@ let minimumValue : Int16 Minimum 16-bit integer value, `-2 ** 15`. +Example: +```motoko include=import +Int16.minimumValue // => -32_768 : Int16 +``` + ## Value `maximumValue` ``` motoko no-repl let maximumValue : Int16 @@ -25,6 +34,11 @@ let maximumValue : Int16 Maximum 16-bit integer value, `+2 ** 15 - 1`. +Example: +```motoko include=import +Int16.maximumValue // => +32_767 : Int16 +``` + ## Value `toInt` ``` motoko no-repl let toInt : Int16 -> Int @@ -33,9 +47,7 @@ let toInt : Int16 -> Int Converts a 16-bit signed integer to a signed integer with infinite precision. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.toInt(12_345) // => 12_345 : Int ``` @@ -49,9 +61,7 @@ Converts a signed integer with infinite precision to a 16-bit signed integer. Traps on overflow/underflow. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.fromInt(12_345) // => +12_345 : Int16 ``` @@ -65,12 +75,62 @@ Converts a signed integer with infinite precision to a 16-bit signed integer. Wraps on overflow/underflow. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.fromIntWrap(-12_345) // => -12_345 : Int ``` +## Value `fromInt8` +``` motoko no-repl +let fromInt8 : Int8 -> Int16 +``` + +Converts a 8-bit signed integer to a 16-bit signed integer. + +Example: +```motoko include=import +Int16.fromInt8(-123) // => -123 : Int16 +``` + +## Value `toInt8` +``` motoko no-repl +let toInt8 : Int16 -> Int8 +``` + +Converts a 16-bit signed integer to a 8-bit signed integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Int16.toInt8(-123) // => -123 : Int8 +``` + +## Value `fromInt32` +``` motoko no-repl +let fromInt32 : Int32 -> Int16 +``` + +Converts a 32-bit signed integer to a 16-bit signed integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Int16.fromInt32(-12_345) // => -12_345 : Int16 +``` + +## Value `toInt32` +``` motoko no-repl +let toInt32 : Int16 -> Int32 +``` + +Converts a 16-bit signed integer to a 32-bit signed integer. + +Example: +```motoko include=import +Int16.toInt32(-12_345) // => -12_345 : Int32 +``` + ## Value `fromNat16` ``` motoko no-repl let fromNat16 : Nat16 -> Int16 @@ -81,9 +141,7 @@ Converts an unsigned 16-bit integer to a signed 16-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.fromNat16(12_345) // => +12_345 : Int16 ``` @@ -97,9 +155,7 @@ Converts a signed 16-bit integer to an unsigned 16-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.toNat16(-1) // => 65_535 : Nat16 // underflow ``` @@ -108,13 +164,11 @@ Int16.toNat16(-1) // => 65_535 : Nat16 // underflow func toText(x : Int16) : Text ``` -Returns the Text representation of `x`. -Formats the integer in decimal representation without underscore separators for thousand figures. +Returns the Text representation of `x`. Textual representation _do not_ +contain underscores to represent commas. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.toText(-12345) // => "-12345" ``` @@ -128,9 +182,7 @@ Returns the absolute value of `x`. Traps when `x == -2 ** 15` (the minimum `Int16` value). Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.abs(-12345) // => +12_345 ``` @@ -142,9 +194,7 @@ func min(x : Int16, y : Int16) : Int16 Returns the minimum of `x` and `y`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.min(+2, -3) // => -3 ``` @@ -156,9 +206,7 @@ func max(x : Int16, y : Int16) : Int16 Returns the maximum of `x` and `y`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.max(+2, -3) // => +2 ``` @@ -167,13 +215,28 @@ Int16.max(+2, -3) // => +2 func equal(x : Int16, y : Int16) : Bool ``` -Returns `x == y`. +Equality function for Int16 types. +This is equivalent to `x == y`. Example: -```motoko -import Int16 "mo:base/Int16"; +```motoko include=import +Int16.equal(-1, -1); // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. -Int16.equal(123, 123) // => true +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; + +let buffer1 = Buffer.Buffer(1); +buffer1.add(-3); +let buffer2 = Buffer.Buffer(1); +buffer2.add(-3); +Buffer.equal(buffer1, buffer2, Int16.equal) // => true ``` ## Function `notEqual` @@ -181,55 +244,66 @@ Int16.equal(123, 123) // => true func notEqual(x : Int16, y : Int16) : Bool ``` -Returns `x != y`. +Inequality function for Int16 types. +This is equivalent to `x != y`. Example: -```motoko -import Int16 "mo:base/Int16"; - -Int16.notEqual(123, 123) // => false +```motoko include=import +Int16.notEqual(-1, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. + ## Function `less` ``` motoko no-repl func less(x : Int16, y : Int16) : Bool ``` -Returns `x < y`. +"Less than" function for Int16 types. +This is equivalent to `x < y`. Example: -```motoko -import Int16 "mo:base/Int16"; - -Int16.less(123, 1234) // => true +```motoko include=import +Int16.less(-2, 1); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. + ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Int16, y : Int16) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Int16 types. +This is equivalent to `x <= y`. Example: -```motoko -import Int16 "mo:base/Int16"; - -Int16.lessOrEqual(123, 1234) // => true +```motoko include=import +Int16.lessOrEqual(-2, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. + ## Function `greater` ``` motoko no-repl func greater(x : Int16, y : Int16) : Bool ``` -Returns `x > y`. +"Greater than" function for Int16 types. +This is equivalent to `x > y`. Example: -```motoko -import Int16 "mo:base/Int16"; - -Int16.greater(1234, 123) // => true +```motoko include=import +Int16.greater(-2, 1); // => false ``` ## Function `greaterOrEqual` @@ -237,13 +311,12 @@ Int16.greater(1234, 123) // => true func greaterOrEqual(x : Int16, y : Int16) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Int16 types. +This is equivalent to `x >= y`. Example: -```motoko -import Int16 "mo:base/Int16"; - -Int16.greaterOrEqual(1234, 123) // => true +```motoko include=import +Int16.greaterOrEqual(-2, -2); // => true ``` ## Function `compare` @@ -251,13 +324,20 @@ Int16.greaterOrEqual(1234, 123) // => true func compare(x : Int16, y : Int16) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General-purpose comparison function for `Int16`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. Example: -```motoko -import Int16 "mo:base/Int16"; +```motoko include=import +Int16.compare(-3, 2) // => #less +``` -Int16.compare(123, 1234) // => #less +This function can be used as value for a high order function, such as a sort function. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([1, -2, -3] : [Int16], Int16.compare) // => [-3, -2, 1] ``` ## Function `neg` @@ -269,14 +349,16 @@ Returns the negation of `x`, `-x`. Traps on overflow, i.e. for `neg(-2 ** 15)`. - Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.neg(123) // => -123 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + ## Function `add` ``` motoko no-repl func add(x : Int16, y : Int16) : Int16 @@ -287,10 +369,19 @@ Returns the sum of `x` and `y`, `x + y`. Traps on overflow/underflow. Example: -```motoko -import Int16 "mo:base/Int16"; +```motoko include=import +Int16.add(100, 23) // => +123 +``` -Int16.add(1234, 123) // => +1_357 +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int16.add) // => -4 ``` ## Function `sub` @@ -303,10 +394,19 @@ Returns the difference of `x` and `y`, `x - y`. Traps on overflow/underflow. Example: -```motoko -import Int16 "mo:base/Int16"; +```motoko include=import +Int16.sub(123, 100) // => +23 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. -Int16.sub(1234, 123) // => +1_111 +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int16.sub) // => 4 ``` ## Function `mul` @@ -319,10 +419,19 @@ Returns the product of `x` and `y`, `x * y`. Traps on overflow/underflow. Example: -```motoko -import Int16 "mo:base/Int16"; +```motoko include=import +Int16.mul(12, 10) // => +120 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. -Int16.mul(123, 100) // => +12_300 +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 1, Int16.mul) // => 6 ``` ## Function `div` @@ -336,12 +445,15 @@ Rounds the quotient towards zero, which is the same as truncating the decimal pl Traps when `y` is zero. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.div(123, 10) // => +12 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Int16, y : Int16) : Int16 @@ -353,12 +465,15 @@ which is defined as `x - x / y * y`. Traps when `y` is zero. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.rem(123, 10) // => +3 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Int16, y : Int16) : Int16 @@ -369,12 +484,15 @@ Returns `x` to the power of `y`, `x ** y`. Traps on overflow/underflow and when `y < 0 or y >= 16`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.pow(2, 10) // => +1_024 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. + ## Function `bitnot` ``` motoko no-repl func bitnot(x : Int16) : Int16 @@ -383,12 +501,15 @@ func bitnot(x : Int16) : Int16 Returns the bitwise negation of `x`, `^x`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitnot(-256 /* 0xff00 */) // => +255 // 0xff ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Int16, y : Int16) : Int16 @@ -397,12 +518,15 @@ func bitand(x : Int16, y : Int16) : Int16 Returns the bitwise "and" of `x` and `y`, `x & y`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitand(0x0fff, 0x00f0) // => +240 // 0xf0 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Int16, y : Int16) : Int16 @@ -411,11 +535,13 @@ func bitor(x : Int16, y : Int16) : Int16 Returns the bitwise "or" of `x` and `y`, `x | y`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitor(0x0f0f, 0x00f0) // => +4_095 // 0x0fff ``` +Note: The reason why this function is defined in this library (in addition +to the existing `|` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `|` +as a function value at the moment. ## Function `bitxor` ``` motoko no-repl @@ -425,11 +551,13 @@ func bitxor(x : Int16, y : Int16) : Int16 Returns the bitwise "exclusive or" of `x` and `y`, `x ^ y`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitxor(0x0fff, 0x00f0) // => +3_855 // 0x0f0f ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. ## Function `bitshiftLeft` ``` motoko no-repl @@ -444,12 +572,15 @@ For `y >= 16`, the semantics is the same as for `bitshiftLeft(x, y % 16)`. For `y < 0`, the semantics is the same as for `bitshiftLeft(x, y + y % 16)`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitshiftLeft(1, 8) // => +256 // 0x100 equivalent to `2 ** 8`. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Int16, y : Int16) : Int16 @@ -463,12 +594,15 @@ For `y >= 16`, the semantics is the same as for `bitshiftRight(x, y % 16)`. For `y < 0`, the semantics is the same as for `bitshiftRight (x, y + y % 16)`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitshiftRight(1024, 8) // => +4 // equivalent to `1024 / (2 ** 8)` ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Int16, y : Int16) : Int16 @@ -482,12 +616,15 @@ Changes the direction of rotation for negative `y`. For `y >= 16`, the semantics is the same as for `bitrotLeft(x, y % 16)`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitrotLeft(0x2001, 4) // => +18 // 0x12. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Int16, y : Int16) : Int16 @@ -501,12 +638,15 @@ Changes the direction of rotation for negative `y`. For `y >= 16`, the semantics is the same as for `bitrotRight(x, y % 16)`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitrotRight(0x2010, 8) // => +4_128 // 0x01020. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Int16, p : Nat) : Bool @@ -514,11 +654,10 @@ func bittest(x : Int16, p : Nat) : Bool Returns the value of bit `p` in `x`, `x & 2**p == 2**p`. If `p >= 16`, the semantics is the same as for `bittest(x, p % 16)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bittest(128, 7) // => true ``` @@ -531,9 +670,7 @@ Returns the value of setting bit `p` in `x` to `1`. If `p >= 16`, the semantics is the same as for `bitset(x, p % 16)`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitset(0, 7) // => +128 ``` @@ -546,9 +683,7 @@ Returns the value of clearing bit `p` in `x` to `0`. If `p >= 16`, the semantics is the same as for `bitclear(x, p % 16)`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitclear(-1, 7) // => -129 ``` @@ -561,9 +696,7 @@ Returns the value of flipping bit `p` in `x`. If `p >= 16`, the semantics is the same as for `bitclear(x, p % 16)`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitflip(255, 7) // => +127 ``` @@ -575,9 +708,7 @@ let bitcountNonZero : (x : Int16) -> Int16 Returns the count of non-zero bits in `x`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitcountNonZero(0xff) // => +8 ``` @@ -589,9 +720,7 @@ let bitcountLeadingZero : (x : Int16) -> Int16 Returns the count of leading zero bits in `x`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitcountLeadingZero(0x80) // => +8 ``` @@ -603,9 +732,7 @@ let bitcountTrailingZero : (x : Int16) -> Int16 Returns the count of trailing zero bits in `x`. Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.bitcountTrailingZero(0x0100) // => +8 ``` @@ -618,14 +745,16 @@ Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.addWrap(2 ** 14, 2 ** 14) // => -32_768 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Int16, y : Int16) : Int16 @@ -635,14 +764,16 @@ Returns the difference of `x` and `y`, `x -% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.subWrap(-2 ** 15, 1) // => +32_767 // underflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Int16, y : Int16) : Int16 @@ -652,14 +783,16 @@ Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. Wraps on overflow/underflow. - Example: -```motoko -import Int16 "mo:base/Int16"; - +```motoko include=import Int16.mulWrap(2 ** 8, 2 ** 8) // => 0 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Int16, y : Int16) : Int16 @@ -670,10 +803,13 @@ Returns `x` to the power of `y`, `x **% y`. Wraps on overflow/underflow. Traps if `y < 0 or y >= 16`. - Example: -```motoko -import Int16 "mo:base/Int16"; +```motoko include=import Int16.powWrap(2, 15) // => -32_768 // overflow ``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Int32.md b/doc/md/base/Int32.md index b967d1bf1ff..a257a4917f0 100644 --- a/doc/md/base/Int32.md +++ b/doc/md/base/Int32.md @@ -1,8 +1,12 @@ # Int32 -32-bit signed integers with checked arithmetic. +Provides utility functions on 32-bit signed integers. -Common 32-bit integer functions. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Int32 "mo:base/Int32"; +``` ## Type `Int32` ``` motoko no-repl @@ -18,6 +22,11 @@ let minimumValue : Int32 Minimum 32-bit integer value, `-2 ** 31`. +Example: +```motoko include=import +Int32.minimumValue // => -2_147_483_648 +``` + ## Value `maximumValue` ``` motoko no-repl let maximumValue : Int32 @@ -25,6 +34,11 @@ let maximumValue : Int32 Maximum 32-bit integer value, `+2 ** 31 - 1`. +Example: +```motoko include=import +Int32.maximumValue // => +2_147_483_647 +``` + ## Value `toInt` ``` motoko no-repl let toInt : Int32 -> Int @@ -33,9 +47,7 @@ let toInt : Int32 -> Int Converts a 32-bit signed integer to a signed integer with infinite precision. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.toInt(123_456) // => 123_456 : Int ``` @@ -49,9 +61,7 @@ Converts a signed integer with infinite precision to a 32-bit signed integer. Traps on overflow/underflow. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.fromInt(123_456) // => +123_456 : Int32 ``` @@ -65,12 +75,62 @@ Converts a signed integer with infinite precision to a 32-bit signed integer. Wraps on overflow/underflow. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.fromIntWrap(-123_456) // => -123_456 : Int ``` +## Value `fromInt16` +``` motoko no-repl +let fromInt16 : Int16 -> Int32 +``` + +Converts a 16-bit signed integer to a 32-bit signed integer. + +Example: +```motoko include=import +Int32.fromInt16(-123) // => -123 : Int32 +``` + +## Value `toInt16` +``` motoko no-repl +let toInt16 : Int32 -> Int16 +``` + +Converts a 32-bit signed integer to a 16-bit signed integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Int32.toInt16(-123) // => -123 : Int16 +``` + +## Value `fromInt64` +``` motoko no-repl +let fromInt64 : Int64 -> Int32 +``` + +Converts a 64-bit signed integer to a 32-bit signed integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Int32.fromInt64(-123_456) // => -123_456 : Int32 +``` + +## Value `toInt64` +``` motoko no-repl +let toInt64 : Int32 -> Int64 +``` + +Converts a 32-bit signed integer to a 64-bit signed integer. + +Example: +```motoko include=import +Int32.toInt64(-123_456) // => -123_456 : Int64 +``` + ## Value `fromNat32` ``` motoko no-repl let fromNat32 : Nat32 -> Int32 @@ -81,9 +141,7 @@ Converts an unsigned 32-bit integer to a signed 32-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.fromNat32(123_456) // => +123_456 : Int32 ``` @@ -97,9 +155,7 @@ Converts a signed 32-bit integer to an unsigned 32-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.toNat32(-1) // => 4_294_967_295 : Nat32 // underflow ``` @@ -108,13 +164,11 @@ Int32.toNat32(-1) // => 4_294_967_295 : Nat32 // underflow func toText(x : Int32) : Text ``` -Returns the Text representation of `x`. -Formats the integer in decimal representation without underscore separators for thousand figures. +Returns the Text representation of `x`. Textual representation _do not_ +contain underscores to represent commas. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.toText(-123456) // => "-123456" ``` @@ -128,9 +182,7 @@ Returns the absolute value of `x`. Traps when `x == -2 ** 31` (the minimum `Int32` value). Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.abs(-123456) // => +123_456 ``` @@ -142,9 +194,7 @@ func min(x : Int32, y : Int32) : Int32 Returns the minimum of `x` and `y`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.min(+2, -3) // => -3 ``` @@ -156,9 +206,7 @@ func max(x : Int32, y : Int32) : Int32 Returns the maximum of `x` and `y`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.max(+2, -3) // => +2 ``` @@ -167,13 +215,28 @@ Int32.max(+2, -3) // => +2 func equal(x : Int32, y : Int32) : Bool ``` -Returns `x == y`. +Equality function for Int32 types. +This is equivalent to `x == y`. Example: -```motoko -import Int32 "mo:base/Int32"; +```motoko include=import +Int32.equal(-1, -1); // => true +``` -Int32.equal(123, 123) // => true +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. + +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; + +let buffer1 = Buffer.Buffer(1); +buffer1.add(-3); +let buffer2 = Buffer.Buffer(1); +buffer2.add(-3); +Buffer.equal(buffer1, buffer2, Int32.equal) // => true ``` ## Function `notEqual` @@ -181,83 +244,110 @@ Int32.equal(123, 123) // => true func notEqual(x : Int32, y : Int32) : Bool ``` -Returns `x != y`. +Inequality function for Int32 types. +This is equivalent to `x != y`. Example: -```motoko -import Int32 "mo:base/Int32"; - -Int32.notEqual(123, 123) // => false +```motoko include=import +Int32.notEqual(-1, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. + ## Function `less` ``` motoko no-repl func less(x : Int32, y : Int32) : Bool ``` -Returns `x < y`. +"Less than" function for Int32 types. +This is equivalent to `x < y`. Example: -```motoko -import Int32 "mo:base/Int32"; - -Int32.less(123, 1234) // => true +```motoko include=import +Int32.less(-2, 1); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. + ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Int32, y : Int32) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Int32 types. +This is equivalent to `x <= y`. Example: -```motoko -import Int32 "mo:base/Int32"; - -Int32.lessOrEqual(123, 1234) // => true +```motoko include=import +Int32.lessOrEqual(-2, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. + ## Function `greater` ``` motoko no-repl func greater(x : Int32, y : Int32) : Bool ``` -Returns `x > y`. +"Greater than" function for Int32 types. +This is equivalent to `x > y`. Example: -```motoko -import Int32 "mo:base/Int32"; - -Int32.greater(1234, 123) // => true +```motoko include=import +Int32.greater(-2, -3); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>` +as a function value at the moment. + ## Function `greaterOrEqual` ``` motoko no-repl func greaterOrEqual(x : Int32, y : Int32) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Int32 types. +This is equivalent to `x >= y`. Example: -```motoko -import Int32 "mo:base/Int32"; - -Int32.greaterOrEqual(1234, 123) // => true +```motoko include=import +Int32.greaterOrEqual(-2, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>=` +as a function value at the moment. + ## Function `compare` ``` motoko no-repl func compare(x : Int32, y : Int32) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General-purpose comparison function for `Int32`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. Example: -```motoko -import Int32 "mo:base/Int32"; +```motoko include=import +Int32.compare(-3, 2) // => #less +``` -Int32.compare(123, 1234) // => #less +This function can be used as value for a high order function, such as a sort function. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([1, -2, -3] : [Int32], Int32.compare) // => [-3, -2, 1] ``` ## Function `neg` @@ -269,14 +359,16 @@ Returns the negation of `x`, `-x`. Traps on overflow, i.e. for `neg(-2 ** 31)`. - Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.neg(123) // => -123 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + ## Function `add` ``` motoko no-repl func add(x : Int32, y : Int32) : Int32 @@ -287,10 +379,19 @@ Returns the sum of `x` and `y`, `x + y`. Traps on overflow/underflow. Example: -```motoko -import Int32 "mo:base/Int32"; +```motoko include=import +Int32.add(100, 23) // => +123 +``` -Int32.add(1234, 123) // => +1_357 +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int32.add) // => -4 ``` ## Function `sub` @@ -303,12 +404,21 @@ Returns the difference of `x` and `y`, `x - y`. Traps on overflow/underflow. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.sub(1234, 123) // => +1_111 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int32.sub) // => 6 +``` + ## Function `mul` ``` motoko no-repl func mul(x : Int32, y : Int32) : Int32 @@ -319,12 +429,21 @@ Returns the product of `x` and `y`, `x * y`. Traps on overflow/underflow. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.mul(123, 100) // => +12_300 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 1, Int32.mul) // => 6 +``` + ## Function `div` ``` motoko no-repl func div(x : Int32, y : Int32) : Int32 @@ -336,12 +455,15 @@ Rounds the quotient towards zero, which is the same as truncating the decimal pl Traps when `y` is zero. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.div(123, 10) // => +12 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Int32, y : Int32) : Int32 @@ -353,12 +475,15 @@ which is defined as `x - x / y * y`. Traps when `y` is zero. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.rem(123, 10) // => +3 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Int32, y : Int32) : Int32 @@ -369,12 +494,15 @@ Returns `x` to the power of `y`, `x ** y`. Traps on overflow/underflow and when `y < 0 or y >= 32`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.pow(2, 10) // => +1_024 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. + ## Function `bitnot` ``` motoko no-repl func bitnot(x : Int32) : Int32 @@ -383,12 +511,15 @@ func bitnot(x : Int32) : Int32 Returns the bitwise negation of `x`, `^x`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitnot(-256 /* 0xffff_ff00 */) // => +255 // 0xff ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Int32, y : Int32) : Int32 @@ -397,12 +528,15 @@ func bitand(x : Int32, y : Int32) : Int32 Returns the bitwise "and" of `x` and `y`, `x & y`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitand(0xffff, 0x00f0) // => +240 // 0xf0 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Int32, y : Int32) : Int32 @@ -411,12 +545,15 @@ func bitor(x : Int32, y : Int32) : Int32 Returns the bitwise "or" of `x` and `y`, `x | y`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitor(0xffff, 0x00f0) // => +65_535 // 0xffff ``` +Note: The reason why this function is defined in this library (in addition +to the existing `|` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `|` +as a function value at the moment. + ## Function `bitxor` ``` motoko no-repl func bitxor(x : Int32, y : Int32) : Int32 @@ -425,12 +562,15 @@ func bitxor(x : Int32, y : Int32) : Int32 Returns the bitwise "exclusive or" of `x` and `y`, `x ^ y`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitxor(0xffff, 0x00f0) // => +65_295 // 0xff0f ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitshiftLeft` ``` motoko no-repl func bitshiftLeft(x : Int32, y : Int32) : Int32 @@ -444,12 +584,15 @@ For `y >= 32`, the semantics is the same as for `bitshiftLeft(x, y % 32)`. For `y < 0`, the semantics is the same as for `bitshiftLeft(x, y + y % 32)`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitshiftLeft(1, 8) // => +256 // 0x100 equivalent to `2 ** 8`. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Int32, y : Int32) : Int32 @@ -463,12 +606,15 @@ For `y >= 32`, the semantics is the same as for `bitshiftRight(x, y % 32)`. For `y < 0`, the semantics is the same as for `bitshiftRight (x, y + y % 32)`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitshiftRight(1024, 8) // => +4 // equivalent to `1024 / (2 ** 8)` ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Int32, y : Int32) : Int32 @@ -482,12 +628,15 @@ Changes the direction of rotation for negative `y`. For `y >= 32`, the semantics is the same as for `bitrotLeft(x, y % 32)`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitrotLeft(0x2000_0001, 4) // => +18 // 0x12. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Int32, y : Int32) : Int32 @@ -501,12 +650,15 @@ Changes the direction of rotation for negative `y`. For `y >= 32`, the semantics is the same as for `bitrotRight(x, y % 32)`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitrotRight(0x0002_0001, 8) // => +16_777_728 // 0x0100_0200. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Int32, p : Nat) : Bool @@ -514,11 +666,10 @@ func bittest(x : Int32, p : Nat) : Bool Returns the value of bit `p` in `x`, `x & 2**p == 2**p`. If `p >= 32`, the semantics is the same as for `bittest(x, p % 32)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bittest(128, 7) // => true ``` @@ -531,9 +682,7 @@ Returns the value of setting bit `p` in `x` to `1`. If `p >= 32`, the semantics is the same as for `bitset(x, p % 32)`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitset(0, 7) // => +128 ``` @@ -546,9 +695,7 @@ Returns the value of clearing bit `p` in `x` to `0`. If `p >= 32`, the semantics is the same as for `bitclear(x, p % 32)`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitclear(-1, 7) // => -129 ``` @@ -561,9 +708,7 @@ Returns the value of flipping bit `p` in `x`. If `p >= 32`, the semantics is the same as for `bitclear(x, p % 32)`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitflip(255, 7) // => +127 ``` @@ -575,9 +720,7 @@ let bitcountNonZero : (x : Int32) -> Int32 Returns the count of non-zero bits in `x`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitcountNonZero(0xffff) // => +16 ``` @@ -589,9 +732,7 @@ let bitcountLeadingZero : (x : Int32) -> Int32 Returns the count of leading zero bits in `x`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitcountLeadingZero(0x8000) // => +16 ``` @@ -603,9 +744,7 @@ let bitcountTrailingZero : (x : Int32) -> Int32 Returns the count of trailing zero bits in `x`. Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.bitcountTrailingZero(0x0201_0000) // => +16 ``` @@ -618,14 +757,16 @@ Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.addWrap(2 ** 30, 2 ** 30) // => -2_147_483_648 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Int32, y : Int32) : Int32 @@ -635,14 +776,16 @@ Returns the difference of `x` and `y`, `x -% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.subWrap(-2 ** 31, 1) // => +2_147_483_647 // underflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Int32, y : Int32) : Int32 @@ -652,14 +795,16 @@ Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. Wraps on overflow/underflow. - Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.mulWrap(2 ** 16, 2 ** 16) // => 0 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Int32, y : Int32) : Int32 @@ -670,10 +815,12 @@ Returns `x` to the power of `y`, `x **% y`. Wraps on overflow/underflow. Traps if `y < 0 or y >= 32`. - Example: -```motoko -import Int32 "mo:base/Int32"; - +```motoko include=import Int32.powWrap(2, 31) // => -2_147_483_648 // overflow ``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Int64.md b/doc/md/base/Int64.md index 62934851e48..1ded9b5fff1 100644 --- a/doc/md/base/Int64.md +++ b/doc/md/base/Int64.md @@ -1,8 +1,12 @@ # Int64 -64-bit signed integers with checked arithmetic. +Provides utility functions on 64-bit signed integers. -Common 64-bit integer functions. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Int64 "mo:base/Int64"; +``` ## Type `Int64` ``` motoko no-repl @@ -18,6 +22,11 @@ let minimumValue : Int64 Minimum 64-bit integer value, `-2 ** 63`. +Example: +```motoko include=import +Int64.minimumValue // => -9_223_372_036_854_775_808 +``` + ## Value `maximumValue` ``` motoko no-repl let maximumValue : Int64 @@ -25,6 +34,11 @@ let maximumValue : Int64 Maximum 64-bit integer value, `+2 ** 63 - 1`. +Example: +```motoko include=import +Int64.maximumValue // => +9_223_372_036_854_775_807 +``` + ## Value `toInt` ``` motoko no-repl let toInt : Int64 -> Int @@ -33,9 +47,7 @@ let toInt : Int64 -> Int Converts a 64-bit signed integer to a signed integer with infinite precision. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.toInt(123_456) // => 123_456 : Int ``` @@ -49,12 +61,38 @@ Converts a signed integer with infinite precision to a 64-bit signed integer. Traps on overflow/underflow. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.fromInt(123_456) // => +123_456 : Int64 ``` +## Value `fromInt32` +``` motoko no-repl +let fromInt32 : Int32 -> Int64 +``` + +Converts a 32-bit signed integer to a 64-bit signed integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Int64.fromInt32(-123_456) // => -123_456 : Int64 +``` + +## Value `toInt32` +``` motoko no-repl +let toInt32 : Int64 -> Int32 +``` + +Converts a 64-bit signed integer to a 32-bit signed integer. + +Wraps on overflow/underflow. + +Example: +```motoko include=import +Int64.toInt32(-123_456) // => -123_456 : Int32 +``` + ## Value `fromIntWrap` ``` motoko no-repl let fromIntWrap : Int -> Int64 @@ -65,9 +103,7 @@ Converts a signed integer with infinite precision to a 64-bit signed integer. Wraps on overflow/underflow. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.fromIntWrap(-123_456) // => -123_456 : Int64 ``` @@ -81,9 +117,7 @@ Converts an unsigned 64-bit integer to a signed 64-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.fromNat64(123_456) // => +123_456 : Int64 ``` @@ -97,9 +131,7 @@ Converts a signed 64-bit integer to an unsigned 64-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.toNat64(-1) // => 18_446_744_073_709_551_615 : Nat64 // underflow ``` @@ -108,13 +140,12 @@ Int64.toNat64(-1) // => 18_446_744_073_709_551_615 : Nat64 // underflow func toText(x : Int64) : Text ``` -Returns the Text representation of `x`. -Formats the integer in decimal representation without underscore separators for thousand figures. +Returns the Text representation of `x`. Textual representation _do not_ +contain underscores to represent commas. -Example: -```motoko -import Int64 "mo:base/Int64"; +Example: +```motoko include=import Int64.toText(-123456) // => "-123456" ``` @@ -128,9 +159,7 @@ Returns the absolute value of `x`. Traps when `x == -2 ** 63` (the minimum `Int64` value). Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.abs(-123456) // => +123_456 ``` @@ -142,9 +171,7 @@ func min(x : Int64, y : Int64) : Int64 Returns the minimum of `x` and `y`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.min(+2, -3) // => -3 ``` @@ -156,9 +183,7 @@ func max(x : Int64, y : Int64) : Int64 Returns the maximum of `x` and `y`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.max(+2, -3) // => +2 ``` @@ -167,13 +192,28 @@ Int64.max(+2, -3) // => +2 func equal(x : Int64, y : Int64) : Bool ``` -Returns `x == y`. +Equality function for Int64 types. +This is equivalent to `x == y`. Example: -```motoko -import Int64 "mo:base/Int64"; +```motoko include=import +Int64.equal(-1, -1); // => true +``` -Int64.equal(123, 123) // => true +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. + +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; + +let buffer1 = Buffer.Buffer(1); +buffer1.add(-3); +let buffer2 = Buffer.Buffer(1); +buffer2.add(-3); +Buffer.equal(buffer1, buffer2, Int64.equal) // => true ``` ## Function `notEqual` @@ -181,83 +221,110 @@ Int64.equal(123, 123) // => true func notEqual(x : Int64, y : Int64) : Bool ``` -Returns `x != y`. +Inequality function for Int64 types. +This is equivalent to `x != y`. Example: -```motoko -import Int64 "mo:base/Int64"; - -Int64.notEqual(123, 123) // => false +```motoko include=import +Int64.notEqual(-1, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. + ## Function `less` ``` motoko no-repl func less(x : Int64, y : Int64) : Bool ``` -Returns `x < y`. +"Less than" function for Int64 types. +This is equivalent to `x < y`. Example: -```motoko -import Int64 "mo:base/Int64"; - -Int64.less(123, 1234) // => true +```motoko include=import +Int64.less(-2, 1); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. + ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Int64, y : Int64) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Int64 types. +This is equivalent to `x <= y`. Example: -```motoko -import Int64 "mo:base/Int64"; - -Int64.lessOrEqual(123, 1234) // => true +```motoko include=import +Int64.lessOrEqual(-2, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. + ## Function `greater` ``` motoko no-repl func greater(x : Int64, y : Int64) : Bool ``` -Returns `x > y`. +"Greater than" function for Int64 types. +This is equivalent to `x > y`. Example: -```motoko -import Int64 "mo:base/Int64"; - -Int64.greater(1234, 123) // => true +```motoko include=import +Int64.greater(-2, -3); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>` +as a function value at the moment. + ## Function `greaterOrEqual` ``` motoko no-repl func greaterOrEqual(x : Int64, y : Int64) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Int64 types. +This is equivalent to `x >= y`. Example: -```motoko -import Int64 "mo:base/Int64"; - -Int64.greaterOrEqual(1234, 123) // => true +```motoko include=import +Int64.greaterOrEqual(-2, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>=` +as a function value at the moment. + ## Function `compare` ``` motoko no-repl func compare(x : Int64, y : Int64) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General-purpose comparison function for `Int64`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. Example: -```motoko -import Int64 "mo:base/Int64"; +```motoko include=import +Int64.compare(-3, 2) // => #less +``` + +This function can be used as value for a high order function, such as a sort function. -Int64.compare(123, 1234) // => #less +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([1, -2, -3] : [Int64], Int64.compare) // => [-3, -2, 1] ``` ## Function `neg` @@ -269,14 +336,16 @@ Returns the negation of `x`, `-x`. Traps on overflow, i.e. for `neg(-2 ** 63)`. - Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.neg(123) // => -123 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + ## Function `add` ``` motoko no-repl func add(x : Int64, y : Int64) : Int64 @@ -287,12 +356,21 @@ Returns the sum of `x` and `y`, `x + y`. Traps on overflow/underflow. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.add(1234, 123) // => +1_357 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int64.add) // => -4 +``` + ## Function `sub` ``` motoko no-repl func sub(x : Int64, y : Int64) : Int64 @@ -303,10 +381,19 @@ Returns the difference of `x` and `y`, `x - y`. Traps on overflow/underflow. Example: -```motoko -import Int64 "mo:base/Int64"; +```motoko include=import +Int64.sub(123, 100) // => +23 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. -Int64.sub(1234, 123) // => +1_111 +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int64.sub) // => 4 ``` ## Function `mul` @@ -319,10 +406,19 @@ Returns the product of `x` and `y`, `x * y`. Traps on overflow/underflow. Example: -```motoko -import Int64 "mo:base/Int64"; +```motoko include=import +Int64.mul(123, 10) // => +1_230 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. -Int64.mul(123, 100) // => +12_300 +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 1, Int64.mul) // => 6 ``` ## Function `div` @@ -336,12 +432,15 @@ Rounds the quotient towards zero, which is the same as truncating the decimal pl Traps when `y` is zero. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.div(123, 10) // => +12 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Int64, y : Int64) : Int64 @@ -353,12 +452,15 @@ which is defined as `x - x / y * y`. Traps when `y` is zero. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.rem(123, 10) // => +3 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Int64, y : Int64) : Int64 @@ -369,12 +471,15 @@ Returns `x` to the power of `y`, `x ** y`. Traps on overflow/underflow and when `y < 0 or y >= 64`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.pow(2, 10) // => +1_024 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. + ## Function `bitnot` ``` motoko no-repl func bitnot(x : Int64) : Int64 @@ -383,12 +488,15 @@ func bitnot(x : Int64) : Int64 Returns the bitwise negation of `x`, `^x`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitnot(-256 /* 0xffff_ffff_ffff_ff00 */) // => +255 // 0xff ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Int64, y : Int64) : Int64 @@ -397,12 +505,15 @@ func bitand(x : Int64, y : Int64) : Int64 Returns the bitwise "and" of `x` and `y`, `x & y`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitand(0xffff, 0x00f0) // => +240 // 0xf0 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Int64, y : Int64) : Int64 @@ -411,12 +522,15 @@ func bitor(x : Int64, y : Int64) : Int64 Returns the bitwise "or" of `x` and `y`, `x | y`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitor(0xffff, 0x00f0) // => +65_535 // 0xffff ``` +Note: The reason why this function is defined in this library (in addition +to the existing `|` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `|` +as a function value at the moment. + ## Function `bitxor` ``` motoko no-repl func bitxor(x : Int64, y : Int64) : Int64 @@ -425,12 +539,15 @@ func bitxor(x : Int64, y : Int64) : Int64 Returns the bitwise "exclusive or" of `x` and `y`, `x ^ y`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitxor(0xffff, 0x00f0) // => +65_295 // 0xff0f ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitshiftLeft` ``` motoko no-repl func bitshiftLeft(x : Int64, y : Int64) : Int64 @@ -444,12 +561,15 @@ For `y >= 64`, the semantics is the same as for `bitshiftLeft(x, y % 64)`. For `y < 0`, the semantics is the same as for `bitshiftLeft(x, y + y % 64)`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitshiftLeft(1, 8) // => +256 // 0x100 equivalent to `2 ** 8`. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Int64, y : Int64) : Int64 @@ -463,12 +583,15 @@ For `y >= 64`, the semantics is the same as for `bitshiftRight(x, y % 64)`. For `y < 0`, the semantics is the same as for `bitshiftRight (x, y + y % 64)`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitshiftRight(1024, 8) // => +4 // equivalent to `1024 / (2 ** 8)` ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Int64, y : Int64) : Int64 @@ -482,12 +605,16 @@ Changes the direction of rotation for negative `y`. For `y >= 64`, the semantics is the same as for `bitrotLeft(x, y % 64)`. Example: -```motoko -import Int64 "mo:base/Int64"; +```motoko include=import Int64.bitrotLeft(0x2000_0000_0000_0001, 4) // => +18 // 0x12. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Int64, y : Int64) : Int64 @@ -501,12 +628,15 @@ Changes the direction of rotation for negative `y`. For `y >= 64`, the semantics is the same as for `bitrotRight(x, y % 64)`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitrotRight(0x0002_0000_0000_0001, 48) // => +65538 // 0x1_0002. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Int64, p : Nat) : Bool @@ -514,11 +644,10 @@ func bittest(x : Int64, p : Nat) : Bool Returns the value of bit `p` in `x`, `x & 2**p == 2**p`. If `p >= 64`, the semantics is the same as for `bittest(x, p % 64)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bittest(128, 7) // => true ``` @@ -531,9 +660,7 @@ Returns the value of setting bit `p` in `x` to `1`. If `p >= 64`, the semantics is the same as for `bitset(x, p % 64)`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitset(0, 7) // => +128 ``` @@ -546,9 +673,7 @@ Returns the value of clearing bit `p` in `x` to `0`. If `p >= 64`, the semantics is the same as for `bitclear(x, p % 64)`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitclear(-1, 7) // => -129 ``` @@ -561,9 +686,7 @@ Returns the value of flipping bit `p` in `x`. If `p >= 64`, the semantics is the same as for `bitclear(x, p % 64)`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitflip(255, 7) // => +127 ``` @@ -575,9 +698,7 @@ let bitcountNonZero : (x : Int64) -> Int64 Returns the count of non-zero bits in `x`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitcountNonZero(0xffff) // => +16 ``` @@ -589,9 +710,7 @@ let bitcountLeadingZero : (x : Int64) -> Int64 Returns the count of leading zero bits in `x`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitcountLeadingZero(0x8000_0000) // => +32 ``` @@ -603,9 +722,7 @@ let bitcountTrailingZero : (x : Int64) -> Int64 Returns the count of trailing zero bits in `x`. Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.bitcountTrailingZero(0x0201_0000) // => +16 ``` @@ -618,14 +735,16 @@ Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.addWrap(2 ** 62, 2 ** 62) // => -9_223_372_036_854_775_808 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Int64, y : Int64) : Int64 @@ -635,14 +754,16 @@ Returns the difference of `x` and `y`, `x -% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.subWrap(-2 ** 63, 1) // => +9_223_372_036_854_775_807 // underflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Int64, y : Int64) : Int64 @@ -652,14 +773,16 @@ Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. Wraps on overflow/underflow. - Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.mulWrap(2 ** 32, 2 ** 32) // => 0 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Int64, y : Int64) : Int64 @@ -670,10 +793,12 @@ Returns `x` to the power of `y`, `x **% y`. Wraps on overflow/underflow. Traps if `y < 0 or y >= 64`. - Example: -```motoko -import Int64 "mo:base/Int64"; - +```motoko include=import Int64.powWrap(2, 63) // => -9_223_372_036_854_775_808 // overflow ``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Int8.md b/doc/md/base/Int8.md index c2f27f8783a..036e188a0ba 100644 --- a/doc/md/base/Int8.md +++ b/doc/md/base/Int8.md @@ -1,8 +1,12 @@ # Int8 -8-bit signed integers with checked arithmetic. +Provides utility functions on 8-bit signed integers. -Common 8-bit integer functions. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Int8 "mo:base/Int8"; +``` ## Type `Int8` ``` motoko no-repl @@ -18,6 +22,11 @@ let minimumValue : Int8 Minimum 8-bit integer value, `-2 ** 7`. +Example: +```motoko include=import +Int8.minimumValue // => -128 +``` + ## Value `maximumValue` ``` motoko no-repl let maximumValue : Int8 @@ -25,17 +34,20 @@ let maximumValue : Int8 Maximum 8-bit integer value, `+2 ** 7 - 1`. +Example: +```motoko include=import +Int8.maximumValue // => +127 +``` + ## Value `toInt` ``` motoko no-repl let toInt : Int8 -> Int ``` -Converts a 8-bit signed integer to a signed integer with infinite precision. +Converts an 8-bit signed integer to a signed integer with infinite precision. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.toInt(123) // => 123 : Int ``` @@ -44,14 +56,12 @@ Int8.toInt(123) // => 123 : Int let fromInt : Int -> Int8 ``` -Converts a signed integer with infinite precision to a 8-bit signed integer. +Converts a signed integer with infinite precision to an 8-bit signed integer. Traps on overflow/underflow. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.fromInt(123) // => +123 : Int8 ``` @@ -60,17 +70,41 @@ Int8.fromInt(123) // => +123 : Int8 let fromIntWrap : Int -> Int8 ``` -Converts a signed integer with infinite precision to a 8-bit signed integer. +Converts a signed integer with infinite precision to an 8-bit signed integer. Wraps on overflow/underflow. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.fromIntWrap(-123) // => -123 : Int ``` +## Value `fromInt16` +``` motoko no-repl +let fromInt16 : Int16 -> Int8 +``` + +Converts a 16-bit signed integer to an 8-bit signed integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Int8.fromInt16(123) // => +123 : Int8 +``` + +## Value `toInt16` +``` motoko no-repl +let toInt16 : Int8 -> Int16 +``` + +Converts an 8-bit signed integer to a 16-bit signed integer. + +Example: +```motoko include=import +Int8.toInt16(123) // => +123 : Int16 +``` + ## Value `fromNat8` ``` motoko no-repl let fromNat8 : Nat8 -> Int8 @@ -81,9 +115,7 @@ Converts an unsigned 8-bit integer to a signed 8-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.fromNat8(123) // => +123 : Int8 ``` @@ -97,9 +129,7 @@ Converts a signed 8-bit integer to an unsigned 8-bit integer. Wraps on overflow/underflow. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.toNat8(-1) // => 255 : Nat8 // underflow ``` @@ -108,13 +138,10 @@ Int8.toNat8(-1) // => 255 : Nat8 // underflow func toText(x : Int8) : Text ``` -Returns the Text representation of `x`. -Formats the integer in decimal representation. +Converts an integer number to its textual representation. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.toText(-123) // => "-123" ``` @@ -128,9 +155,7 @@ Returns the absolute value of `x`. Traps when `x == -2 ** 7` (the minimum `Int8` value). Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.abs(-123) // => +123 ``` @@ -142,9 +167,7 @@ func min(x : Int8, y : Int8) : Int8 Returns the minimum of `x` and `y`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.min(+2, -3) // => -3 ``` @@ -156,9 +179,7 @@ func max(x : Int8, y : Int8) : Int8 Returns the maximum of `x` and `y`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.max(+2, -3) // => +2 ``` @@ -167,13 +188,28 @@ Int8.max(+2, -3) // => +2 func equal(x : Int8, y : Int8) : Bool ``` -Returns `x == y`. +Equality function for Int8 types. +This is equivalent to `x == y`. Example: -```motoko -import Int8 "mo:base/Int8"; +```motoko include=import +Int8.equal(-1, -1); // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. + +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; -Int8.equal(123, 123) // => true +let buffer1 = Buffer.Buffer(1); +buffer1.add(-3); +let buffer2 = Buffer.Buffer(1); +buffer2.add(-3); +Buffer.equal(buffer1, buffer2, Int8.equal) // => true ``` ## Function `notEqual` @@ -181,83 +217,110 @@ Int8.equal(123, 123) // => true func notEqual(x : Int8, y : Int8) : Bool ``` -Returns `x != y`. +Inequality function for Int8 types. +This is equivalent to `x != y`. Example: -```motoko -import Int8 "mo:base/Int8"; - -Int8.notEqual(123, 123) // => false +```motoko include=import +Int8.notEqual(-1, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. + ## Function `less` ``` motoko no-repl func less(x : Int8, y : Int8) : Bool ``` -Returns `x < y`. +"Less than" function for Int8 types. +This is equivalent to `x < y`. Example: -```motoko -import Int8 "mo:base/Int8"; - -Int8.less(123, 124) // => true +```motoko include=import +Int8.less(-2, 1); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. + ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Int8, y : Int8) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Int8 types. +This is equivalent to `x <= y`. Example: -```motoko -import Int8 "mo:base/Int8"; - -Int8.lessOrEqual(123, 124) // => true +```motoko include=import +Int8.lessOrEqual(-2, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. + ## Function `greater` ``` motoko no-repl func greater(x : Int8, y : Int8) : Bool ``` -Returns `x > y`. +"Greater than" function for Int8 types. +This is equivalent to `x > y`. Example: -```motoko -import Int8 "mo:base/Int8"; - -Int8.greater(124, 123) // => true +```motoko include=import +Int8.greater(-2, -3); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>` +as a function value at the moment. + ## Function `greaterOrEqual` ``` motoko no-repl func greaterOrEqual(x : Int8, y : Int8) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Int8 types. +This is equivalent to `x >= y`. Example: -```motoko -import Int8 "mo:base/Int8"; - -Int8.greaterOrEqual(124, 123) // => true +```motoko include=import +Int8.greaterOrEqual(-2, -2); // => true ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>=` +as a function value at the moment. + ## Function `compare` ``` motoko no-repl func compare(x : Int8, y : Int8) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General-purpose comparison function for `Int8`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. Example: -```motoko -import Int8 "mo:base/Int8"; +```motoko include=import +Int8.compare(-3, 2) // => #less +``` -Int8.compare(123, 124) // => #less +This function can be used as value for a high order function, such as a sort function. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([1, -2, -3] : [Int8], Int8.compare) // => [-3, -2, 1] ``` ## Function `neg` @@ -269,14 +332,16 @@ Returns the negation of `x`, `-x`. Traps on overflow, i.e. for `neg(-2 ** 7)`. - Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.neg(123) // => -123 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + ## Function `add` ``` motoko no-repl func add(x : Int8, y : Int8) : Int8 @@ -287,12 +352,21 @@ Returns the sum of `x` and `y`, `x + y`. Traps on overflow/underflow. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.add(100, 23) // => +123 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int8.add) // => -4 +``` + ## Function `sub` ``` motoko no-repl func sub(x : Int8, y : Int8) : Int8 @@ -303,12 +377,21 @@ Returns the difference of `x` and `y`, `x - y`. Traps on overflow/underflow. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.sub(123, 23) // => +100 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 0, Int8.sub) // => 4 +``` + ## Function `mul` ``` motoko no-repl func mul(x : Int8, y : Int8) : Int8 @@ -319,12 +402,21 @@ Returns the product of `x` and `y`, `x * y`. Traps on overflow/underflow. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.mul(12, 10) // => +120 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([1, -2, -3], 1, Int8.mul) // => 6 +``` + ## Function `div` ``` motoko no-repl func div(x : Int8, y : Int8) : Int8 @@ -336,12 +428,15 @@ Rounds the quotient towards zero, which is the same as truncating the decimal pl Traps when `y` is zero. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.div(123, 10) // => +12 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Int8, y : Int8) : Int8 @@ -353,12 +448,15 @@ which is defined as `x - x / y * y`. Traps when `y` is zero. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.rem(123, 10) // => +3 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Int8, y : Int8) : Int8 @@ -369,12 +467,15 @@ Returns `x` to the power of `y`, `x ** y`. Traps on overflow/underflow and when `y < 0 or y >= 8`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.pow(2, 6) // => +64 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. + ## Function `bitnot` ``` motoko no-repl func bitnot(x : Int8) : Int8 @@ -383,12 +484,15 @@ func bitnot(x : Int8) : Int8 Returns the bitwise negation of `x`, `^x`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitnot(-16 /* 0xf0 */) // => +15 // 0x0f ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Int8, y : Int8) : Int8 @@ -397,12 +501,15 @@ func bitand(x : Int8, y : Int8) : Int8 Returns the bitwise "and" of `x` and `y`, `x & y`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitand(0x1f, 0x70) // => +16 // 0x10 ``` +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Int8, y : Int8) : Int8 @@ -411,12 +518,15 @@ func bitor(x : Int8, y : Int8) : Int8 Returns the bitwise "or" of `x` and `y`, `x | y`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitor(0x0f, 0x70) // => +127 // 0x7f ``` +Note: The reason why this function is defined in this library (in addition +to the existing `|` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `|` +as a function value at the moment. + ## Function `bitxor` ``` motoko no-repl func bitxor(x : Int8, y : Int8) : Int8 @@ -425,12 +535,15 @@ func bitxor(x : Int8, y : Int8) : Int8 Returns the bitwise "exclusive or" of `x` and `y`, `x ^ y`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitxor(0x70, 0x7f) // => +15 // 0x0f ``` +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitshiftLeft` ``` motoko no-repl func bitshiftLeft(x : Int8, y : Int8) : Int8 @@ -444,12 +557,15 @@ For `y >= 8`, the semantics is the same as for `bitshiftLeft(x, y % 8)`. For `y < 0`, the semantics is the same as for `bitshiftLeft(x, y + y % 8)`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitshiftLeft(1, 4) // => +16 // 0x10 equivalent to `2 ** 4`. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Int8, y : Int8) : Int8 @@ -463,12 +579,15 @@ For `y >= 8`, the semantics is the same as for `bitshiftRight(x, y % 8)`. For `y < 0`, the semantics is the same as for `bitshiftRight (x, y + y % 8)`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitshiftRight(64, 4) // => +4 // equivalent to `64 / (2 ** 4)` ``` +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Int8, y : Int8) : Int8 @@ -482,12 +601,15 @@ Changes the direction of rotation for negative `y`. For `y >= 8`, the semantics is the same as for `bitrotLeft(x, y % 8)`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitrotLeft(0x11 /* 0b0001_0001 */, 2) // => +68 // 0b0100_0100 == 0x44. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Int8, y : Int8) : Int8 @@ -501,12 +623,15 @@ Changes the direction of rotation for negative `y`. For `y >= 8`, the semantics is the same as for `bitrotRight(x, y % 8)`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitrotRight(0x11 /* 0b0001_0001 */, 1) // => -120 // 0b1000_1000 == 0x88. ``` +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Int8, p : Nat) : Bool @@ -514,11 +639,10 @@ func bittest(x : Int8, p : Nat) : Bool Returns the value of bit `p` in `x`, `x & 2**p == 2**p`. If `p >= 8`, the semantics is the same as for `bittest(x, p % 8)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bittest(64, 6) // => true ``` @@ -531,9 +655,7 @@ Returns the value of setting bit `p` in `x` to `1`. If `p >= 8`, the semantics is the same as for `bitset(x, p % 8)`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitset(0, 6) // => +64 ``` @@ -546,9 +668,7 @@ Returns the value of clearing bit `p` in `x` to `0`. If `p >= 8`, the semantics is the same as for `bitclear(x, p % 8)`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitclear(-1, 6) // => -65 ``` @@ -561,9 +681,7 @@ Returns the value of flipping bit `p` in `x`. If `p >= 8`, the semantics is the same as for `bitclear(x, p % 8)`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitflip(127, 6) // => +63 ``` @@ -575,9 +693,7 @@ let bitcountNonZero : (x : Int8) -> Int8 Returns the count of non-zero bits in `x`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitcountNonZero(0x0f) // => +4 ``` @@ -589,9 +705,7 @@ let bitcountLeadingZero : (x : Int8) -> Int8 Returns the count of leading zero bits in `x`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitcountLeadingZero(0x08) // => +4 ``` @@ -603,9 +717,7 @@ let bitcountTrailingZero : (x : Int8) -> Int8 Returns the count of trailing zero bits in `x`. Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.bitcountTrailingZero(0x10) // => +4 ``` @@ -618,14 +730,16 @@ Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.addWrap(2 ** 6, 2 ** 6) // => -128 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Int8, y : Int8) : Int8 @@ -635,14 +749,16 @@ Returns the difference of `x` and `y`, `x -% y`. Wraps on overflow/underflow. - Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.subWrap(-2 ** 7, 1) // => +127 // underflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Int8, y : Int8) : Int8 @@ -652,14 +768,16 @@ Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. Wraps on overflow/underflow. - Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.mulWrap(2 ** 4, 2 ** 4) // => 0 // overflow ``` +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Int8, y : Int8) : Int8 @@ -670,10 +788,12 @@ Returns `x` to the power of `y`, `x **% y`. Wraps on overflow/underflow. Traps if `y < 0 or y >= 8`. - Example: -```motoko -import Int8 "mo:base/Int8"; - +```motoko include=import Int8.powWrap(2, 7) // => -128 // overflow ``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Nat16.md b/doc/md/base/Nat16.md index 713000b2e98..1d011098f20 100644 --- a/doc/md/base/Nat16.md +++ b/doc/md/base/Nat16.md @@ -1,7 +1,12 @@ # Nat16 -16-bit unsigned integers with checked arithmetic +Provides utility functions on 16-bit unsigned integers. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Nat16 "mo:base/Nat16"; +``` ## Type `Nat16` ``` motoko no-repl @@ -10,33 +15,122 @@ type Nat16 = Prim.Types.Nat16 16-bit natural numbers. +## Value `maximumValue` +``` motoko no-repl +let maximumValue : Nat16 +``` + +Maximum 16-bit natural number. `2 ** 16 - 1`. + +Example: +```motoko include=import +Nat16.maximumValue; // => 65536 : Nat16 +``` + ## Value `toNat` ``` motoko no-repl let toNat : Nat16 -> Nat ``` -Conversion. +Converts a 16-bit unsigned integer to an unsigned integer with infinite precision. + +Example: +```motoko include=import +Nat16.toNat(123); // => 123 : Nat +``` ## Value `fromNat` ``` motoko no-repl let fromNat : Nat -> Nat16 ``` -Conversion. Traps on overflow/underflow. +Converts an unsigned integer with infinite precision to a 16-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat16.fromNat(123); // => 123 : Nat16 +``` + +## Function `fromNat8` +``` motoko no-repl +func fromNat8(x : Nat8) : Nat16 +``` + +Converts an 8-bit unsigned integer to a 16-bit unsigned integer. + +Example: +```motoko include=import +Nat16.fromNat8(123); // => 123 : Nat16 +``` + +## Function `toNat8` +``` motoko no-repl +func toNat8(x : Nat16) : Nat8 +``` + +Converts a 16-bit unsigned integer to an 8-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat16.toNat8(123); // => 123 : Nat8 +``` + +## Function `fromNat32` +``` motoko no-repl +func fromNat32(x : Nat32) : Nat16 +``` + +Converts a 32-bit unsigned integer to a 16-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat16.fromNat32(123); // => 123 : Nat16 +``` + +## Function `toNat32` +``` motoko no-repl +func toNat32(x : Nat16) : Nat32 +``` + +Converts a 16-bit unsigned integer to a 32-bit unsigned integer. + +Example: +```motoko include=import +Nat16.toNat32(123); // => 123 : Nat32 +``` ## Value `fromIntWrap` ``` motoko no-repl let fromIntWrap : Int -> Nat16 ``` -Conversion. Wraps on overflow/underflow. +Converts a signed integer with infinite precision to a 16-bit unsigned integer. + +Wraps on overflow/underflow. + +Example: +```motoko include=import +Nat16.fromIntWrap(123 : Int); // => 123 : Nat16 +``` ## Function `toText` ``` motoko no-repl func toText(x : Nat16) : Text ``` -Returns the Text representation of `x`. +Converts `x` to its textual representation. Textual representation _do not_ +contain underscores to represent commas. + +Example: +```motoko include=import +Nat16.toText(1234); // => "1234" : Text +``` ## Function `min` ``` motoko no-repl @@ -45,6 +139,11 @@ func min(x : Nat16, y : Nat16) : Nat16 Returns the minimum of `x` and `y`. +Example: +```motoko include=import +Nat16.min(123, 200); // => 123 : Nat16 +``` + ## Function `max` ``` motoko no-repl func max(x : Nat16, y : Nat16) : Nat16 @@ -52,84 +151,249 @@ func max(x : Nat16, y : Nat16) : Nat16 Returns the maximum of `x` and `y`. +Example: +```motoko include=import +Nat16.max(123, 200); // => 200 : Nat16 +``` + ## Function `equal` ``` motoko no-repl func equal(x : Nat16, y : Nat16) : Bool ``` -Returns `x == y`. +Equality function for Nat16 types. +This is equivalent to `x == y`. + +Example: +```motoko include=import +ignore Nat16.equal(1, 1); // => true +(1 : Nat16) == (1 : Nat16) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. + +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; + +let buffer1 = Buffer.Buffer(3); +let buffer2 = Buffer.Buffer(3); +Buffer.equal(buffer1, buffer2, Nat16.equal) // => true +``` ## Function `notEqual` ``` motoko no-repl func notEqual(x : Nat16, y : Nat16) : Bool ``` -Returns `x != y`. +Inequality function for Nat16 types. +This is equivalent to `x != y`. + +Example: +```motoko include=import +ignore Nat16.notEqual(1, 2); // => true +(1 : Nat16) != (2 : Nat16) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. ## Function `less` ``` motoko no-repl func less(x : Nat16, y : Nat16) : Bool ``` -Returns `x < y`. +"Less than" function for Nat16 types. +This is equivalent to `x < y`. + +Example: +```motoko include=import +ignore Nat16.less(1, 2); // => true +(1 : Nat16) < (2 : Nat16) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Nat16, y : Nat16) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Nat16 types. +This is equivalent to `x <= y`. + +Example: +```motoko include=import +ignore Nat16.lessOrEqual(1, 2); // => true +(1 : Nat16) <= (2 : Nat16) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. ## Function `greater` ``` motoko no-repl func greater(x : Nat16, y : Nat16) : Bool ``` -Returns `x > y`. +"Greater than" function for Nat16 types. +This is equivalent to `x > y`. + +Example: +```motoko include=import +ignore Nat16.greater(2, 1); // => true +(2 : Nat16) > (1 : Nat16) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>` +as a function value at the moment. ## Function `greaterOrEqual` ``` motoko no-repl func greaterOrEqual(x : Nat16, y : Nat16) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Nat16 types. +This is equivalent to `x >= y`. + +Example: +```motoko include=import +ignore Nat16.greaterOrEqual(2, 1); // => true +(2 : Nat16) >= (1 : Nat16) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>=` +as a function value at the moment. ## Function `compare` ``` motoko no-repl func compare(x : Nat16, y : Nat16) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General purpose comparison function for `Nat16`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. + +Example: +```motoko include=import +Nat16.compare(2, 3) // => #less +``` + +This function can be used as value for a high order function, such as a sort function. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([2, 3, 1] : [Nat16], Nat16.compare) // => [1, 2, 3] +``` ## Function `add` ``` motoko no-repl func add(x : Nat16, y : Nat16) : Nat16 ``` -Returns the sum of `x` and `y`, `x + y`. Traps on overflow. +Returns the sum of `x` and `y`, `x + y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat16.add(1, 2); // => 3 +(1 : Nat16) + (2 : Nat16) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 0, Nat16.add) // => 6 +``` ## Function `sub` ``` motoko no-repl func sub(x : Nat16, y : Nat16) : Nat16 ``` -Returns the difference of `x` and `y`, `x - y`. Traps on underflow. +Returns the difference of `x` and `y`, `x - y`. +Traps on underflow. + +Example: +```motoko include=import +ignore Nat16.sub(2, 1); // => 1 +(2 : Nat16) - (1 : Nat16) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 20, Nat16.sub) // => 14 +``` ## Function `mul` ``` motoko no-repl func mul(x : Nat16, y : Nat16) : Nat16 ``` -Returns the product of `x` and `y`, `x * y`. Traps on overflow. +Returns the product of `x` and `y`, `x * y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat16.mul(2, 3); // => 6 +(2 : Nat16) * (3 : Nat16) // => 6 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 1, Nat16.mul) // => 6 +``` ## Function `div` ``` motoko no-repl func div(x : Nat16, y : Nat16) : Nat16 ``` -Returns the division of `x by y`, `x / y`. +Returns the quotient of `x` divided by `y`, `x / y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat16.div(6, 2); // => 3 +(6 : Nat16) / (2 : Nat16) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Nat16, y : Nat16) : Nat16 @@ -138,12 +402,35 @@ func rem(x : Nat16, y : Nat16) : Nat16 Returns the remainder of `x` divided by `y`, `x % y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat16.rem(6, 4); // => 2 +(6 : Nat16) % (4 : Nat16) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Nat16, y : Nat16) : Nat16 ``` -Returns `x` to the power of `y`, `x ** y`. Traps on overflow. +Returns the power of `x` to `y`, `x ** y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat16.pow(2, 3); // => 8 +(2 : Nat16) ** (3 : Nat16) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. ## Function `bitnot` ``` motoko no-repl @@ -152,6 +439,17 @@ func bitnot(x : Nat16) : Nat16 Returns the bitwise negation of `x`, `^x`. +Example: +```motoko include=import +ignore Nat16.bitnot(0); // => 65535 +^(0 : Nat16) // => 65535 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Nat16, y : Nat16) : Nat16 @@ -159,12 +457,29 @@ func bitand(x : Nat16, y : Nat16) : Nat16 Returns the bitwise and of `x` and `y`, `x & y`. +Example: +```motoko include=import +ignore Nat16.bitand(0, 1); // => 0 +(0 : Nat16) & (1 : Nat16) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Nat16, y : Nat16) : Nat16 ``` -Returns the bitwise or of `x` and `y`, `x \| y`. +Returns the bitwise or of `x` and `y`, `x | y`. + +Example: +```motoko include=import +ignore Nat16.bitor(0, 1); // => 1 +(0 : Nat16) | (1 : Nat16) // => 1 +``` ## Function `bitxor` ``` motoko no-repl @@ -173,6 +488,12 @@ func bitxor(x : Nat16, y : Nat16) : Nat16 Returns the bitwise exclusive or of `x` and `y`, `x ^ y`. +Example: +```motoko include=import +ignore Nat16.bitxor(0, 1); // => 1 +(0 : Nat16) ^ (1 : Nat16) // => 1 +``` + ## Function `bitshiftLeft` ``` motoko no-repl func bitshiftLeft(x : Nat16, y : Nat16) : Nat16 @@ -180,6 +501,17 @@ func bitshiftLeft(x : Nat16, y : Nat16) : Nat16 Returns the bitwise shift left of `x` by `y`, `x << y`. +Example: +```motoko include=import +ignore Nat16.bitshiftLeft(1, 3); // => 8 +(1 : Nat16) << (3 : Nat16) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Nat16, y : Nat16) : Nat16 @@ -187,6 +519,17 @@ func bitshiftRight(x : Nat16, y : Nat16) : Nat16 Returns the bitwise shift right of `x` by `y`, `x >> y`. +Example: +```motoko include=import +ignore Nat16.bitshiftRight(8, 3); // => 1 +(8 : Nat16) >> (3 : Nat16) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Nat16, y : Nat16) : Nat16 @@ -194,6 +537,17 @@ func bitrotLeft(x : Nat16, y : Nat16) : Nat16 Returns the bitwise rotate left of `x` by `y`, `x <<> y`. +Example: +```motoko include=import +ignore Nat16.bitrotLeft(2, 1); // => 4 +(2 : Nat16) <<> (1 : Nat16) // => 4 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Nat16, y : Nat16) : Nat16 @@ -201,12 +555,29 @@ func bitrotRight(x : Nat16, y : Nat16) : Nat16 Returns the bitwise rotate right of `x` by `y`, `x <>> y`. +Example: +```motoko include=import +ignore Nat16.bitrotRight(1, 1); // => 32768 +(1 : Nat16) <>> (1 : Nat16) // => 32768 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Nat16, p : Nat) : Bool ``` Returns the value of bit `p mod 16` in `x`, `(x & 2^(p mod 16)) == 2^(p mod 16)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. + +Example: +```motoko include=import +Nat16.bittest(5, 2); // => true +``` ## Function `bitset` ``` motoko no-repl @@ -215,6 +586,11 @@ func bitset(x : Nat16, p : Nat) : Nat16 Returns the value of setting bit `p mod 16` in `x` to `1`. +Example: +```motoko include=import +Nat16.bitset(0, 2); // => 4 +``` + ## Function `bitclear` ``` motoko no-repl func bitclear(x : Nat16, p : Nat) : Nat16 @@ -222,6 +598,11 @@ func bitclear(x : Nat16, p : Nat) : Nat16 Returns the value of clearing bit `p mod 16` in `x` to `0`. +Example: +```motoko include=import +Nat16.bitclear(5, 2); // => 1 +``` + ## Function `bitflip` ``` motoko no-repl func bitflip(x : Nat16, p : Nat) : Nat16 @@ -229,6 +610,11 @@ func bitflip(x : Nat16, p : Nat) : Nat16 Returns the value of flipping bit `p mod 16` in `x`. +Example: +```motoko include=import +Nat16.bitflip(5, 2); // => 1 +``` + ## Value `bitcountNonZero` ``` motoko no-repl let bitcountNonZero : (x : Nat16) -> Nat16 @@ -236,6 +622,11 @@ let bitcountNonZero : (x : Nat16) -> Nat16 Returns the count of non-zero bits in `x`. +Example: +```motoko include=import +Nat16.bitcountNonZero(5); // => 2 +``` + ## Value `bitcountLeadingZero` ``` motoko no-repl let bitcountLeadingZero : (x : Nat16) -> Nat16 @@ -243,6 +634,11 @@ let bitcountLeadingZero : (x : Nat16) -> Nat16 Returns the count of leading zero bits in `x`. +Example: +```motoko include=import +Nat16.bitcountLeadingZero(5); // => 13 +``` + ## Value `bitcountTrailingZero` ``` motoko no-repl let bitcountTrailingZero : (x : Nat16) -> Nat16 @@ -250,6 +646,11 @@ let bitcountTrailingZero : (x : Nat16) -> Nat16 Returns the count of trailing zero bits in `x`. +Example: +```motoko include=import +Nat16.bitcountTrailingZero(5); // => 0 +``` + ## Function `addWrap` ``` motoko no-repl func addWrap(x : Nat16, y : Nat16) : Nat16 @@ -257,6 +658,17 @@ func addWrap(x : Nat16, y : Nat16) : Nat16 Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat16.addWrap(65532, 5); // => 1 +(65532 : Nat16) +% (5 : Nat16) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Nat16, y : Nat16) : Nat16 @@ -264,6 +676,17 @@ func subWrap(x : Nat16, y : Nat16) : Nat16 Returns the difference of `x` and `y`, `x -% y`. Wraps on underflow. +Example: +```motoko include=import +ignore Nat16.subWrap(1, 2); // => 65535 +(1 : Nat16) -% (2 : Nat16) // => 65535 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Nat16, y : Nat16) : Nat16 @@ -271,9 +694,31 @@ func mulWrap(x : Nat16, y : Nat16) : Nat16 Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat16.mulWrap(655, 101); // => 619 +(655 : Nat16) *% (101 : Nat16) // => 619 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Nat16, y : Nat16) : Nat16 ``` Returns `x` to the power of `y`, `x **% y`. Wraps on overflow. + +Example: +```motoko include=import +ignore Nat16.powWrap(2, 16); // => 0 +(2 : Nat16) **% (16 : Nat16) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Nat32.md b/doc/md/base/Nat32.md index 17efaee29e6..c080931ec35 100644 --- a/doc/md/base/Nat32.md +++ b/doc/md/base/Nat32.md @@ -1,7 +1,12 @@ # Nat32 -32-bit unsigned integers with checked arithmetic +Provides utility functions on 32-bit unsigned integers. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Nat32 "mo:base/Nat32"; +``` ## Type `Nat32` ``` motoko no-repl @@ -10,33 +15,122 @@ type Nat32 = Prim.Types.Nat32 32-bit natural numbers. +## Value `maximumValue` +``` motoko no-repl +let maximumValue : Nat32 +``` + +Maximum 32-bit natural number. `2 ** 32 - 1`. + +Example: +```motoko include=import +Nat32.maximumValue; // => 4294967295 : Nat32 +``` + ## Value `toNat` ``` motoko no-repl let toNat : Nat32 -> Nat ``` -Conversion. +Converts a 32-bit unsigned integer to an unsigned integer with infinite precision. + +Example: +```motoko include=import +Nat32.toNat(123); // => 123 : Nat +``` ## Value `fromNat` ``` motoko no-repl let fromNat : Nat -> Nat32 ``` -Conversion. Traps on overflow/underflow. +Converts an unsigned integer with infinite precision to a 32-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat32.fromNat(123); // => 123 : Nat32 +``` + +## Function `fromNat16` +``` motoko no-repl +func fromNat16(x : Nat16) : Nat32 +``` + +Converts a 16-bit unsigned integer to a 32-bit unsigned integer. + +Example: +```motoko include=import +Nat32.fromNat16(123); // => 123 : Nat32 +``` + +## Function `toNat16` +``` motoko no-repl +func toNat16(x : Nat32) : Nat16 +``` + +Converts a 32-bit unsigned integer to a 16-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat32.toNat16(123); // => 123 : Nat16 +``` + +## Function `fromNat64` +``` motoko no-repl +func fromNat64(x : Nat64) : Nat32 +``` + +Converts a 64-bit unsigned integer to a 32-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat32.fromNat64(123); // => 123 : Nat32 +``` + +## Function `toNat64` +``` motoko no-repl +func toNat64(x : Nat32) : Nat64 +``` + +Converts a 32-bit unsigned integer to a 64-bit unsigned integer. + +Example: +```motoko include=import +Nat32.toNat64(123); // => 123 : Nat64 +``` ## Value `fromIntWrap` ``` motoko no-repl let fromIntWrap : Int -> Nat32 ``` -Conversion. Wraps on overflow/underflow. +Converts a signed integer with infinite precision to a 32-bit unsigned integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Nat32.fromIntWrap(123); // => 123 : Nat32 +``` ## Function `toText` ``` motoko no-repl func toText(x : Nat32) : Text ``` -Returns the Text representation of `x`. +Converts `x` to its textual representation. Textual representation _do not_ +contain underscores to represent commas. + +Example: +```motoko include=import +Nat32.toText(1234); // => "1234" : Text +``` ## Function `min` ``` motoko no-repl @@ -45,6 +139,11 @@ func min(x : Nat32, y : Nat32) : Nat32 Returns the minimum of `x` and `y`. +Example: +```motoko include=import +Nat32.min(123, 456); // => 123 : Nat32 +``` + ## Function `max` ``` motoko no-repl func max(x : Nat32, y : Nat32) : Nat32 @@ -52,75 +151,229 @@ func max(x : Nat32, y : Nat32) : Nat32 Returns the maximum of `x` and `y`. +Example: +```motoko include=import +Nat32.max(123, 456); // => 456 : Nat32 +``` + ## Function `equal` ``` motoko no-repl func equal(x : Nat32, y : Nat32) : Bool ``` -Returns `x == y`. +Equality function for Nat32 types. +This is equivalent to `x == y`. + +Example: +```motoko include=import +ignore Nat32.equal(1, 1); // => true +(1 : Nat32) == (1 : Nat32) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. + +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; + +let buffer1 = Buffer.Buffer(3); +let buffer2 = Buffer.Buffer(3); +Buffer.equal(buffer1, buffer2, Nat32.equal) // => true +``` ## Function `notEqual` ``` motoko no-repl func notEqual(x : Nat32, y : Nat32) : Bool ``` -Returns `x != y`. +Inequality function for Nat32 types. +This is equivalent to `x != y`. + +Example: +```motoko include=import +ignore Nat32.notEqual(1, 2); // => true +(1 : Nat32) != (2 : Nat32) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. ## Function `less` ``` motoko no-repl func less(x : Nat32, y : Nat32) : Bool ``` -Returns `x < y`. +"Less than" function for Nat32 types. +This is equivalent to `x < y`. + +Example: +```motoko include=import +ignore Nat32.less(1, 2); // => true +(1 : Nat32) < (2 : Nat32) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Nat32, y : Nat32) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Nat32 types. +This is equivalent to `x <= y`. + +Example: +```motoko include=import +ignore Nat32.lessOrEqual(1, 2); // => true +(1 : Nat32) <= (2 : Nat32) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. ## Function `greater` ``` motoko no-repl func greater(x : Nat32, y : Nat32) : Bool ``` -Returns `x > y`. +"Greater than" function for Nat32 types. +This is equivalent to `x > y`. + +Example: +```motoko include=import +ignore Nat32.greater(2, 1); // => true +(2 : Nat32) > (1 : Nat32) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>` +as a function value at the moment. ## Function `greaterOrEqual` ``` motoko no-repl func greaterOrEqual(x : Nat32, y : Nat32) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Nat32 types. +This is equivalent to `x >= y`. + +Example: +```motoko include=import +ignore Nat32.greaterOrEqual(2, 1); // => true +(2 : Nat32) >= (1 : Nat32) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>=` +as a function value at the moment. ## Function `compare` ``` motoko no-repl func compare(x : Nat32, y : Nat32) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General purpose comparison function for `Nat32`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. + +Example: +```motoko include=import +Nat32.compare(2, 3) // => #less +``` + +This function can be used as value for a high order function, such as a sort function. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([2, 3, 1] : [Nat32], Nat32.compare) // => [1, 2, 3] +``` ## Function `add` ``` motoko no-repl func add(x : Nat32, y : Nat32) : Nat32 ``` -Returns the sum of `x` and `y`, `x + y`. Traps on overflow. +Returns the sum of `x` and `y`, `x + y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat32.add(1, 2); // => 3 +(1 : Nat32) + (2 : Nat32) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 0, Nat32.add) // => 6 +``` ## Function `sub` ``` motoko no-repl func sub(x : Nat32, y : Nat32) : Nat32 ``` -Returns the difference of `x` and `y`, `x - y`. Traps on underflow. +Returns the difference of `x` and `y`, `x - y`. +Traps on underflow. + +Example: +```motoko include=import +ignore Nat32.sub(2, 1); // => 1 +(2 : Nat32) - (1 : Nat32) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 20, Nat32.sub) // => 14 +``` ## Function `mul` ``` motoko no-repl func mul(x : Nat32, y : Nat32) : Nat32 ``` -Returns the product of `x` and `y`, `x * y`. Traps on overflow. +Returns the product of `x` and `y`, `x * y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat32.mul(2, 3); // => 6 +(2 : Nat32) * (3 : Nat32) // => 6 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 1, Nat32.mul) // => 6 +``` ## Function `div` ``` motoko no-repl @@ -130,6 +383,17 @@ func div(x : Nat32, y : Nat32) : Nat32 Returns the division of `x by y`, `x / y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat32.div(6, 2); // => 3 +(6 : Nat32) / (2 : Nat32) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Nat32, y : Nat32) : Nat32 @@ -138,6 +402,17 @@ func rem(x : Nat32, y : Nat32) : Nat32 Returns the remainder of `x` divided by `y`, `x % y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat32.rem(6, 4); // => 2 +(6 : Nat32) % (4 : Nat32) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Nat32, y : Nat32) : Nat32 @@ -145,6 +420,17 @@ func pow(x : Nat32, y : Nat32) : Nat32 Returns `x` to the power of `y`, `x ** y`. Traps on overflow. +Example: +```motoko include=import +ignore Nat32.pow(2, 3); // => 8 +(2 : Nat32) ** (3 : Nat32) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. + ## Function `bitnot` ``` motoko no-repl func bitnot(x : Nat32) : Nat32 @@ -152,6 +438,17 @@ func bitnot(x : Nat32) : Nat32 Returns the bitwise negation of `x`, `^x`. +Example: +```motoko include=import +ignore Nat32.bitnot(0) // => 4294967295 +^(0 : Nat32) // => 4294967295 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Nat32, y : Nat32) : Nat32 @@ -159,12 +456,34 @@ func bitand(x : Nat32, y : Nat32) : Nat32 Returns the bitwise and of `x` and `y`, `x & y`. +Example: +```motoko include=import +ignore Nat32.bitand(1, 3); // => 1 +(1 : Nat32) & (3 : Nat32) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Nat32, y : Nat32) : Nat32 ``` -Returns the bitwise or of `x` and `y`, `x \| y`. +Returns the bitwise or of `x` and `y`, `x | y`. + +Example: +```motoko include=import +ignore Nat32.bitor(1, 3); // => 3 +(1 : Nat32) | (3 : Nat32) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `|` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `|` +as a function value at the moment. ## Function `bitxor` ``` motoko no-repl @@ -173,6 +492,17 @@ func bitxor(x : Nat32, y : Nat32) : Nat32 Returns the bitwise exclusive or of `x` and `y`, `x ^ y`. +Example: +```motoko include=import +ignore Nat32.bitxor(1, 3); // => 2 +(1 : Nat32) ^ (3 : Nat32) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitshiftLeft` ``` motoko no-repl func bitshiftLeft(x : Nat32, y : Nat32) : Nat32 @@ -180,6 +510,17 @@ func bitshiftLeft(x : Nat32, y : Nat32) : Nat32 Returns the bitwise shift left of `x` by `y`, `x << y`. +Example: +```motoko include=import +ignore Nat32.bitshiftLeft(1, 3); // => 8 +(1 : Nat32) << (3 : Nat32) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Nat32, y : Nat32) : Nat32 @@ -187,6 +528,17 @@ func bitshiftRight(x : Nat32, y : Nat32) : Nat32 Returns the bitwise shift right of `x` by `y`, `x >> y`. +Example: +```motoko include=import +ignore Nat32.bitshiftRight(8, 3); // => 1 +(8 : Nat32) >> (3 : Nat32) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Nat32, y : Nat32) : Nat32 @@ -194,6 +546,17 @@ func bitrotLeft(x : Nat32, y : Nat32) : Nat32 Returns the bitwise rotate left of `x` by `y`, `x <<> y`. +Example: +```motoko include=import +ignore Nat32.bitrotLeft(1, 3); // => 8 +(1 : Nat32) <<> (3 : Nat32) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Nat32, y : Nat32) : Nat32 @@ -201,12 +564,29 @@ func bitrotRight(x : Nat32, y : Nat32) : Nat32 Returns the bitwise rotate right of `x` by `y`, `x <>> y`. +Example: +```motoko include=import +ignore Nat32.bitrotRight(1, 1); // => 2147483648 +(1 : Nat32) <>> (1 : Nat32) // => 2147483648 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Nat32, p : Nat) : Bool ``` Returns the value of bit `p mod 32` in `x`, `(x & 2^(p mod 32)) == 2^(p mod 32)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. + +Example: +```motoko include=import +Nat32.bittest(5, 2); // => true +``` ## Function `bitset` ``` motoko no-repl @@ -215,6 +595,11 @@ func bitset(x : Nat32, p : Nat) : Nat32 Returns the value of setting bit `p mod 32` in `x` to `1`. +Example: +```motoko include=import +Nat32.bitset(5, 1); // => 7 +``` + ## Function `bitclear` ``` motoko no-repl func bitclear(x : Nat32, p : Nat) : Nat32 @@ -222,6 +607,11 @@ func bitclear(x : Nat32, p : Nat) : Nat32 Returns the value of clearing bit `p mod 32` in `x` to `0`. +Example: +```motoko include=import +Nat32.bitclear(5, 2); // => 1 +``` + ## Function `bitflip` ``` motoko no-repl func bitflip(x : Nat32, p : Nat) : Nat32 @@ -229,6 +619,11 @@ func bitflip(x : Nat32, p : Nat) : Nat32 Returns the value of flipping bit `p mod 32` in `x`. +Example: +```motoko include=import +Nat32.bitflip(5, 2); // => 1 +``` + ## Value `bitcountNonZero` ``` motoko no-repl let bitcountNonZero : (x : Nat32) -> Nat32 @@ -236,6 +631,11 @@ let bitcountNonZero : (x : Nat32) -> Nat32 Returns the count of non-zero bits in `x`. +Example: +```motoko include=import +Nat32.bitcountNonZero(5); // => 2 +``` + ## Value `bitcountLeadingZero` ``` motoko no-repl let bitcountLeadingZero : (x : Nat32) -> Nat32 @@ -243,6 +643,11 @@ let bitcountLeadingZero : (x : Nat32) -> Nat32 Returns the count of leading zero bits in `x`. +Example: +```motoko include=import +Nat32.bitcountLeadingZero(5); // => 29 +``` + ## Value `bitcountTrailingZero` ``` motoko no-repl let bitcountTrailingZero : (x : Nat32) -> Nat32 @@ -250,6 +655,11 @@ let bitcountTrailingZero : (x : Nat32) -> Nat32 Returns the count of trailing zero bits in `x`. +Example: +```motoko include=import +Nat32.bitcountTrailingZero(16); // => 4 +``` + ## Function `addWrap` ``` motoko no-repl func addWrap(x : Nat32, y : Nat32) : Nat32 @@ -257,6 +667,17 @@ func addWrap(x : Nat32, y : Nat32) : Nat32 Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat32.addWrap(4294967295, 1); // => 0 +(4294967295 : Nat32) +% (1 : Nat32) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Nat32, y : Nat32) : Nat32 @@ -264,6 +685,17 @@ func subWrap(x : Nat32, y : Nat32) : Nat32 Returns the difference of `x` and `y`, `x -% y`. Wraps on underflow. +Example: +```motoko include=import +ignore Nat32.subWrap(0, 1); // => 4294967295 +(0 : Nat32) -% (1 : Nat32) // => 4294967295 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Nat32, y : Nat32) : Nat32 @@ -271,9 +703,31 @@ func mulWrap(x : Nat32, y : Nat32) : Nat32 Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat32.mulWrap(2147483648, 2); // => 0 +(2147483648 : Nat32) *% (2 : Nat32) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Nat32, y : Nat32) : Nat32 ``` Returns `x` to the power of `y`, `x **% y`. Wraps on overflow. + +Example: +```motoko include=import +ignore Nat32.powWrap(2, 32); // => 0 +(2 : Nat32) **% (32 : Nat32) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Nat64.md b/doc/md/base/Nat64.md index 8ad9048790b..315202d0816 100644 --- a/doc/md/base/Nat64.md +++ b/doc/md/base/Nat64.md @@ -1,7 +1,12 @@ # Nat64 -64-bit unsigned integers with checked arithmetic +Provides utility functions on 64-bit unsigned integers. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Nat64 "mo:base/Nat64"; +``` ## Type `Nat64` ``` motoko no-repl @@ -10,33 +15,96 @@ type Nat64 = Prim.Types.Nat64 64-bit natural numbers. +## Value `maximumValue` +``` motoko no-repl +let maximumValue : Nat64 +``` + +Maximum 64-bit natural number. `2 ** 64 - 1`. + +Example: +```motoko include=import +Nat64.maximumValue; // => 18446744073709551615 : Nat64 +``` + ## Value `toNat` ``` motoko no-repl let toNat : Nat64 -> Nat ``` -Conversion. +Converts a 64-bit unsigned integer to an unsigned integer with infinite precision. + +Example: +```motoko include=import +Nat64.toNat(123); // => 123 : Nat +``` ## Value `fromNat` ``` motoko no-repl let fromNat : Nat -> Nat64 ``` -Conversion. Traps on overflow/underflow. +Converts an unsigned integer with infinite precision to a 64-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat64.fromNat(123); // => 123 : Nat64 +``` + +## Function `fromNat32` +``` motoko no-repl +func fromNat32(x : Nat32) : Nat64 +``` + +Converts a 32-bit unsigned integer to a 64-bit unsigned integer. + +Example: +```motoko include=import +Nat64.fromNat32(123); // => 123 : Nat64 +``` + +## Function `toNat32` +``` motoko no-repl +func toNat32(x : Nat64) : Nat32 +``` + +Converts a 64-bit unsigned integer to a 32-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat64.toNat32(123); // => 123 : Nat32 +``` ## Value `fromIntWrap` ``` motoko no-repl let fromIntWrap : Int -> Nat64 ``` -Conversion. Wraps on overflow/underflow. +Converts a signed integer with infinite precision to a 64-bit unsigned integer. + +Traps on overflow/underflow. + +Example: +```motoko include=import +Nat64.fromIntWrap(123); // => 123 : Nat64 +``` ## Function `toText` ``` motoko no-repl func toText(x : Nat64) : Text ``` -Returns the Text representation of `x`. +Converts `x` to its textual representation. Textual representation _do not_ +contain underscores to represent commas. + +Example: +```motoko include=import +Nat64.toText(1234); // => "1234" : Text +``` ## Function `min` ``` motoko no-repl @@ -45,6 +113,11 @@ func min(x : Nat64, y : Nat64) : Nat64 Returns the minimum of `x` and `y`. +Example: +```motoko include=import +Nat64.min(123, 456); // => 123 : Nat64 +``` + ## Function `max` ``` motoko no-repl func max(x : Nat64, y : Nat64) : Nat64 @@ -52,84 +125,249 @@ func max(x : Nat64, y : Nat64) : Nat64 Returns the maximum of `x` and `y`. +Example: +```motoko include=import +Nat64.max(123, 456); // => 456 : Nat64 +``` + ## Function `equal` ``` motoko no-repl func equal(x : Nat64, y : Nat64) : Bool ``` -Returns `x == y`. +Equality function for Nat64 types. +This is equivalent to `x == y`. + +Example: +```motoko include=import +ignore Nat64.equal(1, 1); // => true +(1 : Nat64) == (1 : Nat64) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. + +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; + +let buffer1 = Buffer.Buffer(3); +let buffer2 = Buffer.Buffer(3); +Buffer.equal(buffer1, buffer2, Nat64.equal) // => true +``` ## Function `notEqual` ``` motoko no-repl func notEqual(x : Nat64, y : Nat64) : Bool ``` -Returns `x != y`. +Inequality function for Nat64 types. +This is equivalent to `x != y`. + +Example: +```motoko include=import +ignore Nat64.notEqual(1, 2); // => true +(1 : Nat64) != (2 : Nat64) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. ## Function `less` ``` motoko no-repl func less(x : Nat64, y : Nat64) : Bool ``` -Returns `x < y`. +"Less than" function for Nat64 types. +This is equivalent to `x < y`. + +Example: +```motoko include=import +ignore Nat64.less(1, 2); // => true +(1 : Nat64) < (2 : Nat64) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Nat64, y : Nat64) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Nat64 types. +This is equivalent to `x <= y`. + +Example: +```motoko include=import +ignore Nat64.lessOrEqual(1, 2); // => true +(1 : Nat64) <= (2 : Nat64) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. ## Function `greater` ``` motoko no-repl func greater(x : Nat64, y : Nat64) : Bool ``` -Returns `x > y`. +"Greater than" function for Nat64 types. +This is equivalent to `x > y`. + +Example: +```motoko include=import +ignore Nat64.greater(2, 1); // => true +(2 : Nat64) > (1 : Nat64) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>` +as a function value at the moment. ## Function `greaterOrEqual` ``` motoko no-repl func greaterOrEqual(x : Nat64, y : Nat64) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Nat64 types. +This is equivalent to `x >= y`. + +Example: +```motoko include=import +ignore Nat64.greaterOrEqual(2, 1); // => true +(2 : Nat64) >= (1 : Nat64) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>=` +as a function value at the moment. ## Function `compare` ``` motoko no-repl func compare(x : Nat64, y : Nat64) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General purpose comparison function for `Nat64`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. + +Example: +```motoko include=import +Nat64.compare(2, 3) // => #less +``` + +This function can be used as value for a high order function, such as a sort function. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([2, 3, 1] : [Nat64], Nat64.compare) // => [1, 2, 3] +``` ## Function `add` ``` motoko no-repl func add(x : Nat64, y : Nat64) : Nat64 ``` -Returns the sum of `x` and `y`, `x + y`. Traps on overflow. +Returns the sum of `x` and `y`, `x + y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat64.add(1, 2); // => 3 +(1 : Nat64) + (2 : Nat64) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 0, Nat64.add) // => 6 +``` ## Function `sub` ``` motoko no-repl func sub(x : Nat64, y : Nat64) : Nat64 ``` -Returns the difference of `x` and `y`, `x - y`. Traps on underflow. +Returns the difference of `x` and `y`, `x - y`. +Traps on underflow. + +Example: +```motoko include=import +ignore Nat64.sub(3, 1); // => 2 +(3 : Nat64) - (1 : Nat64) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 10, Nat64.sub) // => 4 +``` ## Function `mul` ``` motoko no-repl func mul(x : Nat64, y : Nat64) : Nat64 ``` -Returns the product of `x` and `y`, `x * y`. Traps on overflow. +Returns the product of `x` and `y`, `x * y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat64.mul(2, 3); // => 6 +(2 : Nat64) * (3 : Nat64) // => 6 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 1, Nat64.mul) // => 6 +``` ## Function `div` ``` motoko no-repl func div(x : Nat64, y : Nat64) : Nat64 ``` -Returns the division of `x by y`, `x / y`. +Returns the quotient of `x` divided by `y`, `x / y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat64.div(6, 2); // => 3 +(6 : Nat64) / (2 : Nat64) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Nat64, y : Nat64) : Nat64 @@ -138,6 +376,17 @@ func rem(x : Nat64, y : Nat64) : Nat64 Returns the remainder of `x` divided by `y`, `x % y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat64.rem(6, 4); // => 2 +(6 : Nat64) % (4 : Nat64) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Nat64, y : Nat64) : Nat64 @@ -145,6 +394,17 @@ func pow(x : Nat64, y : Nat64) : Nat64 Returns `x` to the power of `y`, `x ** y`. Traps on overflow. +Example: +```motoko include=import +ignore Nat64.pow(2, 3); // => 8 +(2 : Nat64) ** (3 : Nat64) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. + ## Function `bitnot` ``` motoko no-repl func bitnot(x : Nat64) : Nat64 @@ -152,6 +412,17 @@ func bitnot(x : Nat64) : Nat64 Returns the bitwise negation of `x`, `^x`. +Example: +```motoko include=import +ignore Nat64.bitnot(0); // => 18446744073709551615 +^(0 : Nat64) // => 18446744073709551615 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Nat64, y : Nat64) : Nat64 @@ -159,12 +430,34 @@ func bitand(x : Nat64, y : Nat64) : Nat64 Returns the bitwise and of `x` and `y`, `x & y`. +Example: +```motoko include=import +ignore Nat64.bitand(1, 3); // => 1 +(1 : Nat64) & (3 : Nat64) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Nat64, y : Nat64) : Nat64 ``` -Returns the bitwise or of `x` and `y`, `x \| y`. +Returns the bitwise or of `x` and `y`, `x | y`. + +Example: +```motoko include=import +ignore Nat64.bitor(1, 3); // => 3 +(1 : Nat64) | (3 : Nat64) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `|` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `|` +as a function value at the moment. ## Function `bitxor` ``` motoko no-repl @@ -173,6 +466,17 @@ func bitxor(x : Nat64, y : Nat64) : Nat64 Returns the bitwise exclusive or of `x` and `y`, `x ^ y`. +Example: +```motoko include=import +ignore Nat64.bitxor(1, 3); // => 2 +(1 : Nat64) ^ (3 : Nat64) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitshiftLeft` ``` motoko no-repl func bitshiftLeft(x : Nat64, y : Nat64) : Nat64 @@ -180,6 +484,17 @@ func bitshiftLeft(x : Nat64, y : Nat64) : Nat64 Returns the bitwise shift left of `x` by `y`, `x << y`. +Example: +```motoko include=import +ignore Nat64.bitshiftLeft(1, 3); // => 8 +(1 : Nat64) << (3 : Nat64) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Nat64, y : Nat64) : Nat64 @@ -187,6 +502,17 @@ func bitshiftRight(x : Nat64, y : Nat64) : Nat64 Returns the bitwise shift right of `x` by `y`, `x >> y`. +Example: +```motoko include=import +ignore Nat64.bitshiftRight(8, 3); // => 1 +(8 : Nat64) >> (3 : Nat64) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Nat64, y : Nat64) : Nat64 @@ -194,6 +520,17 @@ func bitrotLeft(x : Nat64, y : Nat64) : Nat64 Returns the bitwise rotate left of `x` by `y`, `x <<> y`. +Example: +```motoko include=import +ignore Nat64.bitrotLeft(1, 3); // => 8 +(1 : Nat64) <<> (3 : Nat64) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Nat64, y : Nat64) : Nat64 @@ -201,12 +538,29 @@ func bitrotRight(x : Nat64, y : Nat64) : Nat64 Returns the bitwise rotate right of `x` by `y`, `x <>> y`. +Example: +```motoko include=import +ignore Nat64.bitrotRight(8, 3); // => 1 +(8 : Nat64) <>> (3 : Nat64) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Nat64, p : Nat) : Bool ``` Returns the value of bit `p mod 64` in `x`, `(x & 2^(p mod 64)) == 2^(p mod 64)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. + +Example: +```motoko include=import +Nat64.bittest(5, 2); // => true +``` ## Function `bitset` ``` motoko no-repl @@ -215,6 +569,11 @@ func bitset(x : Nat64, p : Nat) : Nat64 Returns the value of setting bit `p mod 64` in `x` to `1`. +Example: +```motoko include=import +Nat64.bitset(5, 1); // => 7 +``` + ## Function `bitclear` ``` motoko no-repl func bitclear(x : Nat64, p : Nat) : Nat64 @@ -222,6 +581,11 @@ func bitclear(x : Nat64, p : Nat) : Nat64 Returns the value of clearing bit `p mod 64` in `x` to `0`. +Example: +```motoko include=import +Nat64.bitclear(5, 2); // => 1 +``` + ## Function `bitflip` ``` motoko no-repl func bitflip(x : Nat64, p : Nat) : Nat64 @@ -229,6 +593,11 @@ func bitflip(x : Nat64, p : Nat) : Nat64 Returns the value of flipping bit `p mod 64` in `x`. +Example: +```motoko include=import +Nat64.bitflip(5, 2); // => 1 +``` + ## Value `bitcountNonZero` ``` motoko no-repl let bitcountNonZero : (x : Nat64) -> Nat64 @@ -236,6 +605,11 @@ let bitcountNonZero : (x : Nat64) -> Nat64 Returns the count of non-zero bits in `x`. +Example: +```motoko include=import +Nat64.bitcountNonZero(5); // => 2 +``` + ## Value `bitcountLeadingZero` ``` motoko no-repl let bitcountLeadingZero : (x : Nat64) -> Nat64 @@ -243,6 +617,11 @@ let bitcountLeadingZero : (x : Nat64) -> Nat64 Returns the count of leading zero bits in `x`. +Example: +```motoko include=import +Nat64.bitcountLeadingZero(5); // => 61 +``` + ## Value `bitcountTrailingZero` ``` motoko no-repl let bitcountTrailingZero : (x : Nat64) -> Nat64 @@ -250,6 +629,11 @@ let bitcountTrailingZero : (x : Nat64) -> Nat64 Returns the count of trailing zero bits in `x`. +Example: +```motoko include=import +Nat64.bitcountTrailingZero(16); // => 4 +``` + ## Function `addWrap` ``` motoko no-repl func addWrap(x : Nat64, y : Nat64) : Nat64 @@ -257,6 +641,17 @@ func addWrap(x : Nat64, y : Nat64) : Nat64 Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat64.addWrap(Nat64.maximumValue, 1); // => 0 +Nat64.maximumValue +% (1 : Nat64) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Nat64, y : Nat64) : Nat64 @@ -264,6 +659,17 @@ func subWrap(x : Nat64, y : Nat64) : Nat64 Returns the difference of `x` and `y`, `x -% y`. Wraps on underflow. +Example: +```motoko include=import +ignore Nat64.subWrap(0, 1); // => 18446744073709551615 +(0 : Nat64) -% (1 : Nat64) // => 18446744073709551615 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Nat64, y : Nat64) : Nat64 @@ -271,9 +677,31 @@ func mulWrap(x : Nat64, y : Nat64) : Nat64 Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat64.mulWrap(4294967296, 4294967296); // => 0 +(4294967296 : Nat64) *% (4294967296 : Nat64) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Nat64, y : Nat64) : Nat64 ``` Returns `x` to the power of `y`, `x **% y`. Wraps on overflow. + +Example: +```motoko include=import +ignore Nat64.powWrap(2, 64); // => 0 +(2 : Nat64) **% (64 : Nat64) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Nat8.md b/doc/md/base/Nat8.md index 6f257989317..0c23c20f1c1 100644 --- a/doc/md/base/Nat8.md +++ b/doc/md/base/Nat8.md @@ -1,7 +1,12 @@ # Nat8 -8-bit unsigned integers with checked arithmetic +Provides utility functions on 8-bit unsigned integers. -Most operations are available as built-in operators (e.g. `1 + 1`). +Note that most operations are available as built-in operators (e.g. `1 + 1`). + +Import from the base library to use this module. +```motoko name=import +import Nat8 "mo:base/Nat8"; +``` ## Type `Nat8` ``` motoko no-repl @@ -10,33 +15,95 @@ type Nat8 = Prim.Types.Nat8 8-bit natural numbers. +## Value `maximumValue` +``` motoko no-repl +let maximumValue : Nat8 +``` + +Maximum 8-bit natural number. `2 ** 8 - 1`. + +Example: +```motoko include=import +Nat8.maximumValue; // => 255 : Nat8 +``` + ## Value `toNat` ``` motoko no-repl let toNat : Nat8 -> Nat ``` -Conversion. +Converts an 8-bit unsigned integer to an unsigned integer with infinite precision. + +Example: +```motoko include=import +Nat8.toNat(123); // => 123 : Nat +``` ## Value `fromNat` ``` motoko no-repl let fromNat : Nat -> Nat8 ``` -Conversion. Traps on overflow/underflow. +Converts an unsigned integer with infinite precision to an 8-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat8.fromNat(123); // => 123 : Nat8 +``` + +## Value `fromNat16` +``` motoko no-repl +let fromNat16 : Nat16 -> Nat8 +``` + +Converts a 16-bit unsigned integer to a 8-bit unsigned integer. + +Traps on overflow. + +Example: +```motoko include=import +Nat8.fromNat16(123); // => 123 : Nat8 +``` + +## Value `toNat16` +``` motoko no-repl +let toNat16 : Nat8 -> Nat16 +``` + +Converts an 8-bit unsigned integer to a 16-bit unsigned integer. + +Example: +```motoko include=import +Nat8.toNat16(123); // => 123 : Nat16 +``` ## Value `fromIntWrap` ``` motoko no-repl let fromIntWrap : Int -> Nat8 ``` -Conversion. Wraps on overflow/underflow. +Converts a signed integer with infinite precision to an 8-bit unsigned integer. + +Wraps on overflow/underflow. + +Example: +```motoko include=import +Nat8.fromIntWrap(123); // => 123 : Nat8 +``` ## Function `toText` ``` motoko no-repl func toText(x : Nat8) : Text ``` -Returns the Text representation of `x`. +Converts `x` to its textual representation. + +Example: +```motoko include=import +Nat8.toText(123); // => "123" : Text +``` ## Function `min` ``` motoko no-repl @@ -45,6 +112,11 @@ func min(x : Nat8, y : Nat8) : Nat8 Returns the minimum of `x` and `y`. +Example: +```motoko include=import +Nat8.min(123, 200); // => 123 : Nat8 +``` + ## Function `max` ``` motoko no-repl func max(x : Nat8, y : Nat8) : Nat8 @@ -52,84 +124,249 @@ func max(x : Nat8, y : Nat8) : Nat8 Returns the maximum of `x` and `y`. +Example: +```motoko include=import +Nat8.max(123, 200); // => 200 : Nat8 +``` + ## Function `equal` ``` motoko no-repl func equal(x : Nat8, y : Nat8) : Bool ``` -Returns `x == y`. +Equality function for Nat8 types. +This is equivalent to `x == y`. + +Example: +```motoko include=import +ignore Nat8.equal(1, 1); // => true +(1 : Nat8) == (1 : Nat8) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `==` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `==` +as a function value at the moment. + +Example: +```motoko include=import +import Buffer "mo:base/Buffer"; + +let buffer1 = Buffer.Buffer(3); +let buffer2 = Buffer.Buffer(3); +Buffer.equal(buffer1, buffer2, Nat8.equal) // => true +``` ## Function `notEqual` ``` motoko no-repl func notEqual(x : Nat8, y : Nat8) : Bool ``` -Returns `x != y`. +Inequality function for Nat8 types. +This is equivalent to `x != y`. + +Example: +```motoko include=import +ignore Nat8.notEqual(1, 2); // => true +(1 : Nat8) != (2 : Nat8) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `!=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `!=` +as a function value at the moment. ## Function `less` ``` motoko no-repl func less(x : Nat8, y : Nat8) : Bool ``` -Returns `x < y`. +"Less than" function for Nat8 types. +This is equivalent to `x < y`. + +Example: +```motoko include=import +ignore Nat8.less(1, 2); // => true +(1 : Nat8) < (2 : Nat8) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<` +as a function value at the moment. ## Function `lessOrEqual` ``` motoko no-repl func lessOrEqual(x : Nat8, y : Nat8) : Bool ``` -Returns `x <= y`. +"Less than or equal" function for Nat8 types. +This is equivalent to `x <= y`. + +Example: +```motoko include=import +ignore Nat.lessOrEqual(1, 2); // => true +1 <= 2 // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<=` +as a function value at the moment. ## Function `greater` ``` motoko no-repl func greater(x : Nat8, y : Nat8) : Bool ``` -Returns `x > y`. +"Greater than" function for Nat8 types. +This is equivalent to `x > y`. + +Example: +```motoko include=import +ignore Nat8.greater(2, 1); // => true +(2 : Nat8) > (1 : Nat8) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>` +as a function value at the moment. ## Function `greaterOrEqual` ``` motoko no-repl func greaterOrEqual(x : Nat8, y : Nat8) : Bool ``` -Returns `x >= y`. +"Greater than or equal" function for Nat8 types. +This is equivalent to `x >= y`. + +Example: +```motoko include=import +ignore Nat8.greaterOrEqual(2, 1); // => true +(2 : Nat8) >= (1 : Nat8) // => true +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>=` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>=` +as a function value at the moment. ## Function `compare` ``` motoko no-repl func compare(x : Nat8, y : Nat8) : {#less; #equal; #greater} ``` -Returns the order of `x` and `y`. +General purpose comparison function for `Nat8`. Returns the `Order` ( +either `#less`, `#equal`, or `#greater`) of comparing `x` with `y`. + +Example: +```motoko include=import +Nat8.compare(2, 3) // => #less +``` + +This function can be used as value for a high order function, such as a sort function. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.sort([2, 3, 1] : [Nat8], Nat8.compare) // => [1, 2, 3] +``` ## Function `add` ``` motoko no-repl func add(x : Nat8, y : Nat8) : Nat8 ``` -Returns the sum of `x` and `y`, `x + y`. Traps on overflow. +Returns the sum of `x` and `y`, `x + y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat8.add(1, 2); // => 3 +(1 : Nat8) + (2 : Nat8) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 0, Nat8.add) // => 6 +``` ## Function `sub` ``` motoko no-repl func sub(x : Nat8, y : Nat8) : Nat8 ``` -Returns the difference of `x` and `y`, `x - y`. Traps on underflow. +Returns the difference of `x` and `y`, `x - y`. +Traps on underflow. + +Example: +```motoko include=import +ignore Nat8.sub(2, 1); // => 1 +(2 : Nat8) - (1 : Nat8) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `-` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 20, Nat8.sub) // => 14 +``` ## Function `mul` ``` motoko no-repl func mul(x : Nat8, y : Nat8) : Nat8 ``` -Returns the product of `x` and `y`, `x * y`. Traps on overflow. +Returns the product of `x` and `y`, `x * y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat8.mul(2, 3); // => 6 +(2 : Nat8) * (3 : Nat8) // => 6 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*` +as a function value at the moment. + +Example: +```motoko include=import +import Array "mo:base/Array"; +Array.foldLeft([2, 3, 1], 1, Nat8.mul) // => 6 +``` ## Function `div` ``` motoko no-repl func div(x : Nat8, y : Nat8) : Nat8 ``` -Returns the division of `x by y`, `x / y`. +Returns the quotient of `x` divided by `y`, `x / y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat8.div(6, 2); // => 3 +(6 : Nat8) / (2 : Nat8) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `/` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `/` +as a function value at the moment. + ## Function `rem` ``` motoko no-repl func rem(x : Nat8, y : Nat8) : Nat8 @@ -138,12 +375,35 @@ func rem(x : Nat8, y : Nat8) : Nat8 Returns the remainder of `x` divided by `y`, `x % y`. Traps when `y` is zero. +Example: +```motoko include=import +ignore Nat8.rem(6, 4); // => 2 +(6 : Nat8) % (4 : Nat8) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `%` +as a function value at the moment. + ## Function `pow` ``` motoko no-repl func pow(x : Nat8, y : Nat8) : Nat8 ``` -Returns `x` to the power of `y`, `x ** y`. Traps on overflow. +Returns `x` to the power of `y`, `x ** y`. +Traps on overflow. + +Example: +```motoko include=import +ignore Nat8.pow(2, 3); // => 8 +(2 : Nat8) ** (3 : Nat8) // => 8 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**` +as a function value at the moment. ## Function `bitnot` ``` motoko no-repl @@ -152,6 +412,17 @@ func bitnot(x : Nat8) : Nat8 Returns the bitwise negation of `x`, `^x`. +Example: +```motoko include=import +ignore Nat8.bitnot(0); // => 255 +^(0 : Nat8) // => 255 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitand` ``` motoko no-repl func bitand(x : Nat8, y : Nat8) : Nat8 @@ -159,12 +430,34 @@ func bitand(x : Nat8, y : Nat8) : Nat8 Returns the bitwise and of `x` and `y`, `x & y`. +Example: +```motoko include=import +ignore Nat8.bitand(3, 2); // => 2 +(3 : Nat8) & (2 : Nat8) // => 2 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `&` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `&` +as a function value at the moment. + ## Function `bitor` ``` motoko no-repl func bitor(x : Nat8, y : Nat8) : Nat8 ``` -Returns the bitwise or of `x` and `y`, `x \| y`. +Returns the bitwise or of `x` and `y`, `x | y`. + +Example: +```motoko include=import +ignore Nat8.bitor(3, 2); // => 3 +(3 : Nat8) | (2 : Nat8) // => 3 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `|` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `|` +as a function value at the moment. ## Function `bitxor` ``` motoko no-repl @@ -173,6 +466,17 @@ func bitxor(x : Nat8, y : Nat8) : Nat8 Returns the bitwise exclusive or of `x` and `y`, `x ^ y`. +Example: +```motoko include=import +ignore Nat8.bitxor(3, 2); // => 1 +(3 : Nat8) ^ (2 : Nat8) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `^` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `^` +as a function value at the moment. + ## Function `bitshiftLeft` ``` motoko no-repl func bitshiftLeft(x : Nat8, y : Nat8) : Nat8 @@ -180,6 +484,17 @@ func bitshiftLeft(x : Nat8, y : Nat8) : Nat8 Returns the bitwise shift left of `x` by `y`, `x << y`. +Example: +```motoko include=import +ignore Nat8.bitshiftLeft(1, 2); // => 4 +(1 : Nat8) << (2 : Nat8) // => 4 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<` +as a function value at the moment. + ## Function `bitshiftRight` ``` motoko no-repl func bitshiftRight(x : Nat8, y : Nat8) : Nat8 @@ -187,6 +502,17 @@ func bitshiftRight(x : Nat8, y : Nat8) : Nat8 Returns the bitwise shift right of `x` by `y`, `x >> y`. +Example: +```motoko include=import +ignore Nat8.bitshiftRight(4, 2); // => 1 +(4 : Nat8) >> (2 : Nat8) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `>>` +as a function value at the moment. + ## Function `bitrotLeft` ``` motoko no-repl func bitrotLeft(x : Nat8, y : Nat8) : Nat8 @@ -194,6 +520,17 @@ func bitrotLeft(x : Nat8, y : Nat8) : Nat8 Returns the bitwise rotate left of `x` by `y`, `x <<> y`. +Example: +```motoko include=import +ignore Nat8.bitrotLeft(128, 1); // => 1 +(128 : Nat8) <<> (1 : Nat8) // => 1 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<<>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<<>` +as a function value at the moment. + ## Function `bitrotRight` ``` motoko no-repl func bitrotRight(x : Nat8, y : Nat8) : Nat8 @@ -201,12 +538,29 @@ func bitrotRight(x : Nat8, y : Nat8) : Nat8 Returns the bitwise rotate right of `x` by `y`, `x <>> y`. +Example: +```motoko include=import +ignore Nat8.bitrotRight(1, 1); // => 128 +(1 : Nat8) <>> (1 : Nat8) // => 128 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `<>>` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `<>>` +as a function value at the moment. + ## Function `bittest` ``` motoko no-repl func bittest(x : Nat8, p : Nat) : Bool ``` Returns the value of bit `p mod 8` in `x`, `(x & 2^(p mod 8)) == 2^(p mod 8)`. +This is equivalent to checking if the `p`-th bit is set in `x`, using 0 indexing. + +Example: +```motoko include=import +Nat8.bittest(5, 2); // => true +``` ## Function `bitset` ``` motoko no-repl @@ -215,6 +569,11 @@ func bitset(x : Nat8, p : Nat) : Nat8 Returns the value of setting bit `p mod 8` in `x` to `1`. +Example: +```motoko include=import +Nat8.bitset(5, 1); // => 7 +``` + ## Function `bitclear` ``` motoko no-repl func bitclear(x : Nat8, p : Nat) : Nat8 @@ -222,6 +581,11 @@ func bitclear(x : Nat8, p : Nat) : Nat8 Returns the value of clearing bit `p mod 8` in `x` to `0`. +Example: +```motoko include=import +Nat8.bitclear(5, 2); // => 1 +``` + ## Function `bitflip` ``` motoko no-repl func bitflip(x : Nat8, p : Nat) : Nat8 @@ -229,6 +593,11 @@ func bitflip(x : Nat8, p : Nat) : Nat8 Returns the value of flipping bit `p mod 8` in `x`. +Example: +```motoko include=import +Nat8.bitflip(5, 2); // => 1 +``` + ## Value `bitcountNonZero` ``` motoko no-repl let bitcountNonZero : (x : Nat8) -> Nat8 @@ -236,6 +605,11 @@ let bitcountNonZero : (x : Nat8) -> Nat8 Returns the count of non-zero bits in `x`. +Example: +```motoko include=import +Nat8.bitcountNonZero(5); // => 2 +``` + ## Value `bitcountLeadingZero` ``` motoko no-repl let bitcountLeadingZero : (x : Nat8) -> Nat8 @@ -243,6 +617,11 @@ let bitcountLeadingZero : (x : Nat8) -> Nat8 Returns the count of leading zero bits in `x`. +Example: +```motoko include=import +Nat8.bitcountLeadingZero(5); // => 5 +``` + ## Value `bitcountTrailingZero` ``` motoko no-repl let bitcountTrailingZero : (x : Nat8) -> Nat8 @@ -250,6 +629,11 @@ let bitcountTrailingZero : (x : Nat8) -> Nat8 Returns the count of trailing zero bits in `x`. +Example: +```motoko include=import +Nat8.bitcountTrailingZero(6); // => 1 +``` + ## Function `addWrap` ``` motoko no-repl func addWrap(x : Nat8, y : Nat8) : Nat8 @@ -257,6 +641,17 @@ func addWrap(x : Nat8, y : Nat8) : Nat8 Returns the sum of `x` and `y`, `x +% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat8.addWrap(230, 26); // => 0 +(230 : Nat8) +% (26 : Nat8) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `+%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `+%` +as a function value at the moment. + ## Function `subWrap` ``` motoko no-repl func subWrap(x : Nat8, y : Nat8) : Nat8 @@ -264,6 +659,16 @@ func subWrap(x : Nat8, y : Nat8) : Nat8 Returns the difference of `x` and `y`, `x -% y`. Wraps on underflow. +Example: +```motoko include=import +ignore Nat8.subWrap(0, 1); // => 255 +(0 : Nat8) -% (1 : Nat8) // => 255 +``` +Note: The reason why this function is defined in this library (in addition +to the existing `-%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `-%` +as a function value at the moment. + ## Function `mulWrap` ``` motoko no-repl func mulWrap(x : Nat8, y : Nat8) : Nat8 @@ -271,9 +676,31 @@ func mulWrap(x : Nat8, y : Nat8) : Nat8 Returns the product of `x` and `y`, `x *% y`. Wraps on overflow. +Example: +```motoko include=import +ignore Nat8.mulWrap(230, 26); // => 92 +(230 : Nat8) *% (26 : Nat8) // => 92 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `*%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `*%` +as a function value at the moment. + ## Function `powWrap` ``` motoko no-repl func powWrap(x : Nat8, y : Nat8) : Nat8 ``` Returns `x` to the power of `y`, `x **% y`. Wraps on overflow. + +Example: +```motoko include=import +ignore Nat8.powWrap(2, 8); // => 0 +(2 : Nat8) **% (8 : Nat8) // => 0 +``` + +Note: The reason why this function is defined in this library (in addition +to the existing `**%` operator) is so that you can use it as a function +value to pass to a higher order function. It is not possible to use `**%` +as a function value at the moment. diff --git a/doc/md/base/Region.md b/doc/md/base/Region.md new file mode 100644 index 00000000000..840abe55750 --- /dev/null +++ b/doc/md/base/Region.md @@ -0,0 +1,469 @@ +# Region +Byte-level access to isolated, (virtual) stable memory _regions_. + +This is a moderately lightweight abstraction over IC _stable memory_ and supports persisting +regions of binary data across Motoko upgrades. +Use of this module is fully compatible with Motoko's use of +_stable variables_, whose persistence mechanism also uses (real) IC stable memory internally, but does not interfere with this API. +It is also fully compatible with existing uses of the `ExperimentalStableMemory` library, which has a similar interface, but, +only supported a single memory region, without isolation between different applications. + +Memory is allocated, using `grow(region, pages)`, sequentially and on demand, in units of 64KiB logical pages, starting with 0 allocated pages. +New pages are zero initialized. +Growth is capped by a soft limit on physical page count controlled by compile-time flag +`--max-stable-pages ` (the default is 65536, or 4GiB). + +Each `load` operation loads from region relative byte address `offset` in little-endian +format using the natural bit-width of the type in question. +The operation traps if attempting to read beyond the current region size. + +Each `store` operation stores to region relative byte address `offset` in little-endian format using the natural bit-width of the type in question. +The operation traps if attempting to write beyond the current region size. + +Text values can be handled by using `Text.decodeUtf8` and `Text.encodeUtf8`, in conjunction with `loadBlob` and `storeBlob`. + +The current region allocation and region contents are preserved across upgrades. + +NB: The IC's actual stable memory size (`ic0.stable_size`) may exceed the +total page size reported by summing all regions sizes. +This (and the cap on growth) are to accommodate Motoko's stable variables and bookkeeping for regions. +Applications that plan to use Motoko stable variables sparingly or not at all can +increase `--max-stable-pages` as desired, approaching the IC maximum (initially 8GiB, then 32Gib, currently 64Gib). +All applications should reserve at least one page for stable variable data, even when no stable variables are used. + +Usage: +```motoko no-repl +import Region "mo:base/Region"; +``` + +## Type `Region` +``` motoko no-repl +type Region = Prim.Types.Region +``` + +A stateful handle to an isolated region of IC stable memory. +`Region` is a stable type and regions can be stored in stable variables. + +## Value `new` +``` motoko no-repl +let new : () -> Region +``` + +Allocate a new, isolated Region of size 0. + +Example: + +```motoko no-repl +let region = Region.new(); +assert Region.size(region) == 0; +``` + +## Value `id` +``` motoko no-repl +let id : Region -> Nat +``` + +Return a Nat identifying the given region. +Maybe be used for equality, comparison and hashing. +NB: Regions returned by `new()` are numbered from 16 +(regions 0..15 are currently reserved for internal use). +Allocate a new, isolated Region of size 0. + +Example: + +```motoko no-repl +let region = Region.new(); +assert Region.id(region) == 16; +``` + +## Value `size` +``` motoko no-repl +let size : (region : Region) -> (pages : Nat64) +``` + +Current size of `region`, in pages. +Each page is 64KiB (65536 bytes). +Initially `0`. +Preserved across upgrades, together with contents of allocated +stable memory. + +Example: +```motoko no-repl +let region = Region.new(); +let beforeSize = Region.size(region); +ignore Region.grow(region, 10); +let afterSize = Region.size(region); +afterSize - beforeSize // => 10 +``` + +## Value `grow` +``` motoko no-repl +let grow : (region : Region, newPages : Nat64) -> (oldPages : Nat64) +``` + +Grow current `size` of `region` by the given number of pages. +Each page is 64KiB (65536 bytes). +Returns the previous `size` when able to grow. +Returns `0xFFFF_FFFF_FFFF_FFFF` if remaining pages insufficient. +Every new page is zero-initialized, containing byte 0x00 at every offset. +Function `grow` is capped by a soft limit on `size` controlled by compile-time flag + `--max-stable-pages ` (the default is 65536, or 4GiB). + +Example: +```motoko no-repl +import Error "mo:base/Error"; + +let region = Region.new(); +let beforeSize = Region.grow(region, 10); +if (beforeSize == 0xFFFF_FFFF_FFFF_FFFF) { + throw Error.reject("Out of memory"); +}; +let afterSize = Region.size(region); +afterSize - beforeSize // => 10 +``` + +## Value `loadNat8` +``` motoko no-repl +let loadNat8 : (region : Region, offset : Nat64) -> Nat8 +``` + +Within `region`, load a `Nat8` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat8(region, offset, value); +Region.loadNat8(region, offset) // => 123 +``` + +## Value `storeNat8` +``` motoko no-repl +let storeNat8 : (region : Region, offset : Nat64, value : Nat8) -> () +``` + +Within `region`, store a `Nat8` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat8(region, offset, value); +Region.loadNat8(region, offset) // => 123 +``` + +## Value `loadNat16` +``` motoko no-repl +let loadNat16 : (region : Region, offset : Nat64) -> Nat16 +``` + +Within `region`, load a `Nat16` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat16(region, offset, value); +Region.loadNat16(region, offset) // => 123 +``` + +## Value `storeNat16` +``` motoko no-repl +let storeNat16 : (region : Region, offset : Nat64, value : Nat16) -> () +``` + +Within `region`, store a `Nat16` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat16(region, offset, value); +Region.loadNat16(region, offset) // => 123 +``` + +## Value `loadNat32` +``` motoko no-repl +let loadNat32 : (region : Region, offset : Nat64) -> Nat32 +``` + +Within `region`, load a `Nat32` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat32(region, offset, value); +Region.loadNat32(region, offset) // => 123 +``` + +## Value `storeNat32` +``` motoko no-repl +let storeNat32 : (region : Region, offset : Nat64, value : Nat32) -> () +``` + +Within `region`, store a `Nat32` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat32(region, offset, value); +Region.loadNat32(region, offset) // => 123 +``` + +## Value `loadNat64` +``` motoko no-repl +let loadNat64 : (region : Region, offset : Nat64) -> Nat64 +``` + +Within `region`, load a `Nat64` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat64(region, offset, value); +Region.loadNat64(region, offset) // => 123 +``` + +## Value `storeNat64` +``` motoko no-repl +let storeNat64 : (region : Region, offset : Nat64, value : Nat64) -> () +``` + +Within `region`, store a `Nat64` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeNat64(region, offset, value); +Region.loadNat64(region, offset) // => 123 +``` + +## Value `loadInt8` +``` motoko no-repl +let loadInt8 : (region : Region, offset : Nat64) -> Int8 +``` + +Within `region`, load a `Int8` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt8(region, offset, value); +Region.loadInt8(region, offset) // => 123 +``` + +## Value `storeInt8` +``` motoko no-repl +let storeInt8 : (region : Region, offset : Nat64, value : Int8) -> () +``` + +Within `region`, store a `Int8` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt8(region, offset, value); +Region.loadInt8(region, offset) // => 123 +``` + +## Value `loadInt16` +``` motoko no-repl +let loadInt16 : (region : Region, offset : Nat64) -> Int16 +``` + +Within `region`, load a `Int16` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt16(region, offset, value); +Region.loadInt16(region, offset) // => 123 +``` + +## Value `storeInt16` +``` motoko no-repl +let storeInt16 : (region : Region, offset : Nat64, value : Int16) -> () +``` + +Within `region`, store a `Int16` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt16(region, offset, value); +Region.loadInt16(region, offset) // => 123 +``` + +## Value `loadInt32` +``` motoko no-repl +let loadInt32 : (region : Region, offset : Nat64) -> Int32 +``` + +Within `region`, load a `Int32` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt32(region, offset, value); +Region.loadInt32(region, offset) // => 123 +``` + +## Value `storeInt32` +``` motoko no-repl +let storeInt32 : (region : Region, offset : Nat64, value : Int32) -> () +``` + +Within `region`, store a `Int32` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt32(region, offset, value); +Region.loadInt32(region, offset) // => 123 +``` + +## Value `loadInt64` +``` motoko no-repl +let loadInt64 : (region : Region, offset : Nat64) -> Int64 +``` + +Within `region`, load a `Int64` value from `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt64(region, offset, value); +Region.loadInt64(region, offset) // => 123 +``` + +## Value `storeInt64` +``` motoko no-repl +let storeInt64 : (region : Region, offset : Nat64, value : Int64) -> () +``` + +Within `region`, store a `Int64` value at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 123; +Region.storeInt64(region, offset, value); +Region.loadInt64(region, offset) // => 123 +``` + +## Value `loadFloat` +``` motoko no-repl +let loadFloat : (region : Region, offset : Nat64) -> Float +``` + +Within `region`, loads a `Float` value from the given `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 1.25; +Region.storeFloat(region, offset, value); +Region.loadFloat(region, offset) // => 1.25 +``` + +## Value `storeFloat` +``` motoko no-repl +let storeFloat : (region : Region, offset : Nat64, value : Float) -> () +``` + +Within `region`, store float `value` at the given `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +let region = Region.new(); +let offset = 0; +let value = 1.25; +Region.storeFloat(region, offset, value); +Region.loadFloat(region, offset) // => 1.25 +``` + +## Value `loadBlob` +``` motoko no-repl +let loadBlob : (region : Region, offset : Nat64, size : Nat) -> Blob +``` + +Within `region,` load `size` bytes starting from `offset` as a `Blob`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +import Blob "mo:base/Blob"; + +let region = Region.new(); +let offset = 0; +let value = Blob.fromArray([1, 2, 3]); +let size = value.size(); +Region.storeBlob(region, offset, value); +Blob.toArray(Region.loadBlob(region, offset, size)) // => [1, 2, 3] +``` + +## Value `storeBlob` +``` motoko no-repl +let storeBlob : (region : Region, offset : Nat64, value : Blob) -> () +``` + +Within `region, write `blob.size()` bytes of `blob` beginning at `offset`. +Traps on an out-of-bounds access. + +Example: +```motoko no-repl +import Blob "mo:base/Blob"; + +let region = Region.new(); +let offset = 0; +let value = Blob.fromArray([1, 2, 3]); +let size = value.size(); +Region.storeBlob(region, offset, value); +Blob.toArray(Region.loadBlob(region, offset, size)) // => [1, 2, 3] +``` diff --git a/doc/md/base/index.md b/doc/md/base/index.md index fe309f94892..4bfee579a7f 100644 --- a/doc/md/base/index.md +++ b/doc/md/base/index.md @@ -19,18 +19,18 @@ * [HashMap](HashMap.md) Class `HashMap` provides a hashmap from keys of type `K` to values of type `V`. * [Heap](Heap.md) Class `Heap` provides a priority queue of elements of type `X`. * [Int](Int.md) Signed integer numbers with infinite precision (also called big integers). -* [Int16](Int16.md) 16-bit signed integers with checked arithmetic. -* [Int32](Int32.md) 32-bit signed integers with checked arithmetic. -* [Int64](Int64.md) 64-bit signed integers with checked arithmetic. -* [Int8](Int8.md) 8-bit signed integers with checked arithmetic. +* [Int16](Int16.md) Provides utility functions on 16-bit signed integers. +* [Int32](Int32.md) Provides utility functions on 32-bit signed integers. +* [Int64](Int64.md) Provides utility functions on 64-bit signed integers. +* [Int8](Int8.md) Provides utility functions on 8-bit signed integers. * [Iter](Iter.md) Iterators * [IterType](IterType.md) The Iterator type * [List](List.md) Purely-functional, singly-linked lists. * [Nat](Nat.md) Natural numbers with infinite precision. -* [Nat16](Nat16.md) 16-bit unsigned integers with checked arithmetic -* [Nat32](Nat32.md) 32-bit unsigned integers with checked arithmetic -* [Nat64](Nat64.md) 64-bit unsigned integers with checked arithmetic -* [Nat8](Nat8.md) 8-bit unsigned integers with checked arithmetic +* [Nat16](Nat16.md) Provides utility functions on 16-bit unsigned integers. +* [Nat32](Nat32.md) Provides utility functions on 32-bit unsigned integers. +* [Nat64](Nat64.md) Provides utility functions on 64-bit unsigned integers. +* [Nat8](Nat8.md) Provides utility functions on 8-bit unsigned integers. * [None](None.md) The absent value * [Option](Option.md) Typesafe nulls * [Order](Order.md) Order @@ -38,6 +38,7 @@ * [Principal](Principal.md) Module for interacting with Principals (users and canisters). * [RBTree](RBTree.md) Key-value map implemented as a red-black tree (RBTree) with nodes storing key-value pairs. * [Random](Random.md) A module for obtaining randomness on the Internet Computer (IC). +* [Region](Region.md) Byte-level access to isolated, (virtual) stable memory _regions_. * [Result](Result.md) Error handling with the Result type. * [Stack](Stack.md) Class `Stack` provides a Minimal LIFO stack of elements of type `X`. * [Text](Text.md) Utility functions for `Text` values. diff --git a/doc/md/compiler-ref.md b/doc/md/compiler-ref.md index 27a9bcea383..c21ece1161d 100644 --- a/doc/md/compiler-ref.md +++ b/doc/md/compiler-ref.md @@ -49,6 +49,7 @@ You can use the following options with the `moc` command. | `--print-deps` | Prints the dependencies for a given source file. | | `-r` | Interprets programs. | | `--release` | Ignores debug expressions in the source. | +| `--stable-regions` | Force eager initialization of stable regions metadata (for testing purposes); consumes between 386KiB or 8MiB of additional physical stable memory, depending on current use of ExperimentalStableMemory. | | `--stable-types` | Compile binary and emit signature of stable types to `.most` file. | | `--stable-compatible
 `        | Test upgrade compatibility between stable-type signatures `
` and ``.                                                                       |
 | `--rts-stack-pages `                   | Set maximum number of pages available for runtime system stack (default 32).
diff --git a/doc/md/examples/StableMultiLog.mo b/doc/md/examples/StableMultiLog.mo
new file mode 100644
index 00000000000..f1e2f99c1d4
--- /dev/null
+++ b/doc/md/examples/StableMultiLog.mo
@@ -0,0 +1,67 @@
+import Nat32 "mo:base/Nat32";
+import Nat64 "mo:base/Nat64";
+import Text "mo:base/Text";
+import Region "mo:base/Region";
+
+actor StableLog {
+
+  // Index of saved log entry.
+  public type Index = Nat64;
+
+  // Internal representation uses two regions, working together.
+  stable var state = {
+    bytes = Region.new();
+    var bytes_count : Nat64 = 0;
+    elems = Region.new ();
+    var elems_count : Nat64 = 0;
+  };
+
+  // Grow a region to hold a certain number of total bytes.
+  func regionEnsureSizeBytes(r : Region, new_byte_count : Nat64) {
+    let pages = Region.size(r);
+    if (new_byte_count > pages << 16) {
+      let new_pages = pages - ((new_byte_count + ((1 << 16) - 1)) / (1 << 16));
+      assert Region.grow(r, new_pages) == pages
+    }
+  };
+
+  // Element = Position and size of a saved a Blob.
+  type Elem = {
+    pos : Nat64;
+    size : Nat64;
+  };
+
+  let elem_size = 16 : Nat64; /* two Nat64s, for pos and size. */
+
+  // Count of elements (Blobs) that have been logged.
+  public func size() : async Nat64 {
+      state.elems_count
+  };
+
+  // Constant-time random access to previously-logged Blob.
+  public func get(index : Index) : async Blob {
+    assert index < state.elems_count;
+    let pos = Region.loadNat64(state.elems, index * elem_size);
+    let size = Region.loadNat64(state.elems, index * elem_size + 8);
+    let elem = { pos ; size };
+    Region.loadBlob(state.bytes, elem.pos, Nat64.toNat(elem.size))
+  };
+
+  // Add Blob to the log, and return the index of it.
+  public func add(blob : Blob) : async Index {
+    let elem_i = state.elems_count;
+    state.elems_count += 1;
+
+    let elem_pos = state.bytes_count;
+    state.bytes_count += Nat64.fromNat(blob.size());
+
+    regionEnsureSizeBytes(state.bytes, state.bytes_count);
+    Region.storeBlob(state.bytes, elem_pos, blob);
+
+    regionEnsureSizeBytes(state.elems, state.elems_count * elem_size);
+    Region.storeNat64(state.elems, elem_i * elem_size + 0, elem_pos);
+    Region.storeNat64(state.elems, elem_i * elem_size + 8, Nat64.fromNat(blob.size()));
+    elem_i
+  }
+
+};
diff --git a/doc/md/language-manual.md b/doc/md/language-manual.md
index 47008e9a8c0..ad6508c1626 100644
--- a/doc/md/language-manual.md
+++ b/doc/md/language-manual.md
@@ -611,6 +611,7 @@ The category of a type determines the operators (unary, binary, relational and i
 | [`Blob`](./base/Blob.md)           | O        | binary blobs with iterators                                            |
 | [`Principal`](./base/Principal.md) | O        | principals                                                             |
 | [`Error`](./base/Error.md)         |          | (opaque) error values                                                  |
+| [`Region`](./base/Region.md)       |          | (opaque) stable memory region objects                                  |
 
 Although many of these types have linguistic support for literals and operators, each primitive type also has an eponymous base library providing related functions and values (see [Motoko Base Library](./base-intro.md)). For example, the [`Text`](./base/Text.md) library provides common functions on `Text` values.
 
@@ -746,6 +747,15 @@ Like other errors, call errors can be caught and handled using `try ... catch ..
 
 :::
 
+### Type `Region`
+
+The type `Region` represents opague stable memory regions.
+Region objects are dynamically allocated and independently growable.
+They represent isolated partitions of IC stable memory.
+The region type is stable (but not shared) and its objects, which are stateful, may be stored in stable variables and data structures.
+Objects of type `Region` are created and updated using the functions provided by base library `Region`.
+See [Stable Regions](stable-regions.md) and library [Region](./base/Region.md) for more information.
+
 ### Constructed types
 
 ` ?` is the application of a type identifier or path, either built-in (i.e. `Int`) or user defined, to zero or more type **arguments**. The type arguments must satisfy the bounds, if any, expected by the type constructor’s type parameters (see [Well-formed types](#well-formed-types)).
@@ -1057,6 +1067,8 @@ The types of actor fields declared with the `stable` qualifier must have stable
 
 The (current) value of such a field is preserved upon *upgrade*, whereas the values of other fields are reinitialized after an upgrade.
 
+Note: the primitive `Region` type is stable.
+
 ## Static and dynamic semantics
 
 Below, we give a detailed account of the semantics of Motoko programs.
diff --git a/doc/md/stable-regions.md b/doc/md/stable-regions.md
new file mode 100644
index 00000000000..5318ff215fd
--- /dev/null
+++ b/doc/md/stable-regions.md
@@ -0,0 +1,90 @@
+# Stable Regions
+
+The `Region` library provides low-level access to Internet Computer stable memory.
+
+
+
+Motoko stable variables, while convenient to use, require serialization and deserialization of all stable variables on upgrade (see [Stable variables and upgrade methods](upgrades.md)). During an upgrade, the current values of stable variables are first saved to IC stable memory, then restored from stable memory after the new code is installed. Unfortunately, this mechanism does not scale to canisters that maintain *large* amounts of data in stable variables: there may not be enough cycle budget to store then restore all stable variables within an upgrade, resulting in failed upgrades.
+Due to the current 32-bit address space of Motoko, stable variables cannot store more than 4GiB of data.
+
+Additionally, some stable variables use a representation that is not itself `stable`, requiring a non-trivial pre-upgrade routine to pre-process the data into a `stable` form.  These pre-upgrade steps are critical, and if they trap for any reason, the Motoko canister is forever stuck in a useless, inoperable state.
+
+To avoid these upgrade hazards, actors can elect to use a lower-level `Region` library for stable memory. The library allows the programmer to incrementally allocate pages of (64-bit) IC stable memory and use those pages to incrementally read and write data in a user-defined binary format.
+
+Several pages may be allocated at once, with each page containing 64KiB. Allocation may fail due to resource limits imposed by the Internet Computer. Pages are zero-initialized.
+
+While the user allocates at the granularity of 64KiB pages, the implementation will allocate at the coarser granularity of a block (currently 128) of physical IC stable memory pages.
+
+
+The Motoko runtime system ensures there is no interference between the abstraction presented by the `Region` library and an actor’s stable variables, even though the two abstractions ultimately use the same underlying (concrete) stable memory facilities available to all IC canisters. This runtime support means that is safe for a Motoko program to exploit both stable variables and `Region`, within the same application.
+
+Further, distinct `Region`s use distinct pages of stable memory, ensuring that two distinct `Region`s can not interfere with each other's data representations during normal operation, or during an upgrade.
+
+## The Library
+
+Support for stable memory is provided by the [Region](./base/Region.md) library in package `base`.
+
+The interface to the `Region` library consists of functions for querying and growing the currently allocated set of stable memory pages, plus matching pairs of `load`, `store` operations for most of Motoko’s fixed-size scalar types.
+
+More general `loadBlob` and `storeBlob` operations are also available for reading/writing binary blobs and other types that can be encoded as `Blob`s (e.g. `Text` values) of arbitrary sizes, using Motoko supplied or user-provided encoders and decoders.
+
+``` motoko no-repl
+module {
+
+  // A stateful handle to an isolated region of IC stable memory.
+  //  `Region` is a stable type and regions can be stored in stable variables.
+  type Region = Prim.Types.Region;
+
+  // Allocate a new, isolated `Region` of size 0.
+  new : () -> Region;
+
+  // Current size of the region `r` in pages.
+  // Each page is 64KiB (65536 bytes).
+  // Initially `0`.
+  size : (r : Region) -> (pages : Nat64);
+
+  // Grow current `size` of region `r` by `pagecount` pages.
+  // Each page is 64KiB (65536 bytes).
+  // Returns previous `size` when able to grow.
+  // Returns `0xFFFF_FFFF_FFFF_FFFF` if remaining pages of physical stable memory insufficient.
+  grow : (r : Region, new_pages : Nat64) -> (oldpages : Nat64);
+
+  // read ("load") a byte from a region, by offset.
+  loadNat8 : (r : Region, offset : Nat64) -> Nat8;
+
+  // write ("store") a byte into a region, by offset.
+  storeNat8 : (r : Region, offset : Nat64, value: Nat8) -> ();
+
+  // ... and similar for Nat16, Nat32, Nat64,
+  // Int8, Int16, Int32 and Int64 ...
+
+  loadFloat : (r : Region, offset : Nat64) -> Float;
+  storeFloat : (r : Region, offset : Nat64, value : Float) -> ();
+
+  // Load `size` bytes starting from `offset` in region `r` as a `Blob`.
+  // Traps on out-of-bounds access.
+  loadBlob : (r : Region, offset : Nat64, size : Nat) -> Blob;
+
+  // Write all bytes of `blob` to region `r` beginning at `offset`.
+  // Traps on out-of-bounds access.
+  storeBlob : (r : Region, offset : Nat64, value : Blob) -> ()
+
+}
+```
+
+## Example
+
+To demonstrate the `Region` library, we present a simple implementation of a logging actor that records text messages in a scalable, persistent log.
+
+The example illustrates the simultaneous use of stable variables and stable memory. It uses a single stable variable, `state`, to keep track of the two regions and their size in bytes, but stores the contents of the log directly in stable memory.
+
+``` motoko no-repl file=./examples/StableMultiLog.mo
+```
+
+The shared `add(blob)` function allocates enough stable memory to store the given blob, and writes the blob contents, its size, and its position into the pre-allocated regions.  One region is dedicated to storing the blobs of varying sizes, and the other is dedicated to storing their (fixed-sized) meta data.
+
+The shared `get(index)` query reads anywhere from the log without traversing any unrelated memory.
+
+Because `StableLog` allocates and maintains its (potentially large) log data directly in stable memory and uses just a small and fixed amount of storage for actual stable variables (here `state`), upgrading `StableLog` to a new implementation (perhaps to provide more functionality) should not consume too many cycles, regardless of the current size of the log.
diff --git a/nix/sources.json b/nix/sources.json
index fddd2981f48..728972120f0 100644
--- a/nix/sources.json
+++ b/nix/sources.json
@@ -76,10 +76,10 @@
         "homepage": null,
         "owner": "dfinity",
         "repo": "motoko-base",
-        "rev": "1f52c6b8fe64b15ae6ea68fc0fc2130b2112f6ce",
-        "sha256": "1vam0mbyia55a1ik8jylv1amvq8sm5ibshjcy8wkhzwmwqsjx35n",
+        "rev": "1b116fe923ee51c1520cce373fd34ea14d93408a",
+        "sha256": "14q9zji0b5padaj71wcycdmwy3cp3jma945353yyqybszwd5x98g",
         "type": "tarball",
-        "url": "https://github.com/dfinity/motoko-base/archive/1f52c6b8fe64b15ae6ea68fc0fc2130b2112f6ce.tar.gz",
+        "url": "https://github.com/dfinity/motoko-base/archive/1b116fe923ee51c1520cce373fd34ea14d93408a.tar.gz",
         "url_template": "https://github.com///archive/.tar.gz"
     },
     "motoko-matchers": {
diff --git a/rts/motoko-rts-tests/src/gc.rs b/rts/motoko-rts-tests/src/gc.rs
index faacd4301c8..4e3b5a26658 100644
--- a/rts/motoko-rts-tests/src/gc.rs
+++ b/rts/motoko-rts-tests/src/gc.rs
@@ -76,6 +76,7 @@ fn test_heaps() -> Vec {
             ],
             roots: vec![0, 2, 3],
             continuation_table: vec![0],
+            region0_ptr_loc: vec![0],
         },
         // Tests pointing to the same object in multiple fields of an object. Also has unreachable
         // objects.
@@ -83,12 +84,14 @@ fn test_heaps() -> Vec {
             heap: vec![(0, vec![]), (1, vec![]), (2, vec![])],
             roots: vec![1],
             continuation_table: vec![0, 0],
+            region0_ptr_loc: vec![0],
         },
         // Root points backwards in heap. Caught a bug in mark-compact collector.
         TestHeap {
             heap: vec![(0, vec![]), (1, vec![2]), (2, vec![1])],
             roots: vec![2],
             continuation_table: vec![],
+            region0_ptr_loc: vec![0],
         },
     ]
 }
@@ -106,6 +109,7 @@ struct TestHeap {
     heap: Vec<(ObjectIdx, Vec)>,
     roots: Vec,
     continuation_table: Vec,
+    region0_ptr_loc: Vec,
 }
 
 /// Test all GC implementations with the given heap
@@ -116,6 +120,7 @@ fn test_gcs(heap_descr: &TestHeap) {
             &heap_descr.heap,
             &heap_descr.roots,
             &heap_descr.continuation_table,
+            &heap_descr.region0_ptr_loc,
         );
     }
 
@@ -127,8 +132,9 @@ fn test_gc(
     refs: &[(ObjectIdx, Vec)],
     roots: &[ObjectIdx],
     continuation_table: &[ObjectIdx],
+    region0_ptr_loc: &[ObjectIdx],
 ) {
-    let mut heap = MotokoHeap::new(refs, roots, continuation_table, gc);
+    let mut heap = MotokoHeap::new(refs, roots, continuation_table, region0_ptr_loc, gc);
 
     initialize_gc(&mut heap);
 
@@ -446,6 +452,7 @@ impl GC {
     fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool {
         let heap_base = heap.heap_base_address();
         let static_roots = Value::from_ptr(heap.static_root_array_address());
+        let mut region_0 = Value::from_scalar(0);
         let continuation_table_ptr_address = heap.continuation_table_ptr_address() as *mut Value;
 
         let heap_1 = heap.clone();
@@ -463,6 +470,7 @@ impl GC {
                         move |hp| heap_2.set_heap_ptr_address(hp as usize),
                         static_roots,
                         continuation_table_ptr_address,
+                        &mut region_0,
                         // note_live_size
                         |_live_size| {},
                         // note_reclaimed
@@ -483,6 +491,7 @@ impl GC {
                         move |hp| heap_2.set_heap_ptr_address(hp as usize),
                         static_roots,
                         continuation_table_ptr_address,
+                        &mut region_0,
                         // note_live_size
                         |_live_size| {},
                         // note_reclaimed
@@ -515,6 +524,7 @@ impl GC {
                     let roots = motoko_rts::gc::generational::Roots {
                         static_roots,
                         continuation_table_ptr_loc: continuation_table_ptr_address,
+                        region0_ptr_loc: &mut region_0,
                     };
                     let gc_heap = motoko_rts::gc::generational::Heap {
                         mem: heap,
@@ -536,6 +546,7 @@ impl GC {
     fn run(&self, heap: &mut MotokoHeap, _round: usize) -> bool {
         let static_roots = Value::from_ptr(heap.static_root_array_address());
         let continuation_table_ptr_address = heap.continuation_table_ptr_address() as *mut Value;
+        let region0_ptr_location = heap.region0_ptr_location() as *mut Value;
 
         match self {
             GC::Incremental => unsafe {
@@ -545,6 +556,7 @@ impl GC {
                     let roots = motoko_rts::gc::incremental::roots::Roots {
                         static_roots,
                         continuation_table_location: continuation_table_ptr_address,
+                        region0_ptr_location,
                     };
                     IncrementalGC::instance(heap, incremental_gc_state())
                         .empty_call_stack_increment(roots);
diff --git a/rts/motoko-rts-tests/src/gc/heap.rs b/rts/motoko-rts-tests/src/gc/heap.rs
index b687d97045a..df13f663ca8 100644
--- a/rts/motoko-rts-tests/src/gc/heap.rs
+++ b/rts/motoko-rts-tests/src/gc/heap.rs
@@ -38,6 +38,7 @@ impl MotokoHeap {
         map: &[(ObjectIdx, Vec)],
         roots: &[ObjectIdx],
         continuation_table: &[ObjectIdx],
+        region0_ptr_loc: &[ObjectIdx],
         gc: GC,
     ) -> MotokoHeap {
         MotokoHeap {
@@ -105,6 +106,11 @@ impl MotokoHeap {
         self.inner.borrow().continuation_table_ptr_address()
     }
 
+    /// Get the address of the continuation table pointer
+    pub fn region0_ptr_location(&self) -> usize {
+        self.inner.borrow().region0_ptr_address()
+    }
+
     /// Get the heap as an array. Use `offset` values returned by the methods above to read.
     pub fn heap(&self) -> Ref> {
         Ref::map(self.inner.borrow(), |heap| &heap.heap)
@@ -146,6 +152,8 @@ struct MotokoHeapInner {
     /// Reminder: this location is in static heap and will have pointer to an array in dynamic
     /// heap.
     continuation_table_ptr_offset: usize,
+
+    region0_ptr_location_offset: usize,
 }
 
 impl MotokoHeapInner {
@@ -194,6 +202,10 @@ impl MotokoHeapInner {
         self.offset_to_address(self.continuation_table_ptr_offset)
     }
 
+    fn region0_ptr_address(&self) -> usize {
+        self.offset_to_address(self.region0_ptr_location_offset)
+    }
+
     fn new(
         map: &[(ObjectIdx, Vec)],
         roots: &[ObjectIdx],
@@ -216,7 +228,7 @@ impl MotokoHeapInner {
         let static_heap_size_bytes = (size_of::().as_usize()
             + roots.len()
             + (roots.len() * size_of::().as_usize())
-            + 1)
+            + 2)
             * WORD_SIZE;
 
         let dynamic_heap_size_without_continuation_table_bytes = {
@@ -255,7 +267,10 @@ impl MotokoHeapInner {
         );
 
         // Closure table pointer is the last word in static heap
-        let continuation_table_ptr_offset = static_heap_size_bytes - WORD_SIZE;
+        let continuation_table_ptr_offset = static_heap_size_bytes - WORD_SIZE * 2;
+
+        let region0_ptr_location_offset = static_heap_size_bytes - WORD_SIZE;
+
         create_static_heap(
             roots,
             &object_addrs,
@@ -271,6 +286,7 @@ impl MotokoHeapInner {
             heap_ptr_offset: total_heap_size_bytes + realign,
             static_root_array_offset: realign,
             continuation_table_ptr_offset: continuation_table_ptr_offset + realign,
+            region0_ptr_location_offset: region0_ptr_location_offset + realign,
         }
     }
 
diff --git a/rts/motoko-rts-tests/src/gc/incremental/roots.rs b/rts/motoko-rts-tests/src/gc/incremental/roots.rs
index ddcfd3e03bd..74efa5f507a 100644
--- a/rts/motoko-rts-tests/src/gc/incremental/roots.rs
+++ b/rts/motoko-rts-tests/src/gc/incremental/roots.rs
@@ -16,8 +16,15 @@ pub unsafe fn test() {
     let object_map: [(ObjectIdx, Vec); 10] = from_fn(|id| (id as u32, vec![]));
     let root_ids = [2, 4, 6, 8];
     let continuation_ids = [3, 5, 7];
+    let region0_ptr = [0];
 
-    let heap = MotokoHeap::new(&object_map, &root_ids, &continuation_ids, GC::Incremental);
+    let heap = MotokoHeap::new(
+        &object_map,
+        &root_ids,
+        &continuation_ids,
+        ®ion0_ptr,
+        GC::Incremental,
+    );
     check_visit_static_roots(&heap, &root_ids);
     check_visit_continuation_table(&heap, &continuation_ids);
 }
@@ -67,10 +74,12 @@ unsafe fn check_visit_continuation_table(heap: &MotokoHeap, continuation_ids: &[
 unsafe fn get_roots(heap: &MotokoHeap) -> Roots {
     let static_roots = Value::from_ptr(heap.static_root_array_address());
     let continuation_table_location = heap.continuation_table_ptr_address() as *mut Value;
+    let region0_ptr_location = heap.region0_ptr_location() as *mut Value;
     assert_ne!(continuation_table_location, null_mut());
     Roots {
         static_roots,
         continuation_table_location,
+        region0_ptr_location,
     }
 }
 
diff --git a/rts/motoko-rts-tests/src/gc/random.rs b/rts/motoko-rts-tests/src/gc/random.rs
index 7be7fc41c4a..26602541f5a 100644
--- a/rts/motoko-rts-tests/src/gc/random.rs
+++ b/rts/motoko-rts-tests/src/gc/random.rs
@@ -52,9 +52,21 @@ pub(super) fn generate(seed: u64, max_objects: u32) -> TestHeap {
         })
         .collect();
 
+    // Same as roots
+    let region0_ptr_loc: Vec = (0..n_objects)
+        .filter_map(|obj_idx| {
+            if rand_bool(&mut rng) {
+                Some(obj_idx)
+            } else {
+                None
+            }
+        })
+        .collect();
+
     TestHeap {
         heap,
         roots,
         continuation_table,
+        region0_ptr_loc,
     }
 }
diff --git a/rts/motoko-rts/src/gc/copying.rs b/rts/motoko-rts/src/gc/copying.rs
index f1b0b20c5f7..b3ef0fdcccb 100644
--- a/rts/motoko-rts/src/gc/copying.rs
+++ b/rts/motoko-rts/src/gc/copying.rs
@@ -36,6 +36,7 @@ unsafe fn copying_gc(mem: &mut M) {
         |hp| linear_memory::set_hp_unskewed(hp),
         ic::get_static_roots(),
         crate::continuation_table::continuation_table_loc(),
+        crate::region::region0_get_ptr_loc(),
         // note_live_size
         |live_size| ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, live_size),
         // note_reclaimed
@@ -58,6 +59,7 @@ pub unsafe fn copying_gc_internal<
     mut set_hp: SetHp,
     static_roots: Value,
     continuation_table_ptr_loc: *mut Value,
+    region0_ptr_loc: *mut Value,
     note_live_size: NoteLiveSize,
     note_reclaimed: NoteReclaimed,
 ) {
@@ -79,6 +81,16 @@ pub unsafe fn copying_gc_internal<
         );
     }
 
+    if (*region0_ptr_loc).is_ptr() {
+        // Region0 is not always a pointer during GC?
+        evac(
+            mem,
+            begin_from_space,
+            begin_to_space,
+            region0_ptr_loc as usize,
+        );
+    }
+
     // Scavenge to-space
     let mut p = begin_to_space;
     while p < get_hp() {
diff --git a/rts/motoko-rts/src/gc/generational.rs b/rts/motoko-rts/src/gc/generational.rs
index 1ea995573ea..531e2d887bd 100644
--- a/rts/motoko-rts/src/gc/generational.rs
+++ b/rts/motoko-rts/src/gc/generational.rs
@@ -48,6 +48,7 @@ unsafe fn generational_gc(mem: &mut M) {
     let old_limits = get_limits();
     let roots = Roots {
         static_roots: ic::get_static_roots(),
+        region0_ptr_loc: crate::region::region0_get_ptr_loc(),
         continuation_table_ptr_loc: crate::continuation_table::continuation_table_loc(),
     };
     let heap = Heap {
@@ -160,6 +161,7 @@ pub struct Heap<'a, M: Memory> {
 pub struct Roots {
     pub static_roots: Value,
     pub continuation_table_ptr_loc: *mut Value,
+    pub region0_ptr_loc: *mut Value,
     // For possible future additional roots, please extend the functionality in:
     // * `mark_root_set`
     // * `thread_initial_phase`
@@ -223,6 +225,11 @@ impl<'a, M: Memory> GenerationalGC<'a, M> {
             self.mark_object(continuation_table);
         }
 
+        let region0 = *self.heap.roots.region0_ptr_loc;
+        if region0.is_ptr() && region0.get_ptr() >= self.generation_base() {
+            self.mark_object(region0);
+        }
+
         if self.strategy == Strategy::Young {
             self.mark_additional_young_root_set();
         }
@@ -365,6 +372,11 @@ impl<'a, M: Memory> GenerationalGC<'a, M> {
             self.thread(self.heap.roots.continuation_table_ptr_loc);
         }
 
+        let region0 = *self.heap.roots.region0_ptr_loc;
+        if region0.is_ptr() && region0.get_ptr() >= self.generation_base() {
+            self.thread(self.heap.roots.region0_ptr_loc);
+        }
+
         // For the young generation GC run, the forward pointers from the old generation must be threaded too.
         if self.strategy == Strategy::Young {
             self.thread_old_generation_pointers();
@@ -505,7 +517,7 @@ impl<'a, M: Memory> GenerationalGC<'a, M> {
             (*(header as *mut Value)) = Value::from_ptr(new_location);
             header = tmp;
         }
-        assert!(header >= TAG_OBJECT && header <= TAG_NULL);
+        debug_assert!(header >= TAG_OBJECT && header <= TAG_NULL);
         (*object).tag = header;
     }
 
diff --git a/rts/motoko-rts/src/gc/incremental/roots.rs b/rts/motoko-rts/src/gc/incremental/roots.rs
index b33be52ef3c..495ad6b410c 100644
--- a/rts/motoko-rts/src/gc/incremental/roots.rs
+++ b/rts/motoko-rts/src/gc/incremental/roots.rs
@@ -5,6 +5,7 @@ use crate::{types::Value, visitor::pointer_to_dynamic_heap};
 pub struct Roots {
     pub static_roots: Value,
     pub continuation_table_location: *mut Value,
+    pub region0_ptr_location: *mut Value,
     // If new roots are added in future, extend `visit_roots()`.
 }
 
@@ -14,6 +15,7 @@ pub unsafe fn root_set() -> Roots {
     Roots {
         static_roots: ic::get_static_roots(),
         continuation_table_location: crate::continuation_table::continuation_table_loc(),
+        region0_ptr_location: crate::region::region0_get_ptr_loc(),
     }
 }
 
@@ -30,6 +32,7 @@ pub unsafe fn visit_roots(
         context,
         &visit_field,
     );
+    visit_region0_ptr(roots.region0_ptr_location, heap_base, context, &visit_field);
 }
 
 unsafe fn visit_static_roots(
@@ -59,3 +62,14 @@ unsafe fn visit_continuation_table(
         visit_field(context, continuation_table_location);
     }
 }
+
+unsafe fn visit_region0_ptr(
+    region0_ptr_location: *mut Value,
+    heap_base: usize,
+    context: &mut C,
+    visit_field: &V,
+) {
+    if pointer_to_dynamic_heap(region0_ptr_location, heap_base) {
+        visit_field(context, region0_ptr_location);
+    }
+}
diff --git a/rts/motoko-rts/src/gc/mark_compact.rs b/rts/motoko-rts/src/gc/mark_compact.rs
index c19ce3e8d57..9d43f8bb075 100644
--- a/rts/motoko-rts/src/gc/mark_compact.rs
+++ b/rts/motoko-rts/src/gc/mark_compact.rs
@@ -51,6 +51,7 @@ unsafe fn compacting_gc(mem: &mut M) {
         |hp| linear_memory::set_hp_unskewed(hp),
         ic::get_static_roots(),
         crate::continuation_table::continuation_table_loc(),
+        crate::region::region0_get_ptr_loc(),
         // note_live_size
         |live_size| ic::MAX_LIVE = ::core::cmp::max(ic::MAX_LIVE, live_size),
         // note_reclaimed
@@ -73,6 +74,7 @@ pub unsafe fn compacting_gc_internal<
     set_hp: SetHp,
     static_roots: Value,
     continuation_table_ptr_loc: *mut Value,
+    region0_ptr_loc: *mut Value,
     note_live_size: NoteLiveSize,
     note_reclaimed: NoteReclaimed,
 ) {
@@ -87,6 +89,7 @@ pub unsafe fn compacting_gc_internal<
         old_hp,
         static_roots,
         continuation_table_ptr_loc,
+        region0_ptr_loc,
     );
 
     let reclaimed = old_hp - (get_hp() as u32);
@@ -103,6 +106,7 @@ unsafe fn mark_compact(
     heap_end: u32,
     static_roots: Value,
     continuation_table_ptr_loc: *mut Value,
+    region0_ptr_loc: *mut Value,
 ) {
     let mem_size = Bytes(heap_end - heap_base as u32);
 
@@ -118,6 +122,11 @@ unsafe fn mark_compact(
         thread(continuation_table_ptr_loc);
     }
 
+    if (*region0_ptr_loc).is_ptr() {
+        mark_object(mem, *region0_ptr_loc);
+        thread(region0_ptr_loc);
+    }
+
     mark_stack(mem, heap_base);
 
     update_refs(set_hp, heap_base);
diff --git a/rts/motoko-rts/src/idl.rs b/rts/motoko-rts/src/idl.rs
index 233514013a2..fa291835b7f 100644
--- a/rts/motoko-rts/src/idl.rs
+++ b/rts/motoko-rts/src/idl.rs
@@ -41,6 +41,7 @@ const IDL_CON_func: i32 = -22;
 const IDL_CON_service: i32 = -23;
 
 const IDL_REF_principal: i32 = -24;
+const IDL_EXT_region: i32 = -128;
 
 const IDL_CON_alias: i32 = 1;
 
@@ -51,7 +52,7 @@ pub unsafe fn leb128_decode_ptr(buf: *mut Buf) -> (u32, *mut u8) {
 }
 
 unsafe fn is_primitive_type(ty: i32) -> bool {
-    ty < 0 && (ty >= IDL_PRIM_lowest || ty == IDL_REF_principal)
+    ty < 0 && (ty >= IDL_PRIM_lowest || ty == IDL_REF_principal || ty == IDL_EXT_region)
 }
 
 // TBR; based on Text.text_compare
@@ -358,6 +359,10 @@ unsafe extern "C" fn skip_any(buf: *mut Buf, typtbl: *mut *mut u8, t: i32, depth
                     skip_blob(buf);
                 }
             }
+            IDL_EXT_region => {
+                buf.advance(12); // id (u64) & page_count (u32)
+                skip_blob(buf); // vec_pages
+            }
             _ => {
                 idl_trap_with("skip_any: unknown prim");
             }
diff --git a/rts/motoko-rts/src/lib.rs b/rts/motoko-rts/src/lib.rs
index 51b68028d19..ca3958ee39f 100644
--- a/rts/motoko-rts/src/lib.rs
+++ b/rts/motoko-rts/src/lib.rs
@@ -32,6 +32,9 @@ pub mod leb128;
 mod mem_utils;
 pub mod memory;
 pub mod principal_id;
+pub mod region;
+//#[cfg(feature = "ic")]
+mod stable_mem;
 mod static_checks;
 pub mod stream;
 pub mod text;
diff --git a/rts/motoko-rts/src/mem_utils.rs b/rts/motoko-rts/src/mem_utils.rs
index 9cdab95bca7..b71d1b8d253 100644
--- a/rts/motoko-rts/src/mem_utils.rs
+++ b/rts/motoko-rts/src/mem_utils.rs
@@ -11,3 +11,7 @@ pub(crate) unsafe fn memcpy_bytes(to: usize, from: usize, n: Bytes) {
 pub(crate) unsafe fn memzero(to: usize, n: Words) {
     libc::memset(to as *mut _, 0, n.to_bytes().as_usize());
 }
+
+pub(crate) unsafe fn memzero_bytes(to: usize, n: Bytes) {
+    libc::memset(to as *mut _, 0, n.as_usize());
+}
diff --git a/rts/motoko-rts/src/region.rs b/rts/motoko-rts/src/region.rs
new file mode 100644
index 00000000000..c79d0d69547
--- /dev/null
+++ b/rts/motoko-rts/src/region.rs
@@ -0,0 +1,1118 @@
+use crate::barriers::{allocation_barrier, init_with_barrier, write_with_barrier};
+use crate::memory::{alloc_blob, Memory};
+use crate::trap_with_prefix;
+use crate::types::{size_of, Blob, Bytes, Region, Value, TAG_REGION};
+
+// Versions
+// Should agree with constants StableMem.version_no_stable_memory etc. in compile.ml
+const VERSION_NO_STABLE_MEMORY: u32 = 0; // never manifest in serialized form
+const VERSION_SOME_STABLE_MEMORY: u32 = 1;
+const VERSION_REGIONS: u32 = 2;
+
+const _: () = assert!(meta_data::size::PAGES_IN_BLOCK <= u8::MAX as u32);
+const _: () = assert!(meta_data::max::BLOCKS <= u16::MAX);
+const _: () = assert!(meta_data::max::REGIONS <= u64::MAX - 1);
+
+use motoko_rts_macros::ic_mem_fn;
+
+unsafe fn region_trap_with(msg: &str) -> ! {
+    trap_with_prefix("Region error: ", msg)
+}
+
+unsafe fn stable_memory_trap_with(msg: &str) -> ! {
+    trap_with_prefix("StableMemory ", msg)
+}
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct BlockId(pub u16);
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct RegionId(pub u64);
+
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct RegionSizeInPages(pub u64);
+
+// Note: Use this type only in local variables, as it contains raw pointers
+// that are not handled by the GCs.
+#[derive(Clone)]
+pub struct AccessVector(pub *mut Blob);
+
+// Note: Use this type only in local variables, as it contains raw pointers
+// that are not handled by the GCs.
+#[derive(Clone)]
+pub struct RegionObject(pub *mut Region);
+
+const NIL_REGION_ID: u64 = 0;
+
+const LAST_RESERVED_REGION_ID: u64 = 15;
+
+// Mirrored field from stable memory, for handling upgrade logic.
+pub(crate) static mut REGION_TOTAL_ALLOCATED_BLOCKS: u32 = 0;
+
+// Base offset for blocks
+pub(crate) static mut BLOCK_BASE: u64 = 0;
+
+// Scalar sentinel value recognized in the GC as "no root", i.e. (!`is_ptr()`).
+// Same design like `continuation_table::TABLE`.
+pub(crate) const NO_REGION: Value = Value::from_scalar(0);
+
+// Region 0 -- classic API for stable memory, as a dedicated region.
+pub(crate) static mut REGION_0: Value = NO_REGION;
+
+// This impl encapsulates encoding of optional region IDs within a u64.
+// Used by block-region table to encode the (optional) region ID of a block.
+impl RegionId {
+    pub fn id_is_nil(id: u64) -> bool {
+        id == NIL_REGION_ID
+    }
+    pub fn from_id(id: u64) -> Self {
+        RegionId(id)
+    }
+    pub fn from_u64(id: u64) -> Option {
+        if Self::id_is_nil(id) {
+            None
+        } else {
+            Some(RegionId(id - 1))
+        }
+    }
+    pub fn into_u64(opreg: Option) -> u64 {
+        match opreg {
+            None => 0,
+            Some(id) => {
+                debug_assert!(id.0 <= meta_data::max::REGIONS);
+                id.0 + 1
+            }
+        }
+    }
+}
+
+// This impl encapsulates encoding of optional region (sizes) within a u64.
+// Used by region table to encode sizes of allocated regions (the size of which are Some(s)),
+// and which regions are available to allocate (the size of which are None).
+impl RegionSizeInPages {
+    pub fn from_size_in_pages(s: u64) -> Self {
+        RegionSizeInPages(s)
+    }
+    pub fn u64_is_nil(u: u64) -> bool {
+        u == 0
+    }
+    pub fn from_u64(u: u64) -> Option {
+        if Self::u64_is_nil(u) {
+            None
+        } else {
+            Some(RegionSizeInPages(u - 1))
+        }
+    }
+    pub fn into_u64(opreg: Option) -> u64 {
+        match opreg {
+            None => 0,
+            Some(s) => {
+                debug_assert!(
+                    s.0 <= meta_data::max::BLOCKS as u64 * meta_data::size::PAGES_IN_BLOCK as u64
+                );
+                s.0 + 1
+            }
+        }
+    }
+}
+
+impl AccessVector {
+    pub unsafe fn from_value(v: &Value) -> Self {
+        AccessVector(v.as_blob_mut())
+    }
+
+    pub unsafe fn set_ith_block_id(&self, i: u32, block_id: &BlockId) {
+        debug_assert!(i * 2 + 1 < self.0.len().as_u32());
+        self.0.set_u16(i, block_id.0)
+    }
+
+    pub unsafe fn get_ith_block_id(&self, i: u32) -> BlockId {
+        debug_assert!(i * 2 + 1 < self.0.len().as_u32());
+        BlockId(self.0.get_u16(i))
+    }
+}
+
+impl RegionObject {
+    pub unsafe fn from_value(v: &Value) -> Self {
+        RegionObject(v.as_region())
+    }
+
+    pub unsafe fn id(&self) -> RegionId {
+        RegionId(self.0.read_id64())
+    }
+
+    pub unsafe fn trap_with(&self, msg: &str) -> ! {
+        if (*self).id() == RegionId(0) {
+            stable_memory_trap_with(msg)
+        } else {
+            region_trap_with(msg)
+        };
+    }
+
+    // Check both offset and [offset,.., offset + len) within bounds *)
+    // c.f. StableMem.guard_range in compile.ml *)
+    // TODO: simplify and specialize on len
+    pub unsafe fn check_relative_range(&self, offset: u64, len: u64) {
+        if len <= 1 {
+            if offset >= ((*self.0).page_count as u64) * meta_data::size::PAGE_IN_BYTES {
+                self.trap_with("offset out of bounds");
+            }
+        } else {
+            if u64::MAX - len < offset {
+                self.trap_with("range overflow")
+            };
+            if offset + len > (((*self.0).page_count as u64) * meta_data::size::PAGE_IN_BYTES) {
+                self.trap_with("range out of bounds");
+            };
+        }
+    }
+
+    // Computes absolute offset, BlockId, and remaining length (of the
+    // given block) for a relative offset.
+    pub unsafe fn relative_into_absolute_info(&self, offset: u64) -> (u64, BlockId, u64) {
+        let av = AccessVector::from_value(&(*self.0).vec_pages);
+
+        // Which block (rank relative to this region)?
+        let block_rank = offset / meta_data::size::BLOCK_IN_BYTES;
+
+        // Where in that block?
+        let intra_block_index = offset % meta_data::size::BLOCK_IN_BYTES;
+
+        // Where is that block located in stable memory (global rank)?
+        let block_id = av.get_ith_block_id(block_rank as u32);
+
+        // address of the byte to load from stable memory:
+        let offset =
+            BLOCK_BASE + block_id.0 as u64 * meta_data::size::BLOCK_IN_BYTES + intra_block_index;
+        (
+            offset,
+            block_id,
+            meta_data::size::BLOCK_IN_BYTES - intra_block_index,
+        )
+    }
+
+    // compute the absolute offset, begin block, remaining length of
+    // begin block, and end block for a relative offset and length.
+    //
+    // NB: BlockIds can be used to do case analysis (same or diff
+    // block?) when planning successive reads/writes.
+    pub unsafe fn relative_into_absolute_span(
+        &self,
+        offset: u64,
+        len: u64,
+    ) -> (u64, BlockId, u64, BlockId) {
+        let (off, b1, b1_len) = self.relative_into_absolute_info(offset);
+        if len == 0 {
+            return (off, b1.clone(), b1_len, b1);
+        };
+        debug_assert!(u64::MAX - offset >= len - 1);
+        let final_offset = offset + len - 1;
+        let (_, b2, _) = self.relative_into_absolute_info(final_offset);
+        (off, b1, b1_len, b2)
+    }
+}
+
+// Mutable meta data stored in stable memory header (See motoko/design/StableRegions.md)
+mod meta_data {
+
+    pub const fn bytes_of() -> u64 {
+        core::mem::size_of::() as u64
+    }
+
+    pub mod version {
+        pub const MAGIC: &[u8; 8] = b"MOREGION";
+        pub const VERSION: u32 = 2;
+    }
+
+    /// Maximum number of entities.
+    pub mod max {
+        pub const BLOCKS: u16 = 32 * 1024;
+        pub const REGIONS: u64 = u64::MAX - 1;
+    }
+
+    /// Sizes of table entries, and tables.
+    pub mod size {
+        use super::bytes_of;
+
+        pub const BLOCK_REGION_TABLE_ENTRY: u16 =
+            (bytes_of::() + bytes_of::() + bytes_of::()) as u16;
+
+        pub const BLOCK_REGION_TABLE: u64 =
+            super::max::BLOCKS as u64 * BLOCK_REGION_TABLE_ENTRY as u64;
+
+        pub const PAGES_IN_BLOCK: u32 = 128;
+        pub const PAGE_IN_BYTES: u64 = 1 << 16;
+        pub const BLOCK_IN_BYTES: u64 = PAGE_IN_BYTES * (PAGES_IN_BLOCK as u64);
+
+        // Static memory footprint, ignoring any dynamically-allocated pages.
+        pub unsafe fn static_mem_in_pages(block_base: u64) -> u64 {
+            debug_assert!(block_base % PAGE_IN_BYTES as u64 == 0);
+            debug_assert!(block_base != 0);
+            block_base / PAGE_IN_BYTES as u64 /* meta data plus slack for future use */
+        }
+
+        pub unsafe fn total_required_pages(block_base: u64, total_allocated_blocks: u64) -> u64 {
+            static_mem_in_pages(block_base) + (total_allocated_blocks * (PAGES_IN_BLOCK as u64))
+        }
+    }
+
+    /// Offsets into stable memory for statically-sized fields and tables.
+    pub mod offset {
+
+        use super::bytes_of;
+
+        pub const MAGIC: u64 = 0;
+
+        pub const VERSION: u64 = MAGIC + bytes_of::();
+
+        pub const BLOCK_PAGES: u64 = VERSION + bytes_of::();
+
+        pub const BLOCK_BASE: u64 = BLOCK_PAGES + bytes_of::();
+
+        pub const TOTAL_ALLOCATED_BLOCKS: u64 = BLOCK_BASE + bytes_of::();
+
+        pub const TOTAL_ALLOCATED_REGIONS: u64 = TOTAL_ALLOCATED_BLOCKS + bytes_of::();
+
+        pub const BLOCK_REGION_TABLE: u64 = TOTAL_ALLOCATED_REGIONS + bytes_of::();
+
+        pub const FREE: u64 = BLOCK_REGION_TABLE + super::size::BLOCK_REGION_TABLE;
+
+        pub const BASE_LOW: u64 = 16 * super::size::PAGE_IN_BYTES;
+
+        pub const BASE_HIGH: u64 = super::size::BLOCK_IN_BYTES;
+    }
+
+    pub mod total_allocated_blocks {
+        use super::offset;
+        use crate::stable_mem::{read_u32, write_u32};
+
+        use crate::region::REGION_TOTAL_ALLOCATED_BLOCKS;
+
+        pub fn get() -> u32 {
+            read_u32(offset::TOTAL_ALLOCATED_BLOCKS)
+        }
+        pub fn set(n: u32) {
+            // Here we keep these copies of the total in sync.
+            //
+            // NB. The non-stable one is used when the stable one is
+            // unavailable (temp relocated by stable variable
+            // serialization/deserialization).
+            unsafe {
+                REGION_TOTAL_ALLOCATED_BLOCKS = n;
+            };
+            write_u32(offset::TOTAL_ALLOCATED_BLOCKS, n)
+        }
+    }
+
+    pub mod total_allocated_regions {
+        use super::offset;
+        use crate::stable_mem::{read_u64, write_u64};
+
+        pub fn get() -> u64 {
+            read_u64(offset::TOTAL_ALLOCATED_REGIONS)
+        }
+        pub fn set(n: u64) {
+            write_u64(offset::TOTAL_ALLOCATED_REGIONS, n)
+        }
+    }
+
+    pub mod block_region_table {
+        // invariant: all blocks whose IDs are below the total_allocated_blocks are valid.
+
+        use super::{bytes_of, offset, size};
+        use crate::region::{BlockId, RegionId};
+        use crate::stable_mem::{read_u16, read_u64, read_u8, write_u16, write_u64, write_u8};
+
+        // Compute an offset in stable memory for a particular block ID (based zero).
+        fn index(b: &BlockId) -> u64 {
+            offset::BLOCK_REGION_TABLE + (b.0 as u64 * size::BLOCK_REGION_TABLE_ENTRY as u64)
+        }
+
+        /// Some(r, j, c) means that the block is in use by region r, at slot j with c allocated pages.
+        /// None means that the block is available for (re-)allocation.
+        pub fn get(b: BlockId) -> Option<(RegionId, u16, u8)> {
+            let region_offset = index(&b);
+            let rank_offset = region_offset + bytes_of::();
+            let page_count_offset = rank_offset + bytes_of::();
+            let region = read_u64(region_offset);
+            let rid = RegionId::from_u64(region);
+            rid.map(|r| (r, read_u16(rank_offset), read_u8(page_count_offset)))
+        }
+
+        /// Some(r, j, c) means that the block is in use by region r, at slot j with c allocated pages.
+        /// None means that the block is available for (re-)allocation.
+        pub fn set(b: BlockId, r: Option<(RegionId, u16, u8)>) {
+            let region_offset = index(&b);
+            let rank_offset = region_offset + bytes_of::();
+            let page_count_offset = rank_offset + bytes_of::();
+            let (region_id, rank, allocated_pages) = match r {
+                None => (None, 0, 0),
+                Some((r, j, c)) => (Some(r), j, c),
+            };
+            write_u64(region_offset, RegionId::into_u64(region_id));
+            write_u16(rank_offset, rank);
+            write_u8(page_count_offset, allocated_pages);
+        }
+    }
+}
+
+unsafe fn write_magic() {
+    use crate::stable_mem::{size, write, write_u16, write_u32, write_u64};
+    assert!(size() > 0);
+    assert!(BLOCK_BASE >= meta_data::offset::FREE);
+    write(meta_data::offset::MAGIC, meta_data::version::MAGIC);
+    write_u32(meta_data::offset::VERSION, meta_data::version::VERSION);
+    write_u16(
+        meta_data::offset::BLOCK_PAGES,
+        meta_data::size::PAGES_IN_BLOCK as u16,
+    );
+    write_u64(meta_data::offset::BLOCK_BASE, BLOCK_BASE);
+}
+
+#[ic_mem_fn]
+unsafe fn alloc_region(
+    mem: &mut M,
+    id: u64,
+    page_count: u32,
+    vec_pages: Value,
+) -> Value {
+    let r_ptr = mem.alloc_words(size_of::());
+    // NB. cannot use as_region() here as we didn't write the header yet
+    let region = r_ptr.get_ptr() as *mut Region;
+    (*region).header.tag = TAG_REGION;
+    (*region).header.init_forward(r_ptr);
+    debug_assert!(id <= meta_data::max::REGIONS);
+    region.write_id64(id);
+    debug_assert!(
+        page_count
+            <= (vec_pages.as_blob().len().as_u32() / meta_data::bytes_of::() as u32)
+                * meta_data::size::PAGES_IN_BLOCK
+    );
+    (*region).page_count = page_count;
+    init_with_barrier(mem, &mut (*region).vec_pages, vec_pages);
+
+    allocation_barrier(r_ptr);
+    r_ptr
+}
+
+#[ic_mem_fn]
+unsafe fn init_region(
+    mem: &mut M,
+    r: Value,
+    id: u64,
+    page_count: u32,
+    vec_pages: Value,
+) {
+    let r = r.as_region();
+    debug_assert!(id <= meta_data::max::REGIONS);
+    r.write_id64(id);
+    debug_assert!(
+        page_count
+            <= (vec_pages.as_blob().len().as_u32() / meta_data::bytes_of::() as u32)
+                * meta_data::size::PAGES_IN_BLOCK
+    );
+    (*r).page_count = page_count;
+    write_with_barrier(mem, &mut (*r).vec_pages, vec_pages);
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_id(_mem: &mut M, r: Value) -> u64 {
+    let r = r.as_untagged_region();
+    r.read_id64()
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_page_count(_mem: &mut M, r: Value) -> u32 {
+    let r = r.as_untagged_region();
+    (*r).page_count
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_vec_pages(_mem: &mut M, r: Value) -> Value {
+    let r = r.as_untagged_region();
+    (*r).vec_pages
+}
+
+// Helper for commmon logic that reserves low-valued RegionIds in a certain span for future use.
+// When first is some, we are actually reserving.  When first is none, we are checking that the reservation has occured.
+unsafe fn region_reserve_id_span(
+    _mem: &mut M,
+    first_opt: Option,
+    last: RegionId,
+) {
+    let next_id = meta_data::total_allocated_regions::get();
+    match first_opt {
+        Some(first) => {
+            assert_eq!(first.0 as u64, next_id);
+            assert!(first.0 <= last.0);
+            meta_data::total_allocated_regions::set((last.0 + 1) as u64);
+        }
+        None => {
+            assert!((last.0 as u64) < next_id);
+        }
+    }
+}
+
+#[ic_mem_fn]
+pub unsafe fn region0_get(_mem: &mut M) -> Value {
+    debug_assert_ne!(REGION_0, NO_REGION);
+    REGION_0
+}
+
+// Expose Region0 object to GC algorithms as root
+#[allow(dead_code)]
+#[cfg(feature = "ic")]
+pub(crate) unsafe fn region0_get_ptr_loc() -> *mut Value {
+    &mut REGION_0
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_new(mem: &mut M) -> Value {
+    match crate::stable_mem::get_version() {
+        VERSION_NO_STABLE_MEMORY => {
+            assert_eq!(crate::stable_mem::size(), 0);
+            region_migration_from_no_stable_memory(mem);
+        }
+        VERSION_SOME_STABLE_MEMORY => {
+            region_migration_from_some_stable_memory(mem);
+        }
+        VERSION_REGIONS => {}
+        _ => {
+            assert!(false);
+        }
+    };
+
+    let next_id = meta_data::total_allocated_regions::get();
+
+    if next_id == meta_data::max::REGIONS {
+        region_trap_with("out of regions")
+    };
+
+    meta_data::total_allocated_regions::set(next_id + 1);
+
+    let vec_pages = alloc_blob(mem, Bytes(0));
+    allocation_barrier(vec_pages);
+    let r_ptr = alloc_region(mem, next_id, 0, vec_pages);
+
+    r_ptr
+}
+
+pub unsafe fn region_recover(mem: &mut M, rid: &RegionId) -> Value {
+    use meta_data::bytes_of;
+    use meta_data::size::PAGES_IN_BLOCK;
+
+    if rid.0 >= meta_data::total_allocated_regions::get() {
+        region_trap_with("cannot recover un-allocated region");
+    };
+
+    let tb = meta_data::total_allocated_blocks::get();
+
+    // determine page_count of this region
+    let mut page_count: u32 = 0;
+    {
+        for block_id in 0..tb as u16 {
+            match meta_data::block_region_table::get(BlockId(block_id)) {
+                None => {}
+                Some((rid_, _rank, block_page_count)) => {
+                    if &rid_ == rid {
+                        page_count += block_page_count as u32;
+                    }
+                }
+            }
+        }
+    };
+    debug_assert!(page_count < (u32::MAX - (PAGES_IN_BLOCK - 1)));
+
+    let block_count = (page_count + PAGES_IN_BLOCK - 1) / PAGES_IN_BLOCK;
+    let vec_pages = alloc_blob(mem, Bytes(block_count * bytes_of::() as u32));
+
+    let av = AccessVector(vec_pages.as_blob_mut());
+    let mut recovered_blocks = 0;
+    let mut block_id: u16 = 0;
+    while recovered_blocks < block_count && (block_id as u32) < tb {
+        match meta_data::block_region_table::get(BlockId(block_id)) {
+            None => {}
+            Some((rid_, rank, _page_count)) => {
+                if &rid_ == rid {
+                    av.set_ith_block_id(rank.into(), &BlockId(block_id));
+                    recovered_blocks += 1;
+                }
+            }
+        }
+        block_id += 1;
+    }
+    assert_eq!(recovered_blocks, block_count);
+    allocation_barrier(vec_pages);
+
+    let r_ptr = alloc_region(mem, rid.0, page_count as u32, vec_pages);
+    r_ptr
+}
+
+pub(crate) unsafe fn region_migration_from_no_stable_memory(mem: &mut M) {
+    use crate::stable_mem::{get_version, grow, size, write};
+    use meta_data::size::{PAGES_IN_BLOCK, PAGE_IN_BYTES};
+
+    assert!(get_version() == VERSION_NO_STABLE_MEMORY);
+    assert_eq!(size(), 0);
+
+    // pages required for meta_data (9/ 960KiB), much less than PAGES_IN_BLOCK (128/ 8MB) for a full block
+    let meta_data_pages =
+        (meta_data::offset::FREE - 1 + (PAGE_IN_BYTES as u64 - 1)) / PAGE_IN_BYTES as u64;
+
+    assert!(meta_data_pages <= PAGES_IN_BLOCK as u64);
+
+    // initially, only allocate meta_data_pages, not a full block, to reduce overhead for
+    // canisters that don't allocate regions
+    let prev_pages = grow(meta_data_pages);
+
+    if prev_pages == u64::MAX {
+        region_trap_with("migration failure (insufficient pages)");
+    };
+
+    debug_assert!(prev_pages == 0);
+
+    // Zero metadata region, for good measure.
+    let zero_page: [u8; PAGE_IN_BYTES as usize] = [0; PAGE_IN_BYTES as usize];
+    for i in 0..meta_data_pages {
+        write((i as u64) * (PAGE_IN_BYTES as u64), &zero_page);
+    }
+
+    BLOCK_BASE = meta_data::offset::BASE_LOW;
+
+    // Write magic header
+    write_magic();
+
+    crate::stable_mem::set_version(VERSION_REGIONS);
+
+    // Region 0 -- classic API for stable memory, as a dedicated region.
+    REGION_0 = region_new(mem);
+
+    assert_eq!(REGION_0.as_region().read_id64(), 0);
+
+    // Regions 1 through LAST_RESERVED_REGION_ID, reserved for future use by future Motoko compiler-RTS features.
+    region_reserve_id_span(mem, Some(RegionId(1)), RegionId(LAST_RESERVED_REGION_ID));
+}
+
+pub fn block_page_count(rank: u16, block_count: u32, page_count: u32) -> u8 {
+    use meta_data::size::PAGES_IN_BLOCK;
+    debug_assert!(block_count > 0);
+    debug_assert!((rank as u32) < block_count);
+    debug_assert_eq!(
+        block_count,
+        (page_count + (PAGES_IN_BLOCK as u32 - 1)) / meta_data::size::PAGES_IN_BLOCK
+    );
+    if (rank as u32) == block_count - 1 {
+        // final, full or partial block
+        let rem = page_count - ((block_count - 1) * PAGES_IN_BLOCK as u32);
+        debug_assert!(rem <= PAGES_IN_BLOCK);
+        rem as u8
+    } else {
+        // internal, full block
+        meta_data::size::PAGES_IN_BLOCK as u8
+    }
+}
+
+//
+// region manager migration/initialization, with pre-existing stable data.
+// Case: Version 1 into version 2.
+//
+pub(crate) unsafe fn region_migration_from_some_stable_memory(mem: &mut M) {
+    // Grow region0 to nearest block boundary, and add a block to fit a region manager meta data.
+    //
+    // Existing stable data becomes region 0, with first block relocated for region manager meta data.
+    //
+    // - allocate a block-sized blob on the heap (8MB).
+    // - copy the head block of data into that blob, using a stable memory read of a blob.
+    // - copy the head block of data from temp blob into new "final block" (logically still first) for region 0.
+    // - initialize the meta data for the region system in vacated initial block.
+
+    use crate::stable_mem::{grow, read, size, write};
+
+    let header_len = meta_data::size::BLOCK_IN_BYTES as u32;
+
+    let stable_mem_pages = size();
+
+    if stable_mem_pages > (meta_data::size::PAGES_IN_BLOCK * meta_data::max::BLOCKS as u32) as u64 {
+        region_trap_with("migration failure (too many pages for region0 )")
+    };
+
+    let region0_pages = stable_mem_pages as u32;
+
+    let region0_blocks =
+        (region0_pages + (meta_data::size::PAGES_IN_BLOCK - 1)) / (meta_data::size::PAGES_IN_BLOCK);
+    assert!(region0_blocks > 0);
+
+    let prev_pages = grow(
+        (meta_data::size::PAGES_IN_BLOCK + // <-- For new region manager
+	 /* Bump out region0 to nearest block boundary: */
+	 region0_blocks * meta_data::size::PAGES_IN_BLOCK
+            - region0_pages)
+            .into(),
+    );
+
+    if prev_pages == u64::MAX {
+        region_trap_with("migration failure (insufficient pages)")
+    };
+
+    debug_assert!(prev_pages == region0_pages.into());
+
+    // Temp for the head block, which we move to be physically last.
+    // NB: no allocation_barrier is required: header_val is temporary and can be reclaimed by the next GC increment/run.
+    // TODO: instead of allocating an 8MB blob, just stack-allocate a tmp page and zero page, and transfer/zero-init via the stack, using a loop.
+    let header_val = crate::memory::alloc_blob(mem, crate::types::Bytes(header_len));
+    let header_blob = header_val.as_blob_mut();
+    let header_bytes =
+        core::slice::from_raw_parts_mut(header_blob.payload_addr(), header_len as usize);
+
+    // Move it:
+    read(0, header_bytes); // Save first block as "head block".
+    write(
+        (region0_blocks * (meta_data::size::BLOCK_IN_BYTES as u32)).into(),
+        header_bytes,
+    );
+
+    crate::mem_utils::memzero_bytes((header_blob.payload_addr()) as usize, Bytes(header_len));
+
+    write(0, header_bytes); // Zero out first block, for region manager meta data.
+
+    BLOCK_BASE = meta_data::offset::BASE_HIGH;
+    // Write magic header
+    write_magic();
+
+    /* Initialize meta data as if there is only region 0. */
+    meta_data::total_allocated_blocks::set(region0_blocks.into());
+    meta_data::total_allocated_regions::set(1);
+
+    /*
+     * Initialize region0's table entries, for "recovery".
+     */
+
+    /* head block is physically last block, but logically first block in region. */
+    let head_block_id: u16 = (region0_blocks - 1) as u16; /* invariant: region0_blocks is non-zero. */
+    let head_block_region = RegionId(0);
+    let head_block_rank: u16 = 0;
+    let head_block_page_count: u8 =
+        block_page_count(head_block_rank, region0_blocks, region0_pages);
+    meta_data::block_region_table::set(
+        BlockId(head_block_id as u16),
+        Some((head_block_region, head_block_rank, head_block_page_count)),
+    );
+
+    /* Any other blocks that follow head block are numbered [0,...,head_block_id) */
+    /* They're logical placements are [1,...,) -- one more than their new identity, as a number. */
+    for i in 0..head_block_id {
+        let rank = i + 1;
+        let page_count: u8 = block_page_count(rank, region0_blocks, region0_pages);
+        meta_data::block_region_table::set(BlockId(i), Some((RegionId(0), rank, page_count)))
+    }
+
+    crate::stable_mem::set_version(VERSION_REGIONS);
+
+    /* "Recover" the region data into a heap object. */
+    REGION_0 = region_recover(mem, &RegionId(0));
+
+    // Ensure that regions 1 through LAST_RESERVED_REGION_ID are already reserved for
+    // future use by future Motoko compiler-RTS features.
+    region_reserve_id_span(mem, Some(RegionId(1)), RegionId(LAST_RESERVED_REGION_ID));
+}
+
+//
+// region manager migration/initialization, with pre-existing stable data.
+// Case: Version 2 into version 2 ("Trivial migration" case).
+//
+pub(crate) unsafe fn region_migration_from_regions_plus(mem: &mut M) {
+    use crate::stable_mem::{read, read_u16, read_u32, read_u64, size};
+
+    // Check if the magic in the memory corresponds to this object.
+    assert!(size() > 1);
+    let mut magic_bytes: [u8; 8] = [0; 8];
+    read(meta_data::offset::MAGIC, &mut magic_bytes);
+    if &magic_bytes != meta_data::version::MAGIC {
+        region_trap_with("migration failure (bad magic bytes)")
+    };
+    let version = read_u32(meta_data::offset::VERSION);
+    if version > meta_data::version::VERSION {
+        region_trap_with("migration failure (unexpected higher version)")
+    };
+
+    let block_pages = read_u16(meta_data::offset::BLOCK_PAGES);
+    if block_pages as u32 != meta_data::size::PAGES_IN_BLOCK {
+        region_trap_with("migration failure (unexpected block size)")
+    };
+
+    let block_base = read_u64(meta_data::offset::BLOCK_BASE);
+    if block_base < meta_data::offset::FREE {
+        region_trap_with("migration failure (base too low)")
+    };
+
+    BLOCK_BASE = block_base;
+
+    REGION_0 = region_recover(mem, &RegionId(0));
+
+    // Ensure that regions 1 through LAST_RESERVED_REGION_ID are already reserved for
+    // future use by future Motoko compiler-RTS features.
+    region_reserve_id_span(mem, None, RegionId(LAST_RESERVED_REGION_ID));
+}
+
+//
+// region manager migration/initialization, with pre-existing stable data.
+//
+#[ic_mem_fn]
+pub(crate) unsafe fn region_init(mem: &mut M, use_stable_regions: u32) {
+    match crate::stable_mem::get_version() {
+        VERSION_NO_STABLE_MEMORY => {
+            assert!(crate::stable_mem::size() == 0);
+            if use_stable_regions != 0 {
+                region_migration_from_no_stable_memory(mem);
+                debug_assert!(meta_data::offset::FREE < BLOCK_BASE);
+                debug_assert!(BLOCK_BASE == meta_data::offset::BASE_LOW);
+            };
+        }
+        VERSION_SOME_STABLE_MEMORY => {
+            assert!(crate::stable_mem::size() > 0);
+            if use_stable_regions != 0 {
+                region_migration_from_some_stable_memory(mem);
+                debug_assert!(meta_data::offset::FREE < BLOCK_BASE);
+                debug_assert!(BLOCK_BASE == meta_data::offset::BASE_HIGH);
+            };
+        }
+        _ => {
+            region_migration_from_regions_plus(mem); //check format & recover region0
+            debug_assert!(meta_data::offset::FREE < BLOCK_BASE);
+            debug_assert!(
+                BLOCK_BASE == meta_data::offset::BASE_LOW
+                    || BLOCK_BASE == meta_data::offset::BASE_HIGH
+            );
+        }
+    }
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_size(_mem: &mut M, r: Value) -> u64 {
+    let r = r.as_region();
+    (*r).page_count.into()
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_grow(mem: &mut M, r: Value, new_pages: u64) -> u64 {
+    use meta_data::size::{total_required_pages, PAGES_IN_BLOCK};
+
+    let max_pages_in_region = meta_data::max::BLOCKS as u32 * PAGES_IN_BLOCK;
+
+    let r = r.as_region();
+    let old_page_count = (*r).page_count;
+
+    if new_pages > (max_pages_in_region - old_page_count) as u64 {
+        return u64::MAX;
+    }
+
+    debug_assert!(max_pages_in_region <= u32::MAX);
+
+    let new_pages_ = new_pages as u32;
+
+    let old_block_count = (old_page_count + (PAGES_IN_BLOCK - 1)) / PAGES_IN_BLOCK;
+    let new_block_count = (old_page_count + new_pages_ + (PAGES_IN_BLOCK - 1)) / PAGES_IN_BLOCK;
+    let inc_block_count = new_block_count - old_block_count;
+
+    // Determine the required total number of allocated blocks,
+    let old_total_blocks = meta_data::total_allocated_blocks::get();
+    let new_total_blocks = old_total_blocks as u64 + inc_block_count as u64;
+
+    // Actually grow stable memory with more pages as required,
+    // while respecting the global maximum limit on pages.
+    {
+        let have = crate::stable_mem::size();
+        let need = total_required_pages(BLOCK_BASE, new_total_blocks);
+        if have < need {
+            let diff = need - have;
+            if crate::stable_mem::grow(diff) == u64::MAX {
+                return u64::MAX; // Propagate error
+            }
+        }
+    }
+
+    // Commit the allocation
+    meta_data::total_allocated_blocks::set(new_total_blocks as u32);
+
+    // Update this region's page count, in both places where we record it (heap object, region table).
+    {
+        let r_id = RegionId::from_id(r.read_id64());
+
+        // Increase both:
+        (*r).page_count += new_pages_;
+        if old_block_count > 0 {
+            let last_block_rank = (old_block_count - 1) as u16;
+            let last_block_id =
+                AccessVector((*r).vec_pages.as_blob_mut()).get_ith_block_id(last_block_rank as u32);
+            debug_assert_eq!((*r).page_count, old_page_count + new_pages_);
+            let last_page_count =
+                block_page_count(last_block_rank, new_block_count, (*r).page_count);
+            let assoc = Some((r_id, last_block_rank, last_page_count));
+            meta_data::block_region_table::set(last_block_id, assoc);
+        }
+    }
+
+    let new_vec_pages = alloc_blob(
+        mem,
+        Bytes(new_block_count * meta_data::bytes_of::() as u32),
+    );
+    let old_vec_byte_count = old_block_count * meta_data::bytes_of::() as u32;
+
+    // Copy old region-block associations into new heap object.
+    crate::mem_utils::memcpy_bytes(
+        new_vec_pages.as_blob_mut().payload_addr() as usize,
+        (*r).vec_pages.as_blob().payload_const() as usize,
+        Bytes(old_vec_byte_count),
+    );
+
+    let new_pages = AccessVector::from_value(&new_vec_pages);
+
+    // Record new associations, between the region and each new block:
+    // - in block_region_table (stable memory, for persistence).
+    // - in region representation (heap memory, for fast access operations).
+    for i in old_block_count..new_block_count {
+        // rel_i starts at zero.
+        let rel_i: u16 = (i - old_block_count) as u16;
+
+        // (to do -- handle case where allocating this way has run out.)
+        let block_id: u16 = (old_total_blocks + rel_i as u32) as u16;
+
+        // Update stable memory with new association.
+        let block_page_count = block_page_count(i as u16, new_block_count, (*r).page_count);
+        let assoc = Some((RegionId::from_id(r.read_id64()), i as u16, block_page_count));
+        meta_data::block_region_table::set(BlockId(block_id), assoc);
+
+        new_pages.set_ith_block_id(i, &BlockId(block_id));
+    }
+
+    allocation_barrier(new_vec_pages);
+    write_with_barrier(mem, &mut (*r).vec_pages, new_vec_pages);
+    old_page_count.into()
+}
+
+pub(crate) unsafe fn region_load(_mem: &mut M, r: Value, offset: u64, dst: &mut [u8]) {
+    use crate::stable_mem::read;
+    use meta_data::size::BLOCK_IN_BYTES;
+
+    let r = RegionObject::from_value(&r);
+
+    r.check_relative_range(offset, dst.len() as u64);
+
+    if dst.len() == 0 {
+        return;
+    };
+
+    let (b1_off, b1, b1_len, b2) = r.relative_into_absolute_span(offset, dst.len() as u64);
+    if b1 == b2 {
+        // Case: only uses a single block, and thus only requires one read.
+        read(b1_off, dst);
+    } else {
+        // Case: Staged reads, one per block that holds requested data.
+
+        let mut i = 0; // invariant: i = # of bytes loaded.
+        let mut s = b1_off; // source of bytes, as absolute index.
+        let mut d = dst.as_mut_ptr(); // dest for bytes.
+
+        // do initial read (a special case, generally not full block length).
+        read(s, core::slice::from_raw_parts_mut(d, b1_len as usize));
+
+        // Advance input and output positions (i, d and s respectively).
+        i += b1_len;
+        d = d.offset(b1_len as isize);
+
+        // Do rest of block-sized reads.
+        // (invariant: they always occur at the start of a block).
+        loop {
+            let (s_, _, b_len) = r.relative_into_absolute_info(offset + i);
+            s = s_;
+            if i + b_len > dst.len() as u64 {
+                // case: last (generally partial) block.
+                if dst.len() as u64 > i {
+                    read(
+                        s,
+                        core::slice::from_raw_parts_mut(d, dst.len() - i as usize),
+                    );
+                }
+                break;
+            } else {
+                // case: internal (full) block.
+                read(
+                    s,
+                    core::slice::from_raw_parts_mut(d, BLOCK_IN_BYTES as usize),
+                );
+                d = d.offset(BLOCK_IN_BYTES as isize);
+                i += BLOCK_IN_BYTES;
+            }
+        }
+    }
+}
+
+pub(crate) unsafe fn region_store(_mem: &mut M, r: Value, offset: u64, src: &[u8]) {
+    use crate::stable_mem::write;
+    use meta_data::size::BLOCK_IN_BYTES;
+
+    let r = RegionObject::from_value(&r);
+
+    r.check_relative_range(offset, src.len() as u64);
+
+    if src.len() == 0 {
+        return;
+    };
+
+    let (b1_off, b1, b1_len, b2) = r.relative_into_absolute_span(offset, src.len() as u64);
+    if b1 == b2 {
+        write(b1_off, src);
+    } else {
+        // Case: Staged writes, one per block that holds requested data.
+
+        let mut i = 0; // invariant: i = # of bytes stored.
+        let mut s = src.as_ptr(); // source for bytes.
+        let mut d = b1_off; // dest of bytes, as absolute index.o
+
+        // do initial write (a special case, generally not full block length).
+        write(d, core::slice::from_raw_parts(s, b1_len as usize));
+
+        // Advance input and output positions (i, s and d respectively).
+        i += b1_len;
+        s = s.offset(b1_len as isize);
+
+        // Do rest of block-sized writes.
+        // (invariant: they always occur at the start of a block).
+        loop {
+            let (d_, _, b_len) = r.relative_into_absolute_info(offset + i);
+            d = d_;
+            if i + b_len > src.len() as u64 {
+                // case: last (generally partial) block.
+                if src.len() as u64 > i {
+                    write(d, core::slice::from_raw_parts(s, src.len() - i as usize));
+                }
+                break;
+            } else {
+                // case: internal (full) block.
+                write(d, core::slice::from_raw_parts(s, BLOCK_IN_BYTES as usize));
+                s = s.offset(BLOCK_IN_BYTES as isize);
+                i += BLOCK_IN_BYTES;
+            }
+        }
+    }
+}
+
+// -- Region load operations.
+
+#[ic_mem_fn]
+pub unsafe fn region_load_word8(mem: &mut M, r: Value, offset: u64) -> u32 {
+    let mut bytes: [u8; 1] = [0];
+    region_load(mem, r, offset, &mut bytes);
+    core::primitive::u8::from_le_bytes(bytes).into()
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_load_word16(mem: &mut M, r: Value, offset: u64) -> u32 {
+    let mut bytes: [u8; 2] = [0; 2];
+    region_load(mem, r, offset, &mut bytes);
+    core::primitive::u16::from_le_bytes(bytes).into()
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_load_word32(mem: &mut M, r: Value, offset: u64) -> u32 {
+    let mut bytes: [u8; 4] = [0; 4];
+    region_load(mem, r, offset, &mut bytes);
+    core::primitive::u32::from_le_bytes(bytes).into()
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_load_word64(mem: &mut M, r: Value, offset: u64) -> u64 {
+    let mut bytes: [u8; 8] = [0; 8];
+    region_load(mem, r, offset, &mut bytes);
+    core::primitive::u64::from_le_bytes(bytes).into()
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_load_float64(mem: &mut M, r: Value, offset: u64) -> f64 {
+    let mut bytes: [u8; 8] = [0; 8];
+    region_load(mem, r, offset, &mut bytes);
+    core::primitive::f64::from_le_bytes(bytes).into()
+}
+
+#[ic_mem_fn]
+pub(crate) unsafe fn region_load_blob(
+    mem: &mut M,
+    r: Value,
+    offset: u64,
+    len: u32,
+) -> Value {
+    let blob_val = crate::memory::alloc_blob(mem, crate::types::Bytes(len));
+    let blob = blob_val.as_blob_mut();
+
+    if len < (isize::MAX as u32) {
+        let bytes: &mut [u8] = core::slice::from_raw_parts_mut(blob.payload_addr(), len as usize);
+        region_load(mem, r, offset, bytes);
+    } else {
+        assert!((len / 2) < isize::MAX as u32);
+        let bytes_low: &mut [u8] =
+            core::slice::from_raw_parts_mut(blob.payload_addr(), (len / 2) as usize);
+        region_load(mem, r, offset, bytes_low);
+        let bytes_high: &mut [u8] = core::slice::from_raw_parts_mut(
+            blob.payload_addr().add((len / 2) as usize),
+            (len - len / 2) as usize,
+        );
+        region_load(mem, r, offset + (len / 2) as u64, bytes_high);
+    }
+    allocation_barrier(blob_val);
+    blob_val
+}
+
+// -- Region store operations.
+
+#[ic_mem_fn]
+pub unsafe fn region_store_word8(mem: &mut M, r: Value, offset: u64, val: u32) {
+    region_store(mem, r, offset, &core::primitive::u8::to_le_bytes(val as u8))
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_store_word16(mem: &mut M, r: Value, offset: u64, val: u32) {
+    region_store(
+        mem,
+        r,
+        offset,
+        &core::primitive::u16::to_le_bytes(val as u16),
+    )
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_store_word32(mem: &mut M, r: Value, offset: u64, val: u32) {
+    region_store(
+        mem,
+        r,
+        offset,
+        &core::primitive::u32::to_le_bytes(val as u32),
+    )
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_store_word64(mem: &mut M, r: Value, offset: u64, val: u64) {
+    region_store(mem, r, offset, &core::primitive::u64::to_le_bytes(val))
+}
+
+#[ic_mem_fn]
+pub unsafe fn region_store_float64(mem: &mut M, r: Value, offset: u64, val: f64) {
+    region_store(mem, r, offset, &core::primitive::f64::to_le_bytes(val))
+}
+
+#[ic_mem_fn]
+pub(crate) unsafe fn region_store_blob(mem: &mut M, r: Value, offset: u64, blob: Value) {
+    let blob = blob.as_blob();
+    let len = blob.len().0;
+    let bytes = blob.payload_const();
+    if len < (isize::MAX as u32) {
+        let bytes: &[u8] = core::slice::from_raw_parts(bytes, len as usize);
+        region_store(mem, r, offset, bytes);
+    } else {
+        assert!((len / 2) < isize::MAX as u32);
+        let bytes_low: &[u8] = core::slice::from_raw_parts(bytes, (len / 2) as usize);
+        region_store(mem, r, offset, bytes_low);
+        let bytes_high: &[u8] =
+            core::slice::from_raw_parts(bytes.add((len / 2) as usize), (len - len / 2) as usize);
+        region_store(mem, r, offset + (len / 2) as u64, bytes_high);
+    }
+}
diff --git a/rts/motoko-rts/src/stable_mem.rs b/rts/motoko-rts/src/stable_mem.rs
new file mode 100644
index 00000000000..9d447d7d4de
--- /dev/null
+++ b/rts/motoko-rts/src/stable_mem.rs
@@ -0,0 +1,86 @@
+extern "C" {
+    // physical ic0_stable64 operations re-exported by moc
+    pub fn ic0_stable64_write(offset: u64, src: u64, size: u64);
+    pub fn ic0_stable64_read(dst: u64, offset: u64, size: u64);
+    // (virtual) stable_mem operations implemented by moc
+    pub fn moc_stable_mem_get_version() -> u32;
+    pub fn moc_stable_mem_set_version(version: u32);
+    pub fn moc_stable_mem_size() -> u64;
+    pub fn moc_stable_mem_grow(additional_pages: u64) -> u64;
+}
+
+pub fn get_version() -> u32 {
+    unsafe { moc_stable_mem_get_version() }
+}
+
+pub fn set_version(version: u32) {
+    unsafe { moc_stable_mem_set_version(version) }
+}
+
+pub fn size() -> u64 {
+    // SAFETY: This is safe because of the ic0 api guarantees.
+    unsafe { moc_stable_mem_size() }
+}
+
+pub fn grow(pages: u64) -> u64 {
+    // SAFETY: This is safe because of the ic0 api guarantees.
+    unsafe { moc_stable_mem_grow(pages) }
+}
+
+pub fn read(offset: u64, dst: &mut [u8]) {
+    // SAFETY: This is safe because of the ic0 api guarantees.
+    unsafe { ic0_stable64_read(dst.as_ptr() as u64, offset, dst.len() as u64) }
+}
+
+pub fn write(offset: u64, src: &[u8]) {
+    // SAFETY: This is safe because of the ic0 api guarantees.
+    unsafe { ic0_stable64_write(offset, src.as_ptr() as u64, src.len() as u64) }
+}
+
+// Little endian.
+pub fn read_u8(offset: u64) -> u8 {
+    let mut res: [u8; 1] = [0; 1];
+    read(offset, &mut res);
+    core::primitive::u8::from_le_bytes(res)
+}
+
+// Little endian.
+pub fn write_u8(offset: u64, value: u8) {
+    write(offset, &core::primitive::u8::to_le_bytes(value));
+}
+
+// Little endian.
+pub fn read_u16(offset: u64) -> u16 {
+    let mut res: [u8; 2] = [0; 2];
+    read(offset, &mut res);
+    core::primitive::u16::from_le_bytes(res)
+}
+
+// Little endian.
+pub fn write_u16(offset: u64, value: u16) {
+    write(offset, &core::primitive::u16::to_le_bytes(value));
+}
+
+// Little endian.
+pub fn read_u32(offset: u64) -> u32 {
+    let mut res: [u8; 4] = [0; 4];
+    read(offset, &mut res);
+    core::primitive::u32::from_le_bytes(res)
+}
+
+// Little endian.
+pub fn write_u32(offset: u64, value: u32) {
+    write(offset, &core::primitive::u32::to_le_bytes(value));
+}
+
+// Little endian.
+pub fn read_u64(offset: u64) -> u64 {
+    let mut res: [u8; 8] = [0; 8];
+    read(offset, &mut res);
+    core::primitive::u64::from_le_bytes(res)
+}
+
+// Little endian.
+pub fn write_u64(offset: u64, n: u64) {
+    write(offset, &core::primitive::u64::to_le_bytes(n));
+}
diff --git a/rts/motoko-rts/src/stream.rs b/rts/motoko-rts/src/stream.rs
index f8717238927..4a9299ae3ed 100644
--- a/rts/motoko-rts/src/stream.rs
+++ b/rts/motoko-rts/src/stream.rs
@@ -69,7 +69,7 @@ pub unsafe fn alloc_stream(mem: &mut M, size: Bytes) -> *mut Str
 #[allow(dead_code)]
 extern "C" {
     // generated by `moc`
-    fn stable64_write_moc(to: u64, ptr: u64, n: u64);
+    fn ic0_stable64_write(to: u64, ptr: u64, n: u64);
 }
 
 impl Stream {
@@ -100,7 +100,7 @@ impl Stream {
     fn send_to_stable(self: *mut Self, ptr: *const u8, n: Bytes) {
         unsafe {
             let next_ptr64 = self.read_ptr64() + n.as_u32() as u64;
-            stable64_write_moc(self.read_ptr64(), ptr as u64, n.as_u32() as u64);
+            ic0_stable64_write(self.read_ptr64(), ptr as u64, n.as_u32() as u64);
             self.write_ptr64(next_ptr64);
         }
     }
diff --git a/rts/motoko-rts/src/types.rs b/rts/motoko-rts/src/types.rs
index 76e4cef973c..dbe1690e6bc 100644
--- a/rts/motoko-rts/src/types.rs
+++ b/rts/motoko-rts/src/types.rs
@@ -163,7 +163,7 @@ pub const TRUE_VALUE: u32 = 0x1;
 
 /// A value in a heap slot
 #[repr(transparent)]
-#[derive(Clone, Copy, PartialEq, Eq)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
 pub struct Value(u32);
 
 /// A view of `Value` for analyzing the slot contents.
@@ -348,6 +348,20 @@ impl Value {
         self.forward().get_ptr() as *mut Array
     }
 
+    /// Get the pointer as `Region` using forwarding.
+    pub unsafe fn as_region(self) -> *mut Region {
+        debug_assert!(self.tag() == TAG_REGION);
+        self.check_forwarding_pointer();
+        self.forward().get_ptr() as *mut Region
+    }
+
+    /// Get the pointer as `Region` using forwarding, without checking the tag.
+    /// NB: One cannot check the tag during stabilization.
+    pub unsafe fn as_untagged_region(self) -> *mut Region {
+        self.check_forwarding_pointer();
+        self.forward().get_ptr() as *mut Region
+    }
+
     /// Get the pointer as `Concat` using forwarding. In debug mode panics if the value is not a pointer or the
     /// pointed object is not a `Concat`.
     pub unsafe fn as_concat(self) -> *const Concat {
@@ -444,9 +458,10 @@ pub const TAG_FWD_PTR: Tag = 19; // Only used by the copying GC - not to be conf
 pub const TAG_BITS32: Tag = 21;
 pub const TAG_BIGINT: Tag = 23;
 pub const TAG_CONCAT: Tag = 25;
-pub const TAG_NULL: Tag = 27;
-pub const TAG_ONE_WORD_FILLER: Tag = 29;
-pub const TAG_FREE_SPACE: Tag = 31;
+pub const TAG_REGION: Tag = 27;
+pub const TAG_NULL: Tag = 29;
+pub const TAG_ONE_WORD_FILLER: Tag = 31;
+pub const TAG_FREE_SPACE: Tag = 33;
 
 // Special value to visit only a range of array fields.
 // This and all values above it are reserved and mean
@@ -454,7 +469,7 @@ pub const TAG_FREE_SPACE: Tag = 31;
 // purposes of `visit_pointer_fields`.
 // Invariant: the value of this (pseudo-)tag must be
 //            higher than all other tags defined above
-pub const TAG_ARRAY_SLICE_MIN: Tag = 32;
+pub const TAG_ARRAY_SLICE_MIN: Tag = 34;
 
 // Common parts of any object. Other object pointers can be coerced into a pointer to this.
 #[repr(C)] // See the note at the beginning of this module
@@ -559,6 +574,27 @@ impl Array {
     }
 }
 
+#[rustfmt::skip]
+#[repr(C)] // See the note at the beginning of this module
+pub struct Region {
+    pub header: Obj,
+    // 64-bit id split into lower and upper halves for alignment reasons
+    pub id_lower: u32,
+    pub id_upper: u32,
+    pub page_count: u32,
+    pub vec_pages: Value, // Blob of u16's (each a page block ID).
+}
+
+impl Region {
+    pub unsafe fn write_id64(self: *mut Self, value: u64) {
+        write64(&mut (*self).id_lower, &mut (*self).id_upper, value);
+    }
+
+    pub unsafe fn read_id64(self: *mut Self) -> u64 {
+        read64((*self).id_lower, (*self).id_upper)
+    }
+}
+
 #[repr(C)] // See the note at the beginning of this module
 pub struct Object {
     pub header: Obj,
@@ -633,6 +669,22 @@ impl Blob {
         *self.payload_addr().add(idx as usize) = byte;
     }
 
+    pub unsafe fn payload_addr_u16(self: *mut Self) -> *mut u16 {
+        self.add(1) as *mut u16 // skip blob header
+    }
+
+    pub unsafe fn payload_const_u16(self: *const Self) -> *const u16 {
+        self.add(1) as *mut u16 // skip blob header
+    }
+
+    pub unsafe fn get_u16(self: *const Self, idx: u32) -> u16 {
+        *self.payload_const_u16().add(idx as usize)
+    }
+
+    pub unsafe fn set_u16(self: *mut Self, idx: u32, value: u16) {
+        *self.payload_addr_u16().add(idx as usize) = value;
+    }
+
     /// Shrink blob to the given size. Slop after the new size is filled with filler objects.
     pub unsafe fn shrink(self: *mut Self, new_len: Bytes) {
         let current_len_words = self.len().to_words();
@@ -935,6 +987,8 @@ pub(crate) unsafe fn block_size(address: usize) -> Words {
             free_space.size()
         }
 
+        TAG_REGION => size_of::(),
+
         _ => {
             rts_trap_with("object_size: invalid object tag");
         }
diff --git a/rts/motoko-rts/src/visitor.rs b/rts/motoko-rts/src/visitor.rs
index 7c05492e269..a06370b2405 100644
--- a/rts/motoko-rts/src/visitor.rs
+++ b/rts/motoko-rts/src/visitor.rs
@@ -92,6 +92,14 @@ pub unsafe fn visit_pointer_fields(
             }
         }
 
+        TAG_REGION => {
+            let region = obj as *mut Region;
+            let field_addr = &mut (*region).vec_pages;
+            if pointer_to_dynamic_heap(field_addr, heap_base) {
+                visit_ptr_field(ctx, field_addr);
+            }
+        }
+
         TAG_CONCAT => {
             let concat = obj as *mut Concat;
             let field1_addr = &mut (*concat).text1;
diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml
index 5e840bed4ac..fa4a602ca40 100644
--- a/src/codegen/compile.ml
+++ b/src/codegen/compile.ml
@@ -532,8 +532,17 @@ module E = struct
     | ts -> VarBlockType (nr (func_type env (FuncType ([], ts))))
 
   let if_ env tys thn els = G.if_ (as_block_type env tys) thn els
+
+  (* NB: confuses wasm-opt, don't use for now
+  let _multi_if_ env tys1 tys2 thn els =
+    G.if_
+      (VarBlockType (nr (func_type env (FuncType (tys1, tys2)))))
+      thn els
+  *)
+
   let block_ env tys bdy = G.block_ (as_block_type env tys) bdy
 
+
   let trap_with env msg = env.trap_with env msg
   let then_trap_with env msg = G.if0 (trap_with env msg) G.nop
   let else_trap_with env msg = G.if0 G.nop (trap_with env msg)
@@ -997,6 +1006,28 @@ module RTS = struct
     E.add_func_import env "rts" "text_singleton" [I32Type] [I32Type];
     E.add_func_import env "rts" "text_size" [I32Type] [I32Type];
     E.add_func_import env "rts" "text_to_buf" [I32Type; I32Type] [];
+    E.add_func_import env "rts" "region_init" [I32Type] [];
+    E.add_func_import env "rts" "alloc_region" [I64Type; I32Type; I32Type] [I32Type];
+    E.add_func_import env "rts" "init_region" [I32Type; I64Type; I32Type; I32Type] [];
+    E.add_func_import env "rts" "region_new" [] [I32Type];
+    E.add_func_import env "rts" "region_id" [I32Type] [I64Type];
+    E.add_func_import env "rts" "region_page_count" [I32Type] [I32Type];
+    E.add_func_import env "rts" "region_vec_pages" [I32Type] [I32Type];
+    E.add_func_import env "rts" "region_size" [I32Type] [I64Type];
+    E.add_func_import env "rts" "region_grow" [I32Type; I64Type] [I64Type];
+    E.add_func_import env "rts" "region_load_blob" [I32Type; I64Type; I32Type] [I32Type];
+    E.add_func_import env "rts" "region_store_blob" [I32Type; I64Type; I32Type] [];
+    E.add_func_import env "rts" "region_load_word8" [I32Type; I64Type] [I32Type];
+    E.add_func_import env "rts" "region_store_word8" [I32Type; I64Type; I32Type] [];
+    E.add_func_import env "rts" "region_load_word16" [I32Type; I64Type] [I32Type];
+    E.add_func_import env "rts" "region_store_word16" [I32Type; I64Type; I32Type] [];
+    E.add_func_import env "rts" "region_load_word32" [I32Type; I64Type] [I32Type];
+    E.add_func_import env "rts" "region_store_word32" [I32Type; I64Type; I32Type] [];
+    E.add_func_import env "rts" "region_load_word64" [I32Type; I64Type] [I64Type];
+    E.add_func_import env "rts" "region_store_word64" [I32Type; I64Type; I64Type] [];
+    E.add_func_import env "rts" "region_load_float64" [I32Type; I64Type] [F64Type];
+    E.add_func_import env "rts" "region_store_float64" [I32Type; I64Type; F64Type] [];
+    E.add_func_import env "rts" "region0_get" [] [I32Type];
     E.add_func_import env "rts" "blob_of_principal" [I32Type] [I32Type];
     E.add_func_import env "rts" "principal_of_blob" [I32Type] [I32Type];
     E.add_func_import env "rts" "compute_crc32" [I32Type] [I32Type];
@@ -1576,6 +1607,7 @@ module Tagged = struct
     | Null (* For opt. Static singleton! *)
     | OneWordFiller (* Only used by the RTS *)
     | FreeSpace (* Only used by the RTS *)
+    | Region
     | ArraySliceMinimum (* Used by the GC for incremental array marking *)
     | StableSeen (* Marker that we have seen this thing before *)
     | CoercionFailure (* Used in the Candid decoder. Static singleton! *)
@@ -1599,10 +1631,11 @@ module Tagged = struct
     | Bits32 -> 21l
     | BigInt -> 23l
     | Concat -> 25l
-    | Null -> 27l
-    | OneWordFiller -> 29l
-    | FreeSpace -> 31l
-    | ArraySliceMinimum -> 32l
+    | Region -> 27l
+    | Null -> 29l
+    | OneWordFiller -> 31l
+    | FreeSpace -> 33l
+    | ArraySliceMinimum -> 34l
     (* Next two tags won't be seen by the GC, so no need to set the lowest bit
        for `CoercionFailure` and `StableSeen` *)
     | CoercionFailure -> 0xfffffffel
@@ -1774,6 +1807,13 @@ module Tagged = struct
   let can_have_tag ty tag =
     let open Mo_types.Type in
     match (tag : tag) with
+    | Region ->
+      begin match normalize ty with
+      | (Con _ | Any) -> true
+      | (Prim Region) -> true
+      | (Prim _ | Obj _ | Array _ | Tup _ | Opt _ | Variant _ | Func _ | Non) -> false
+      | (Pre | Async _ | Mut _ | Var _ | Typ _) -> assert false
+      end
     | Array ->
       begin match normalize ty with
       | (Con _ | Any) -> true
@@ -3852,6 +3892,67 @@ module Blob = struct
 
 end (* Blob *)
 
+module Region = struct
+  (*
+    See rts/motoko-rts/src/region.rs
+   *)
+
+  (* Object layout:
+
+     ┌─────┬──────────┬──────────────────┬─────────────────┐
+     │ tag │ id_field │ page_count_field │ vec_pages_field │
+     └─────┴──────────┴──────────────────┴─────────────────┘
+            (unboxed, low 16 bits, rest 0-initialized padding)
+                        unboxed u32
+                                          Blob
+  *)
+
+  let alloc_region env =
+    E.call_import env "rts" "alloc_region"
+
+  let init_region env =
+    E.call_import env "rts" "init_region"
+
+  (* field accessors *)
+  (* NB: all these opns must resolve forwarding pointers here or in RTS *)
+  let id env =
+    E.call_import env "rts" "region_id"
+
+  let page_count env =
+    E.call_import env "rts" "region_page_count"
+
+  let vec_pages env =
+    E.call_import env "rts" "region_vec_pages"
+
+  let new_ env =
+    E.call_import env "rts" "region_new"
+
+  let size env =
+    E.call_import env "rts" "region_size"
+
+  let grow env =
+    E.call_import env "rts" "region_grow"
+
+  let load_blob env = E.call_import env "rts" "region_load_blob"
+  let store_blob env = E.call_import env "rts" "region_store_blob"
+
+  let load_word8 env = E.call_import env "rts" "region_load_word8"
+  let store_word8 env = E.call_import env "rts" "region_store_word8"
+
+  let load_word16 env = E.call_import env "rts" "region_load_word16"
+  let store_word16 env = E.call_import env "rts" "region_store_word16"
+
+  let load_word32 env = E.call_import env "rts" "region_load_word32"
+  let store_word32 env = E.call_import env "rts" "region_store_word32"
+
+  let load_word64 env = E.call_import env "rts" "region_load_word64"
+  let store_word64 env = E.call_import env "rts" "region_store_word64"
+
+  let load_float64 env = E.call_import env "rts" "region_load_float64"
+  let store_float64 env = E.call_import env "rts" "region_store_float64"
+
+end
+
 module Text = struct
   (*
   Most of the heavy lifting around text values is in rts/motoko-rts/src/text.rs
@@ -4501,11 +4602,10 @@ module IC = struct
     assert (E.mode env = Flags.ICMode || E.mode env = Flags.RefMode);
     let empty_f = Func.of_body env [] [] (fun env ->
       Lifecycle.trans env Lifecycle.InInit ^^
-
       G.i (Call (nr (E.built_in env "init"))) ^^
       GC.collect_garbage env ^^
-
       Lifecycle.trans env Lifecycle.Idle
+
     ) in
     let fi = E.add_fun env "canister_init" empty_f in
     E.add_export env (nr {
@@ -4912,59 +5012,82 @@ module Cycles = struct
 
 end (* Cycles *)
 
-
+(* Low-level, almost raw access to IC stable memory.
+   Essentially a virtual page allocator
+   * enforcing limit --max-stable-pages not exceeded
+   * tracking virtual page count, ignoring physical pages added for stable variable serialization (global`__stable_mem_size`)
+   * recording current format of contents (global `__stable_version`)
+   Used to implement stable variable serialization, (experimental) stable memory library and Region type (see region.rs)
+*)
 module StableMem = struct
 
-  (* start from 1 to avoid accidental reads of 0 *)
-  let version = Int32.of_int 1
+  (* Versioning (c.f. Region.rs) *)
+  (* NB: these constants must agree with VERSION_NO_STABLE_MEMORY etc. in Region.rs *)
+  let version_no_stable_memory = Int32.of_int 0 (* never manifest in serialized form *)
+  let version_some_stable_memory = Int32.of_int 1
+  let version_regions = Int32.of_int 2
+  let version_max = version_regions
 
   let register_globals env =
     (* size (in pages) *)
-    E.add_global64 env "__stablemem_size" Mutable 0L
+    E.add_global64 env "__stablemem_size" Mutable 0L;
+    E.add_global32 env "__stablemem_version" Mutable version_no_stable_memory
 
   let get_mem_size env =
     G.i (GlobalGet (nr (E.get_global env "__stablemem_size")))
+
   let set_mem_size env =
     G.i (GlobalSet (nr (E.get_global env "__stablemem_size")))
 
+  let get_version env =
+    G.i (GlobalGet (nr (E.get_global env "__stablemem_version")))
+
+  let set_version env =
+    G.i (GlobalSet (nr (E.get_global env "__stablemem_version")))
+
   (* stable memory bounds check *)
   let guard env =
     match E.mode env with
     | Flags.ICMode | Flags.RefMode ->
-      Func.share_code1 env "__stablemem_guard"
-        ("offset", I64Type) []
-        (fun env get_offset ->
-          get_offset ^^
-          compile_const_64 (Int64.of_int page_size_bits) ^^
-          G.i (Binary (Wasm.Values.I64 I64Op.ShrU)) ^^
-          get_mem_size env ^^
-          G.i (Compare (Wasm.Values.I64 I64Op.LtU)) ^^
-          E.else_trap_with env "StableMemory offset out of bounds")
+       get_mem_size env ^^
+       compile_const_64 (Int64.of_int page_size_bits) ^^
+       G.i (Binary (Wasm.Values.I64 I64Op.Shl)) ^^
+       G.i (Compare (Wasm.Values.I64 I64Op.GeU)) ^^
+       E.then_trap_with env "StableMemory offset out of bounds"
     | _ -> assert false
 
-  (* check [offset,.., offset + size) within bounds, assumes size > 0 *)
+  (* check both offset and [offset,.., offset + size) within bounds *)
+  (* c.f. region.rs check_relative_range *)
+  (* TODO: specialize on size *)
   let guard_range env =
     match E.mode env with
     | Flags.ICMode | Flags.RefMode ->
       Func.share_code2 env "__stablemem_guard_range"
         (("offset", I64Type), ("size", I32Type)) []
         (fun env get_offset get_size ->
-          let (set_sum, get_sum) = new_local64 env "sum" in
-          get_offset ^^
-          get_size ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^
-          G.i (Binary (Wasm.Values.I64 I64Op.Add)) ^^
-          set_sum ^^
-          get_sum ^^
-          get_offset ^^
-          G.i (Compare (Wasm.Values.I64 I64Op.LtU)) ^^
-          E.then_trap_with env "StableMemory range overflow" ^^
-          get_sum
-          ^^
-          get_mem_size env ^^
-          compile_const_64 (Int64.of_int page_size_bits) ^^
-          G.i (Binary (Wasm.Values.I64 I64Op.Shl)) ^^
-          G.i (Compare (Wasm.Values.I64 I64Op.LeU)) ^^
-          E.else_trap_with env "StableMemory range out of bounds")
+          get_size ^^
+          compile_unboxed_one ^^
+          G.i (Compare (Wasm.Values.I32 I64Op.LeU)) ^^
+          G.if0 begin
+            get_offset ^^
+            guard env
+          end
+          begin
+            compile_const_64 (Int64.minus_one) ^^
+            get_size ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^
+            G.i (Binary (Wasm.Values.I64 I64Op.Sub)) ^^
+            get_offset ^^
+            G.i (Compare (Wasm.Values.I64 I64Op.LtU)) ^^
+            E.then_trap_with env "StableMemory range overflow" ^^
+            get_offset ^^
+            get_size ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^
+            G.i (Binary (Wasm.Values.I64 I64Op.Add)) ^^
+            get_mem_size env ^^
+            compile_const_64 (Int64.of_int page_size_bits) ^^
+            G.i (Binary (Wasm.Values.I64 I64Op.Shl)) ^^
+            G.i (Compare (Wasm.Values.I64 I64Op.GtU)) ^^
+            E.then_trap_with env "StableMemory range out of bounds"
+          end)
     | _ -> assert false
 
   let add_guard env guarded get_offset bytes =
@@ -4977,6 +5100,7 @@ module StableMem = struct
         guard_range env)
     else G.nop
 
+  (* TODO: crusso in read/write could avoid stack allocation by reserving and re-using scratch memory instead *)
   let read env guarded name typ bytes load =
     match E.mode env with
     | Flags.ICMode | Flags.RefMode ->
@@ -5047,7 +5171,7 @@ module StableMem = struct
   let ensure_pages env =
     match E.mode env with
     | Flags.ICMode | Flags.RefMode ->
-      Func.share_code1 env "__stablemem_grow"
+      Func.share_code1 env "__stablemem_ensure_pages"
         ("pages", I64Type) [I64Type]
         (fun env get_pages ->
           let (set_size, get_size) = new_local64 env "size" in
@@ -5099,12 +5223,11 @@ module StableMem = struct
           E.then_trap_with env "Out of stable memory.")
     | _ -> assert false
 
-  (* API *)
-
-  let logical_grow env =
+  (* low-level grow, respecting --max-stable-pages *)
+  let grow env =
     match E.mode env with
     | Flags.ICMode | Flags.RefMode ->
-      Func.share_code1 env "__stablemem_logical_grow"
+      Func.share_code1 env "__stablemem_grow"
         ("pages", I64Type) [I64Type] (fun env get_pages ->
           let (set_size, get_size) = new_local64 env "size" in
           get_mem_size env ^^
@@ -5180,7 +5303,6 @@ module StableMem = struct
     write env true "float64" F64Type 8l
       (G.i (Store {ty = F64Type; align = 0; offset = 0l; sz = None}))
 
-
   let load_blob env =
     match E.mode env with
     | Flags.ICMode | Flags.RefMode ->
@@ -5216,7 +5338,185 @@ module StableMem = struct
           IC.system_call env "stable64_write")
     | _ -> assert false
 
-end (* StableMemory *)
+end (* StableMem *)
+
+
+(* StableMemoryInterface *)
+(* Core, legacy interface to IC stable memory, used to implement prims `stableMemoryXXX` of
+   library `ExperimentalStableMemory.mo`.
+   Each operation dispatches on the state of `StableMem.get_version()`.
+   * StableMem.version_no_stable_memory/StableMem.version_some_stable_memory:
+     * use StableMem directly
+     * switch to version_some_stable_memory on non-trivial grow.
+   * StableMem.version_regions: use Region.mo
+*)
+module StableMemoryInterface = struct
+
+  (* Helpers *)
+  let get_region0 env = E.call_import env "rts" "region0_get"
+
+  let if_regions env args tys is1 is2 =
+    StableMem.get_version env ^^
+    compile_unboxed_const StableMem.version_regions ^^
+    G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^
+    E.if_ env tys
+      (get_region0 env ^^ args ^^ is1 env)
+      (args ^^ is2 env)
+
+  (* Prims *)
+  let size env =
+    Func.share_code0 env "__stablememory_size" [I64Type]
+      (fun env ->
+        if_regions env
+          G.nop
+          [I64Type]
+          Region.size
+          StableMem.get_mem_size)
+
+  let grow env =
+    Func.share_code1 env "__stablememory_grow" ("pages", I64Type) [I64Type]
+      (fun env get_pages ->
+        if_regions env
+          get_pages
+          [I64Type]
+          Region.grow
+          (fun env ->
+            (* do StableMem.grow, but detect and record change in version as well *)
+            let (set_res, get_res) = new_local64 env "size" in
+            (* logical grow *)
+            StableMem.grow env ^^
+            set_res ^^
+            (* if version = version_no_stable_memory and new mem_size > 0
+               then version := version_some_stable_memory *)
+            StableMem.get_version env ^^
+            compile_eq_const StableMem.version_no_stable_memory ^^
+            StableMem.get_mem_size env ^^
+            compile_const_64 0L ^^
+            G.i (Compare (Wasm.Values.I64 I32Op.GtU)) ^^
+            G.i (Binary (Wasm.Values.I32 I32Op.And)) ^^
+            (G.if0
+               begin
+                 compile_unboxed_const StableMem.version_some_stable_memory ^^
+                 StableMem.set_version env
+               end
+               G.nop) ^^
+            (* return res *)
+            get_res))
+
+  let load_blob env =
+    Func.share_code2 env "__stablememory_load_blob"
+      (("offset", I64Type), ("len", I32Type)) [I32Type]
+      (fun env offset len ->
+        if_regions env
+          (offset ^^ len)
+          [I32Type]
+          Region.load_blob
+          StableMem.load_blob)
+  let store_blob env =
+    Func.share_code2 env "__stablememory_store_blob"
+      (("offset", I64Type), ("blob", I32Type)) []
+      (fun env offset blob ->
+        if_regions env
+          (offset ^^ blob)
+          []
+          Region.store_blob
+          StableMem.store_blob)
+
+  let load_word8 env =
+    Func.share_code1 env "__stablememory_load_word8"
+      ("offset", I64Type) [I32Type]
+      (fun env offset ->
+        if_regions env
+          offset
+          [I32Type]
+          Region.load_word8
+          StableMem.load_word8)
+  let store_word8 env =
+    Func.share_code2 env "__stablememory_store_word8"
+      (("offset", I64Type), ("value", I32Type)) []
+      (fun env offset value ->
+        if_regions env
+          (offset ^^ value)
+          []
+          Region.store_word8
+          StableMem.store_word8)
+
+  let load_word16 env =
+    Func.share_code1 env "__stablememory_load_word16"
+      ("offset", I64Type) [I32Type]
+      (fun env offset->
+        if_regions env
+          offset
+          [I32Type]
+          Region.load_word16
+          StableMem.load_word16)
+  let store_word16 env =
+    Func.share_code2 env "__stablememory_store_word16"
+      (("offset", I64Type), ("value", I32Type)) []
+      (fun env offset value ->
+        if_regions env
+          (offset ^^ value)
+          []
+          Region.store_word16
+          StableMem.store_word16)
+
+  let load_word32 env =
+    Func.share_code1 env "__stablememory_load_word32"
+      ("offset", I64Type) [I32Type]
+      (fun env offset ->
+        if_regions env
+          offset
+          [I32Type]
+          Region.load_word32
+          StableMem.load_word32)
+  let store_word32 env =
+    Func.share_code2 env "__stablememory_store_word32"
+      (("offset", I64Type), ("value", I32Type)) []
+      (fun env offset value ->
+        if_regions env
+          (offset ^^ value)
+          []
+          Region.store_word32
+          StableMem.store_word32)
+
+  let load_word64 env =
+    Func.share_code1 env "__stablememory_load_word64" ("offset", I64Type) [I64Type]
+      (fun env offset ->
+        if_regions env
+          offset
+          [I64Type]
+          Region.load_word64
+          StableMem.load_word64)
+  let store_word64 env =
+    Func.share_code2 env "__stablememory_store_word64"
+      (("offset", I64Type), ("value", I64Type)) []
+      (fun env offset value ->
+        if_regions env
+          (offset ^^ value)
+          []
+          Region.store_word64
+          StableMem.store_word64)
+
+  let load_float64 env =
+    Func.share_code1 env "__stablememory_load_float64"
+      ("offset", I64Type) [F64Type]
+      (fun env offset ->
+        if_regions env
+          offset
+          [F64Type]
+          Region.load_float64
+          StableMem.load_float64)
+  let store_float64 env =
+    Func.share_code2 env "__stablememory_store_float64"
+      (("offset", I64Type), ("value", F64Type)) []
+      (fun env offset value ->
+        if_regions env
+          (offset ^^ value)
+          []
+          Region.store_float64
+          StableMem.store_float64)
+
+end
 
 module RTS_Exports = struct
   let system_exports env =
@@ -5267,18 +5567,101 @@ module RTS_Exports = struct
       })
     end;
 
-    let stable64_write_moc_fi =
+    let ic0_stable64_write_fi =
       if E.mode env = Flags.WASIMode then
-        E.add_fun env "stable64_write_moc" (
+        E.add_fun env "ic0_stable64_write" (
             Func.of_body env ["to", I64Type; "from", I64Type; "len", I64Type] []
               (fun env ->
-                E.trap_with env "stable64_write_moc is not supposed to be called in WASI"
+                E.trap_with env "ic0_stable64_write is not supposed to be called in WASI"
               )
           )
       else E.reuse_import env "ic0" "stable64_write" in
     E.add_export env (nr {
-      name = Lib.Utf8.decode "stable64_write_moc";
-      edesc = nr (FuncExport (nr stable64_write_moc_fi))
+      name = Lib.Utf8.decode "ic0_stable64_write";
+      edesc = nr (FuncExport (nr ic0_stable64_write_fi))
+    });
+
+    let ic0_stable64_read_fi =
+      if E.mode env = Flags.WASIMode then
+        E.add_fun env "ic0_stable64_read" (
+            Func.of_body env ["dst", I64Type; "offset", I64Type; "len", I64Type] []
+              (fun env ->
+                E.trap_with env "ic0_stable64_read is not supposed to be called in WASI"
+              )
+          )
+      else E.reuse_import env "ic0" "stable64_read" in
+    E.add_export env (nr {
+      name = Lib.Utf8.decode "ic0_stable64_read";
+      edesc = nr (FuncExport (nr ic0_stable64_read_fi))
+    });
+
+    let moc_stable_mem_grow_fi =
+      E.add_fun env "moc_stable_mem_grow" (
+        Func.of_body env ["newPages", I64Type] [I64Type]
+          (fun env ->
+            match E.mode env with
+            | Flags.ICMode | Flags.RefMode ->
+              G.i (LocalGet (nr 0l)) ^^
+              StableMem.grow env
+            | _ ->
+              E.trap_with env "moc_stable_mem_grow is not supposed to be called in WASI" (* improve me *)
+        ))
+    in
+    E.add_export env (nr {
+      name = Lib.Utf8.decode "moc_stable_mem_grow";
+      edesc = nr (FuncExport (nr moc_stable_mem_grow_fi))
+    });
+
+    let moc_stable_mem_size_fi =
+      E.add_fun env "moc_stable_mem_size" (
+        Func.of_body env [] [I64Type]
+          (fun env ->
+            match E.mode env with
+            | Flags.ICMode | Flags.RefMode ->
+               StableMem.get_mem_size env
+            | _ ->
+               E.trap_with env "moc_stable_mem_size is not supposed to be called in WASI" (* improve me *)
+          )
+        )
+    in
+    E.add_export env (nr {
+      name = Lib.Utf8.decode "moc_stable_mem_size";
+      edesc = nr (FuncExport (nr moc_stable_mem_size_fi))
+    });
+
+    let moc_stable_mem_get_version_fi =
+      E.add_fun env "moc_stable_mem_get_version" (
+        Func.of_body env [] [I32Type]
+          (fun env ->
+            match E.mode env with
+            | Flags.ICMode | Flags.RefMode ->
+               StableMem.get_version env
+            | _ ->
+               E.trap_with env "moc_stable_mem_get_version is not supposed to be called in WASI" (* improve me *)
+          )
+        )
+    in
+    E.add_export env (nr {
+      name = Lib.Utf8.decode "moc_stable_mem_get_version";
+      edesc = nr (FuncExport (nr moc_stable_mem_get_version_fi))
+    });
+
+    let moc_stable_mem_set_version_fi =
+      E.add_fun env "moc_stable_mem_set_version" (
+        Func.of_body env ["version", I32Type] []
+          (fun env ->
+            match E.mode env with
+            | Flags.ICMode | Flags.RefMode ->
+               G.i (LocalGet (nr 0l)) ^^
+               StableMem.set_version env
+            | _ ->
+               E.trap_with env "moc_stable_mem_set_version is not supposed to be called in WASI" (* improve me *)
+          )
+        )
+    in
+    E.add_export env (nr {
+      name = Lib.Utf8.decode "moc_stable_mem_set_version";
+      edesc = nr (FuncExport (nr moc_stable_mem_set_version_fi))
     })
 
 end (* RTS_Exports *)
@@ -5452,6 +5835,7 @@ module MakeSerialization (Strm : Stream) = struct
   (* Globals recording known Candid types
      See Note [Candid subtype checks]
    *)
+
   let register_delayed_globals env =
     (E.add_global32_delayed env "__typtbl" Immutable,
      E.add_global32_delayed env "__typtbl_end" Immutable,
@@ -5543,6 +5927,7 @@ module MakeSerialization (Strm : Stream) = struct
     | Any -> Some 16l
     | Non -> Some 17l
     | Prim Principal -> Some 24l
+    | Prim Region -> Some 128l
     | _ -> None
 
   (* some constants, also see rts/idl.c *)
@@ -5641,6 +6026,8 @@ module MakeSerialization (Strm : Stream) = struct
       | Non -> assert false
       | Prim Blob ->
         add_typ Type.(Array (Prim Nat8))
+      | Prim Region ->
+        add_sleb128 idl_alias; add_idx t
       | Prim _ -> assert false
       | Tup ts ->
         add_sleb128 idl_record;
@@ -5798,7 +6185,9 @@ module MakeSerialization (Strm : Stream) = struct
         G.i (Binary (Wasm.Values.I32 I32Op.Or)) ^^
         get_tag ^^ compile_eq_const Tagged.(int_of_tag Array) ^^
         G.i (Binary (Wasm.Values.I32 I32Op.Or)) ^^
-        E.else_trap_with env "object_size/Mut: Unexpected tag" ^^
+        get_tag ^^ compile_eq_const Tagged.(int_of_tag Region) ^^
+        G.i (Binary (Wasm.Values.I32 I32Op.Or)) ^^
+        E.else_trap_with env "object_size/Mut: Unexpected tag." ^^
         (* Check if we have seen this before *)
         get_tag ^^ compile_eq_const Tagged.(int_of_tag StableSeen) ^^
         G.if0 begin
@@ -5881,6 +6270,10 @@ module MakeSerialization (Strm : Stream) = struct
         get_x ^^ size env (Prim Blob)
       | Non ->
         E.trap_with env "buffer_size called on value of type None"
+      | Prim Region ->
+         size_alias (fun () ->
+          inc_data_size (compile_unboxed_const 12l) ^^ (* |id| + |page_count| = 8 + 4 *)
+          get_x ^^ Region.vec_pages env ^^ size env (Prim Blob))
       | Mut t ->
         size_alias (fun () -> get_x ^^ MutBox.load_field env ^^ size env t)
       | _ -> todo "buffer_size" (Arrange_ir.typ t) G.nop
@@ -5945,6 +6338,8 @@ module MakeSerialization (Strm : Stream) = struct
           E.then_trap_with env "unvisited mutable data in serialize_go (ObjInd)" ^^
           get_tag ^^ compile_eq_const Tagged.(int_of_tag Array) ^^
           E.then_trap_with env "unvisited mutable data in serialize_go (Array)" ^^
+          get_tag ^^ compile_eq_const Tagged.(int_of_tag Region) ^^
+          E.then_trap_with env "unvisited mutable data in serialize_go (Region)" ^^
           (* Second time we see this *)
           (* Calculate relative offset *)
           let set_offset, get_offset = new_local env "offset" in
@@ -6000,6 +6395,14 @@ module MakeSerialization (Strm : Stream) = struct
         ) (sort_by_hash fs)
       | Array (Mut t) ->
         write_alias (fun () -> get_x ^^ write env (Array t))
+      | Prim Region ->
+        write_alias (fun () ->
+          reserve env get_data_buf 8l ^^
+          get_x ^^ Region.id env ^^
+          G.i (Store {ty = I64Type; align = 0; offset = 0l; sz = None}) ^^
+          write_word_32 env get_data_buf (get_x ^^ Region.page_count env) ^^
+          write_blob env get_data_buf (get_x ^^ Region.vec_pages env)
+        )
       | Array t ->
         write_word_leb env get_data_buf (get_x ^^ Arr.len env) ^^
         get_x ^^ Arr.len env ^^
@@ -6353,6 +6756,22 @@ module MakeSerialization (Strm : Stream) = struct
         end
       in
 
+      let with_alias_typ get_arg_typ =
+        get_arg_typ ^^
+        compile_unboxed_const 0l ^^ G.i (Compare (Wasm.Values.I32 I32Op.GeS)) ^^
+        G.if1 I32Type
+        begin
+            with_composite_arg_typ get_arg_typ idl_alias (ReadBuf.read_sleb128 env)
+        end
+        begin
+          (* sanity check *)
+          get_arg_typ ^^
+          compile_eq_const (Int32.neg (Option.get (to_idl_prim (Prim Region)))) ^^
+          E.else_trap_with env "IDL error: unexpecting primitive alias type" ^^
+          get_arg_typ
+        end
+      in
+
       let with_composite_typ idl_tycon_id f =
         with_composite_arg_typ get_idltyp idl_tycon_id f
       in
@@ -6387,7 +6806,8 @@ module MakeSerialization (Strm : Stream) = struct
         let (set_memo, get_memo) = new_local env "memo" in
 
         let (set_arg_typ, get_arg_typ) = new_local env "arg_typ" in
-        with_composite_typ idl_alias (ReadBuf.read_sleb128 env) ^^ set_arg_typ ^^
+
+        with_alias_typ get_idltyp ^^ set_arg_typ ^^
 
         (* Find out if it is a reference or not *)
         ReadBuf.read_byte env get_data_buf ^^ set_is_ref ^^
@@ -6426,7 +6846,7 @@ module MakeSerialization (Strm : Stream) = struct
             get_memo ^^ get_result ^^ store_unskewed_ptr ^^
             get_memo ^^ compile_add_const 4l ^^ Blob.lit env (typ_hash t) ^^ store_unskewed_ptr
           )
-        end begin
+          end begin
           (* Decoded before. Check type hash *)
           ReadBuf.read_word32 env get_data_buf ^^ Blob.lit env (typ_hash t) ^^
           G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^
@@ -6602,6 +7022,27 @@ module MakeSerialization (Strm : Stream) = struct
           Tagged.allocation_barrier env ^^
           G.i Drop
         )
+      | Prim Region ->
+         read_alias env (Prim Region) (fun get_region_typ on_alloc ->
+          let (set_region, get_region) = new_local env "region" in
+          (* sanity check *)
+          get_region_typ ^^
+          compile_eq_const (Int32.neg (Option.get (to_idl_prim (Prim Region)))) ^^
+          E.else_trap_with env "deserialize_go (Region): unexpected idl_typ" ^^
+          (* pre-allocate a region object, with dummy fields *)
+          compile_const_64 0L ^^ (* id *)
+          compile_unboxed_const 0l ^^ (* pagecount *)
+          Blob.lit env "" ^^ (* vec_pages *)
+          Region.alloc_region env ^^
+          set_region ^^
+          on_alloc get_region ^^
+          (* read and initialize the region's fields *)
+          get_region ^^
+          ReadBuf.read_word64 env get_data_buf ^^ (* id *)
+          ReadBuf.read_word32 env get_data_buf ^^ (* pagecount *)
+          read_blob () ^^ (* vec_pages *)
+          Region.init_region env
+        )
       | Array t ->
         let (set_len, get_len) = new_local env "len" in
         let (set_x, get_x) = new_local env "x" in
@@ -7255,7 +7696,15 @@ module Stabilization = struct
     StableMem.get_mem_size env ^^
     G.i (Test (Wasm.Values.I64 I64Op.Eqz)) ^^
     G.if0
-      begin (* ensure [0,..,3,...len+4) *)
+      begin
+        (* assert StableMem.get_version() == StableMem.version_no_stable_memory *)
+        StableMem.get_version env ^^
+        compile_eq_const StableMem.version_no_stable_memory ^^
+        E.else_trap_with env "StableMem.get_version() != version_no_stable_memory" ^^
+
+        (* Case-true: Stable variables only --
+           no use of either regions or experimental API. *)
+        (* ensure [0,..,3,...len+4) *)
         compile_const_64 0L ^^
         extend64 get_len ^^
         compile_add64_const 4L ^^  (* reserve one word for size *)
@@ -7276,6 +7725,7 @@ module Stabilization = struct
           end
       end
       begin
+        (* Case-false: Either regions or experimental API. *)
         let (set_N, get_N) = new_local64 env "N" in
 
         (* let N = !size * page_size *)
@@ -7305,7 +7755,7 @@ module Stabilization = struct
             IC.system_call env "stable64_write"
           end ^^
 
-        (* let M = pagesize * ic0.stable64_size64() - 1 *)
+        (* let M = pagesize * ic0.stable64_size() - 1 *)
         (* M is beginning of last page *)
         let (set_M, get_M) = new_local64 env "M" in
         IC.system_call env "stable64_size" ^^
@@ -7331,14 +7781,25 @@ module Stabilization = struct
 
         (* save version at M + (pagesize - 4) *)
         get_M ^^
-          compile_add64_const (Int64.sub page_size64 4L) ^^
-        (* TODO bump version? *)
-        compile_unboxed_const StableMem.version ^^
+        compile_add64_const (Int64.sub page_size64 4L) ^^
+
+        (* assert StableMem.get_version() > StableMem.version_no_stable_memory *)
+        StableMem.get_version env ^^
+        compile_rel_const I32Op.GtU StableMem.version_no_stable_memory ^^
+        E.else_trap_with env "StableMem.get_version() == version_no_stable_memory" ^^
+
+        (* assert StableMem.get_version() <= StableMem.version_max *)
+        StableMem.get_version env ^^
+        compile_rel_const I32Op.LeU StableMem.version_max ^^
+        E.else_trap_with env "StableMem.get_version() > version_max" ^^
+
+        (* record the version *)
+        StableMem.get_version env ^^
         StableMem.write_word32 env
 
       end
 
-  let destabilize env ty =
+  let destabilize env ty save_version =
     match E.mode env with
     | Flags.ICMode | Flags.RefMode ->
       let (set_pages, get_pages) = new_local64 env "pages" in
@@ -7349,6 +7810,8 @@ module Stabilization = struct
       G.i (Test (Wasm.Values.I64 I64Op.Eqz)) ^^
       G.if1 I32Type
         begin
+          (* Case: Size zero ==> Nothing in stable memory,
+             so result becomes the nil-valued record. *)
           let (_, fs) = Type.as_obj ty in
           let fs' = List.map
            (fun f -> (f.Type.lab, fun () -> Opt.null_lit env))
@@ -7357,9 +7820,12 @@ module Stabilization = struct
           StableMem.get_mem_size env ^^
           G.i (Test (Wasm.Values.I64 I64Op.Eqz)) ^^
           E.else_trap_with env "StableMem.mem_size non-zero" ^^
+          compile_unboxed_const 0l ^^
+          StableMem.set_version env ^^
           Object.lit_raw env fs'
         end
         begin
+          (* Case: Non-zero size. *)
           let (set_marker, get_marker) = new_local env "marker" in
           let (set_len, get_len) = new_local env "len" in
           let (set_offset, get_offset) = new_local64 env "offset" in
@@ -7371,6 +7837,8 @@ module Stabilization = struct
           G.i (Test (Wasm.Values.I32 I32Op.Eqz)) ^^
           G.if0
             begin
+              (* Sub-Case: version 1 or 2:
+                 Regions/Experimental API and stable vars. *)
               let (set_M, get_M) = new_local64 env "M" in
               let (set_version, get_version) = new_local env "version" in
               let (set_N, get_N) = new_local64 env "N" in
@@ -7385,14 +7853,16 @@ module Stabilization = struct
               compile_add64_const (Int64.sub page_size64 4L) ^^
               StableMem.read_and_clear_word32 env ^^
               set_version ^^
+              get_version ^^
+              save_version ^^
 
               (* check version *)
               get_version ^^
-              compile_unboxed_const StableMem.version ^^
+              compile_unboxed_const (StableMem.version_max) ^^
               G.i (Compare (Wasm.Values.I32 I32Op.GtU)) ^^
               E.then_trap_with env (Printf.sprintf
-                "higher stable memory version (expected %s)"
-                (Int32.to_string StableMem.version)) ^^
+                "higher stable memory version (expected 1..%s)"
+                (Int32.to_string StableMem.version_max)) ^^
 
               (* restore StableMem bytes [0..4) *)
               compile_const_64 0L ^^
@@ -7422,6 +7892,8 @@ module Stabilization = struct
               set_offset
             end
             begin
+              (* Sub-Case: Version 0.
+                 Stable vars with NO Regions/Experimental API. *)
               (* assert mem_size == 0 *)
               StableMem.get_mem_size env ^^
               G.i (Test (Wasm.Values.I64 I64Op.Eqz)) ^^
@@ -7433,7 +7905,10 @@ module Stabilization = struct
 
               (* set offset *)
               compile_const_64 4L ^^
-              set_offset
+              set_offset ^^
+
+              compile_unboxed_const (Int32.of_int 0) ^^
+              save_version
             end ^^ (* if_ *)
 
           let (set_blob, get_blob) = new_local env "blob" in
@@ -10058,6 +10533,122 @@ and compile_prim_invocation (env : E.t) ae p es at =
     SR.Vanilla,
     GC.get_collector_instructions env ^^ BigNum.from_word64 env
 
+  | OtherPrim "rts_stable_memory_size", [] ->
+    SR.Vanilla,
+    IC.ic_system_call "stable64_size" env ^^ BigNum.from_word64 env
+
+  | OtherPrim "rts_logical_stable_memory_size", [] ->
+    SR.Vanilla,
+    StableMem.get_mem_size env ^^ BigNum.from_word64 env
+
+  (* Regions *)
+
+  | OtherPrim "regionNew", [] ->
+    SR.Vanilla,
+    Region.new_ env
+
+  | OtherPrim "regionId", [e0] ->
+     SR.Vanilla,
+     compile_exp_as env ae SR.Vanilla e0 ^^
+     Region.id env ^^
+     BigNum.from_word64 env
+
+  | OtherPrim ("regionGrow"), [e0; e1] ->
+    SR.UnboxedWord64,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    Region.grow env
+
+  | OtherPrim "regionSize", [e0] ->
+    SR.UnboxedWord64,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    Region.size env
+
+  | OtherPrim ("regionLoadBlob"), [e0; e1; e2] ->
+    SR.Vanilla,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    compile_exp_as env ae SR.Vanilla e2 ^^
+    Blob.lit env "Blob size out of bounds" ^^
+    BigNum.to_word32_with env ^^
+    Region.load_blob env
+
+  | OtherPrim ("regionStoreBlob"), [e0; e1; e2] ->
+    SR.unit,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    compile_exp_as env ae SR.Vanilla e2 ^^
+    Region.store_blob env
+
+  | OtherPrim ("regionLoadNat8"), [e0; e1] ->
+    SR.Vanilla,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    Region.load_word8 env ^^
+    TaggedSmallWord.msb_adjust Type.Nat8
+
+  | OtherPrim ("regionStoreNat8"), [e0; e1; e2] ->
+    SR.unit,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    compile_exp_as env ae SR.Vanilla e2 ^^
+    TaggedSmallWord.lsb_adjust Type.Nat8 ^^
+    Region.store_word8 env
+
+  | OtherPrim ("regionLoadNat16"), [e0; e1] ->
+    SR.Vanilla,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    Region.load_word16 env ^^
+    TaggedSmallWord.msb_adjust Type.Nat16
+
+  | OtherPrim ("regionStoreNat16"), [e0; e1; e2] ->
+    SR.unit,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    compile_exp_as env ae SR.Vanilla e2 ^^
+    TaggedSmallWord.lsb_adjust Type.Nat16 ^^
+    Region.store_word16 env
+
+  | OtherPrim ("regionLoadNat32" | "regionLoadInt32"), [e0; e1] ->
+    SR.Vanilla,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    Region.load_word32 env
+
+  | OtherPrim ("regionStoreNat32" | "regionStoreInt32"), [e0; e1; e2] ->
+    SR.unit,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    compile_exp_as env ae SR.Vanilla e2 ^^
+    Region.store_word32 env
+
+  | OtherPrim ("regionLoadNat64" | "regionLoadInt64"), [e0; e1] ->
+    SR.UnboxedWord64,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    Region.load_word64 env
+
+  | OtherPrim ("regionStoreNat64" | "regionStoreInt64"), [e0; e1; e2] ->
+    SR.unit,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e2 ^^
+    Region.store_word64 env
+
+  | OtherPrim ("regionLoadFloat64"), [e0; e1] ->
+    SR.UnboxedFloat64,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    Region.load_float64 env
+
+  | OtherPrim ("regionStoreFloat64"), [e0; e1; e2] ->
+    SR.unit,
+    compile_exp_as env ae SR.Vanilla e0 ^^
+    compile_exp_as env ae SR.UnboxedWord64 e1 ^^
+    compile_exp_as env ae SR.UnboxedFloat64 e2 ^^
+    Region.store_float64 env
+
   (* Other prims, unary *)
 
   | OtherPrim "global_timer_set", [e] ->
@@ -10162,24 +10753,25 @@ and compile_prim_invocation (env : E.t) ae p es at =
   | OtherPrim ("stableMemoryLoadNat32" | "stableMemoryLoadInt32"), [e] ->
     SR.UnboxedWord32,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.load_word32 env
+    StableMemoryInterface.load_word32 env
 
   | OtherPrim ("stableMemoryStoreNat32" | "stableMemoryStoreInt32"), [e1; e2] ->
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.UnboxedWord32 e2 ^^
-    StableMem.store_word32 env
+    StableMemoryInterface.store_word32 env
 
   | OtherPrim "stableMemoryLoadNat8", [e] ->
     SR.Vanilla,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.load_word8 env ^^
+    StableMemoryInterface.load_word8 env ^^
     TaggedSmallWord.msb_adjust Type.Nat8
 
   | OtherPrim "stableMemoryLoadInt8", [e] ->
     SR.Vanilla,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.load_word8 env ^^
+    StableMemoryInterface.load_word8 env
+    ^^
     TaggedSmallWord.msb_adjust Type.Int8
 
   (* Other prims, binary *)
@@ -10188,59 +10780,59 @@ and compile_prim_invocation (env : E.t) ae p es at =
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.Vanilla e2 ^^ TaggedSmallWord.lsb_adjust Type.Nat8 ^^
-    StableMem.store_word8 env
+    StableMemoryInterface.store_word8 env
 
   | OtherPrim "stableMemoryStoreInt8", [e1; e2] ->
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.Vanilla e2 ^^ TaggedSmallWord.lsb_adjust Type.Int8 ^^
-    StableMem.store_word8 env
+    StableMemoryInterface.store_word8 env
 
   | OtherPrim "stableMemoryLoadNat16", [e] ->
     SR.Vanilla,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.load_word16 env ^^
+    StableMemoryInterface.load_word16 env ^^
     TaggedSmallWord.msb_adjust Type.Nat16
 
   | OtherPrim "stableMemoryLoadInt16", [e] ->
     SR.Vanilla,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.load_word16 env ^^
+    StableMemoryInterface.load_word16 env ^^
     TaggedSmallWord.msb_adjust Type.Int16
 
   | OtherPrim "stableMemoryStoreNat16", [e1; e2] ->
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.Vanilla e2 ^^ TaggedSmallWord.lsb_adjust Type.Nat16 ^^
-    StableMem.store_word16 env
+    StableMemoryInterface.store_word16 env
 
   | OtherPrim "stableMemoryStoreInt16", [e1; e2] ->
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.Vanilla e2 ^^ TaggedSmallWord.lsb_adjust Type.Int16 ^^
-    StableMem.store_word16 env
+    StableMemoryInterface.store_word16 env
 
   | OtherPrim ("stableMemoryLoadNat64" | "stableMemoryLoadInt64"), [e] ->
     SR.UnboxedWord64,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.load_word64 env
+    StableMemoryInterface.load_word64 env
 
   | OtherPrim ("stableMemoryStoreNat64" | "stableMemoryStoreInt64"), [e1; e2] ->
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.UnboxedWord64 e2 ^^
-    StableMem.store_word64 env
+    StableMemoryInterface.store_word64 env
 
   | OtherPrim "stableMemoryLoadFloat", [e] ->
     SR.UnboxedFloat64,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.load_float64 env
+    StableMemoryInterface.load_float64 env
 
   | OtherPrim "stableMemoryStoreFloat", [e1; e2] ->
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.UnboxedFloat64 e2 ^^
-    StableMem.store_float64 env
+    StableMemoryInterface.store_float64 env
 
   | OtherPrim "stableMemoryLoadBlob", [e1; e2] ->
     SR.Vanilla,
@@ -10248,21 +10840,22 @@ and compile_prim_invocation (env : E.t) ae p es at =
     compile_exp_as env ae SR.Vanilla e2 ^^
     Blob.lit env "Blob size out of bounds" ^^
     BigNum.to_word32_with env ^^
-    StableMem.load_blob env
+    StableMemoryInterface.load_blob env
 
   | OtherPrim "stableMemoryStoreBlob", [e1; e2] ->
     SR.unit,
     compile_exp_as env ae SR.UnboxedWord64 e1 ^^
     compile_exp_as env ae SR.Vanilla e2 ^^
-    StableMem.store_blob env
+    StableMemoryInterface.store_blob env
 
   | OtherPrim "stableMemorySize", [] ->
     SR.UnboxedWord64,
-    StableMem.get_mem_size env
+    StableMemoryInterface.size env
+
   | OtherPrim "stableMemoryGrow", [e] ->
     SR.UnboxedWord64,
     compile_exp_as env ae SR.UnboxedWord64 e ^^
-    StableMem.logical_grow env
+    StableMemoryInterface.grow env
 
   | OtherPrim "stableVarQuery", [] ->
     SR.UnboxedTuple 2,
@@ -10374,10 +10967,14 @@ and compile_prim_invocation (env : E.t) ae p es at =
         1. return record of nulls
       * On upgrade:
         1. deserialize stable store to v : ty,
-        2. return v
+        2. possibly run region manager initialization logic.
+        3. return v
     *)
     SR.Vanilla,
-    Stabilization.destabilize env ty
+    Stabilization.destabilize env ty (StableMem.set_version env) ^^
+    compile_unboxed_const (if !Flags.use_stable_regions then 1l else 0l) ^^
+    E.call_import env "rts" "region_init"
+
   | ICStableWrite ty, [e] ->
     SR.unit,
     compile_exp_vanilla env ae e ^^
diff --git a/src/exes/moc.ml b/src/exes/moc.ml
index f118b506296..b231888b95f 100644
--- a/src/exes/moc.ml
+++ b/src/exes/moc.ml
@@ -141,6 +141,11 @@ let argspec = [
     set_mode Compile ()), (* similar to --idl *)
       " compile and emit signature of stable types to `.most` file";
 
+  "--stable-regions",
+  Arg.Unit (fun () ->
+    Flags.use_stable_regions := true),
+      " force eager initialization of stable regions metadata (for testing purposes); consumes between 386KiB or 8MiB of additional physical stable memory, depending on current use of ExperimentalStableMemory library";
+
   "--generational-gc",
   Arg.Unit (fun () -> Flags.gc_strategy := Mo_config.Flags.Generational),
   " use generational GC";
@@ -148,7 +153,7 @@ let argspec = [
   "--incremental-gc",
   Arg.Unit (fun () -> Flags.gc_strategy := Mo_config.Flags.Incremental),
   " use incremental GC";
-    
+
   "--compacting-gc",
   Arg.Unit (fun () -> Flags.gc_strategy := Mo_config.Flags.MarkCompact),
   " use compacting GC";
diff --git a/src/mo_config/flags.ml b/src/mo_config/flags.ml
index aa031b5548e..a9e0b10f7fe 100644
--- a/src/mo_config/flags.ml
+++ b/src/mo_config/flags.ml
@@ -47,3 +47,4 @@ let ocaml_js = ref false
 let rts_stack_pages_default = 32 (* 2MB *)
 let rts_stack_pages : int ref = ref rts_stack_pages_default
 let trap_on_call_error = ref false
+let use_stable_regions = ref false
diff --git a/src/mo_frontend/coverage.ml b/src/mo_frontend/coverage.ml
index d98f2db5f35..0c285f0e950 100644
--- a/src/mo_frontend/coverage.ml
+++ b/src/mo_frontend/coverage.ml
@@ -94,6 +94,7 @@ let pick_val vs = function
   | T.Blob
   | T.Error
   | T.Principal
+  | T.Region
   | T.Float -> Any
 
 let rec expand_notval t n vs : desc list =
diff --git a/src/mo_idl/mo_to_idl.ml b/src/mo_idl/mo_to_idl.ml
index 040d6c25e19..3b852197573 100644
--- a/src/mo_idl/mo_to_idl.ml
+++ b/src/mo_idl/mo_to_idl.ml
@@ -74,6 +74,7 @@ module MakeState() = struct
     | Text -> I.PrimT I.Text
     | Blob -> I.BlobT
     | Principal -> I.PrincipalT
+    | Region
     | Error -> assert false
 
   let rec typ t =
diff --git a/src/mo_types/arrange_type.ml b/src/mo_types/arrange_type.ml
index 148a16b9eb9..ac25dd686cb 100644
--- a/src/mo_types/arrange_type.ml
+++ b/src/mo_types/arrange_type.ml
@@ -39,6 +39,7 @@ let prim = function
   | Blob -> Atom "Blob"
   | Error -> Atom "Error"
   | Principal -> Atom "Principal"
+  | Region -> Atom "Region"
 
 let con c = Atom (Type.string_of_con c)
 
diff --git a/src/mo_types/typ_hash.ml b/src/mo_types/typ_hash.ml
index 16225ceae09..8bcb2c747d8 100644
--- a/src/mo_types/typ_hash.ml
+++ b/src/mo_types/typ_hash.ml
@@ -91,6 +91,7 @@ let prim = function
   | Blob -> "B"
   | Error -> "E"
   | Principal -> "P"
+  | Region -> "R"
 
 let rec go = function
   | Prim p -> ((Nullary, prim p), [])
diff --git a/src/mo_types/type.ml b/src/mo_types/type.ml
index 13e316cc353..bf9b1dfa9ac 100644
--- a/src/mo_types/type.ml
+++ b/src/mo_types/type.ml
@@ -38,6 +38,7 @@ type prim =
   | Blob (* IR use: Packed representation, vec u8 IDL type *)
   | Error
   | Principal
+  | Region
 
 type t = typ
 and typ =
@@ -91,6 +92,7 @@ let tag_prim = function
   | Blob -> 15
   | Error -> 16
   | Principal -> 17
+  | Region -> 18
 
 let tag_func_sort = function
   | Local -> 0
@@ -317,6 +319,7 @@ let blob = Prim Blob
 let error = Prim Error
 let char = Prim Char
 let principal = Prim Principal
+let region = Prim Region
 
 let fields flds =
   List.sort compare_field
@@ -371,6 +374,7 @@ let prim = function
   | "Blob" -> Blob
   | "Error" -> Error
   | "Principal" -> Principal
+  | "Region" -> Region
   | s -> raise (Invalid_argument ("Type.prim: " ^ s))
 
 let seq = function [t] -> t | ts -> Tup ts
@@ -707,7 +711,7 @@ let rec span = function
   | Con _ as t -> span (promote t)
   | Prim Null -> Some 1
   | Prim Bool -> Some 2
-  | Prim (Nat | Int | Float | Text | Blob | Error | Principal) -> None
+  | Prim (Nat | Int | Float | Text | Blob | Error | Principal | Region) -> None
   | Prim (Nat8 | Int8) -> Some 0x100
   | Prim (Nat16 | Int16) -> Some 0x10000
   | Prim (Nat32 | Int32 | Nat64 | Int64 | Char) -> None  (* for all practical purposes *)
@@ -816,6 +820,7 @@ let serializable allow_mut t =
       match t with
       | Var _ | Pre -> assert false
       | Prim Error -> false
+      | Prim Region -> allow_mut (* stable, but not shared *)
       | Any | Non | Prim _ | Typ _ -> true
       | Async _ -> false
       | Mut t -> allow_mut && go t
@@ -1421,6 +1426,7 @@ let string_of_prim = function
   | Blob -> "Blob"
   | Error -> "Error"
   | Principal -> "Principal"
+  | Region -> "Region"
 
 let string_of_obj_sort = function
   | Object -> ""
diff --git a/src/mo_types/type.mli b/src/mo_types/type.mli
index 605873ae586..319383098e3 100644
--- a/src/mo_types/type.mli
+++ b/src/mo_types/type.mli
@@ -30,6 +30,7 @@ type prim =
   | Blob (* IR use: Packed representation, vec u8 IDL type *)
   | Error
   | Principal
+  | Region
 
 type t = typ
 
@@ -95,6 +96,7 @@ val blob : typ
 val error : typ
 val char : typ
 val principal : typ
+val region : typ
 
 val sum : (lab * typ) list -> typ
 val obj : obj_sort -> (lab * typ) list -> typ
diff --git a/src/mo_values/operator.ml b/src/mo_values/operator.ml
index 0bd9088918c..414989d9e53 100644
--- a/src/mo_values/operator.ml
+++ b/src/mo_values/operator.ml
@@ -171,6 +171,8 @@ let structural_equality t =
     match t with
     | T.Var _ | T.Pre | T.Non | T.Async _ | T.Mut _ -> assert false
     | T.Any | T.Typ _ -> fun v1 v2 -> Bool true
+    | T.Prim T.Error
+    | T.Prim T.Region -> assert false
     | T.Prim p -> eq_prim p
     | T.Con (c, ts) -> (
         match Mo_types.Cons.kind c with
@@ -190,7 +192,7 @@ let structural_equality t =
         fun v1 v2 ->
           match (v1, v2) with
           | Null, Null -> Bool true
-          | Null, Opt _ 
+          | Null, Opt _
           | Opt _, Null -> Bool false
           | Opt v1, Opt v2 -> go t v1 v2
           | _, _ -> assert false )
diff --git a/src/prelude/prelude.mo b/src/prelude/prelude.mo
index 1d8d4604b7a..ecc5933913e 100644
--- a/src/prelude/prelude.mo
+++ b/src/prelude/prelude.mo
@@ -23,3 +23,4 @@ type Text = prim "Text";
 type Blob = prim "Blob";
 type Error = prim "Error";
 type Principal = prim "Principal";
+type Region = prim "Region";
diff --git a/src/prelude/prim.mo b/src/prelude/prim.mo
index f8161852ea7..6203d855881 100644
--- a/src/prelude/prim.mo
+++ b/src/prelude/prim.mo
@@ -36,6 +36,7 @@ module Types = {
   public type Blob = prim "Blob";
   public type Error = prim "Error";
   public type Principal = prim "Principal";
+  public type Region = prim "Region";
 };
 
 func abs(x : Int) : Nat { (prim "abs" : Int -> Nat) x };
@@ -84,6 +85,14 @@ func rts_collector_instructions() : Nat {
   (prim "rts_collector_instructions" : () -> Nat)();
 };
 
+func rts_stable_memory_size() : Nat {
+  (prim "rts_stable_memory_size" : () -> Nat) ()
+};
+
+func rts_logical_stable_memory_size() : Nat {
+  (prim "rts_logical_stable_memory_size" : () -> Nat) ()
+};
+
 // Total conversions (fixed to big)
 
 let int64ToInt = @int64ToInt;
@@ -381,6 +390,81 @@ func stableMemoryStoreBlob(offset : Nat64, val : Blob) : () = (prim "stableMemor
 // Returns a query that computes the current actor's stable variable statistics (for now, the current size, in bytes, of serialized stable variable data).
 func stableVarQuery() : shared query () -> async { size : Nat64 } = (prim "stableVarQuery" : () -> (shared query () -> async { size : Nat64 }))();
 
+// stable regions
+
+func regionNew() : Region =
+  (prim "regionNew" : () -> Region) ();
+
+func regionId(r : Region) : Nat =
+  (prim "regionId" : Region -> Nat) r;
+
+func regionSize(r : Region) : Nat64 =
+  (prim "regionSize" : Region -> Nat64) r;
+
+func regionGrow(r : Region, pages : Nat64) : Nat64 =
+  (prim "regionGrow" : (Region, Nat64) -> Nat64) (r, pages);
+
+func regionLoadNat32(r : Region, offset : Nat64) : Nat32 =
+  (prim "regionLoadNat32" : (Region, Nat64) -> Nat32) (r, offset);
+
+func regionStoreNat32(r : Region, offset : Nat64, val : Nat32) : () =
+  (prim "regionStoreNat32" : (Region, Nat64, Nat32) -> ()) (r, offset, val);
+
+func regionLoadNat8(r : Region, offset : Nat64) : Nat8 =
+  (prim "regionLoadNat8" : (Region, Nat64) -> Nat8) (r, offset);
+
+func regionStoreNat8(r : Region, offset : Nat64, val : Nat8) : () =
+  (prim "regionStoreNat8" : (Region, Nat64, Nat8) -> ()) (r, offset, val);
+
+func regionLoadNat16(r : Region, offset : Nat64) : Nat16 =
+  (prim "regionLoadNat16" : (Region, Nat64) -> Nat16) (r, offset);
+
+func regionStoreNat16(r : Region, offset : Nat64, val : Nat16) : () =
+  (prim "regionStoreNat16" : (Region, Nat64, Nat16) -> ()) (r, offset, val);
+
+func regionLoadNat64(r : Region, offset : Nat64) : Nat64 =
+  (prim "regionLoadNat64" : (Region, Nat64) -> Nat64) (r, offset);
+
+func regionStoreNat64(r : Region, offset : Nat64, val : Nat64) : () =
+  (prim "regionStoreNat64" : (Region, Nat64, Nat64) -> ()) (r, offset, val);
+
+func regionLoadInt32(r : Region, offset : Nat64) : Int32 =
+  (prim "regionLoadInt32" : (Region, Nat64) -> Int32) (r, offset);
+
+func regionStoreInt32(r : Region, offset : Nat64, val : Int32) : () =
+  (prim "regionStoreInt32" : (Region, Nat64, Int32) -> ()) (r, offset, val);
+
+func regionLoadInt8(r : Region, offset : Nat64) : Int8 =
+  (prim "regionLoadInt8" : (Region, Nat64) -> Int8) (r, offset);
+
+func regionStoreInt8(r : Region, offset : Nat64, val : Int8) : () =
+  (prim "regionStoreInt8" : (Region, Nat64, Int8) -> ()) (r, offset, val);
+
+func regionLoadInt16(r : Region, offset : Nat64) : Int16 =
+  (prim "regionLoadInt16" : (Region, Nat64) -> Int16) (r, offset);
+
+func regionStoreInt16(r : Region, offset : Nat64, val : Int16) : () =
+  (prim "regionStoreInt16" : (Region, Nat64, Int16) -> ()) (r, offset, val);
+
+func regionLoadInt64(r : Region, offset : Nat64) : Int64 =
+  (prim "regionLoadInt64" : (Region, Nat64) -> Int64) (r, offset);
+
+func regionStoreInt64(r : Region, offset : Nat64, val : Int64) : () =
+  (prim "regionStoreInt64" : (Region, Nat64, Int64) -> ()) (r, offset, val);
+
+func regionLoadFloat(r : Region, offset : Nat64) : Float =
+  (prim "regionLoadFloat" : (Region, Nat64) -> Float) (r, offset);
+
+func regionStoreFloat(r : Region, offset : Nat64, val :  Float) : () =
+  (prim "regionStoreFloat" : (Region, Nat64, Float) -> ()) (r, offset, val);
+
+func regionLoadBlob(r : Region, offset : Nat64, size : Nat) : Blob =
+  (prim "regionLoadBlob" : (Region, Nat64, Nat) -> Blob) (r, offset, size);
+
+func regionStoreBlob(r : Region, offset : Nat64, val :  Blob) : () =
+  (prim "regionStoreBlob" : (Region, Nat64, Blob) -> ()) (r, offset, val);
+
+
 let call_raw = @call_raw;
 
 func performanceCounter(counter : Nat32) : Nat64 = (prim "performanceCounter" : (Nat32) -> Nat64) counter;
diff --git a/test/bench/ok/bignum.drun-run-opt.ok b/test/bench/ok/bignum.drun-run-opt.ok
index 5b40a565944..41346b9a78f 100644
--- a/test/bench/ok/bignum.drun-run-opt.ok
+++ b/test/bench/ok/bignum.drun-run-opt.ok
@@ -2,5 +2,5 @@ ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a000000000000000001
 ingress Completed: Reply: 0x4449444c0000
 debug.print: {cycles = 2_389_389; size = +59_652}
 ingress Completed: Reply: 0x4449444c0000
-debug.print: {cycles = 102_989_116; size = +1_817_872}
+debug.print: {cycles = 102_989_128; size = +1_817_872}
 ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/bignum.drun-run.ok b/test/bench/ok/bignum.drun-run.ok
index 7e490e3f3a2..08608313b46 100644
--- a/test/bench/ok/bignum.drun-run.ok
+++ b/test/bench/ok/bignum.drun-run.ok
@@ -2,5 +2,5 @@ ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a000000000000000001
 ingress Completed: Reply: 0x4449444c0000
 debug.print: {cycles = 2_493_575; size = +59_652}
 ingress Completed: Reply: 0x4449444c0000
-debug.print: {cycles = 103_046_061; size = +1_817_872}
+debug.print: {cycles = 103_046_049; size = +1_817_872}
 ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/heap-32.drun-run-opt.ok b/test/bench/ok/heap-32.drun-run-opt.ok
index 95d5dd1aa92..a71b24c8954 100644
--- a/test/bench/ok/heap-32.drun-run-opt.ok
+++ b/test/bench/ok/heap-32.drun-run-opt.ok
@@ -1,5 +1,5 @@
 ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
 ingress Completed: Reply: 0x4449444c0000
-debug.print: (50_227, +30_261_252, 620_394_365)
-debug.print: (50_070, +32_992_212, 671_304_553)
+debug.print: (50_227, +30_261_252, 620_394_287)
+debug.print: (50_070, +32_992_212, 671_304_488)
 ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/heap-32.drun-run.ok b/test/bench/ok/heap-32.drun-run.ok
index 96f23a8b836..9d4a76848d0 100644
--- a/test/bench/ok/heap-32.drun-run.ok
+++ b/test/bench/ok/heap-32.drun-run.ok
@@ -1,5 +1,5 @@
 ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
 ingress Completed: Reply: 0x4449444c0000
-debug.print: (50_227, +30_261_252, 667_797_613)
-debug.print: (50_070, +32_992_212, 720_521_485)
+debug.print: (50_227, +30_261_252, 667_797_463)
+debug.print: (50_070, +32_992_212, 720_521_360)
 ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/region-mem.drun-run-opt.ok b/test/bench/ok/region-mem.drun-run-opt.ok
new file mode 100644
index 00000000000..347dc52dd1c
--- /dev/null
+++ b/test/bench/ok/region-mem.drun-run-opt.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {heap_diff = 0; instr_diff = 5_096_079_644}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/region-mem.drun-run.ok b/test/bench/ok/region-mem.drun-run.ok
new file mode 100644
index 00000000000..d163a240d75
--- /dev/null
+++ b/test/bench/ok/region-mem.drun-run.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {heap_diff = 0; instr_diff = 5_398_069_554}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/region0-mem.drun-run-opt.ok b/test/bench/ok/region0-mem.drun-run-opt.ok
new file mode 100644
index 00000000000..d3ebcd3f433
--- /dev/null
+++ b/test/bench/ok/region0-mem.drun-run-opt.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {heap_diff = 0; instr_diff = 5_221_908_764}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/region0-mem.drun-run.ok b/test/bench/ok/region0-mem.drun-run.ok
new file mode 100644
index 00000000000..e40430fa5af
--- /dev/null
+++ b/test/bench/ok/region0-mem.drun-run.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {heap_diff = 0; instr_diff = 5_637_144_882}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/stable-mem.drun-run-opt.ok b/test/bench/ok/stable-mem.drun-run-opt.ok
new file mode 100644
index 00000000000..07ceff68c2d
--- /dev/null
+++ b/test/bench/ok/stable-mem.drun-run-opt.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {heap_diff = 0; instr_diff = 2_642_411_804}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/ok/stable-mem.drun-run.ok b/test/bench/ok/stable-mem.drun-run.ok
new file mode 100644
index 00000000000..2419c21d635
--- /dev/null
+++ b/test/bench/ok/stable-mem.drun-run.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {heap_diff = 0; instr_diff = 3_007_316_274}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/bench/region-mem.mo b/test/bench/region-mem.mo
new file mode 100644
index 00000000000..aabcd4c1f6c
--- /dev/null
+++ b/test/bench/region-mem.mo
@@ -0,0 +1,51 @@
+//MOC-FLAG --force-gc --stable-regions
+import {
+   performanceCounter;
+   rts_heap_size;
+   debugPrint;
+   regionNew = new;
+   regionSize = size;
+   regionGrow = grow;
+   regionLoadNat16 = loadNat16;
+   regionStoreNat16 = storeNat16;
+} = "mo:⛔";
+
+actor stablemem {
+
+    func counters() : (Int, Nat64) = (rts_heap_size(), performanceCounter(0));
+
+    let block_pages : Nat64 = 128;
+    let blocks : Nat64 = 3;
+    let pages : Nat64 = blocks * block_pages;
+    let r = new();
+    let _ = grow(r, pages);
+    assert size(r) == pages;
+
+    // write and read 3 blocks of nat16
+    public func go() : async () {
+        let (m0, n0) = counters();
+        var o : Nat64 = 0;
+        var n16 : Nat16 = 0;
+        while (o < pages * 65536) {
+           storeNat16(r, o, n16);
+           o += 2;
+           n16 +%= 1;
+        };
+        o := 0;
+        n16 := 0;
+        while (o < pages * 65536) {
+           let m16 = loadNat16(r, o);
+           assert m16 == n16;
+           o += 2;
+           n16 +%= 1;
+        };
+        let (m1, n1) = counters();
+        debugPrint(debug_show {heap_diff = m1 - m0; instr_diff = n1 - n0});
+    }
+}
+//SKIP run-low
+//SKIP run
+//SKIP run-ir
+//SKIP ic-ref-run
+//CALL ingress go 0x4449444C0000
+
diff --git a/test/bench/region0-mem.mo b/test/bench/region0-mem.mo
new file mode 100644
index 00000000000..f109d968a66
--- /dev/null
+++ b/test/bench/region0-mem.mo
@@ -0,0 +1,50 @@
+//MOC-FLAG --force-gc --stable-regions
+import {
+   performanceCounter;
+   rts_heap_size;
+   debugPrint;
+   stableMemorySize = size;
+   stableMemoryGrow = grow;
+   stableMemoryLoadNat16 = loadNat16;
+   stableMemoryStoreNat16 = storeNat16;
+} = "mo:⛔";
+
+actor stablemem {
+
+    func counters() : (Int, Nat64) = (rts_heap_size(), performanceCounter(0));
+
+    let block_pages : Nat64 = 128;
+    let blocks : Nat64 = 3;
+    let pages : Nat64 = blocks * block_pages;
+
+    let _ = grow(pages);
+    assert size() == pages;
+
+    // write and read 3 blocks of nat16
+    public func go() : async () {
+        let (m0, n0) = counters();
+        var o : Nat64 = 0;
+        var n16 : Nat16 = 0;
+        while (o < pages * 65536) {
+           storeNat16(o, n16);
+           o += 2;
+           n16 +%= 1;
+        };
+        o := 0;
+        n16 := 0;
+        while (o < pages * 65536) {
+           let m16 = loadNat16(o);
+           assert m16 == n16;
+           o += 2;
+           n16 +%= 1;
+        };
+        let (m1, n1) = counters();
+        debugPrint(debug_show {heap_diff = m1 - m0; instr_diff = n1 - n0});
+    }
+}
+//SKIP run-low
+//SKIP run
+//SKIP run-ir
+//SKIP ic-ref-run
+//CALL ingress go 0x4449444C0000
+
diff --git a/test/bench/stable-mem.mo b/test/bench/stable-mem.mo
new file mode 100644
index 00000000000..666f45f13bd
--- /dev/null
+++ b/test/bench/stable-mem.mo
@@ -0,0 +1,51 @@
+//MOC-FLAG --force-gc
+import {
+   performanceCounter;
+   rts_heap_size;
+   debugPrint;
+   stableMemorySize = size;
+   stableMemoryGrow = grow;
+   stableMemoryLoadNat16 = loadNat16;
+   stableMemoryStoreNat16 = storeNat16;
+} = "mo:⛔";
+
+
+actor stablemem {
+
+    func counters() : (Int, Nat64) = (rts_heap_size(), performanceCounter(0));
+
+    let block_pages : Nat64 = 128;
+    let blocks : Nat64 = 3;
+    let pages : Nat64 = blocks * block_pages;
+
+    let _ = grow(pages);
+    assert size() == pages;
+
+    // write and read 3 blocks of nat16
+    public func go() : async () {
+        let (m0, n0) = counters();
+        var o : Nat64 = 0;
+        var n16 : Nat16 = 0;
+        while (o < pages * 65536) {
+           storeNat16(o, n16);
+           o += 2;
+           n16 +%= 1;
+        };
+        o := 0;
+        n16 := 0;
+        while (o < pages * 65536) {
+           let m16 = loadNat16(o);
+           assert m16 == n16;
+           o += 2;
+           n16 +%= 1;
+        };
+        let (m1, n1) = counters();
+        debugPrint(debug_show {heap_diff = m1 - m0; instr_diff = n1 - n0});
+    }
+}
+//SKIP run-low
+//SKIP run
+//SKIP run-ir
+//SKIP ic-ref-run
+//CALL ingress go 0x4449444C0000
+
diff --git a/test/drun-wrapper.sh b/test/drun-wrapper.sh
index 648f8b2be7e..826b3a79983 100755
--- a/test/drun-wrapper.sh
+++ b/test/drun-wrapper.sh
@@ -27,7 +27,7 @@ export LANG=C.UTF-8
 # it doesn't work reliably and slows down the test significantly.
 # so until DFN-1269 fixes this properly, let's just not run
 # affected tests on drun (only ic-ref-run).
-EXTRA_BATCHES=1
+EXTRA_BATCHES=128
 
 # on darwin, I have seen
 #   thread 'MR Batch Processor' has overflowed its stack
diff --git a/test/fail/ok/no-timer-canc.tc.ok b/test/fail/ok/no-timer-canc.tc.ok
index cc272e6b02e..18224a5bc63 100644
--- a/test/fail/ok/no-timer-canc.tc.ok
+++ b/test/fail/ok/no-timer-canc.tc.ok
@@ -33,6 +33,7 @@ no-timer-canc.mo:3.10-3.21: type error [M0119], object field cancelTimer is not
         type None = None;
         type Null = Null;
         type Principal = Principal;
+        type Region = Region;
         type Text = Text
       };
     abs : Int -> Nat;
@@ -173,15 +174,41 @@ no-timer-canc.mo:3.10-3.21: type error [M0119], object field cancelTimer is not
     popcntNat8 : Nat8 -> Nat8;
     principalOfActor : (actor {}) -> Principal;
     principalOfBlob : Blob -> Principal;
+    regionGrow : (Region, Nat64) -> Nat64;
+    regionId : Region -> Nat;
+    regionLoadBlob : (Region, Nat64, Nat) -> Blob;
+    regionLoadFloat : (Region, Nat64) -> Float;
+    regionLoadInt16 : (Region, Nat64) -> Int16;
+    regionLoadInt32 : (Region, Nat64) -> Int32;
+    regionLoadInt64 : (Region, Nat64) -> Int64;
+    regionLoadInt8 : (Region, Nat64) -> Int8;
+    regionLoadNat16 : (Region, Nat64) -> Nat16;
+    regionLoadNat32 : (Region, Nat64) -> Nat32;
+    regionLoadNat64 : (Region, Nat64) -> Nat64;
+    regionLoadNat8 : (Region, Nat64) -> Nat8;
+    regionNew : () -> Region;
+    regionSize : Region -> Nat64;
+    regionStoreBlob : (Region, Nat64, Blob) -> ();
+    regionStoreFloat : (Region, Nat64, Float) -> ();
+    regionStoreInt16 : (Region, Nat64, Int16) -> ();
+    regionStoreInt32 : (Region, Nat64, Int32) -> ();
+    regionStoreInt64 : (Region, Nat64, Int64) -> ();
+    regionStoreInt8 : (Region, Nat64, Int8) -> ();
+    regionStoreNat16 : (Region, Nat64, Nat16) -> ();
+    regionStoreNat32 : (Region, Nat64, Nat32) -> ();
+    regionStoreNat64 : (Region, Nat64, Nat64) -> ();
+    regionStoreNat8 : (Region, Nat64, Nat8) -> ();
     rts_callback_table_count : () -> Nat;
     rts_callback_table_size : () -> Nat;
     rts_collector_instructions : () -> Nat;
     rts_heap_size : () -> Nat;
+    rts_logical_stable_memory_size : () -> Nat;
     rts_max_live_size : () -> Nat;
     rts_max_stack_size : () -> Nat;
     rts_memory_size : () -> Nat;
     rts_mutator_instructions : () -> Nat;
     rts_reclaimed : () -> Nat;
+    rts_stable_memory_size : () -> Nat;
     rts_total_allocation : () -> Nat;
     rts_version : () -> Text;
     setCertifiedData : Blob -> ();
diff --git a/test/fail/ok/no-timer-set.tc.ok b/test/fail/ok/no-timer-set.tc.ok
index 94ba0f5a325..d285cb99b89 100644
--- a/test/fail/ok/no-timer-set.tc.ok
+++ b/test/fail/ok/no-timer-set.tc.ok
@@ -33,6 +33,7 @@ no-timer-set.mo:3.10-3.18: type error [M0119], object field setTimer is not cont
         type None = None;
         type Null = Null;
         type Principal = Principal;
+        type Region = Region;
         type Text = Text
       };
     abs : Int -> Nat;
@@ -173,15 +174,41 @@ no-timer-set.mo:3.10-3.18: type error [M0119], object field setTimer is not cont
     popcntNat8 : Nat8 -> Nat8;
     principalOfActor : (actor {}) -> Principal;
     principalOfBlob : Blob -> Principal;
+    regionGrow : (Region, Nat64) -> Nat64;
+    regionId : Region -> Nat;
+    regionLoadBlob : (Region, Nat64, Nat) -> Blob;
+    regionLoadFloat : (Region, Nat64) -> Float;
+    regionLoadInt16 : (Region, Nat64) -> Int16;
+    regionLoadInt32 : (Region, Nat64) -> Int32;
+    regionLoadInt64 : (Region, Nat64) -> Int64;
+    regionLoadInt8 : (Region, Nat64) -> Int8;
+    regionLoadNat16 : (Region, Nat64) -> Nat16;
+    regionLoadNat32 : (Region, Nat64) -> Nat32;
+    regionLoadNat64 : (Region, Nat64) -> Nat64;
+    regionLoadNat8 : (Region, Nat64) -> Nat8;
+    regionNew : () -> Region;
+    regionSize : Region -> Nat64;
+    regionStoreBlob : (Region, Nat64, Blob) -> ();
+    regionStoreFloat : (Region, Nat64, Float) -> ();
+    regionStoreInt16 : (Region, Nat64, Int16) -> ();
+    regionStoreInt32 : (Region, Nat64, Int32) -> ();
+    regionStoreInt64 : (Region, Nat64, Int64) -> ();
+    regionStoreInt8 : (Region, Nat64, Int8) -> ();
+    regionStoreNat16 : (Region, Nat64, Nat16) -> ();
+    regionStoreNat32 : (Region, Nat64, Nat32) -> ();
+    regionStoreNat64 : (Region, Nat64, Nat64) -> ();
+    regionStoreNat8 : (Region, Nat64, Nat8) -> ();
     rts_callback_table_count : () -> Nat;
     rts_callback_table_size : () -> Nat;
     rts_collector_instructions : () -> Nat;
     rts_heap_size : () -> Nat;
+    rts_logical_stable_memory_size : () -> Nat;
     rts_max_live_size : () -> Nat;
     rts_max_stack_size : () -> Nat;
     rts_memory_size : () -> Nat;
     rts_mutator_instructions : () -> Nat;
     rts_reclaimed : () -> Nat;
+    rts_stable_memory_size : () -> Nat;
     rts_total_allocation : () -> Nat;
     rts_version : () -> Text;
     setCertifiedData : Blob -> ();
diff --git a/test/fail/ok/region-ops.tc.ok b/test/fail/ok/region-ops.tc.ok
new file mode 100644
index 00000000000..c66edeb541b
--- /dev/null
+++ b/test/fail/ok/region-ops.tc.ok
@@ -0,0 +1,22 @@
+region-ops.mo:4.3-4.11: type error [M0060], operator is not defined for operand types
+  Region
+and
+  Region
+region-ops.mo:8.3-8.11: type error [M0060], operator is not defined for operand types
+  Region
+and
+  Region
+region-ops.mo:12.3-12.13: type error [M0060], operator is not defined for operand types
+  ?Region
+and
+  ?Region
+region-ops.mo:16.3-16.13: type error [M0060], operator is not defined for operand types
+  ?Region
+and
+  ?Region
+region-ops.mo:20.3-20.15: type error [M0173], to_candid argument must have shared type, but instead has non-shared type
+  Region
+region-ops.mo:24.20-24.33: type error [M0174], from_candid produces an optional shared type, not type
+  ?Region
+region-ops.mo:28.19-28.25: type error [M0033], async has non-shared content type
+  Region
diff --git a/test/fail/ok/region-ops.tc.ret.ok b/test/fail/ok/region-ops.tc.ret.ok
new file mode 100644
index 00000000000..69becfa16f9
--- /dev/null
+++ b/test/fail/ok/region-ops.tc.ret.ok
@@ -0,0 +1 @@
+Return code 1
diff --git a/test/fail/region-ops.mo b/test/fail/region-ops.mo
new file mode 100644
index 00000000000..132e558d0c3
--- /dev/null
+++ b/test/fail/region-ops.mo
@@ -0,0 +1,29 @@
+// Region types are stable, but not shared.
+
+func badEquals(r1: Region, r2 : Region) : Bool {
+  r1 == r2 // reject
+};
+
+func badNotEquals(r1 : Region, r2 : Region) : Bool {
+  r1 != r2 // reject
+};
+
+func badEqualsNested(r1: Region, r2 : Region) : Bool {
+  ?r1 == ?r2 // reject
+};
+
+func badNotEqualsNested(r1 : Region, r2 : Region) : Bool {
+  ?r1 != ?r2 // reject
+};
+
+func ToCandid(r : Region) : Blob {
+  to_candid(r); // reject
+};
+
+func FromCandid(b : Blob) : ?Region {
+  let r: ?Region = from_candid b; // reject
+};
+
+do {
+   type t = async Region; // reject
+};
diff --git a/test/run-drun/ok/region0-big-blob.drun-run.ok b/test/run-drun/ok/region0-big-blob.drun-run.ok
new file mode 100644
index 00000000000..01dcaede9b7
--- /dev/null
+++ b/test/run-drun/ok/region0-big-blob.drun-run.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: 255
+debug.print: ok
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: assertion failed at region0-big-blob.mo:18.3-18.15
diff --git a/test/run-drun/ok/region0-big-blob.tc.ok b/test/run-drun/ok/region0-big-blob.tc.ok
new file mode 100644
index 00000000000..3042e1965f8
--- /dev/null
+++ b/test/run-drun/ok/region0-big-blob.tc.ok
@@ -0,0 +1,4 @@
+region0-big-blob.mo:6.7-6.8: warning [M0145], this pattern of type
+  Nat64
+does not cover value
+  1 or 2 or _
diff --git a/test/run-drun/ok/region0-overflow.drun-run.ok b/test/run-drun/ok/region0-overflow.drun-run.ok
new file mode 100644
index 00000000000..2048ea0a7e3
--- /dev/null
+++ b/test/run-drun/ok/region0-overflow.drun-run.ok
@@ -0,0 +1,9 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory offset out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory range out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory range out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory range out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory offset out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory offset out of bounds
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-rts-stats.drun-run.ok b/test/run-drun/ok/region0-rts-stats.drun-run.ok
new file mode 100644
index 00000000000..566c3bbefc0
--- /dev/null
+++ b/test/run-drun/ok/region0-rts-stats.drun-run.ok
@@ -0,0 +1,10 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: {l1 = 6; s1 = 6}
+debug.print: {l2 = 144; s2 = 144}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {l1 = 144; s1 = 146}
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: pattern failed
+debug.print: {l1 = 144; s1 = 146}
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: pattern failed
+debug.print: {l1 = 144; s1 = 146}
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: pattern failed
diff --git a/test/run-drun/ok/region0-rts-stats.tc.ok b/test/run-drun/ok/region0-rts-stats.tc.ok
new file mode 100644
index 00000000000..a6fcc2eb750
--- /dev/null
+++ b/test/run-drun/ok/region0-rts-stats.tc.ok
@@ -0,0 +1,4 @@
+region0-rts-stats.mo:8.7-8.8: warning [M0145], this pattern of type
+  Nat64
+does not cover value
+  1 or 2 or _
diff --git a/test/run-drun/ok/region0-stable-mem-blob.drun-run.ok b/test/run-drun/ok/region0-stable-mem-blob.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-blob.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-custom-max.drun-run.ok b/test/run-drun/ok/region0-stable-mem-custom-max.drun-run.ok
new file mode 100644
index 00000000000..c0cb2aa4a5f
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-custom-max.drun-run.ok
@@ -0,0 +1,3 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+Ok: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-float.drun-run.ok b/test/run-drun/ok/region0-stable-mem-float.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-float.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-grow-fail.drun-run.ok b/test/run-drun/ok/region0-stable-mem-grow-fail.drun-run.ok
new file mode 100644
index 00000000000..a6f776f43c6
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-grow-fail.drun-run.ok
@@ -0,0 +1,2 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-grow.drun-run.ok b/test/run-drun/ok/region0-stable-mem-grow.drun-run.ok
new file mode 100644
index 00000000000..5b71d74f731
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-grow.drun-run.ok
@@ -0,0 +1,17 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading from 0
+debug.print: to 1
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading from 1
+debug.print: to 2
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading from 2
+debug.print: to 3
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading from 3
+debug.print: to 4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-int16.drun-run.ok b/test/run-drun/ok/region0-stable-mem-int16.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-int16.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-int32.drun-run.ok b/test/run-drun/ok/region0-stable-mem-int32.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-int32.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-int64.drun-run.ok b/test/run-drun/ok/region0-stable-mem-int64.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-int64.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-int8.drun-run.ok b/test/run-drun/ok/region0-stable-mem-int8.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-int8.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-max.drun-run.ok b/test/run-drun/ok/region0-stable-mem-max.drun-run.ok
new file mode 100644
index 00000000000..c0cb2aa4a5f
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-max.drun-run.ok
@@ -0,0 +1,3 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+Ok: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-nat16.drun-run.ok b/test/run-drun/ok/region0-stable-mem-nat16.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-nat16.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-nat32.drun-run.ok b/test/run-drun/ok/region0-stable-mem-nat32.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-nat32.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-nat64.drun-run.ok b/test/run-drun/ok/region0-stable-mem-nat64.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-nat64.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/region0-stable-mem-nat8.drun-run.ok b/test/run-drun/ok/region0-stable-mem-nat8.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/region0-stable-mem-nat8.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/regions-base-high.drun-run.ok b/test/run-drun/ok/regions-base-high.drun-run.ok
new file mode 100644
index 00000000000..038f098be09
--- /dev/null
+++ b/test/run-drun/ok/regions-base-high.drun-run.ok
@@ -0,0 +1,21 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 0; s1 = 0}
+debug.print: SM.grow(1)
+debug.print: {l2 = 1; s2 = 1}
+debug.print: let r1=Region.new()
+debug.print: {l3 = 256; r1 = 16; s3 = 256}
+debug.print: let _ = Region.grow(r1,1)
+debug.print: {l4 = 384; r1 = 16; s4 = 384}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 384; s1 = 385}
+debug.print: SM.grow(1)
+debug.print: {l2 = 384; s2 = 385}
+debug.print: let r1=Region.new()
+debug.print: {l3 = 384; r1 = 16; s3 = 385}
+debug.print: let _ = Region.grow(r1,1)
+debug.print: {l4 = 384; r1 = 16; s4 = 385}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/regions-base-low.drun-run.ok b/test/run-drun/ok/regions-base-low.drun-run.ok
new file mode 100644
index 00000000000..762f0fce54a
--- /dev/null
+++ b/test/run-drun/ok/regions-base-low.drun-run.ok
@@ -0,0 +1,21 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 0; s1 = 0}
+debug.print: SM.grow(0)
+debug.print: {l2 = 0; s2 = 0}
+debug.print: let r1=Region.new()
+debug.print: {l3 = 6; r1 = 16; s3 = 6}
+debug.print: let _ = Region.grow(r1,1)
+debug.print: {l4 = 144; r1 = 16; s4 = 144}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 144; s1 = 145}
+debug.print: SM.grow(0)
+debug.print: {l2 = 144; s2 = 145}
+debug.print: let r1=Region.new()
+debug.print: {l3 = 144; r1 = 16; s3 = 145}
+debug.print: let _ = Region.grow(r1,1)
+debug.print: {l4 = 144; r1 = 16; s4 = 145}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/regions-pay-as-you-go.drun-run.ok b/test/run-drun/ok/regions-pay-as-you-go.drun-run.ok
new file mode 100644
index 00000000000..d212777bb44
--- /dev/null
+++ b/test/run-drun/ok/regions-pay-as-you-go.drun-run.ok
@@ -0,0 +1,41 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 6; s1 = 6}
+debug.print: SM.grow(16)
+debug.print: {l2 = 144; s2 = 144}
+debug.print: Region.new()
+debug.print: {l3 = 144; r1 = 16; s3 = 144}
+debug.print: Region.new()
+debug.print: {l4 = 144; r1 = 16; r2 = 17; s4 = 144}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 144; s1 = 145}
+debug.print: SM.grow(16)
+debug.print: {l2 = 144; s2 = 145}
+debug.print: Region.new()
+debug.print: {l3 = 144; r1 = 18; s3 = 145}
+debug.print: Region.new()
+debug.print: {l4 = 144; r1 = 18; r2 = 17; s4 = 145}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 144; s1 = 145}
+debug.print: SM.grow(16)
+debug.print: {l2 = 144; s2 = 145}
+debug.print: Region.new()
+debug.print: {l3 = 144; r1 = 19; s3 = 145}
+debug.print: Region.new()
+debug.print: {l4 = 144; r1 = 19; r2 = 17; s4 = 145}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 144; s1 = 145}
+debug.print: SM.grow(16)
+debug.print: {l2 = 144; s2 = 145}
+debug.print: Region.new()
+debug.print: {l3 = 144; r1 = 20; s3 = 145}
+debug.print: Region.new()
+debug.print: {l4 = 144; r1 = 20; r2 = 17; s4 = 145}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-mem-big-blob.drun-run.ok b/test/run-drun/ok/stable-mem-big-blob.drun-run.ok
new file mode 100644
index 00000000000..2ba6636bfac
--- /dev/null
+++ b/test/run-drun/ok/stable-mem-big-blob.drun-run.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: 255
+debug.print: ok
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: assertion failed at stable-mem-big-blob.mo:18.3-18.15
diff --git a/test/run-drun/ok/stable-mem-big-blob.tc.ok b/test/run-drun/ok/stable-mem-big-blob.tc.ok
new file mode 100644
index 00000000000..4027b6c4e61
--- /dev/null
+++ b/test/run-drun/ok/stable-mem-big-blob.tc.ok
@@ -0,0 +1,4 @@
+stable-mem-big-blob.mo:6.7-6.8: warning [M0145], this pattern of type
+  Nat64
+does not cover value
+  1 or 2 or _
diff --git a/test/run-drun/ok/stable-mem-custom-max.drun-run.ok b/test/run-drun/ok/stable-mem-custom-max.drun-run.ok
index bda3db772db..c0cb2aa4a5f 100644
--- a/test/run-drun/ok/stable-mem-custom-max.drun-run.ok
+++ b/test/run-drun/ok/stable-mem-custom-max.drun-run.ok
@@ -1,4 +1,3 @@
 ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
 ingress Completed: Reply: 0x4449444c0000
-ingress Completed: Reply: 0x4449444c0000
-ingress Completed: Reply: 0x4449444c0000
+Ok: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-mem-grow-fail.drun-run.ok b/test/run-drun/ok/stable-mem-grow-fail.drun-run.ok
new file mode 100644
index 00000000000..a6f776f43c6
--- /dev/null
+++ b/test/run-drun/ok/stable-mem-grow-fail.drun-run.ok
@@ -0,0 +1,2 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-mem-max.drun-run.ok b/test/run-drun/ok/stable-mem-max.drun-run.ok
index c76c471c7d5..c0cb2aa4a5f 100644
--- a/test/run-drun/ok/stable-mem-max.drun-run.ok
+++ b/test/run-drun/ok/stable-mem-max.drun-run.ok
@@ -1,3 +1,3 @@
 ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
 ingress Completed: Reply: 0x4449444c0000
-ingress Completed: Reply: 0x4449444c0000
+Ok: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-mem-pay-as-you-go.drun-run.ok b/test/run-drun/ok/stable-mem-pay-as-you-go.drun-run.ok
new file mode 100644
index 00000000000..153b0dea829
--- /dev/null
+++ b/test/run-drun/ok/stable-mem-pay-as-you-go.drun-run.ok
@@ -0,0 +1,41 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 0; s1 = 0}
+debug.print: SM.grow(16)
+debug.print: {l2 = 16; s2 = 16}
+debug.print: Region.new()
+debug.print: {l3 = 256; r1 = 16; s3 = 256}
+debug.print: Region.new()
+debug.print: {l4 = 256; r1 = 16; r2 = 17; s4 = 256}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 256; s1 = 257}
+debug.print: SM.grow(16)
+debug.print: {l2 = 256; s2 = 257}
+debug.print: Region.new()
+debug.print: {l3 = 256; r1 = 18; s3 = 257}
+debug.print: Region.new()
+debug.print: {l4 = 256; r1 = 18; r2 = 17; s4 = 257}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 256; s1 = 257}
+debug.print: SM.grow(16)
+debug.print: {l2 = 256; s2 = 257}
+debug.print: Region.new()
+debug.print: {l3 = 256; r1 = 19; s3 = 257}
+debug.print: Region.new()
+debug.print: {l4 = 256; r1 = 19; r2 = 17; s4 = 257}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: =============
+debug.print: do nothing
+debug.print: {l1 = 256; s1 = 257}
+debug.print: SM.grow(16)
+debug.print: {l2 = 256; s2 = 257}
+debug.print: Region.new()
+debug.print: {l3 = 256; r1 = 20; s3 = 257}
+debug.print: Region.new()
+debug.print: {l4 = 256; r1 = 20; r2 = 17; s4 = 257}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-mem-rts-stats.drun-run.ok b/test/run-drun/ok/stable-mem-rts-stats.drun-run.ok
new file mode 100644
index 00000000000..b58413c7edb
--- /dev/null
+++ b/test/run-drun/ok/stable-mem-rts-stats.drun-run.ok
@@ -0,0 +1,10 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: {l1 = 0; s1 = 0}
+debug.print: {l2 = 16; s2 = 16}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {l1 = 16; s1 = 18}
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: pattern failed
+debug.print: {l1 = 16; s1 = 18}
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: pattern failed
+debug.print: {l1 = 16; s1 = 18}
+ingress Err: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: pattern failed
diff --git a/test/run-drun/ok/stable-mem-rts-stats.tc.ok b/test/run-drun/ok/stable-mem-rts-stats.tc.ok
new file mode 100644
index 00000000000..5706232e25e
--- /dev/null
+++ b/test/run-drun/ok/stable-mem-rts-stats.tc.ok
@@ -0,0 +1,4 @@
+stable-mem-rts-stats.mo:7.7-7.8: warning [M0145], this pattern of type
+  Nat64
+does not cover value
+  1 or 2 or _
diff --git a/test/run-drun/ok/stable-overflow.drun-run.ok b/test/run-drun/ok/stable-overflow.drun-run.ok
new file mode 100644
index 00000000000..2048ea0a7e3
--- /dev/null
+++ b/test/run-drun/ok/stable-overflow.drun-run.ok
@@ -0,0 +1,9 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory offset out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory range out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory range out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory range out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory offset out of bounds
+debug.print: IC0503: Canister rwlgt-iiaaa-aaaaa-aaaaa-cai trapped explicitly: StableMemory offset out of bounds
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-region-blob.drun-run.ok b/test/run-drun/ok/stable-region-blob.drun-run.ok
new file mode 100644
index 00000000000..4ee32ff971d
--- /dev/null
+++ b/test/run-drun/ok/stable-region-blob.drun-run.ok
@@ -0,0 +1,24 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...0
+debug.print: {new = 1; old = 0; size = 1}
+debug.print: ...upgraded1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 1}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...1
+debug.print: {new = 2; old = 1; size = 2}
+debug.print: ...upgraded2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 2}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...2
+debug.print: {new = 3; old = 2; size = 3}
+debug.print: ...upgraded3
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {testBounds = 3}
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading...3
+debug.print: {new = 4; old = 3; size = 4}
+debug.print: ...upgraded4
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-region-migration.drun.ok b/test/run-drun/ok/stable-region-migration.drun.ok
new file mode 100644
index 00000000000..c1d3b571c59
--- /dev/null
+++ b/test/run-drun/ok/stable-region-migration.drun.ok
@@ -0,0 +1,9 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: reqPages = 384
+debug.print: actor0: init'ed.
+ingress Completed: Reply: 0x4449444c0000
+debug.print: reqPages = 384
+debug.print: M.size() = 384
+debug.print: actor1: checked region0.
+debug.print: actor1: allocated another region.
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-region-misc.drun-run.ok b/test/run-drun/ok/stable-region-misc.drun-run.ok
new file mode 100644
index 00000000000..85f59ddbe03
--- /dev/null
+++ b/test/run-drun/ok/stable-region-misc.drun-run.ok
@@ -0,0 +1,25 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: Begin
+debug.print: Created region 16
+debug.print: Created regions 17 and 18
+debug.print: Grew all regions.
+debug.print: Grew all regions, again.
+debug.print: Storing data into region 2.
+debug.print: Loading data from region 2.
+debug.print: 137
+debug.print: Done.
+debug.print: Storing data into region 3.
+debug.print: Loading data from region 3.
+debug.print: 138
+debug.print: Done.
+debug.print: Storing data into region 4.
+debug.print: Loading data from region 4.
+debug.print: 139
+debug.print: Done.
+debug.print: re-Loading data from region 2.
+debug.print: 137
+debug.print: Done.
+debug.print: re-Loading data from region 3.
+debug.print: 138
+debug.print: Done.
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-regions-are-isolated.drun-run.ok b/test/run-drun/ok/stable-regions-are-isolated.drun-run.ok
new file mode 100644
index 00000000000..2e4d186b123
--- /dev/null
+++ b/test/run-drun/ok/stable-regions-are-isolated.drun-run.ok
@@ -0,0 +1,7 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: grow three big regions (including region0).
+debug.print: grow three big regions: done.
+debug.print: storing a big blob in each region.
+debug.print: loading the big blob back from each region.
+debug.print: success. done.
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-regions-max-alloc.drun-run.ok b/test/run-drun/ok/stable-regions-max-alloc.drun-run.ok
new file mode 100644
index 00000000000..c7ec8d9c511
--- /dev/null
+++ b/test/run-drun/ok/stable-regions-max-alloc.drun-run.ok
@@ -0,0 +1,4 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: {alloced = 524_288}
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-regions-new-each-upgrade.drun-run.ok b/test/run-drun/ok/stable-regions-new-each-upgrade.drun-run.ok
new file mode 100644
index 00000000000..a5fa9b83a28
--- /dev/null
+++ b/test/run-drun/ok/stable-regions-new-each-upgrade.drun-run.ok
@@ -0,0 +1,28 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+debug.print: sanity check. n=0
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading... calling Region.new(), n=0
+ingress Completed: Reply: 0x4449444c0000
+debug.print: sanity check. n=1
+debug.print:  unwrapping. i=0
+debug.print: 16
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading... calling Region.new(), n=1
+ingress Completed: Reply: 0x4449444c0000
+debug.print: sanity check. n=2
+debug.print:  unwrapping. i=0
+debug.print: 16
+debug.print:  unwrapping. i=1
+debug.print: 17
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading... calling Region.new(), n=2
+ingress Completed: Reply: 0x4449444c0000
+debug.print: sanity check. n=3
+debug.print:  unwrapping. i=0
+debug.print: 16
+debug.print:  unwrapping. i=1
+debug.print: 17
+debug.print:  unwrapping. i=2
+debug.print: 18
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-regions-one.drun-run.ok b/test/run-drun/ok/stable-regions-one.drun-run.ok
new file mode 100644
index 00000000000..5d4b05ebbf5
--- /dev/null
+++ b/test/run-drun/ok/stable-regions-one.drun-run.ok
@@ -0,0 +1,8 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
+debug.print: pre
+debug.print: #pre({size = 8})
+debug.print: #post({id = 65_535; reg_size = 8; size = 8})
+ingress Completed: Reply: 0x4449444c0000
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/ok/stable-regions-upgrade.drun-run.ok b/test/run-drun/ok/stable-regions-upgrade.drun-run.ok
new file mode 100644
index 00000000000..6b55148375d
--- /dev/null
+++ b/test/run-drun/ok/stable-regions-upgrade.drun-run.ok
@@ -0,0 +1,12 @@
+ingress Completed: Reply: 0x4449444c016c01b3c4b1f204680100010a00000000000000000101
+debug.print: grow three big regions (including region0).
+debug.print: grow three big regions: done.
+ingress Completed: Reply: 0x4449444c0000
+debug.print: sanity check. n=0
+ingress Completed: Reply: 0x4449444c0000
+debug.print: upgrading... n=0
+debug.print: grow three big regions (including region0).
+debug.print: grow three big regions: done.
+ingress Completed: Reply: 0x4449444c0000
+debug.print: sanity check. n=1
+ingress Completed: Reply: 0x4449444c0000
diff --git a/test/run-drun/region0-big-blob.mo b/test/run-drun/region0-big-blob.mo
new file mode 100644
index 00000000000..a6972af5107
--- /dev/null
+++ b/test/run-drun/region0-big-blob.mo
@@ -0,0 +1,25 @@
+//MOC-FLAG --stable-regions --sanity-checks
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  let 0 = StableMemory.grow(65535-128);
+  let n = 2**31+16384;
+  let o:Nat64 = 2**31+16384 - 1;
+  StableMemory.storeNat8(o, 255);
+  let b = StableMemory.loadBlob(0, n);
+  StableMemory.storeNat8(o, 0);
+  assert StableMemory.loadNat8(o) == 0;
+  StableMemory.storeBlob(0, b);
+  P.debugPrint(debug_show StableMemory.loadNat8(o));
+  assert StableMemory.loadNat8(o) == 255;
+  assert b.size() == n;
+  P.debugPrint("ok");
+  assert false;
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
diff --git a/test/run-drun/region0-overflow.mo b/test/run-drun/region0-overflow.mo
new file mode 100644
index 00000000000..7aa93069406
--- /dev/null
+++ b/test/run-drun/region0-overflow.mo
@@ -0,0 +1,103 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import {grow; size; loadBlob} "stable-mem/StableMemory";
+
+// test for correct out-of-bounds detection.
+// Both ok() and bad() should fail for the same reason (but don't)
+// ok() reports RTS error: region access out of bounds, caught by RTS
+// bad() reports: "stable memory out of bounds", not caught by RTS but by IC
+
+// I think one could exploit this bound check failure to break isolation between regions...
+
+actor {
+    let n = grow(1);
+    assert n == 0;
+    assert size() == 1;
+
+    public func ok() : async () {
+        ignore loadBlob(0xFFFF, 0);
+        ignore loadBlob(0xFFFF, 1);
+        ignore loadBlob(0xFFFE, 2);
+        ignore loadBlob(0xFFFE, 1);
+        ignore loadBlob(0x0,0x1_0000);
+    };
+
+    public func trap1() : async () {
+      ignore loadBlob(0xFFFF_FFFF_FFFF_FFFF, 0);
+      assert false
+    };
+
+    public func trap2() : async () {
+      ignore loadBlob(0xFFFF, 2);
+      assert false
+    };
+
+    public func trap3() : async () {
+      ignore loadBlob(0xFFFE, 3);
+      assert false
+    };
+
+    public func trap4() : async () {
+      ignore loadBlob(0x0, 0x1_0001);
+      assert false;
+    };
+
+    public func trap5() : async () {
+      ignore loadBlob(0x1_0000, 0);
+      assert false;
+    };
+
+    public func trap6() : async () {
+      ignore loadBlob(0x1_0000, 1);
+      assert false;
+    };
+
+    public func go() : async () {
+
+      try await ok()
+      catch e {
+        assert false;
+      };
+
+      try await trap1()
+      catch e {
+         P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap2()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap3()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap4()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap5()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap6()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+    }
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL ingress go "DIDL\x00\x00"
+
diff --git a/test/run-drun/region0-rts-stats.mo b/test/run-drun/region0-rts-stats.mo
new file mode 100644
index 00000000000..8224f9123c8
--- /dev/null
+++ b/test/run-drun/region0-rts-stats.mo
@@ -0,0 +1,23 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+  let s1 = P.rts_stable_memory_size();
+  let l1 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s1;l1}));
+  let 0 = StableMemory.grow(16);
+  stable var v = StableMemory.loadBlob(0, 65536);
+  let s2 = P.rts_stable_memory_size();
+  ();
+  let l2 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s2;l2}));
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL upgrade ""
+//CALL upgrade ""
+//CALL upgrade ""
\ No newline at end of file
diff --git a/test/run-drun/region0-stable-mem-blob.mo b/test/run-drun/region0-stable-mem-blob.mo
new file mode 100644
index 00000000000..9bb09d70d0b
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-blob.mo
@@ -0,0 +1,118 @@
+//MOC-FLAG --stable-regions
+
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Blob {
+    let size = P.nat64ToNat(n);
+    let a = P.Array_tabulate(size, func i { P.natToNat8(i % 256) });
+    P.arrayToBlob(a);
+  };
+
+  func zeroOfNat64(n : Nat64) : Blob {
+    let size = P.nat64ToNat(n);
+    let a = P.Array_tabulate(size, func i { 0 });
+    P.arrayToBlob(a);
+  };
+
+  let inc : Nat64 = 8;
+
+  var i : Nat64 = 0;
+  var size : Nat64 = 0;
+  let max = n * 65536;
+  while (i + size < max) {
+    let v = valOfNat64(size);
+    //P.debugPrint("store blob, load blob.");
+    StableMemory.storeBlob(i, v);
+    assert (StableMemory.loadBlob(i, P.nat64ToNat(size)) == v);
+    i += size;
+    size += 1;
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    var size : Nat64 = 0;
+    let max = i + 65536;
+    while (i + size < max) {
+      if false {
+      P.debugPrint("i = " # debug_show i # "; size = " # debug_show size);
+      P.debugPrint("inspecting the blob at offset " # debug_show i # " and expecting all zeros...");
+      P.debugPrint(debug_show StableMemory.loadBlob(i, P.nat64ToNat(size)));
+      };
+
+      assert (StableMemory.loadBlob(i, P.nat64ToNat(size)) == zeroOfNat64(size));
+      let blob = valOfNat64(size);
+      // P.debugPrint("storing the blob " # debug_show blob # " at offset " # debug_show i);
+
+      StableMemory.storeBlob(i, blob);
+      i += size;
+      size += 1;
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 7;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadBlob(i, 8);
+        };
+        P.debugPrint(debug_show{loadBlob = (i,8); size = n });
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeBlob(i, valOfNat64(8));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-custom-max.mo b/test/run-drun/region0-stable-mem-custom-max.mo
new file mode 100644
index 00000000000..0cb0f1ce7e2
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-custom-max.mo
@@ -0,0 +1,25 @@
+//MOC-FLAG --stable-regions --max-stable-pages 16384
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+
+actor {
+  let max : Nat64 = 16384 - 128; // -128 to account for stable_regions metadata block
+  public query func testGrow() : async () {
+    assert 0  == StableMemory.grow(max - 1);
+    assert (max - 1) == StableMemory.grow(1);
+    assert max == StableMemory.size();
+    assert max == StableMemory.grow(0);
+    assert 0xFFFF_FFFF_FFFF_FFFF == StableMemory.grow(1);
+    assert 0xFFFF_FFFF_FFFF_FFFF == StableMemory.grow(1024);
+  }
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL query testGrow "DIDL\x00\x00"
+
+
diff --git a/test/run-drun/region0-stable-mem-float.mo b/test/run-drun/region0-stable-mem-float.mo
new file mode 100644
index 00000000000..0764e11170c
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-float.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Float { P.intToFloat(P.nat64ToNat(n)); };
+  let inc : Nat64 = 8;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadFloat(i) == v);
+    StableMemory.storeFloat(i, -v);
+    assert (StableMemory.loadFloat(i) == -v);
+    StableMemory.storeFloat(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadFloat(i) == 0);
+      StableMemory.storeFloat(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 7;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadFloat(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeFloat(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-grow-fail.mo b/test/run-drun/region0-stable-mem-grow-fail.mo
new file mode 100644
index 00000000000..a71e36d9448
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-grow-fail.mo
@@ -0,0 +1,21 @@
+//MOC-FLAG --stable-regions --max-stable-pages 16777216
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+
+// tests that allocation failure is reported when the replica fails to allocate
+// (even when --max-stable-pages not exceeded)
+actor {
+  assert (167777216 > 128*65536);
+  let m = StableMemory.grow(128*65536); //128 GB, should fail!
+  if (m != 0xFFFF_FFFF_FFFF_FFFF) {
+    // force IC stable memory out of bounds error
+    P.debugPrint(debug_show m);
+    ignore StableMemory.loadNat8(128*65536-1);
+  }
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
diff --git a/test/run-drun/region0-stable-mem-grow.mo b/test/run-drun/region0-stable-mem-grow.mo
new file mode 100644
index 00000000000..38923929708
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-grow.mo
@@ -0,0 +1,62 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+
+actor {
+
+  stable var n : Nat64 = 0;
+  stable let a0 = P.Array_init(65536, 1000);
+  stable let a1 = P.Array_init(65536, 1000);
+  stable let b = [a0, a1];
+  stable let c = [b, b];
+
+  system func preupgrade() {
+    P.debugPrint("upgrading from " # debug_show n);
+    let m = StableMemory.grow(1);
+
+    // check all pages clear
+    var i : Nat64 = 0;
+    let max = StableMemory.size() * 65536;
+    while (i < max) {
+      assert (StableMemory.loadNat32(i) == 0);
+      i += 4
+    };
+
+    n += 1;
+    a0[42] := 42;
+    a1[25] := 25;
+  };
+
+  public func testGrow() : async () {
+    var i : Nat64 = 0;
+    while (i < 10) {
+      var pre = StableMemory.size();
+      var post = StableMemory.grow(i);
+      assert StableMemory.size() == StableMemory.grow(0);
+      assert post == pre;
+      assert StableMemory.size() == pre + i;
+      i += 1;
+    }
+  };
+
+  system func postupgrade() {
+    assert a0[42] == 42;
+    assert a1[25] == 25;
+    P.debugPrint("to " # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testGrow "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testGrow "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testGrow "DIDL\x00\x00"
+//CALL upgrade ""
diff --git a/test/run-drun/region0-stable-mem-int16.mo b/test/run-drun/region0-stable-mem-int16.mo
new file mode 100644
index 00000000000..116fd6f5e8d
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-int16.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Int16 { P.intToInt16(P.nat64ToNat(n % 65536) : Int - 32768) };
+  let inc : Nat64 = 2;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadInt16(i) == v);
+    StableMemory.storeInt16(i, ^v);
+    assert (StableMemory.loadInt16(i) == ^v);
+    StableMemory.storeInt16(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadInt16(i) == 0);
+      StableMemory.storeInt16(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 1;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadInt16(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeInt16(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-int32.mo b/test/run-drun/region0-stable-mem-int32.mo
new file mode 100644
index 00000000000..9d79151c929
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-int32.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Int32 { P.intToInt32(P.nat64ToNat(n) : Int - 32768)};
+  let inc : Nat64 = 4;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadInt32(i) == v);
+    StableMemory.storeInt32(i, ^v);
+    assert (StableMemory.loadInt32(i) == ^v);
+    StableMemory.storeInt32(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadInt32(i) == 0);
+      StableMemory.storeInt32(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 3;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadInt32(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeInt32(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-int64.mo b/test/run-drun/region0-stable-mem-int64.mo
new file mode 100644
index 00000000000..65e2c844004
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-int64.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Int64 { P.intToInt64(P.nat64ToNat(n) : Int - 32768)};
+  let inc : Nat64 = 8;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadInt64(i) == v);
+    StableMemory.storeInt64(i, ^v);
+    assert (StableMemory.loadInt64(i) == ^v);
+    StableMemory.storeInt64(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadInt64(i) == 0);
+      StableMemory.storeInt64(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 7;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadInt64(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeInt64(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-int8.mo b/test/run-drun/region0-stable-mem-int8.mo
new file mode 100644
index 00000000000..b7cbe9afdf8
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-int8.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Int8 { P.intToInt8(P.nat64ToNat(n % 256) : Int - 128); };
+  let inc : Nat64 = 1;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadInt8(i) == v);
+    StableMemory.storeInt8(i, ^v);
+    assert (StableMemory.loadInt8(i) == ^v);
+    StableMemory.storeInt8(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadInt8(i) == 0);
+      StableMemory.storeInt8(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 0;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadInt8(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeInt8(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-max.mo b/test/run-drun/region0-stable-mem-max.mo
new file mode 100644
index 00000000000..bf812ea9202
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-max.mo
@@ -0,0 +1,25 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+
+actor {
+  let max : Nat64 = 65536 - 128; // -128 to account for stable_regions metadata block
+  public query func testGrow() : async () {
+    assert 0 == StableMemory.grow(max - 1);
+    assert (max - 1) == StableMemory.grow(1);
+    assert max == StableMemory.size();
+    assert max == StableMemory.grow(0);
+    assert 0xFFFF_FFFF_FFFF_FFFF == StableMemory.grow(1);
+    assert 0xFFFF_FFFF_FFFF_FFFF == StableMemory.grow(1024);
+  }
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL query testGrow "DIDL\x00\x00"
+
+
diff --git a/test/run-drun/region0-stable-mem-nat16.mo b/test/run-drun/region0-stable-mem-nat16.mo
new file mode 100644
index 00000000000..2723f5a3513
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-nat16.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Nat16 { P.natToNat16(P.nat64ToNat(n % 65536)); };
+  let inc : Nat64 = 2;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadNat16(i) == v);
+    StableMemory.storeNat16(i, ^v);
+    assert (StableMemory.loadNat16(i) == ^v);
+    StableMemory.storeNat16(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadNat16(i) == 0);
+      StableMemory.storeNat16(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 1;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadNat16(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeNat16(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-nat32.mo b/test/run-drun/region0-stable-mem-nat32.mo
new file mode 100644
index 00000000000..8e6a451b904
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-nat32.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Nat32 { P.natToNat32(P.nat64ToNat(n)) };
+  let inc : Nat64 = 4;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadNat32(i) == v);
+    StableMemory.storeNat32(i, ^v);
+    assert (StableMemory.loadNat32(i) == ^v);
+    StableMemory.storeNat32(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadNat32(i) == 0);
+      StableMemory.storeNat32(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 3;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadNat32(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeNat32(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-nat64.mo b/test/run-drun/region0-stable-mem-nat64.mo
new file mode 100644
index 00000000000..6b16703ded9
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-nat64.mo
@@ -0,0 +1,93 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Nat64 { P.natToNat64(P.nat64ToNat(n)); };
+  let inc : Nat64 = 8;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadNat64(i) == v);
+    StableMemory.storeNat64(i, ^v);
+    assert (StableMemory.loadNat64(i) == ^v);
+    StableMemory.storeNat64(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadNat64(i) == 0);
+      StableMemory.storeNat64(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 7;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadNat64(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeNat64(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+
diff --git a/test/run-drun/region0-stable-mem-nat8.mo b/test/run-drun/region0-stable-mem-nat8.mo
new file mode 100644
index 00000000000..80d8d7ea62a
--- /dev/null
+++ b/test/run-drun/region0-stable-mem-nat8.mo
@@ -0,0 +1,92 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+
+import Region "stable-region/Region";
+
+actor {
+  stable var n : Nat64 = 0;
+  assert (n == StableMemory.size());
+
+  func valOfNat64(n : Nat64) : Nat8 { P.natToNat8(P.nat64ToNat(n % 256)); };
+  let inc : Nat64 = 1;
+
+  var i : Nat64 = 0;
+  let max = n * 65536;
+  while (i < max) {
+    let v = valOfNat64(i);
+    assert (StableMemory.loadNat8(i) == v);
+    StableMemory.storeNat8(i, ^v);
+    assert (StableMemory.loadNat8(i) == ^v);
+    StableMemory.storeNat8(i, v);
+    i += inc
+  };
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = StableMemory.grow(1);
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
+    assert (n == StableMemory.size());
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    let max = i + 65536;
+    while (i < max) {
+      assert (StableMemory.loadNat8(i) == 0);
+      StableMemory.storeNat8(i, valOfNat64(i));
+      i += inc
+    };
+
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == StableMemory.size());
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 0;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore StableMemory.loadNat8(i);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async StableMemory.storeNat8(i, valOfNat64(i));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
diff --git a/test/run-drun/regions-base-high.mo b/test/run-drun/regions-base-high.mo
new file mode 100644
index 00000000000..6cd2bda2154
--- /dev/null
+++ b/test/run-drun/regions-base-high.mo
@@ -0,0 +1,32 @@
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+import Region "stable-region/Region";
+actor {
+  P.debugPrint("=============");
+  P.debugPrint ("do nothing");
+  let s1 = P.rts_stable_memory_size();
+  let l1 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s1;l1}));
+  P.debugPrint ("SM.grow(1)");
+  let _ = StableMemory.grow(1);
+  let s2 = P.rts_stable_memory_size();
+  let l2 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s2;l2}));
+  P.debugPrint ("let r1=Region.new()");
+  stable let r1 = Region.new();
+  let s3 = P.rts_stable_memory_size();
+  let l3 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s3;l3;r1 = Region.id(r1)}));
+  P.debugPrint ("let _ = Region.grow(r1,1)");
+  let _ = Region.grow(r1, 1);
+  let s4 = P.rts_stable_memory_size();
+  let l4 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s4;l4;r1 = Region.id(r1)}));
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL upgrade ""
diff --git a/test/run-drun/regions-base-low.mo b/test/run-drun/regions-base-low.mo
new file mode 100644
index 00000000000..b7a8121fcc6
--- /dev/null
+++ b/test/run-drun/regions-base-low.mo
@@ -0,0 +1,33 @@
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+import Region "stable-region/Region";
+actor {
+  P.debugPrint("=============");
+  P.debugPrint ("do nothing");
+  let s1 = P.rts_stable_memory_size();
+  let l1 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s1;l1}));
+  P.debugPrint ("SM.grow(0)");
+  let _ = StableMemory.grow(0);
+  let s2 = P.rts_stable_memory_size();
+  let l2 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s2;l2}));
+  P.debugPrint ("let r1=Region.new()");
+  stable let r1 = Region.new();
+  let s3 = P.rts_stable_memory_size();
+  let l3 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s3;l3;r1 = Region.id(r1)}));
+  P.debugPrint ("let _ = Region.grow(r1,1)");
+  let _ = Region.grow(r1, 1);
+  let s4 = P.rts_stable_memory_size();
+  let l4 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s4;l4;r1 = Region.id(r1)}));
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL upgrade ""
+
diff --git a/test/run-drun/regions-pay-as-you-go.mo b/test/run-drun/regions-pay-as-you-go.mo
new file mode 100644
index 00000000000..e358a9002a7
--- /dev/null
+++ b/test/run-drun/regions-pay-as-you-go.mo
@@ -0,0 +1,37 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+import Region "stable-region/Region";
+actor {
+
+  P.debugPrint("=============");
+  P.debugPrint ("do nothing");
+  let s1 = P.rts_stable_memory_size();
+  let l1 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s1;l1}));
+  P.debugPrint ("SM.grow(16)");
+  let _ = StableMemory.grow(16);
+  let s2 = P.rts_stable_memory_size();
+  let l2 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s2;l2}));
+  P.debugPrint ("Region.new()");
+  let r1 = Region.new();
+  let s3 = P.rts_stable_memory_size();
+  let l3 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s3;l3;r1 = Region.id(r1)}));
+  P.debugPrint ("Region.new()");
+  stable let r2 = Region.new();
+  let s4 = P.rts_stable_memory_size();
+  let l4 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s4;l4;r1 = Region.id(r1); r2 = Region.id(r2)}));
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL upgrade ""
+//CALL upgrade ""
+//CALL upgrade ""
diff --git a/test/run-drun/stable-mem-big-blob.mo b/test/run-drun/stable-mem-big-blob.mo
new file mode 100644
index 00000000000..9db89c50620
--- /dev/null
+++ b/test/run-drun/stable-mem-big-blob.mo
@@ -0,0 +1,27 @@
+//MOC-FLAG --sanity-checks
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+
+  let 0 = StableMemory.grow(65535-128);
+  let n = 2**31+16384;
+  let o:Nat64 = 2**31+16384 - 1;
+  StableMemory.storeNat8(o, 255);
+  let b = StableMemory.loadBlob(0, n);
+  StableMemory.storeNat8(o, 0);
+  assert StableMemory.loadNat8(o) == 0;
+  StableMemory.storeBlob(0, b);
+  P.debugPrint(debug_show StableMemory.loadNat8(o));
+  assert StableMemory.loadNat8(o) == 255;
+  assert b.size() == n;
+  P.debugPrint("ok");
+  assert false;
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+
diff --git a/test/run-drun/stable-mem-blob.mo b/test/run-drun/stable-mem-blob.mo
index 9b17ecf4c7a..f13fe78fc2f 100644
--- a/test/run-drun/stable-mem-blob.mo
+++ b/test/run-drun/stable-mem-blob.mo
@@ -24,6 +24,7 @@ actor {
   let max = n * 65536;
   while (i + size < max) {
     let v = valOfNat64(size);
+    //P.debugPrint("store blob, load blob.");
     StableMemory.storeBlob(i, v);
     assert (StableMemory.loadBlob(i, P.nat64ToNat(size)) == v);
     i += size;
@@ -47,8 +48,17 @@ actor {
     var size : Nat64 = 0;
     let max = i + 65536;
     while (i + size < max) {
+      if false {
+      P.debugPrint("i = " # debug_show i # "; size = " # debug_show size);
+      P.debugPrint("inspecting the blob at offset " # debug_show i # " and expecting all zeros...");
+      P.debugPrint(debug_show StableMemory.loadBlob(i, P.nat64ToNat(size)));
+      };
+
       assert (StableMemory.loadBlob(i, P.nat64ToNat(size)) == zeroOfNat64(size));
-      StableMemory.storeBlob(i, valOfNat64(size));
+      let blob = valOfNat64(size);
+      // P.debugPrint("storing the blob " # debug_show blob # " at offset " # debug_show i);
+
+      StableMemory.storeBlob(i, blob);
       i += size;
       size += 1;
     };
diff --git a/test/run-drun/stable-mem-custom-max.mo b/test/run-drun/stable-mem-custom-max.mo
index 0fa0e220ccf..31084221b95 100644
--- a/test/run-drun/stable-mem-custom-max.mo
+++ b/test/run-drun/stable-mem-custom-max.mo
@@ -4,7 +4,7 @@ import StableMemory "stable-mem/StableMemory";
 
 actor {
   let max : Nat64 = 16384;
-  public func testGrow() : async () {
+  public query func testGrow() : async () {
     assert 0  == StableMemory.grow(max - 1);
     assert (max - 1) == StableMemory.grow(1);
     assert max == StableMemory.size();
@@ -20,7 +20,7 @@ actor {
 // too slow on ic-ref-run:
 //SKIP comp-ref
 
-//CALL ingress testGrow "DIDL\x00\x00"
-//CALL upgrade ""
+//CALL query testGrow "DIDL\x00\x00"
+
 
 
diff --git a/test/run-drun/stable-mem-grow-fail.mo b/test/run-drun/stable-mem-grow-fail.mo
new file mode 100644
index 00000000000..bbb0fcf60c7
--- /dev/null
+++ b/test/run-drun/stable-mem-grow-fail.mo
@@ -0,0 +1,21 @@
+//MOC-FLAG --max-stable-pages 16777216
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+
+// tests that allocation failure is reported when the replica fails to allocate
+// (even when --max-stable-pages not exceeded)
+actor {
+  assert (167777216 > 128*65536);
+  let m = StableMemory.grow(128*65536); //128 GB, should fail!
+  if (m != 0xFFFF_FFFF_FFFF_FFFF) {
+    // force IC stable memory out of bounds error
+    P.debugPrint(debug_show m);
+    ignore StableMemory.loadNat8(128*65536-1);
+  }
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
diff --git a/test/run-drun/stable-mem-max.mo b/test/run-drun/stable-mem-max.mo
index 86c79e4a3cf..006ee44b881 100644
--- a/test/run-drun/stable-mem-max.mo
+++ b/test/run-drun/stable-mem-max.mo
@@ -3,7 +3,7 @@ import StableMemory "stable-mem/StableMemory";
 
 actor {
   let max : Nat64 = 65536;
-  public func testGrow() : async () {
+  public query func testGrow() : async () {
     assert 0  == StableMemory.grow(max - 1);
     assert (max - 1) == StableMemory.grow(1);
     assert max == StableMemory.size();
@@ -19,6 +19,6 @@ actor {
 // too slow on ic-ref-run:
 //SKIP comp-ref
 
-//CALL ingress testGrow "DIDL\x00\x00"
+//CALL query testGrow "DIDL\x00\x00"
 
 
diff --git a/test/run-drun/stable-mem-nat8.mo b/test/run-drun/stable-mem-nat8.mo
index cb46e3bb4e1..990765483fe 100644
--- a/test/run-drun/stable-mem-nat8.mo
+++ b/test/run-drun/stable-mem-nat8.mo
@@ -1,7 +1,9 @@
 import P "mo:⛔";
 import StableMemory "stable-mem/StableMemory";
-actor {
 
+import Region "stable-region/Region";
+
+actor {
   stable var n : Nat64 = 0;
   assert (n == StableMemory.size());
 
@@ -22,13 +24,11 @@ actor {
   system func preupgrade() {
     P.debugPrint("upgrading..." # debug_show n);
     let m = StableMemory.grow(1);
-
     assert (n == m);
 
     n += 1;
 
     P.debugPrint(debug_show {old = m; new = n; size = StableMemory.size()});
-
     assert (n == StableMemory.size());
 
     // check new page is clear
@@ -89,4 +89,3 @@ actor {
 //CALL upgrade ""
 //CALL ingress testBounds "DIDL\x00\x00"
 //CALL upgrade ""
-
diff --git a/test/run-drun/stable-mem-pay-as-you-go.mo b/test/run-drun/stable-mem-pay-as-you-go.mo
new file mode 100644
index 00000000000..270d0033e3b
--- /dev/null
+++ b/test/run-drun/stable-mem-pay-as-you-go.mo
@@ -0,0 +1,36 @@
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+import Region "stable-region/Region";
+actor {
+  P.debugPrint("=============");
+  P.debugPrint ("do nothing");
+  let s1 = P.rts_stable_memory_size();
+  let l1 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s1;l1}));
+  P.debugPrint ("SM.grow(16)");
+  let _ = StableMemory.grow(16);
+  let s2 = P.rts_stable_memory_size();
+  let l2 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s2;l2}));
+  P.debugPrint ("Region.new()");
+  let r1 = Region.new();
+  let s3 = P.rts_stable_memory_size();
+  let l3 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s3;l3;r1 = Region.id(r1)}));
+  P.debugPrint ("Region.new()");
+  stable let r2 = Region.new();
+  let s4 = P.rts_stable_memory_size();
+  let l4 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s4;l4;r1 = Region.id(r1); r2 = Region.id(r2)}));
+
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL upgrade ""
+//CALL upgrade ""
+//CALL upgrade ""
diff --git a/test/run-drun/stable-mem-rts-stats.mo b/test/run-drun/stable-mem-rts-stats.mo
new file mode 100644
index 00000000000..5d8da6368c7
--- /dev/null
+++ b/test/run-drun/stable-mem-rts-stats.mo
@@ -0,0 +1,22 @@
+import P "mo:⛔";
+import StableMemory "stable-mem/StableMemory";
+actor {
+  let s1 = P.rts_stable_memory_size();
+  let l1 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s1;l1}));
+  let 0 = StableMemory.grow(16);
+  stable var v = StableMemory.loadBlob(0, 65536);
+  let s2 = P.rts_stable_memory_size();
+  ();
+  let l2 = P.rts_logical_stable_memory_size();
+  P.debugPrint (debug_show({s2;l2}));
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL upgrade ""
+//CALL upgrade ""
+//CALL upgrade ""
\ No newline at end of file
diff --git a/test/run-drun/stable-mem/StableMemory.mo b/test/run-drun/stable-mem/StableMemory.mo
index ef6f485f326..a3d6bb2ed44 100644
--- a/test/run-drun/stable-mem/StableMemory.mo
+++ b/test/run-drun/stable-mem/StableMemory.mo
@@ -3,6 +3,7 @@ import Prim "mo:⛔";
 module {
 
   public let size = Prim.stableMemorySize;
+  //public let region = Prim.stableMemoryRegion;
   public let grow = Prim.stableMemoryGrow;
 
   public let loadNat32 = Prim.stableMemoryLoadNat32;
diff --git a/test/run-drun/stable-overflow.mo b/test/run-drun/stable-overflow.mo
new file mode 100644
index 00000000000..b6c25747f89
--- /dev/null
+++ b/test/run-drun/stable-overflow.mo
@@ -0,0 +1,102 @@
+import P "mo:⛔";
+import {grow; size; loadBlob} "stable-mem/StableMemory";
+
+// test for correct out-of-bounds detection.
+// Both ok() and bad() should fail for the same reason (but don't)
+// ok() reports RTS error: region access out of bounds, caught by RTS
+// bad() reports: "stable memory out of bounds", not caught by RTS but by IC
+
+// I think one could exploit this bound check failure to break isolation between regions...
+
+actor {
+    let n = grow(1);
+    assert n == 0;
+    assert size() == 1;
+
+    public func ok() : async () {
+        ignore loadBlob(0xFFFF, 0);
+        ignore loadBlob(0xFFFF, 1);
+        ignore loadBlob(0xFFFE, 2);
+        ignore loadBlob(0xFFFE, 1);
+        ignore loadBlob(0x0,0x1_0000);
+    };
+
+    public func trap1() : async () {
+      ignore loadBlob(0xFFFF_FFFF_FFFF_FFFF, 0);
+      assert false
+    };
+
+    public func trap2() : async () {
+      ignore loadBlob(0xFFFF, 2);
+      assert false
+    };
+
+    public func trap3() : async () {
+      ignore loadBlob(0xFFFE, 3);
+      assert false
+    };
+
+    public func trap4() : async () {
+      ignore loadBlob(0x0, 0x1_0001);
+      assert false;
+    };
+
+    public func trap5() : async () {
+      ignore loadBlob(0x1_0000, 0);
+      assert false;
+    };
+
+    public func trap6() : async () {
+      ignore loadBlob(0x1_0000, 1);
+      assert false;
+    };
+
+    public func go() : async () {
+
+      try await ok()
+      catch e {
+        assert false;
+      };
+
+      try await trap1()
+      catch e {
+         P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap2()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap3()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap4()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap5()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+      try await trap6()
+      catch e {
+        P.debugPrint(P.errorMessage e);
+      };
+
+    }
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL ingress go "DIDL\x00\x00"
+
diff --git a/test/run-drun/stable-region-blob.mo b/test/run-drun/stable-region-blob.mo
new file mode 100644
index 00000000000..fcb8c60f771
--- /dev/null
+++ b/test/run-drun/stable-region-blob.mo
@@ -0,0 +1,109 @@
+//MOC-FLAG --stable-regions
+
+import P "mo:⛔";
+import Region "stable-region/Region";
+actor {
+
+  stable var n : Nat64 = 0;
+
+  stable let r = Region.new();
+
+  assert (n == Region.size(r));
+
+  func valOfNat64(n : Nat64) : Blob {
+    let size = P.nat64ToNat(n);
+    let a = P.Array_tabulate(size, func i { P.natToNat8(i % 256) });
+    P.arrayToBlob(a);
+  };
+
+  func zeroOfNat64(n : Nat64) : Blob {
+    let size = P.nat64ToNat(n);
+    let a = P.Array_tabulate(size, func i { 0 });
+    P.arrayToBlob(a);
+  };
+
+  let inc : Nat64 = 8;
+
+  var i : Nat64 = 0;
+  var size : Nat64 = 0;
+  let max = n * 65536;
+  while (i + size < max) {
+    let v = valOfNat64(size);
+    Region.storeBlob(r, i, v);
+    assert (Region.loadBlob(r, i, P.nat64ToNat(size)) == v);
+    i += size;
+    size += 1;
+  };
+
+
+  system func preupgrade() {
+    P.debugPrint("upgrading..." # debug_show n);
+    let m = Region.grow(r, 1);
+
+    assert (n == m);
+
+    n += 1;
+
+    P.debugPrint(debug_show {old = m; new = n; size = Region.size(r)});
+
+    assert (n == Region.size(r));
+
+    // check new page is clear
+    var i : Nat64 = m * 65536;
+    var size : Nat64 = 0;
+    let max = i + 65536;
+    while (i + size < max) {
+      assert (Region.loadBlob(r, i, P.nat64ToNat(size)) == zeroOfNat64(size));
+      Region.storeBlob(r, i, valOfNat64(size));
+      i += size;
+      size += 1;
+    };
+  };
+
+  public func testBounds() : async () {
+    if (n == 0) return;
+    assert (n == Region.size(r));
+    P.debugPrint (debug_show {testBounds=n});
+    // test bounds check
+    var i : Nat64 = n * 65536 - 7;
+    let max = i + 16;
+    while (i < max) {
+      try {
+        await async {
+          ignore Region.loadBlob(r, i, 8);
+        };
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      try {
+        await async Region.storeBlob(r, i, valOfNat64(8));
+        assert false;
+      }
+      catch e {
+        assert P.errorCode e == #canister_error;
+      };
+      i += 1;
+    };
+  };
+
+  system func postupgrade() {
+    P.debugPrint("...upgraded" # debug_show n);
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress testBounds "DIDL\x00\x00"
+//CALL upgrade ""
diff --git a/test/run-drun/stable-region-migration.drun b/test/run-drun/stable-region-migration.drun
new file mode 100644
index 00000000000..4057c410b21
--- /dev/null
+++ b/test/run-drun/stable-region-migration.drun
@@ -0,0 +1,4 @@
+# SKIP ic-ref-run
+install $ID stable-region-migration/actor0.mo ""
+upgrade $ID stable-region-migration/actor1.mo ""
+
diff --git a/test/run-drun/stable-region-migration/actor0.mo b/test/run-drun/stable-region-migration/actor0.mo
new file mode 100644
index 00000000000..663b992d8eb
--- /dev/null
+++ b/test/run-drun/stable-region-migration/actor0.mo
@@ -0,0 +1,29 @@
+import P "mo:⛔";
+import M "../stable-mem/StableMemory";
+
+actor {
+
+    // measure out three blocks' worth of bytes.
+    let pageInBytes = 1 << 16 : Nat64;
+    let blockInBytes = pageInBytes * 128 : Nat64;
+    let size = blockInBytes * 3 : Nat64;
+    var i = 0 : Nat64;
+    var b = 0 : Nat8;
+
+    // Grow to the necessary number of pages.
+    let reqPages = size / pageInBytes;
+
+    P.debugPrint("reqPages = " # (debug_show reqPages));
+
+    assert M.grow(reqPages) == 0;
+    assert M.size() == reqPages;
+
+    // write byte pattern, one byte at a time.
+    while (i < size) {
+        M.storeNat8(i, b);
+        i := i + 1;
+        b := b +% 1;
+    };
+
+    P.debugPrint ("actor0: init'ed.");
+}
diff --git a/test/run-drun/stable-region-migration/actor1.mo b/test/run-drun/stable-region-migration/actor1.mo
new file mode 100644
index 00000000000..ab5d67443dd
--- /dev/null
+++ b/test/run-drun/stable-region-migration/actor1.mo
@@ -0,0 +1,40 @@
+//MOC-FLAG --stable-regions
+
+import P "mo:⛔";
+import M "../stable-mem/StableMemory";
+import Region "../stable-region/Region";
+
+actor {
+
+    // measure out three blocks' worth of bytes.
+    let pageInBytes = 1 << 16 : Nat64;
+    let blockInBytes = pageInBytes * 128 : Nat64;
+    let size = blockInBytes * 3 : Nat64;
+    var i = 0 : Nat64;
+    var b = 0 : Nat8;
+
+    // Check size for necessary number of pages.
+    let reqPages = size / pageInBytes;
+
+    P.debugPrint("reqPages = " # (debug_show reqPages));
+    P.debugPrint("M.size() = " # (debug_show M.size()));
+
+    assert M.size() == reqPages;
+
+    // Load out previously-stored byte pattern, one byte at a time.
+    // Check each byte is what we would have written, if we were repeating the same logic again.
+    while (i < size) {
+        let expected = b;
+        let loaded = M.loadNat8(i);
+        //P.debugPrint(" - " # (debug_show {i; expected; loaded}));
+        assert loaded == expected;
+        i := i + 1;
+        b := b +% 1;
+    };
+
+    P.debugPrint ("actor1: checked region0.");
+
+    stable var r1 = Region.new();
+
+    P.debugPrint ("actor1: allocated another region.");
+}
diff --git a/test/run-drun/stable-region/Region.mo b/test/run-drun/stable-region/Region.mo
new file mode 100644
index 00000000000..7422a0a3811
--- /dev/null
+++ b/test/run-drun/stable-region/Region.mo
@@ -0,0 +1,41 @@
+import Prim "mo:⛔";
+
+module {
+
+  public let new = Prim.regionNew;
+  public let id = Prim.regionId;
+
+  public let size = Prim.regionSize;
+  public let grow = Prim.regionGrow;
+
+  public let loadNat32 = Prim.regionLoadNat32;
+  public let storeNat32 = Prim.regionStoreNat32;
+
+  public let loadNat8 = Prim.regionLoadNat8;
+  public let storeNat8 = Prim.regionStoreNat8;
+
+  public let loadNat16 = Prim.regionLoadNat16;
+  public let storeNat16 = Prim.regionStoreNat16;
+
+  public let loadNat64 = Prim.regionLoadNat64;
+  public let storeNat64 = Prim.regionStoreNat64;
+
+  public let loadFloat = Prim.regionLoadFloat;
+  public let storeFloat= Prim.regionStoreFloat;
+
+  public let loadInt32 = Prim.regionLoadInt32;
+  public let storeInt32 = Prim.regionStoreInt32;
+
+  public let loadInt8 = Prim.regionLoadInt8;
+  public let storeInt8 = Prim.regionStoreInt8;
+
+  public let loadInt16 = Prim.regionLoadInt16;
+  public let storeInt16 = Prim.regionStoreInt16;
+
+  public let loadInt64 = Prim.regionLoadInt64;
+  public let storeInt64 = Prim.regionStoreInt64;
+
+  public let loadBlob = Prim.regionLoadBlob;
+  public let storeBlob = Prim.regionStoreBlob;
+
+}
diff --git a/test/run-drun/stable-region0-danger.mo b/test/run-drun/stable-region0-danger.mo
new file mode 100644
index 00000000000..7c4e7e9e88e
--- /dev/null
+++ b/test/run-drun/stable-region0-danger.mo
@@ -0,0 +1,40 @@
+//MOC-FLAG --stable-regions
+// THIS TEST IS NOT RUN ON ANY FLAVOUR
+// Its a counterexample to show what goes wrong if we naively support Region.region0() without
+// extra steps to ensure correct aliasing.
+import P "mo:⛔";
+import Region "stable-region/Region";
+import Region0 "stable-mem/StableMemory";
+
+// counter-example: shows why we can't expose region0 without further steps to ensure
+// correct aliasing of all region0 objects, pre and post upgrade
+actor {
+
+  stable let r0 = Region.region0(); // THIS WOULD BE DANGEROUS if permitted
+
+  ignore Region.grow(r0, 1);
+
+  system func preupgrade() {
+  };
+
+  public func sanityTest() {
+    // test that r01 correctly aliases r0, even after upgrade.
+    let r01 = Region.region0();
+    P.debugPrint(debug_show {s0 = Region.size(r0);  s01 = Region.size(r01)});
+    assert Region.size(r0) == Region.size(r01);
+    ignore Region.grow(r0, 1);
+    P.debugPrint(debug_show {s0 = Region.size(r0);  s01 = Region.size(r01)});
+    assert Region.size(r0) == Region.size(r01);
+  };
+}
+
+//SKIP tc
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+//SKIP run-drun
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL ingress sanityTest "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress sanityTest "DIDL\x00\x00"
diff --git a/test/run-drun/stable-regions-are-isolated.mo b/test/run-drun/stable-regions-are-isolated.mo
new file mode 100644
index 00000000000..703453e7159
--- /dev/null
+++ b/test/run-drun/stable-regions-are-isolated.mo
@@ -0,0 +1,64 @@
+//MOC-FLAG --stable-regions
+//MOC-FLAG --sanity-checks
+
+import P "mo:⛔";
+import Region "stable-region/Region";
+import Region0 "stable-mem/StableMemory";
+
+actor {
+  var r0 = Region.new();
+  var r1 = Region.new();
+  var r2 = Region.new();
+
+  let block_size_in_pages = 128 : Nat64;
+
+  P.debugPrint "grow three big regions (including region0).";
+
+  // Interleave growing regions by a block each:
+  do {
+    ignore Region.grow(r0, block_size_in_pages);
+    ignore Region.grow(r1, block_size_in_pages);
+    ignore Region.grow(r2, block_size_in_pages);
+
+    ignore Region.grow(r0, block_size_in_pages);
+    ignore Region.grow(r1, block_size_in_pages);
+    ignore Region.grow(r2, block_size_in_pages);
+
+    ignore Region.grow(r0, block_size_in_pages);
+    ignore Region.grow(r1, block_size_in_pages);
+    ignore Region.grow(r2, block_size_in_pages);
+  };
+
+  P.debugPrint "grow three big regions: done.";
+
+  func blobOfNat64(n : Nat64) : Blob {
+    let size = P.nat64ToNat(n);
+    let a = P.Array_tabulate(size, func i { P.natToNat8(i % 256) });
+    P.arrayToBlob(a);
+  };
+
+  // A blob that is the size of two region blocks.
+  let big_len = block_size_in_pages * 2 * 65536;
+  let big_blob = blobOfNat64(big_len);
+
+  P.debugPrint "storing a big blob in each region.";
+
+  Region.storeBlob(r0, 1, big_blob);
+  Region.storeBlob(r1, 0, big_blob);
+  Region.storeBlob(r2, 137, big_blob);
+
+  P.debugPrint "loading the big blob back from each region.";
+
+  assert(Region.loadBlob(r0, 1, P.nat64ToNat(big_len)) == big_blob);
+  assert(Region.loadBlob(r1, 0, P.nat64ToNat(big_len)) == big_blob);
+  assert(Region.loadBlob(r2, 137, P.nat64ToNat(big_len)) == big_blob);
+
+  P.debugPrint "success. done.";
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//xCALL ingress test "DIDL\x00\x00"
diff --git a/test/run-drun/stable-regions-max-alloc.mo b/test/run-drun/stable-regions-max-alloc.mo
new file mode 100644
index 00000000000..704f128dac4
--- /dev/null
+++ b/test/run-drun/stable-regions-max-alloc.mo
@@ -0,0 +1,30 @@
+//MOC-FLAG --stable-regions
+import P "mo:⛔";
+import {new; size } "stable-region/Region";
+
+// test region allocation is infinite
+actor {
+
+  public func go() : async() {
+    var l = 65536*8;
+    var n = 16; // first 16 regions are reserved
+    loop {
+      let r = new();
+      assert P.regionId(r) == n;
+//      P.debugPrint(debug_show {n; id = P.regionId(r)});
+      assert size(r) == 0;
+      n += 1;
+    } while (n < l);
+    P.debugPrint(debug_show {alloced = n});
+  };
+
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+
+//CALL ingress go "DIDL\x00\x00"
+
diff --git a/test/run-drun/stable-regions-new-each-upgrade.mo b/test/run-drun/stable-regions-new-each-upgrade.mo
new file mode 100644
index 00000000000..124f16a80ac
--- /dev/null
+++ b/test/run-drun/stable-regions-new-each-upgrade.mo
@@ -0,0 +1,43 @@
+//MOC-FLAG --stable-regions
+//MOC-ENV MOC_UNLOCK_PRIM=yesplease
+
+import P "mo:⛔";
+import Region "stable-region/Region";
+import Region0 "stable-mem/StableMemory";
+
+actor {
+  stable var n = 0;
+  stable let regions : [var ?Region] = [var null, null, null];
+  
+  system func preupgrade() {
+    P.debugPrint("upgrading... calling Region.new(), n=" # debug_show n);
+    regions[n] := ?Region.new();
+    n += 1;
+  };
+  func unwrap(i : Nat, ro : ?Region) : Region {
+      P.debugPrint(" unwrapping. i=" # debug_show i);      
+      switch ro {
+      case null { assert false; loop { } };
+      case (?r) r;
+      }
+  };
+  public func sanityTest() {
+    P.debugPrint("sanity check. n=" # debug_show n);
+    if (n > 0) { P.debugPrint(debug_show Region.id(unwrap(0, regions[0]))) };
+    if (n > 1) { P.debugPrint(debug_show Region.id(unwrap(1, regions[1]))) };
+    if (n > 2) { P.debugPrint(debug_show Region.id(unwrap(2, regions[2]))) };
+  };
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL ingress sanityTest "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress sanityTest "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress sanityTest "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress sanityTest "DIDL\x00\x00"
diff --git a/test/run-drun/stable-regions-one.mo b/test/run-drun/stable-regions-one.mo
new file mode 100644
index 00000000000..9ab1b75c6ce
--- /dev/null
+++ b/test/run-drun/stable-regions-one.mo
@@ -0,0 +1,43 @@
+//MOC-FLAG --stable-regions
+
+import P "mo:⛔";
+import Region "stable-region/Region";
+import Region0 "stable-mem/StableMemory";
+
+actor {
+  stable var r1 = Region.new();
+  stable var aliases = [r1, r1];
+  stable var id : Nat32 = 0xFFFF;
+  stable var size : Nat64 = 0xFFFF_FFFF;
+
+  system func preupgrade() {
+    ignore Region.grow(r1, 8);
+    assert Region.size(r1) == 8;
+    assert Region.size(r1) == Region.size(aliases[0]);
+    assert Region.size(r1) == Region.size(aliases[1]);
+    P.debugPrint("pre");
+    size := Region.size(r1);
+    P.debugPrint(debug_show (#pre{size}));
+  };
+
+  system func postupgrade() {
+    P.debugPrint(debug_show (#post{id;size;reg_size=Region.size(r1)}));
+    assert Region.size(r1) == size;
+    ignore Region.grow(r1, 8);
+    assert Region.size(r1) == 16;
+    assert Region.size(r1) == Region.size(aliases[0]);
+    assert Region.size(r1) == Region.size(aliases[1]);
+  };
+
+  public func sanityTest() {
+  };
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL ingress sanityTest "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress sanityTest "DIDL\x00\x00"
diff --git a/test/run-drun/stable-regions-upgrade.mo b/test/run-drun/stable-regions-upgrade.mo
new file mode 100644
index 00000000000..e12b3702dcf
--- /dev/null
+++ b/test/run-drun/stable-regions-upgrade.mo
@@ -0,0 +1,50 @@
+//MOC-FLAG --stable-regions
+//MOC-ENV MOC_UNLOCK_PRIM=yesplease
+
+import P "mo:⛔";
+import Region "stable-region/Region";
+import StableMemory "stable-mem/StableMemory";
+
+actor {
+  stable var n = 0;
+  stable var r1 = Region.new();
+  stable var r2 = Region.new();
+
+  let block_size_in_pages = 128 : Nat64;
+
+  P.debugPrint "grow three big regions (including region0).";
+  // Interleave growing regions by a block each:
+  do {
+    ignore StableMemory.grow(block_size_in_pages);
+    ignore Region.grow(r1, block_size_in_pages);
+    ignore Region.grow(r2, block_size_in_pages);
+
+    ignore StableMemory.grow(block_size_in_pages);
+    ignore Region.grow(r1, block_size_in_pages);
+    ignore Region.grow(r2, block_size_in_pages);
+
+    ignore StableMemory.grow(block_size_in_pages);
+    ignore Region.grow(r1, block_size_in_pages);
+    ignore Region.grow(r2, block_size_in_pages);
+  };
+  P.debugPrint "grow three big regions: done.";
+
+  system func preupgrade() {
+    P.debugPrint("upgrading... n=" # debug_show n);
+    n += 1;
+  };
+  public func sanityTest() {
+    P.debugPrint("sanity check. n=" # debug_show n);
+    assert Region.id(r1) == 16;
+    assert Region.id(r2) == 17;
+  };
+}
+
+//SKIP run
+//SKIP run-low
+//SKIP run-ir
+// too slow on ic-ref-run:
+//SKIP comp-ref
+//CALL ingress sanityTest "DIDL\x00\x00"
+//CALL upgrade ""
+//CALL ingress sanityTest "DIDL\x00\x00"
diff --git a/test/run-drun/test-feature.sh b/test/run-drun/test-feature.sh
new file mode 100755
index 00000000000..3561a2cfefa
--- /dev/null
+++ b/test/run-drun/test-feature.sh
@@ -0,0 +1,17 @@
+for i in \
+    stable-regions-new-each-upgrade.mo \
+    stable-region-migration.drun \
+	stable-regions-*.mo \
+	stable-region-*.mo \
+	region0-stable-mem-*.mo \
+	stable-mem-*.mo;
+do
+    echo $i;
+    if ../run.sh -d $i ; then
+	echo `date` $i >> success.log ;
+	echo; echo "Success."; echo;
+    else
+	echo `date` $i >> failures.log ;
+	echo; echo "Failure."; echo;
+    fi;
+done
diff --git a/test/run.sh b/test/run.sh
index 7ef28a30bcb..fe377512ebe 100755
--- a/test/run.sh
+++ b/test/run.sh
@@ -196,8 +196,7 @@ then
   else
     if [ $ACCEPT = yes ]
     then
-      echo "ERROR: Could not run ic-ref-run, cannot update expected test output"
-      exit 1
+      echo "WARNING: Could not run ic-ref-run, cannot update expected test output"
     else
       echo "WARNING: Could not run ic-ref-run, will skip running some tests"
       HAVE_ic_ref_run=no
@@ -455,9 +454,9 @@ do
           echo "$base.drun references $mo_file which is not in directory $base"
           exit 1
         fi
-
+        moc_extra_flags="$(eval echo $(grep '//MOC-FLAG' $mo_file | cut -c11- | paste -sd' '))"
         flags_var_name="FLAGS_${runner//-/_}"
-        run $mo_base.$runner.comp moc $EXTRA_MOC_ARGS ${!flags_var_name} --hide-warnings -c $mo_file -o $out/$base/$mo_base.$runner.wasm
+        run $mo_base.$runner.comp moc $EXTRA_MOC_ARGS ${!flags_var_name} $moc_extra_flags --hide-warnings -c $mo_file -o $out/$base/$mo_base.$runner.wasm
       done
 
       # mangle drun script