diff --git a/Changelog.md b/Changelog.md index b94517c8547..402afe505f7 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,14 @@ * motoko (`moc`) + * Code compiled for targets WASI (`-wasi-system-api`) and pure Wasm (`-no-system-api`) can now + use up to 4GB of (efficiently emulated) stable memory, enabling more offline testing of, for example, + stable data structures built using libraries `Regions.mo` and `ExperimentalStableMemory.mo`. + Note that any Wasm engine (such as `wasmtime`), used to execute such binaries, must support and enable + Wasm features multi-memory and bulk-memory (as well as the standard NaN canonicalization) (#4256). + + * bugfix: fully implement `Region.loadXXX/storeXXX` for `Int8`, `Int16` and `Float` (#4270). + * BREAKING CHANGE (Minor): values of type `Principal` are now constrained to contain at most 29 bytes, matching the IC's notion of principal (#4268). diff --git a/src/codegen/compile.ml b/src/codegen/compile.ml index 7d94bb1e5c4..74485f790e7 100644 --- a/src/codegen/compile.ml +++ b/src/codegen/compile.ml @@ -261,6 +261,7 @@ module E = struct module NameEnv = Env.Make(String) module StringEnv = Env.Make(String) module LabSet = Set.Make(String) + module FeatureSet = Set.Make(String) module FunEnv = Env.Make(Int32) type local_names = (int32 * string) list (* For the debug section: Names of locals *) @@ -324,6 +325,11 @@ module E = struct (* Mutable *) locals : value_type list ref; (* Types of locals *) local_names : (int32 * string) list ref; (* Names of locals *) + + features : FeatureSet.t ref; (* Wasm features using wasmtime naming *) + + (* requires stable memory (and emulation on wasm targets) *) + requires_stable_memory : bool ref; } @@ -360,6 +366,8 @@ module E = struct return_arity = 0; locals = ref []; local_names = ref []; + features = ref FeatureSet.empty; + requires_stable_memory = ref false; } (* This wraps Mo_types.Hash.hash to also record which labels we have seen, @@ -634,6 +642,32 @@ module E = struct let get_typtbl_typs (env : t) : Type.typ list = !(env.typtbl_typs) + let add_feature (env : t) f = + env.features := FeatureSet.add f (!(env.features)) + + let get_features (env : t) = FeatureSet.elements (!(env.features)) + + let require_stable_memory (env : t) = + if not !(env.requires_stable_memory) + then + (env.requires_stable_memory := true; + match mode env with + | Flags.ICMode | Flags.RefMode -> + () + | Flags.WASIMode | Flags.WasmMode -> + add_feature env "bulk-memory"; + add_feature env "multi-memory") + + let requires_stable_memory (env : t) = + !(env.requires_stable_memory) + + let get_memories (env : t) = + nr {mtype = MemoryType {min = mem_size env; max = None}} + :: + match mode env with + | Flags.WASIMode | Flags.WasmMode when !(env.requires_stable_memory) -> + [ nr {mtype = MemoryType {min = Int32.zero; max = None}} ] + | _ -> [] end @@ -3957,31 +3991,58 @@ module Region = struct E.call_import env "rts" "region_vec_pages" let new_ env = + E.require_stable_memory env; E.call_import env "rts" "region_new" let size env = + E.require_stable_memory env; E.call_import env "rts" "region_size" let grow env = + E.require_stable_memory 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_blob env = + E.require_stable_memory env; + E.call_import env "rts" "region_load_blob" + let store_blob env = + E.require_stable_memory 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_word8 env = + E.require_stable_memory env; + E.call_import env "rts" "region_load_word8" + let store_word8 env = + E.require_stable_memory 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_word16 env = + E.require_stable_memory env; + E.call_import env "rts" "region_load_word16" + let store_word16 env = + E.require_stable_memory 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_word32 env = + E.require_stable_memory env; + E.call_import env "rts" "region_load_word32" + let store_word32 env = + E.require_stable_memory 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_word64 env = + E.require_stable_memory env; + E.call_import env "rts" "region_load_word64" + let store_word64 env = + E.require_stable_memory 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" + let load_float64 env = + E.require_stable_memory env; + E.call_import env "rts" "region_load_float64" + let store_float64 env = + E.require_stable_memory env; + E.call_import env "rts" "region_store_float64" end @@ -5059,6 +5120,83 @@ end (* Cycles *) *) module StableMem = struct + + let conv_u32 env get_u64 = + get_u64 ^^ + compile_shrU64_const 32L ^^ + G.i (Convert (Wasm.Values.I32 I32Op.WrapI64)) ^^ + E.then_trap_with env "stable64 overflow" ^^ + get_u64 ^^ + G.i (Convert (Wasm.Values.I32 I32Op.WrapI64)) + + (* Raw stable memory API, + using ic0.stable64_xxx or + emulating via (for now) 32-bit memory 1 + *) + let stable64_grow env = + E.require_stable_memory env; + match E.mode env with + | Flags.ICMode | Flags.RefMode -> + IC.system_call env "stable64_grow" + | _ -> + Func.share_code1 Func.Always env "stable64_grow" ("pages", I64Type) [I64Type] + (fun env get_pages -> + let set_old_pages, get_old_pages = new_local env "old_pages" in + conv_u32 env get_pages ^^ + G.i StableGrow ^^ + set_old_pages ^^ + get_old_pages ^^ + compile_unboxed_const (-1l) ^^ + G.i (Compare (Wasm.Values.I32 I32Op.Eq)) ^^ + G.if1 I64Type + begin + compile_const_64 (-1L) + end + begin + get_old_pages ^^ + G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) + end) + + let stable64_size env = + E.require_stable_memory env; + match E.mode env with + | Flags.ICMode | Flags.RefMode -> + IC.system_call env "stable64_size" + | _ -> + Func.share_code0 Func.Always env "stable64_size" [I64Type] + (fun env -> + G.i StableSize ^^ + G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32))) + + let stable64_read env = + E.require_stable_memory env; + match E.mode env with + | Flags.ICMode | Flags.RefMode -> + IC.system_call env "stable64_read" + | _ -> + Func.share_code3 Func.Always env "stable64_read" + (("dst", I64Type), ("offset", I64Type), ("size", I64Type)) [] + (fun env get_dst get_offset get_size -> + conv_u32 env get_dst ^^ + conv_u32 env get_offset ^^ + conv_u32 env get_size ^^ + G.i StableRead) + + let stable64_write env = + E.require_stable_memory env; + match E.mode env with + | Flags.ICMode | Flags.RefMode -> + IC.system_call env "stable64_write" + | _ -> + Func.share_code3 Func.Always env "stable64_write" + (("offset", I64Type), ("src", I64Type), ("size", I64Type)) [] + (fun env get_offset get_src get_size -> + conv_u32 env get_offset ^^ + conv_u32 env get_src ^^ + conv_u32 env get_size ^^ + G.i StableWrite) + + (* 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 *) @@ -5085,21 +5223,16 @@ module StableMem = struct (* stable memory bounds check *) let guard env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> 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 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 Func.Always env "__stablemem_guard_range" (("offset", I64Type), ("size", I32Type)) [] (fun env get_offset get_size -> @@ -5126,7 +5259,6 @@ module StableMem = struct 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 = if guarded then @@ -5140,8 +5272,6 @@ module StableMem = struct (* 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 -> Func.share_code1 Func.Never env (Printf.sprintf "__stablemem_%sread_%s" (if guarded then "guarded_" else "") name) ("offset", I64Type) [typ] (fun env get_offset -> @@ -5151,13 +5281,10 @@ module StableMem = struct get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ get_offset ^^ compile_const_64 (Int64.of_int32 bytes) ^^ - IC.system_call env "stable64_read" ^^ + stable64_read env ^^ get_temp_ptr ^^ load)) - | _ -> assert false let write env guarded name typ bytes store = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> Func.share_code2 Func.Never env (Printf.sprintf "__stablemem_%swrite_%s" (if guarded then "guarded_" else "") name) (("offset", I64Type), ("value", typ)) [] (fun env get_offset get_value -> @@ -5168,8 +5295,7 @@ module StableMem = struct get_offset ^^ get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ compile_const_64 (Int64.of_int32 bytes) ^^ - IC.system_call env "stable64_write")) - | _ -> assert false + stable64_write env)) let _read_word32 env = read env false "word32" I32Type 4l load_unskewed_ptr @@ -5179,8 +5305,6 @@ module StableMem = struct (* read and clear word32 from stable mem offset on stack *) let read_and_clear_word32 env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> Func.share_code1 Func.Always env "__stablemem_read_and_clear_word32" ("offset", I64Type) [I32Type] (fun env get_offset -> @@ -5190,7 +5314,7 @@ module StableMem = struct get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ get_offset ^^ compile_const_64 4L ^^ - IC.system_call env "stable64_read" ^^ + stable64_read env ^^ get_temp_ptr ^^ load_unskewed_ptr ^^ set_word ^^ (* write 0 *) @@ -5198,24 +5322,21 @@ module StableMem = struct get_offset ^^ get_temp_ptr ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ compile_const_64 4L ^^ - IC.system_call env "stable64_write" ^^ + stable64_write env ^^ (* return word *) get_word )) - | _ -> assert false (* ensure_pages : ensure at least num pages allocated, growing (real) stable memory if needed *) let ensure_pages env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> Func.share_code1 Func.Always env "__stablemem_ensure_pages" ("pages", I64Type) [I64Type] (fun env get_pages -> let (set_size, get_size) = new_local64 env "size" in let (set_pages_needed, get_pages_needed) = new_local64 env "pages_needed" in - IC.system_call env "stable64_size" ^^ + stable64_size env ^^ set_size ^^ get_pages ^^ @@ -5228,14 +5349,11 @@ module StableMem = struct G.i (Compare (Wasm.Values.I64 I64Op.GtS)) ^^ G.if1 I64Type (get_pages_needed ^^ - IC.system_call env "stable64_grow") + stable64_grow env) get_size) - | _ -> assert false (* ensure stable memory includes [offset..offset+size), assumes size > 0 *) let ensure env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> Func.share_code2 Func.Always env "__stablemem_ensure" (("offset", I64Type), ("size", I64Type)) [] (fun env get_offset get_size -> @@ -5259,12 +5377,9 @@ module StableMem = struct compile_const_64 0L ^^ G.i (Compare (Wasm.Values.I64 I64Op.LtS)) ^^ E.then_trap_with env "Out of stable memory.") - | _ -> assert false (* low-level grow, respecting --max-stable-pages *) let grow env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> Func.share_code1 Func.Always env "__stablemem_grow" ("pages", I64Type) [I64Type] (fun env get_pages -> let (set_size, get_size) = new_local64 env "size" in @@ -5308,7 +5423,6 @@ module StableMem = struct (* return old logical size *) get_size) end) - | _ -> assert false let load_word32 env = read env true "word32" I32Type 4l load_unskewed_ptr @@ -5342,8 +5456,6 @@ module StableMem = struct (G.i (Store {ty = F64Type; align = 0; offset = 0l; sz = None})) let load_blob env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> Func.share_code2 Func.Always env "__stablemem_load_blob" (("offset", I64Type), ("len", I32Type)) [I32Type] (fun env get_offset get_len -> @@ -5355,13 +5467,10 @@ module StableMem = struct get_blob ^^ Blob.payload_ptr_unskewed env ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ get_offset ^^ get_len ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ - IC.system_call env "stable64_read" ^^ + stable64_read env ^^ get_blob) - | _ -> assert false let store_blob env = - match E.mode env with - | Flags.ICMode | Flags.RefMode -> Func.share_code2 Func.Always env "__stablemem_store_blob" (("offset", I64Type), ("blob", I32Type)) [] (fun env get_offset get_blob -> @@ -5373,8 +5482,7 @@ module StableMem = struct get_offset ^^ get_blob ^^ Blob.payload_ptr_unskewed env ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ get_len ^^ G.i (Convert (Wasm.Values.I64 I64Op.ExtendUI32)) ^^ - IC.system_call env "stable64_write") - | _ -> assert false + stable64_write env) end (* StableMem *) @@ -5403,6 +5511,7 @@ module StableMemoryInterface = struct (* Prims *) let size env = + E.require_stable_memory env; Func.share_code0 Func.Always env "__stablememory_size" [I64Type] (fun env -> if_regions env @@ -5412,6 +5521,7 @@ module StableMemoryInterface = struct StableMem.get_mem_size) let grow env = + E.require_stable_memory env; Func.share_code1 Func.Always env "__stablememory_grow" ("pages", I64Type) [I64Type] (fun env get_pages -> if_regions env @@ -5442,6 +5552,7 @@ module StableMemoryInterface = struct get_res)) let load_blob env = + E.require_stable_memory env; Func.share_code2 Func.Never env "__stablememory_load_blob" (("offset", I64Type), ("len", I32Type)) [I32Type] (fun env offset len -> @@ -5451,6 +5562,7 @@ module StableMemoryInterface = struct Region.load_blob StableMem.load_blob) let store_blob env = + E.require_stable_memory env; Func.share_code2 Func.Never env "__stablememory_store_blob" (("offset", I64Type), ("blob", I32Type)) [] (fun env offset blob -> @@ -5461,6 +5573,7 @@ module StableMemoryInterface = struct StableMem.store_blob) let load_word8 env = + E.require_stable_memory env; Func.share_code1 Func.Never env "__stablememory_load_word8" ("offset", I64Type) [I32Type] (fun env offset -> @@ -5470,6 +5583,7 @@ module StableMemoryInterface = struct Region.load_word8 StableMem.load_word8) let store_word8 env = + E.require_stable_memory env; Func.share_code2 Func.Never env "__stablememory_store_word8" (("offset", I64Type), ("value", I32Type)) [] (fun env offset value -> @@ -5480,6 +5594,7 @@ module StableMemoryInterface = struct StableMem.store_word8) let load_word16 env = + E.require_stable_memory env; Func.share_code1 Func.Never env "__stablememory_load_word16" ("offset", I64Type) [I32Type] (fun env offset-> @@ -5489,6 +5604,7 @@ module StableMemoryInterface = struct Region.load_word16 StableMem.load_word16) let store_word16 env = + E.require_stable_memory env; Func.share_code2 Func.Never env "__stablememory_store_word16" (("offset", I64Type), ("value", I32Type)) [] (fun env offset value -> @@ -5499,6 +5615,7 @@ module StableMemoryInterface = struct StableMem.store_word16) let load_word32 env = + E.require_stable_memory env; Func.share_code1 Func.Never env "__stablememory_load_word32" ("offset", I64Type) [I32Type] (fun env offset -> @@ -5508,6 +5625,7 @@ module StableMemoryInterface = struct Region.load_word32 StableMem.load_word32) let store_word32 env = + E.require_stable_memory env; Func.share_code2 Func.Never env "__stablememory_store_word32" (("offset", I64Type), ("value", I32Type)) [] (fun env offset value -> @@ -5518,6 +5636,7 @@ module StableMemoryInterface = struct StableMem.store_word32) let load_word64 env = + E.require_stable_memory env; Func.share_code1 Func.Never env "__stablememory_load_word64" ("offset", I64Type) [I64Type] (fun env offset -> if_regions env @@ -5526,6 +5645,7 @@ module StableMemoryInterface = struct Region.load_word64 StableMem.load_word64) let store_word64 env = + E.require_stable_memory env; Func.share_code2 Func.Never env "__stablememory_store_word64" (("offset", I64Type), ("value", I64Type)) [] (fun env offset value -> @@ -5536,6 +5656,7 @@ module StableMemoryInterface = struct StableMem.store_word64) let load_float64 env = + E.require_stable_memory env; Func.share_code1 Func.Never env "__stablememory_load_float64" ("offset", I64Type) [F64Type] (fun env offset -> @@ -5557,6 +5678,9 @@ module StableMemoryInterface = struct end module RTS_Exports = struct + (* Must be called late, after main codegen, to ensure correct generation of + of functioning or unused-but-trapping stable memory exports (as required) + *) let system_exports env = let bigint_trap_fi = E.add_fun env "bigint_trap" ( Func.of_body env [] [] (fun env -> @@ -5621,29 +5745,55 @@ module RTS_Exports = struct }) end; + + (* Stable Memory related exports *) + + let when_stable_memory_required_else_trap env code = + if E.requires_stable_memory env then + code() else + E.trap_with env "unreachable" in + let ic0_stable64_write_fi = - if E.mode env = Flags.WASIMode then + match E.mode env with + | Flags.ICMode | Flags.RefMode -> + E.reuse_import env "ic0" "stable64_write" + | Flags.WASIMode | Flags.WasmMode -> E.add_fun env "ic0_stable64_write" ( - Func.of_body env ["to", I64Type; "from", I64Type; "len", I64Type] [] - (fun env -> - E.trap_with env "ic0_stable64_write is not supposed to be called in WASI" - ) + Func.of_body env ["offset", I64Type; "src", I64Type; "size", I64Type] [] + (fun env -> + when_stable_memory_required_else_trap env (fun () -> + let get_offset = G.i (LocalGet (nr 0l)) in + let get_src = G.i (LocalGet (nr 1l)) in + let get_size = G.i (LocalGet (nr 2l)) in + get_offset ^^ + get_src ^^ + get_size ^^ + StableMem.stable64_write env)) ) - else E.reuse_import env "ic0" "stable64_write" in + in E.add_export env (nr { 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 + match E.mode env with + | Flags.ICMode | Flags.RefMode -> + E.reuse_import env "ic0" "stable64_read" + | Flags.WASIMode | Flags.WasmMode -> 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" - ) + Func.of_body env ["dst", I64Type; "offset", I64Type; "size", I64Type] [] + (fun env -> + when_stable_memory_required_else_trap env (fun () -> + let get_dst = G.i (LocalGet (nr 0l)) in + let get_offset = G.i (LocalGet (nr 1l)) in + let get_size = G.i (LocalGet (nr 2l)) in + get_dst ^^ + get_offset ^^ + get_size ^^ + StableMem.stable64_read env)) ) - else E.reuse_import env "ic0" "stable64_read" in + in E.add_export env (nr { name = Lib.Utf8.decode "ic0_stable64_read"; edesc = nr (FuncExport (nr ic0_stable64_read_fi)) @@ -5653,13 +5803,10 @@ module RTS_Exports = struct 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 *) - )) + when_stable_memory_required_else_trap env (fun () -> + G.i (LocalGet (nr 0l)) ^^ + StableMem.grow env)) + ) in E.add_export env (nr { name = Lib.Utf8.decode "moc_stable_mem_grow"; @@ -5670,12 +5817,8 @@ module RTS_Exports = struct 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 *) - ) + when_stable_memory_required_else_trap env (fun () -> + StableMem.get_mem_size env)) ) in E.add_export env (nr { @@ -5687,12 +5830,7 @@ module RTS_Exports = struct 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 *) - ) + StableMem.get_version env) ) in E.add_export env (nr { @@ -5704,12 +5842,8 @@ module RTS_Exports = struct 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 *) + G.i (LocalGet (nr 0l)) ^^ + StableMem.set_version env ) ) in @@ -7781,7 +7915,7 @@ module Stabilization = struct compile_const_64 4L ^^ extend64 get_dst ^^ extend64 get_len ^^ - IC.system_call env "stable64_write" + StableMem.stable64_write env end end begin @@ -7812,13 +7946,13 @@ module Stabilization = struct compile_add64_const 4L ^^ extend64 get_dst ^^ extend64 get_len ^^ - IC.system_call env "stable64_write" + StableMem.stable64_write env end ^^ (* 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" ^^ + StableMem.stable64_size env ^^ compile_sub64_const 1L ^^ compile_shl64_const (Int64.of_int page_size_bits) ^^ set_M ^^ @@ -7863,7 +7997,7 @@ module Stabilization = struct match E.mode env with | Flags.ICMode | Flags.RefMode -> let (set_pages, get_pages) = new_local64 env "pages" in - IC.system_call env "stable64_size" ^^ + StableMem.stable64_size env ^^ set_pages ^^ get_pages ^^ @@ -7903,7 +8037,7 @@ module Stabilization = struct let (set_version, get_version) = new_local env "version" in let (set_N, get_N) = new_local64 env "N" in - IC.system_call env "stable64_size" ^^ + StableMem.stable64_size env ^^ compile_sub64_const 1L ^^ compile_shl64_const (Int64.of_int page_size_bits) ^^ set_M ^^ @@ -7977,7 +8111,7 @@ module Stabilization = struct extend64 (get_blob ^^ Blob.payload_ptr_unskewed env) ^^ get_offset ^^ extend64 get_len ^^ - IC.system_call env "stable64_read" ^^ + StableMem.stable64_read env ^^ let (set_val, get_val) = new_local env "val" in (* deserialize blob to val *) @@ -7994,7 +8128,7 @@ module Stabilization = struct get_offset ^^ extend64 (get_blob ^^ Blob.payload_ptr_unskewed env) ^^ extend64 (get_blob ^^ Blob.len env) ^^ - IC.system_call env "stable64_write" ^^ + StableMem.stable64_write env ^^ (* return val *) get_val @@ -10611,7 +10745,7 @@ and compile_prim_invocation (env : E.t) ae p es at = | OtherPrim "rts_stable_memory_size", [] -> SR.Vanilla, - IC.ic_system_call "stable64_size" env ^^ BigNum.from_word64 env + StableMem.stable64_size env ^^ BigNum.from_word64 env | OtherPrim "rts_logical_stable_memory_size", [] -> SR.Vanilla, @@ -11920,6 +12054,8 @@ and metadata name value = and conclude_module env set_serialization_globals start_fi_o = + RTS_Exports.system_exports env; + FuncDec.export_async_method env; (* See Note [Candid subtype checks] *) @@ -11958,7 +12094,7 @@ and conclude_module env set_serialization_globals start_fi_o = let other_imports = E.get_other_imports env in - let memories = [nr {mtype = MemoryType {min = E.mem_size env; max = None}} ] in + let memories = E.get_memories env in let funcs = E.get_funcs env in @@ -12007,6 +12143,7 @@ and conclude_module env set_serialization_globals start_fi_o = service = !(env.E.service); }; source_mapping_url = None; + wasm_features = E.get_features env; } in match E.get_rts env with @@ -12027,7 +12164,6 @@ let compile mode rts (prog : Ir.prog) : Wasm_exts.CustomModule.extended_module = IC.system_imports env; RTS.system_imports env; - RTS_Exports.system_exports env; compile_init_func env prog; let start_fi_o = match E.mode env with diff --git a/src/exes/candid_tests.ml b/src/exes/candid_tests.ml index e3718135d69..3d56766b002 100644 --- a/src/exes/candid_tests.ml +++ b/src/exes/candid_tests.ml @@ -14,6 +14,7 @@ let name = "candid-tests" let version = "0.1" let banner = "Candid test suite runner " ^ version ^ "" let usage = "Usage: " ^ name ^ " [ -i path/to/candid/test ]" +let _WASMTIME_OPTIONS_ = "--disable-cache --enable-cranelift-nan-canonicalization --wasm-features multi-memory,bulk-memory" (* Argument handling *) @@ -254,7 +255,7 @@ let () = match run_cmd "moc -Werror -wasi-system-api tmp.mo -o tmp.wasm" with | ((Fail | Timeout), stdout, stderr) -> CantCompile (stdout, stderr, src) | (Ok, _, _) -> - match must_not_trap, run_cmd "timeout 10s wasmtime --disable-cache tmp.wasm" with + match must_not_trap, run_cmd ("timeout 10s wasmtime "^ _WASMTIME_OPTIONS_ ^" tmp.wasm") with | ShouldPass, (Ok, _, _) -> WantedPass | ShouldTrap, (Fail, _, _) -> WantedTrap | ShouldPass, (Fail, stdout, stderr) -> UnwantedTrap (stdout, stderr) diff --git a/src/lang_utils/error_codes.ml b/src/lang_utils/error_codes.ml index 486a7d3791a..d156f900537 100644 --- a/src/lang_utils/error_codes.ml +++ b/src/lang_utils/error_codes.ml @@ -194,4 +194,5 @@ let error_codes : (string * string option) list = "M0188", None; (* Send capability required (calling shared from query) *) "M0189", None; (* Different set of bindings in pattern alternatives *) "M0190", None; (* Types inconsistent for alternative pattern variables, losing information *) + "M0191", None; (* Code requires Wasm features ... to execute *) ] diff --git a/src/linking/linkModule.ml b/src/linking/linkModule.ml index dc37b5e0227..f48f9e67eca 100644 --- a/src/linking/linkModule.ml +++ b/src/linking/linkModule.ml @@ -521,11 +521,17 @@ let set_memory_size new_size_bytes : module_' -> module_' = fun m -> let page_size = Int32.of_int (64*1024) in let new_size_pages = Int32.(add (div new_size_bytes page_size) 1l) in match m.memories with + | [t;t1] -> + { m with + memories = [(phrase (fun m -> + { mtype = MemoryType ({min = new_size_pages; max = None}) } + ) t); t1] + } | [t] -> { m with - memories = [ phrase (fun m -> + memories = [phrase (fun m -> { mtype = MemoryType ({min = new_size_pages; max = None}) } - ) t ] + ) t] } | _ -> raise (LinkError "Expect one memory in first module") diff --git a/src/pipeline/pipeline.ml b/src/pipeline/pipeline.ml index 9867532496e..da9bb1d781e 100644 --- a/src/pipeline/pipeline.ml +++ b/src/pipeline/pipeline.ml @@ -717,6 +717,11 @@ let compile_files mode do_link files : compile_result = | Some (_, ss) -> validate_stab_sig ss | _ -> Diag.return () in + let* () = + if Wasm_exts.CustomModule.(ext_module.wasm_features) <> [] + then Diag.warn Source.no_region "M0191" "compile" (Printf.sprintf "code requires Wasm features %s to execute" (String.concat "," Wasm_exts.CustomModule.(ext_module.wasm_features))) + else Diag.return () + in Diag.return (idl, ext_module) diff --git a/src/wasm-exts/ast.ml b/src/wasm-exts/ast.ml index edf5021810c..d13c26f7a27 100644 --- a/src/wasm-exts/ast.ml +++ b/src/wasm-exts/ast.ml @@ -5,7 +5,8 @@ reference implementation. Base revision: WebAssembly/spec@a7a1856. The changes are: - * None for now + * Pseudo-instruction Meta for debug information + * StableMemory, StableGrow, StableRead, StableWrite instructions. The code is otherwise as untouched as possible, so that we can relatively easily apply diffs from the original code (possibly manually). @@ -116,8 +117,19 @@ and instr' = | Unary of unop (* unary numeric operator *) | Binary of binop (* binary numeric operator *) | Convert of cvtop (* conversion *) + + (* Custom addition for debugging *) | Meta of Dwarf5.Meta.die (* debugging metadata *) + (* Custom additions for emulating stable-memory, special cases + of MemorySize, MemoryGrow and MemoryCopy + requiring wasm features bulk-memory and multi-memory + *) + | StableSize (* size of stable memory *) + | StableGrow (* grow stable memory *) + | StableRead (* read from stable memory *) + | StableWrite (* write to stable memory *) + (* Globals & Functions *) type const = instr list phrase diff --git a/src/wasm-exts/customModule.ml b/src/wasm-exts/customModule.ml index b0db974a0e6..f92e04566ac 100644 --- a/src/wasm-exts/customModule.ml +++ b/src/wasm-exts/customModule.ml @@ -73,4 +73,5 @@ type extended_module = { motoko : motoko_sections; (* source map section *) source_mapping_url : string option; + wasm_features : string list; } diff --git a/src/wasm-exts/customModuleDecode.ml b/src/wasm-exts/customModuleDecode.ml index e2bd0ed02e3..5993362c757 100644 --- a/src/wasm-exts/customModuleDecode.ml +++ b/src/wasm-exts/customModuleDecode.ml @@ -846,13 +846,19 @@ let motoko_stable_types_name = icp_name "motoko:stable-types" let is_icp icp_name n = icp_name n <> None +let is_wasm_features n = (n = Utf8.decode "wasm_features") +let wasm_features_section s = + custom_section is_wasm_features + (fun sec_end s -> let t = utf8 sec_end s in String.split_on_char ',' t) [] s + let is_unknown n = not ( is_dylink n || is_name n || is_motoko n || is_icp candid_service_name n || is_icp candid_args_name n || - is_icp motoko_stable_types_name n) + is_icp motoko_stable_types_name n || + is_wasm_features n) let skip_custom sec_end s = skip (sec_end - pos s) s; @@ -901,6 +907,8 @@ let module_ s = iterate skip_custom_section s; let motoko = motoko_sections s in iterate skip_custom_section s; + let wasm_features = wasm_features_section s in + iterate skip_custom_section s; require (pos s = len s) s (len s) "junk after last section"; require (List.length func_types = List.length func_bodies) s (len s) "function and code section have inconsistent lengths"; @@ -915,6 +923,7 @@ let module_ s = motoko; candid; source_mapping_url = None; + wasm_features = wasm_features; } diff --git a/src/wasm-exts/customModuleEncode.ml b/src/wasm-exts/customModuleEncode.ml index cc4512e26a3..5e2c5feacdd 100644 --- a/src/wasm-exts/customModuleEncode.ml +++ b/src/wasm-exts/customModuleEncode.ml @@ -637,6 +637,15 @@ let encode (em : extended_module) = | Convert (F64 F64Op.DemoteF64) -> assert false | Convert (F64 F64Op.ReinterpretInt) -> op 0xbf + (* Custom encodings for emulating stable-memory, special cases + of MemorySize, MemoryGrow and MemoryCopy + requiring wasm features bulk-memory and multi-memory + *) + | StableSize -> op 0x3f; u8 0x01 + | StableGrow -> op 0x40; u8 0x01 + | StableRead -> op 0xfc; vu32 0x0al; u8 0x00; u8 0x01 + | StableWrite -> op 0xfc; vu32 0x0al; u8 0x01; u8 0x00 + let const c = list (instr ignore) c.it; end_ () @@ -854,6 +863,10 @@ let encode (em : extended_module) = icp_custom_section "candid:service" utf8 candid.service; icp_custom_section "candid:args" utf8 candid.args + let wasm_features_section wasm_features = + let text = String.concat "," wasm_features in + custom_section "wasm_features" utf8 text (text <> "") + let uleb128 n = vu64 (Int64.of_int n) let sleb128 n = vs64 (Int64.of_int n) let close_section () = u8 0x00 @@ -1222,6 +1235,7 @@ let encode (em : extended_module) = name_section em.name; candid_sections em.candid; motoko_sections em.motoko; + wasm_features_section em.wasm_features; source_mapping_url_section em.source_mapping_url; if !Mo_config.Flags.debug_info then begin diff --git a/test/check-error-codes.py b/test/check-error-codes.py index 8e39b9283d8..afb663ef5cc 100755 --- a/test/check-error-codes.py +++ b/test/check-error-codes.py @@ -42,6 +42,7 @@ "M0162", # Candid service constructor type not supported as Motoko type "M0164", # unknown record or variant label in textual representation "M0165", # odd expected type + "M0191", # compiler warning about wasm features (hard to trigger) } def populate_error_codes(): diff --git a/test/random/Embedder.hs b/test/random/Embedder.hs index fc88c429d27..21c28ed17c2 100644 --- a/test/random/Embedder.hs +++ b/test/random/Embedder.hs @@ -36,7 +36,7 @@ addCompilerArgs (WasmTime _) = ("-wasi-system-api" :) addCompilerArgs Drun = id addEmbedderArgs Reference = id -addEmbedderArgs (WasmTime _) = ("--disable-cache" :) +addEmbedderArgs (WasmTime _) = ("--disable-cache" :) . ("--enable-cranelift-nan-canonicalization" :) . ("--wasm-features" :) . ("multi-memory,bulk-memory" :) addEmbedderArgs Drun = ("--extra-batches" :) . ("10" :) embedderInvocation :: Embedder -> [Text] -> [Text] diff --git a/test/run-drun/ok/region0-big-blob.drun-run.ok b/test/run-drun/ok/region0-big-blob.drun-run.ok deleted file mode 100644 index 01dcaede9b7..00000000000 --- a/test/run-drun/ok/region0-big-blob.drun-run.ok +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 3042e1965f8..00000000000 --- a/test/run-drun/ok/region0-big-blob.tc.ok +++ /dev/null @@ -1,4 +0,0 @@ -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/stable-mem-big-blob.drun-run.ok b/test/run-drun/ok/stable-mem-big-blob.drun-run.ok deleted file mode 100644 index d0e52bea656..00000000000 --- a/test/run-drun/ok/stable-mem-big-blob.drun-run.ok +++ /dev/null @@ -1,4 +0,0 @@ -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:17.3-17.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 deleted file mode 100644 index b626baec234..00000000000 --- a/test/run-drun/ok/stable-mem-big-blob.tc.ok +++ /dev/null @@ -1,4 +0,0 @@ -stable-mem-big-blob.mo:5.7-5.8: warning [M0145], this pattern of type - Nat64 -does not cover value - 1 or 2 or _ diff --git a/test/run-drun/region0-big-blob.mo b/test/run-drun/region0-big-blob.mo deleted file mode 100644 index 57ec3123afa..00000000000 --- a/test/run-drun/region0-big-blob.mo +++ /dev/null @@ -1,25 +0,0 @@ -//MOC-FLAG --stable-regions -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-big-blob.mo b/test/run-drun/stable-mem-big-blob.mo deleted file mode 100644 index e505558bdfe..00000000000 --- a/test/run-drun/stable-mem-big-blob.mo +++ /dev/null @@ -1,26 +0,0 @@ -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/StableMemory.mo b/test/run-drun/stable-mem/StableMemory.mo index a3d6bb2ed44..8570198b7ef 100644 --- a/test/run-drun/stable-mem/StableMemory.mo +++ b/test/run-drun/stable-mem/StableMemory.mo @@ -3,7 +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.sh b/test/run.sh index 34fd3d36be4..50d81e09fb6 100755 --- a/test/run.sh +++ b/test/run.sh @@ -26,7 +26,7 @@ DTESTS=no IDL=no PERF=no VIPER=no -WASMTIME_OPTIONS="--disable-cache --enable-cranelift-nan-canonicalization" +WASMTIME_OPTIONS="--disable-cache --enable-cranelift-nan-canonicalization --wasm-features multi-memory,bulk-memory" WRAP_drun=$(realpath $(dirname $0)/drun-wrapper.sh) WRAP_ic_ref_run=$(realpath $(dirname $0)/ic-ref-run-wrapper.sh) SKIP_RUNNING=${SKIP_RUNNING:-no} @@ -358,8 +358,8 @@ do if [ "$SKIP_VALIDATE" != yes ] then - run_if wasm valid wasm-validate $out/$base.wasm - run_if ref.wasm valid-ref wasm-validate $out/$base.ref.wasm + run_if wasm valid wasm-validate --enable-multi-memory $out/$base.wasm + run_if ref.wasm valid-ref wasm-validate --enable-multi-memory $out/$base.ref.wasm fi if [ -e $out/$base.wasm ] @@ -370,7 +370,7 @@ do if grep -F -q CHECK $mangled then $ECHO -n " [FileCheck]" - wasm2wat --no-check $out/$base.wasm > $out/$base.wat + wasm2wat --enable-multi-memory --no-check $out/$base.wasm > $out/$base.wat cat $out/$base.wat | FileCheck $mangled > $out/$base.filecheck 2>&1 diff_files="$diff_files $base.filecheck" fi diff --git a/test/run/ok/region-test.tc.ok b/test/run/ok/region-test.tc.ok new file mode 100644 index 00000000000..60fa5bd982d --- /dev/null +++ b/test/run/ok/region-test.tc.ok @@ -0,0 +1,12 @@ +region-test.mo:5.5-5.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ +region-test.mo:6.5-6.7: warning [M0145], this pattern of type + Nat +does not cover value + 0 or 1 or _ +region-test.mo:7.5-7.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ diff --git a/test/run/ok/region-test.wasm-run.ok b/test/run/ok/region-test.wasm-run.ok new file mode 100644 index 00000000000..bb8f39594d0 --- /dev/null +++ b/test/run/ok/region-test.wasm-run.ok @@ -0,0 +1,10 @@ +Nat8 +Nat16 +Nat32 +Nat64 +Int8 +Int16 +Int32 +Int64 +Float +Blob diff --git a/test/run/ok/region0-big-blob.tc.ok b/test/run/ok/region0-big-blob.tc.ok new file mode 100644 index 00000000000..4a3e5f19a97 --- /dev/null +++ b/test/run/ok/region0-big-blob.tc.ok @@ -0,0 +1,4 @@ +region0-big-blob.mo:5.5-5.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ diff --git a/test/run/ok/region0-big-blob.wasm-run.ok b/test/run/ok/region0-big-blob.wasm-run.ok new file mode 100644 index 00000000000..7ed54ca357a --- /dev/null +++ b/test/run/ok/region0-big-blob.wasm-run.ok @@ -0,0 +1,2 @@ +255 +ok diff --git a/test/run/ok/stable-log.wasm-run.ok b/test/run/ok/stable-log.wasm-run.ok new file mode 100644 index 00000000000..1b52ae13b2f --- /dev/null +++ b/test/run/ok/stable-log.wasm-run.ok @@ -0,0 +1,50 @@ +9 +8 +7 +6 +5 +4 +3 +2 +1 +0 +19 +18 +17 +16 +15 +14 +13 +12 +11 +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +0 +19 +18 +17 +16 +15 +14 +13 +12 +11 +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 +0 diff --git a/test/run/ok/stable-mem-big-blob.tc.ok b/test/run/ok/stable-mem-big-blob.tc.ok new file mode 100644 index 00000000000..422a0c590b6 --- /dev/null +++ b/test/run/ok/stable-mem-big-blob.tc.ok @@ -0,0 +1,4 @@ +stable-mem-big-blob.mo:4.5-4.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ diff --git a/test/run/ok/stable-mem-big-blob.wasm-run.ok b/test/run/ok/stable-mem-big-blob.wasm-run.ok new file mode 100644 index 00000000000..7ed54ca357a --- /dev/null +++ b/test/run/ok/stable-mem-big-blob.wasm-run.ok @@ -0,0 +1,2 @@ +255 +ok diff --git a/test/run/ok/stable-memory-test.tc.ok b/test/run/ok/stable-memory-test.tc.ok new file mode 100644 index 00000000000..3a58a2d5f5d --- /dev/null +++ b/test/run/ok/stable-memory-test.tc.ok @@ -0,0 +1,8 @@ +stable-memory-test.mo:4.5-4.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ +stable-memory-test.mo:5.5-5.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ diff --git a/test/run/ok/stable-memory-test.wasm-run.ok b/test/run/ok/stable-memory-test.wasm-run.ok new file mode 100644 index 00000000000..bb8f39594d0 --- /dev/null +++ b/test/run/ok/stable-memory-test.wasm-run.ok @@ -0,0 +1,10 @@ +Nat8 +Nat16 +Nat32 +Nat64 +Int8 +Int16 +Int32 +Int64 +Float +Blob diff --git a/test/run/ok/stable-memory.tc.ok b/test/run/ok/stable-memory.tc.ok new file mode 100644 index 00000000000..6c48daae888 --- /dev/null +++ b/test/run/ok/stable-memory.tc.ok @@ -0,0 +1,32 @@ +stable-memory.mo:4.5-4.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ +stable-memory.mo:5.5-5.7: warning [M0145], this pattern of type + Nat64 +does not cover value + 0 or 1 or _ +stable-memory.mo:7.5-7.6: warning [M0145], this pattern of type + Nat8 +does not cover value + 1 or 2 or _ +stable-memory.mo:11.5-11.7: warning [M0145], this pattern of type + Nat8 +does not cover value + 0 or 1 or _ +stable-memory.mo:17.5-17.6: warning [M0145], this pattern of type + Nat64 +does not cover value + 1 or 2 or _ +stable-memory.mo:18.5-18.7: warning [M0145], this pattern of type + Nat64 +does not cover value + 0 or 1 or _ +stable-memory.mo:20.5-20.6: warning [M0145], this pattern of type + Nat8 +does not cover value + 1 or 2 or _ +stable-memory.mo:24.5-24.7: warning [M0145], this pattern of type + Nat8 +does not cover value + 0 or 1 or _ diff --git a/test/run/ok/stable-memory.wasm-run.ok b/test/run/ok/stable-memory.wasm-run.ok new file mode 100644 index 00000000000..0c6376ea08b --- /dev/null +++ b/test/run/ok/stable-memory.wasm-run.ok @@ -0,0 +1,9 @@ +{size = 0} +{size = 16} +{read = 0} +{read = 66} +{read = 66} +{size = 16} +{read = 0} +{read = 66} +{read = 66} diff --git a/test/run/region-test.mo b/test/run/region-test.mo new file mode 100644 index 00000000000..a2fc1567c9c --- /dev/null +++ b/test/run/region-test.mo @@ -0,0 +1,214 @@ +import Prim "mo:⛔"; +import Region "stable-region/Region"; + +let r = Region.new(); +let 0 = Region.size(r); +let 16 = Region.id(r); +let 0 = Region.grow(r, 64); +assert (64 == Region.size(r)); + +do { + Prim.debugPrint("Nat8"); + type T = Nat8; + let size : Nat64 = 1; + let mod : Nat64 = 256; + let load = Region.loadNat8; + let store = Region.storeNat8; + func conv(n : Nat64) : Nat8 { Prim.natToNat8(Prim.nat64ToNat(n % mod)) }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Nat16"); + type T = Nat16; + let size : Nat64 = 2; + let mod : Nat64 = 65536; + let load = Region.loadNat16; + let store = Region.storeNat16; + func conv(n : Nat64) : Nat16 { Prim.natToNat16(Prim.nat64ToNat(n % mod)) }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Nat32"); + type T = Nat32; + let size : Nat64 = 4; + let mod : Nat64 = 2**32; + let load = Region.loadNat32; + let store = Region.storeNat32; + func conv(n : Nat64) : Nat32 { Prim.natToNat32(Prim.nat64ToNat(n % mod)) }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Nat64"); + type T = Nat8; + let size : Nat64 = 8; + let load = Region.loadNat64; + let store = Region.storeNat64; + func conv(n : Nat64) : Nat64 { n }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int8"); + type T = Int8; + let size : Nat64 = 1; + let mod : Nat64 = 256; + let load = Region.loadInt8; + let store = Region.storeInt8; + func conv(n : Nat64) : Int8 { Prim.intToInt8(Prim.nat64ToNat(n % mod) - 128) }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int16"); + type T = Int16; + let size : Nat64 = 2; + let mod : Nat64 = 65536; + let load = Region.loadInt16; + let store = Region.storeInt16; + func conv(n : Nat64) : Int16 { Prim.intToInt16(Prim.nat64ToNat(n % mod) - 32768 )}; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int32"); + type T = Int32; + let size : Nat64 = 4; + let mod : Nat64 = 2**32; + let load = Region.loadInt32; + let store = Region.storeInt32; + func conv(n : Nat64) : Int32 { Prim.intToInt32(Prim.nat64ToNat(n % mod) - 2147483648) }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int64"); + type T = Int8; + let size : Nat64 = 8; + let load = Region.loadInt64; + let store = Region.storeInt64; + func conv(n : Nat64) : Int64 { Prim.nat64ToInt64(n)+(-9223372036854775808) }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Float"); + type T = Int8; + let size : Nat64 = 8; + let load = Region.loadFloat; + let store = Region.storeFloat; + func conv(n : Nat64) : Float { Prim.int64ToFloat(Prim.nat64ToInt64(n)+(-9223372036854775808)) }; + let max = Region.size(r)*65536; + var i : Nat64 = 0; + while(i < max) { + store(r, i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(r, i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Blob"); + type T = Int8; + let load = Region.loadBlob; + let store = Region.storeBlob; + func conv(n : Nat64) : Blob { load(r, 0, Prim.nat64ToNat(n)) }; + let max : Nat64 = Region.size(r)*65536; + var i : Nat64 = 1; + while(i < max) { + let b = conv(i); + store(r, 0, b); + assert (b == load(r, 0, Prim.nat64ToNat(i))); + i := i * 2; + }; +}; + +//SKIP run-low +//SKIP run +//SKIP run-ir diff --git a/test/run/region0-big-blob.mo b/test/run/region0-big-blob.mo new file mode 100644 index 00000000000..c941716eb2e --- /dev/null +++ b/test/run/region0-big-blob.mo @@ -0,0 +1,22 @@ +//MOC-FLAG --stable-regions +import P "mo:⛔"; +import StableMemory "stable-mem/StableMemory"; + +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"); + +//SKIP run +//SKIP run-low +//SKIP run-ir +// too slow on ic-ref-run: +//SKIP comp-ref diff --git a/test/run/stable-log.mo b/test/run/stable-log.mo new file mode 100644 index 00000000000..0f5d92033cf --- /dev/null +++ b/test/run/stable-log.mo @@ -0,0 +1,77 @@ +import Prim "mo:⛔"; +import StableMemory "stable-mem/StableMemory"; + +func ensure(offset : Nat64) { + let pages = (offset + 65536) >> 16; + if (pages > StableMemory.size()) { + let oldsize = StableMemory.grow(pages - StableMemory.size()); + assert (oldsize != 0xFFFF_FFFF_FFFF_FFFF); + }; +}; + +var base : Nat64 = 0; + +func log(t : Text) { + let blob = Prim.encodeUtf8(t); + let size = Prim.natToNat64(blob.size()); + ensure(base + size + 4); + StableMemory.storeBlob(base, blob); + base += size; + StableMemory.storeNat32(base, Prim.natToNat32(blob.size())); + base += 4; +}; + +func readLast(count : Nat) : [Text] { + let a = Prim.Array_init(count, ""); + var offset = base; + var k = 0; + while (k < count and offset > 0) { + offset -= 4; + let size = StableMemory.loadNat32(offset); + offset -= Prim.natToNat64(Prim.nat32ToNat(size)); + let blob = StableMemory.loadBlob(offset, Prim.nat32ToNat(size)); + switch (Prim.decodeUtf8(blob)) { + case (?t) { a[k] := t }; + case null { assert false }; + }; + k += 1; + }; + return Prim.Array_tabulate(k, func i { a[i] }); +}; + + +// testing +var count = 0; + +func populate() : () { + // populate + let limit = count + 10; + while (count < limit) { + log(debug_show(count)); + count += 1; + }; +}; + +func readAll() : () { + let ts = readLast(count); + for (t in ts.vals()) { + Prim.debugPrint(t); + }; +}; + +func readExtra() : () { + let ts = readLast(2*count); + for (t in ts.vals()) { + Prim.debugPrint(t); + } +}; + +populate(); +readAll(); +populate(); +readAll(); +readExtra(); + +//SKIP run +//SKIP run-low +//SKIP run-ir diff --git a/test/run/stable-mem-big-blob.mo b/test/run/stable-mem-big-blob.mo new file mode 100644 index 00000000000..3531121818c --- /dev/null +++ b/test/run/stable-mem-big-blob.mo @@ -0,0 +1,23 @@ +import P "mo:⛔"; +import StableMemory "stable-mem/StableMemory"; + +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"); + +//SKIP run +//SKIP run-low +//SKIP run-ir +// too slow on ic-ref-run: +//SKIP comp-ref + + diff --git a/test/run/stable-mem/StableMemory.mo b/test/run/stable-mem/StableMemory.mo new file mode 100644 index 00000000000..ef6f485f326 --- /dev/null +++ b/test/run/stable-mem/StableMemory.mo @@ -0,0 +1,38 @@ +import Prim "mo:⛔"; + +module { + + public let size = Prim.stableMemorySize; + public let grow = Prim.stableMemoryGrow; + + public let loadNat32 = Prim.stableMemoryLoadNat32; + public let storeNat32 = Prim.stableMemoryStoreNat32; + + public let loadNat8 = Prim.stableMemoryLoadNat8; + public let storeNat8 = Prim.stableMemoryStoreNat8; + + public let loadNat16 = Prim.stableMemoryLoadNat16; + public let storeNat16 = Prim.stableMemoryStoreNat16; + + public let loadNat64 = Prim.stableMemoryLoadNat64; + public let storeNat64 = Prim.stableMemoryStoreNat64; + + public let loadFloat = Prim.stableMemoryLoadFloat; + public let storeFloat= Prim.stableMemoryStoreFloat; + + public let loadInt32 = Prim.stableMemoryLoadInt32; + public let storeInt32 = Prim.stableMemoryStoreInt32; + + public let loadInt8 = Prim.stableMemoryLoadInt8; + public let storeInt8 = Prim.stableMemoryStoreInt8; + + public let loadInt16 = Prim.stableMemoryLoadInt16; + public let storeInt16 = Prim.stableMemoryStoreInt16; + + public let loadInt64 = Prim.stableMemoryLoadInt64; + public let storeInt64 = Prim.stableMemoryStoreInt64; + + public let loadBlob = Prim.stableMemoryLoadBlob; + public let storeBlob = Prim.stableMemoryStoreBlob; + +} diff --git a/test/run/stable-memory-test.mo b/test/run/stable-memory-test.mo new file mode 100644 index 00000000000..425555f81b8 --- /dev/null +++ b/test/run/stable-memory-test.mo @@ -0,0 +1,212 @@ +import Prim "mo:⛔"; +import StableMemory "stable-mem/StableMemory"; + +let 0 = StableMemory.size(); +let 0 = StableMemory.grow(64); +assert (64 == StableMemory.size()); + +do { + Prim.debugPrint("Nat8"); + type T = Nat8; + let size : Nat64 = 1; + let mod : Nat64 = 256; + let load = StableMemory.loadNat8; + let store = StableMemory.storeNat8; + func conv(n : Nat64) : Nat8 { Prim.natToNat8(Prim.nat64ToNat(n % mod)) }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Nat16"); + type T = Nat16; + let size : Nat64 = 2; + let mod : Nat64 = 65536; + let load = StableMemory.loadNat16; + let store = StableMemory.storeNat16; + func conv(n : Nat64) : Nat16 { Prim.natToNat16(Prim.nat64ToNat(n % mod)) }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Nat32"); + type T = Nat32; + let size : Nat64 = 4; + let mod : Nat64 = 2**32; + let load = StableMemory.loadNat32; + let store = StableMemory.storeNat32; + func conv(n : Nat64) : Nat32 { Prim.natToNat32(Prim.nat64ToNat(n % mod)) }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Nat64"); + type T = Nat8; + let size : Nat64 = 8; + let load = StableMemory.loadNat64; + let store = StableMemory.storeNat64; + func conv(n : Nat64) : Nat64 { n }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int8"); + type T = Int8; + let size : Nat64 = 1; + let mod : Nat64 = 256; + let load = StableMemory.loadInt8; + let store = StableMemory.storeInt8; + func conv(n : Nat64) : Int8 { Prim.intToInt8(Prim.nat64ToNat(n % mod) - 128) }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int16"); + type T = Int16; + let size : Nat64 = 2; + let mod : Nat64 = 65536; + let load = StableMemory.loadInt16; + let store = StableMemory.storeInt16; + func conv(n : Nat64) : Int16 { Prim.intToInt16(Prim.nat64ToNat(n % mod) - 32768 )}; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int32"); + type T = Int32; + let size : Nat64 = 4; + let mod : Nat64 = 2**32; + let load = StableMemory.loadInt32; + let store = StableMemory.storeInt32; + func conv(n : Nat64) : Int32 { Prim.intToInt32(Prim.nat64ToNat(n % mod) - 2147483648) }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Int64"); + type T = Int8; + let size : Nat64 = 8; + let load = StableMemory.loadInt64; + let store = StableMemory.storeInt64; + func conv(n : Nat64) : Int64 { Prim.nat64ToInt64(n)+(-9223372036854775808) }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Float"); + type T = Int8; + let size : Nat64 = 8; + let load = StableMemory.loadFloat; + let store = StableMemory.storeFloat; + func conv(n : Nat64) : Float { Prim.int64ToFloat(Prim.nat64ToInt64(n)+(-9223372036854775808)) }; + let max = StableMemory.size()*65536; + var i : Nat64 = 0; + while(i < max) { + store(i, conv(i)); + i += size; + }; + i := 0; + while(i < max) { + assert (conv(i) == load(i)); + i += size; + }; +}; + +do { + Prim.debugPrint("Blob"); + type T = Int8; + let load = StableMemory.loadBlob; + let store = StableMemory.storeBlob; + func conv(n : Nat64) : Blob { load(0, Prim.nat64ToNat(n)) }; + let max : Nat64 = StableMemory.size()*65536; + var i : Nat64 = 1; + while(i < max) { + let b = conv(i); + store(0, b); + assert (b == load(0, Prim.nat64ToNat(i))); + i := i * 2; + }; +}; + +//SKIP run-low +//SKIP run +//SKIP run-ir diff --git a/test/run/stable-memory.mo b/test/run/stable-memory.mo new file mode 100644 index 00000000000..b0a9bdc7299 --- /dev/null +++ b/test/run/stable-memory.mo @@ -0,0 +1,30 @@ +import Prim "mo:prim"; + +Prim.debugPrint(debug_show {size = Prim.stableMemorySize()}); +let 0 = Prim.stableMemoryGrow(16); +let 16 = Prim.stableMemorySize(); +Prim.debugPrint(debug_show {size = Prim.stableMemorySize()}); +let 0 = Prim.stableMemoryLoadNat8(0); +Prim.debugPrint(debug_show {read = Prim.stableMemoryLoadNat8(0)}); +Prim.stableMemoryStoreNat8(0,66); +Prim.debugPrint(debug_show {read = Prim.stableMemoryLoadNat8(0)}); +let 66 = Prim.stableMemoryLoadNat8(0); +Prim.debugPrint(debug_show {read = Prim.stableMemoryLoadNat8(0)}); + +/* regions */ + +let r1 = Prim.regionNew(); +let 0 = Prim.regionGrow(r1, 16); +let 16 = Prim.regionSize(r1); +Prim.debugPrint(debug_show {size = Prim.regionSize(r1)}); +let 0 = Prim.regionLoadNat8(r1, 0); +Prim.debugPrint(debug_show {read = Prim.regionLoadNat8(r1, 0)}); +Prim.regionStoreNat8(r1, 0, 66); +Prim.debugPrint(debug_show {read = Prim.regionLoadNat8(r1, 0)}); +let 66 = Prim.regionLoadNat8(r1, 0); +Prim.debugPrint(debug_show {read = Prim.regionLoadNat8(r1, 0)}); + + +//SKIP run-low +//SKIP run +//SKIP run-ir diff --git a/test/run/stable-region/Region.mo b/test/run/stable-region/Region.mo new file mode 100644 index 00000000000..7422a0a3811 --- /dev/null +++ b/test/run/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; + +}