From 92d338ff3a3d760abaaf209db5f2ec98cd297d27 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 16 Aug 2021 10:51:24 -0500 Subject: [PATCH] Implement support for memory64 (#96) * Implement support for memory64 Like the Rust crate this deletes the old `Limits` type in favor of specific types/methods where relevant. * Fix bazel build * Remove `*.dll.a` file for windows --- BUILD.bazel | 1 - ci/download-wasmtime.py | 2 ++ config.go | 6 ++++ doc_test.go | 2 +- exporttype_test.go | 2 +- instance_test.go | 4 +-- limits.go | 45 ------------------------------ linker_test.go | 2 +- memory.go | 12 ++++---- memorytype.go | 62 +++++++++++++++++++++++++++++++++++------ memorytype_test.go | 16 +++++++++-- module_test.go | 4 +-- multi_memory_test.go | 16 ++++++++--- reftypes_test.go | 2 +- table_test.go | 2 +- tabletype.go | 41 ++++++++++++++++++++++----- tabletype_test.go | 14 ++++++---- 17 files changed, 144 insertions(+), 89 deletions(-) delete mode 100644 limits.go diff --git a/BUILD.bazel b/BUILD.bazel index d55f2768d6df..90f8f158f3bd 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -30,7 +30,6 @@ go_library( "importtype.go", "instance.go", "instancetype.go", - "limits.go", "linker.go", "maybe_gc_no.go", "memory.go", diff --git a/ci/download-wasmtime.py b/ci/download-wasmtime.py index 52660552ac15..12e7f74445a8 100644 --- a/ci/download-wasmtime.py +++ b/ci/download-wasmtime.py @@ -48,6 +48,8 @@ for dylib in glob.glob("build/**/*.dll"): os.remove(dylib) +for dylib in glob.glob("build/**/*.dll.a"): + os.remove(dylib) for dylib in glob.glob("build/**/*.dylib"): os.remove(dylib) for dylib in glob.glob("build/**/*.so"): diff --git a/config.go b/config.go index 26a0a6fedb56..fa5d226c54a8 100644 --- a/config.go +++ b/config.go @@ -106,6 +106,12 @@ func (cfg *Config) SetWasmMultiMemory(enabled bool) { runtime.KeepAlive(cfg) } +// SetWasmMemory64 configures whether the wasm memory64 proposal is enabled +func (cfg *Config) SetWasmMemory64(enabled bool) { + C.wasmtime_config_wasm_memory64_set(cfg.ptr(), C.bool(enabled)) + runtime.KeepAlive(cfg) +} + // SetStrategy configures what compilation strategy is used to compile wasm code func (cfg *Config) SetStrategy(strat Strategy) error { err := C.wasmtime_config_strategy_set(cfg.ptr(), C.wasmtime_strategy_t(strat)) diff --git a/doc_test.go b/doc_test.go index 70bdfda9190f..6e8931b1e041 100644 --- a/doc_test.go +++ b/doc_test.go @@ -197,7 +197,7 @@ func Example_memory() { // Finally we can also create standalone memories to get imported by // wasm modules too. - memorytype := NewMemoryType(Limits{Min: 5, Max: 5}) + memorytype := NewMemoryType(5, true, 5) memory2, err := NewMemory(store, memorytype) assert(err == nil) assert(memory2.Size(store) == 5) diff --git a/exporttype_test.go b/exporttype_test.go index 297f01240718..c457679e09b3 100644 --- a/exporttype_test.go +++ b/exporttype_test.go @@ -3,7 +3,7 @@ package wasmtime import "testing" func TestExportType(t *testing.T) { - et := NewExportType("x", NewMemoryType(Limits{})) + et := NewExportType("x", NewMemoryType(0, false, 0)) if et.Name() != "x" { panic("bad name") } diff --git a/instance_test.go b/instance_test.go index 81685d4359c7..bd69323a8b91 100644 --- a/instance_test.go +++ b/instance_test.go @@ -81,10 +81,10 @@ func TestInstance(t *testing.T) { if exports[2].Type(store).TableType().Element().Kind() != KindFuncref { panic("bad table type") } - if m.Type(store).Limits().Min != 1 { + if m.Type(store).Minimum() != 1 { panic("bad memory type") } - if exports[3].Type(store).MemoryType().Limits().Min != 1 { + if exports[3].Type(store).MemoryType().Minimum() != 1 { panic("bad memory type") } } diff --git a/limits.go b/limits.go deleted file mode 100644 index 7655dc7b91f1..000000000000 --- a/limits.go +++ /dev/null @@ -1,45 +0,0 @@ -package wasmtime - -// #include -import "C" -import "runtime" - -// LimitsMaxNone is the value for the Max field in Limits -const LimitsMaxNone = 0xffffffff - -// Limits is the resource limits specified for a TableType and MemoryType -type Limits struct { - // The minimum size of this resource, in units specified by the resource - // itself. - Min uint32 - // The maximum size of this resource, in units specified by the resource - // itself. - // - // A value of LimitsMaxNone will mean that there is no maximum. - Max uint32 -} - -// NewLimits creates a new resource limits specified for a TableType and MemoryType, -// in which min and max are the minimum and maximum size of this resource. -func NewLimits(min, max uint32) *Limits { - return &Limits{ - Min: min, - Max: max, - } -} - -func (limits Limits) ffi() C.wasm_limits_t { - return C.wasm_limits_t{ - min: C.uint32_t(limits.Min), - max: C.uint32_t(limits.Max), - } -} - -func mkLimits(ptr *C.wasm_limits_t, owner interface{}) Limits { - ret := Limits{ - Min: uint32(ptr.min), - Max: uint32(ptr.max), - } - runtime.KeepAlive(owner) - return ret -} diff --git a/linker_test.go b/linker_test.go index 8e9f02456b82..b33d242d1fe7 100644 --- a/linker_test.go +++ b/linker_test.go @@ -27,7 +27,7 @@ func TestLinker(t *testing.T) { g, err := NewGlobal(store, NewGlobalType(NewValType(KindI32), false), ValI32(0)) assertNoError(err) assertNoError(linker.Define("", "g", g)) - m, err := NewMemory(store, NewMemoryType(Limits{Min: 1, Max: 0xffffffff})) + m, err := NewMemory(store, NewMemoryType(1, true, 300)) assertNoError(err) assertNoError(linker.Define("", "m", m)) assertNoError(linker.Define("other", "m", m)) diff --git a/memory.go b/memory.go index f060bbfbe73b..4b0d1fd6ee27 100644 --- a/memory.go +++ b/memory.go @@ -73,21 +73,21 @@ func (mem *Memory) DataSize(store Storelike) uintptr { } // Size returns the size, in wasm pages, of this memory -func (mem *Memory) Size(store Storelike) uint32 { - ret := uint32(C.wasmtime_memory_size(store.Context(), &mem.val)) +func (mem *Memory) Size(store Storelike) uint64 { + ret := uint64(C.wasmtime_memory_size(store.Context(), &mem.val)) runtime.KeepAlive(store) return ret } // Grow grows this memory by `delta` pages -func (mem *Memory) Grow(store Storelike, delta uint) (uint32, error) { - prev := C.uint32_t(0) - err := C.wasmtime_memory_grow(store.Context(), &mem.val, C.wasm_memory_pages_t(delta), &prev) +func (mem *Memory) Grow(store Storelike, delta uint64) (uint64, error) { + prev := C.uint64_t(0) + err := C.wasmtime_memory_grow(store.Context(), &mem.val, C.uint64_t(delta), &prev) runtime.KeepAlive(store) if err != nil { return 0, mkError(err) } - return uint32(prev), nil + return uint64(prev), nil } func (mem *Memory) AsExtern() C.wasmtime_extern_t { diff --git a/memorytype.go b/memorytype.go index fb354cf7b517..3a5b7d6ceb09 100644 --- a/memorytype.go +++ b/memorytype.go @@ -1,6 +1,6 @@ package wasmtime -// #include +// #include import "C" import "runtime" @@ -11,10 +11,34 @@ type MemoryType struct { _owner interface{} } -// NewMemoryType creates a new `MemoryType` with the `limits` on size provided -func NewMemoryType(limits Limits) *MemoryType { - limitsFFI := limits.ffi() - ptr := C.wasm_memorytype_new(&limitsFFI) +// NewMemoryType creates a new `MemoryType` with the limits on size provided +// +// The `min` value is the minimum size, in WebAssembly pages, of this memory. +// The `has_max` boolean indicates whether a maximum size is present, and if so +// `max` is used as the maximum size of memory, in wasm pages. +// +// Note that this will create a 32-bit memory type, the default outside of the +// memory64 proposal. +func NewMemoryType(min uint32, has_max bool, max uint32) *MemoryType { + if min > (1<<16) || max > (1<<16) { + panic("provided sizes are too large") + } + ptr := C.wasmtime_memorytype_new(C.uint64_t(min), C._Bool(has_max), C.uint64_t(max), false) + return mkMemoryType(ptr, nil) +} + +// NewMemoryType64 creates a new 64-bit `MemoryType` with the provided limits +// +// The `min` value is the minimum size, in WebAssembly pages, of this memory. +// The `has_max` boolean indicates whether a maximum size is present, and if so +// `max` is used as the maximum size of memory, in wasm pages. +// +// Note that 64-bit memories are part of the memory64 WebAssembly proposal. +func NewMemoryType64(min uint64, has_max bool, max uint64) *MemoryType { + if min > (1<<48) || max > (1<<48) { + panic("provided sizes are too large") + } + ptr := C.wasmtime_memorytype_new(C.uint64_t(min), C._Bool(has_max), C.uint64_t(max), true) return mkMemoryType(ptr, nil) } @@ -41,10 +65,30 @@ func (ty *MemoryType) owner() interface{} { return ty } -// Limits returns the limits on the size of this memory type -func (ty *MemoryType) Limits() Limits { - ptr := C.wasm_memorytype_limits(ty.ptr()) - return mkLimits(ptr, ty.owner()) +// Minimum returns the minimum size of this memory, in WebAssembly pages +func (ty *MemoryType) Minimum() uint64 { + ret := C.wasmtime_memorytype_minimum(ty.ptr()) + runtime.KeepAlive(ty) + return uint64(ret) +} + +// Maximum returns the maximum size of this memory, in WebAssembly pages, if +// specified. +// +// If the maximum size is not specified then `(false, 0)` is returned, otherwise +// `(true, N)` is returned where `N` is the listed maximum size of this memory. +func (ty *MemoryType) Maximum() (bool, uint64) { + size := C.uint64_t(0) + present := C.wasmtime_memorytype_maximum(ty.ptr(), &size) + runtime.KeepAlive(ty) + return bool(present), uint64(size) +} + +// Is64 returns whether this is a 64-bit memory or not. +func (ty *MemoryType) Is64() bool { + ok := C.wasmtime_memorytype_is64(ty.ptr()) + runtime.KeepAlive(ty) + return bool(ok) } // AsExternType converts this type to an instance of `ExternType` diff --git a/memorytype_test.go b/memorytype_test.go index 23276ef271c2..6f4147b74723 100644 --- a/memorytype_test.go +++ b/memorytype_test.go @@ -3,8 +3,9 @@ package wasmtime import "testing" func TestMemoryType(t *testing.T) { - ty := NewMemoryType(Limits{Min: 0, Max: 100}) - ty.Limits() + ty := NewMemoryType(0, true, 100) + ty.Minimum() + ty.Maximum() ty2 := ty.AsExternType().MemoryType() if ty2 == nil { @@ -20,3 +21,14 @@ func TestMemoryType(t *testing.T) { panic("working cast") } } + +func TestMemoryType64(t *testing.T) { + ty := NewMemoryType64(0x100000000, true, 0x100000001) + if ty.Minimum() != 0x100000000 { + panic("bad limits") + } + present, max := ty.Maximum() + if !present || max != 0x100000001 { + panic("bad limits") + } +} diff --git a/module_test.go b/module_test.go index e5c4cb40acbe..259556abc387 100644 --- a/module_test.go +++ b/module_test.go @@ -100,7 +100,7 @@ func TestModuleImports(t *testing.T) { if imports[3].Type().MemoryType() == nil { panic("wrong import type") } - if imports[3].Type().MemoryType().Limits().Min != 1 { + if imports[3].Type().MemoryType().Minimum() != 1 { panic("wrong import type") } } @@ -164,7 +164,7 @@ func TestModuleExports(t *testing.T) { if exports[3].Type().MemoryType() == nil { panic("wrong export type") } - if exports[3].Type().MemoryType().Limits().Min != 1 { + if exports[3].Type().MemoryType().Minimum() != 1 { panic("wrong export type") } } diff --git a/multi_memory_test.go b/multi_memory_test.go index ab491f02b544..4d80e0b76b25 100644 --- a/multi_memory_test.go +++ b/multi_memory_test.go @@ -32,13 +32,21 @@ func TestMultiMemoryExported(t *testing.T) { if exports[0].Type().MemoryType() == nil { panic("wrong export type") } - if (exports[0].Type().MemoryType().Limits() != Limits{Min: 2, Max: 3}) { + if exports[0].Type().MemoryType().Minimum() != 2 { + panic("wrong memory limits") + } + present, max := exports[0].Type().MemoryType().Maximum() + if !present || max != 3 { panic("wrong memory limits") } if exports[1].Type().MemoryType() == nil { panic("wrong export type") } - if (exports[1].Type().MemoryType().Limits() != Limits{Min: 2, Max: 4}) { + if exports[1].Type().MemoryType().Minimum() != 2 { + panic("wrong memory limits") + } + present, max = exports[1].Type().MemoryType().Maximum() + if !present || max != 4 { panic("wrong memory limits") } @@ -63,11 +71,11 @@ func TestMultiMemoryImported(t *testing.T) { } store := multiMemoryStore() - mem0, err := NewMemory(store, NewMemoryType(Limits{Min: 1, Max: 3})) + mem0, err := NewMemory(store, NewMemoryType(1, true, 3)) if err != nil { panic(err) } - mem1, err := NewMemory(store, NewMemoryType(Limits{Min: 2, Max: 4})) + mem1, err := NewMemory(store, NewMemoryType(2, true, 4)) if err != nil { panic(err) } diff --git a/reftypes_test.go b/reftypes_test.go index c24f0e9d9b1b..84a2d59b4097 100644 --- a/reftypes_test.go +++ b/reftypes_test.go @@ -87,7 +87,7 @@ func TestRefTypesTable(t *testing.T) { store := refTypesStore() table, err := NewTable( store, - NewTableType(NewValType(KindExternref), Limits{Min: 10, Max: LimitsMaxNone}), + NewTableType(NewValType(KindExternref), 10, false, 0), ValExternref("init"), ) if err != nil { diff --git a/table_test.go b/table_test.go index 6395fad9a9f3..d66e4b04d110 100644 --- a/table_test.go +++ b/table_test.go @@ -4,7 +4,7 @@ import "testing" func TestTable(t *testing.T) { store := NewStore(NewEngine()) - ty := NewTableType(NewValType(KindFuncref), Limits{Min: 1, Max: 3}) + ty := NewTableType(NewValType(KindFuncref), 1, true, 3) table, err := NewTable(store, ty, ValFuncref(nil)) if err != nil { panic(err) diff --git a/tabletype.go b/tabletype.go index a7a29300619a..50da0c8acc99 100644 --- a/tabletype.go +++ b/tabletype.go @@ -10,12 +10,22 @@ type TableType struct { _owner interface{} } -// NewTableType creates a new `TableType` with the `element` type provided as well as -// `limits` on its size. -func NewTableType(element *ValType, limits Limits) *TableType { +// NewTableType creates a new `TableType` with the `element` type provided as +// well as limits on its size. +// +// The `min` value is the minimum size, in elements, of this table. The +// `has_max` boolean indicates whether a maximum size is present, and if so +// `max` is used as the maximum size of the table, in elements. +func NewTableType(element *ValType, min uint32, has_max bool, max uint32) *TableType { valptr := C.wasm_valtype_new(C.wasm_valtype_kind(element.ptr())) runtime.KeepAlive(element) - limitsFFI := limits.ffi() + if !has_max { + max = 0xffffffff + } + limitsFFI := C.wasm_limits_t{ + min: C.uint32_t(min), + max: C.uint32_t(max), + } ptr := C.wasm_tabletype_new(valptr, &limitsFFI) return mkTableType(ptr, nil) @@ -50,10 +60,27 @@ func (ty *TableType) Element() *ValType { return mkValType(ptr, ty.owner()) } -// Limits returns limits on the size of this table type -func (ty *TableType) Limits() Limits { +// Minimum returns the minimum size, in elements, of this table. +func (ty *TableType) Minimum() uint32 { ptr := C.wasm_tabletype_limits(ty.ptr()) - return mkLimits(ptr, ty.owner()) + ret := uint32(ptr.min) + runtime.KeepAlive(ty) + return ret +} + +// Maximum returns the maximum size, in elements, of this table. +// +// If no maximum size is listed then `(false, 0)` is returned, otherwise +// `(true, N)` is returned where `N` is the maximum size. +func (ty *TableType) Maximum() (bool, uint32) { + ptr := C.wasm_tabletype_limits(ty.ptr()) + ret := uint32(ptr.max) + runtime.KeepAlive(ty) + if ret == 0xffffffff { + return false, 0 + } else { + return true, ret + } } // AsExternType converts this type to an instance of `ExternType` diff --git a/tabletype_test.go b/tabletype_test.go index 321fbc59a11f..69523c8c9681 100644 --- a/tabletype_test.go +++ b/tabletype_test.go @@ -3,25 +3,27 @@ package wasmtime import "testing" func TestTableType(t *testing.T) { - ty := NewTableType(NewValType(KindI32), Limits{}) + ty := NewTableType(NewValType(KindI32), 0, false, 0) if ty.Element().Kind() != KindI32 { panic("invalid kind") } - if ty.Limits().Min != 0 { + if ty.Minimum() != 0 { panic("invalid min") } - if ty.Limits().Max != 0 { + present, _ := ty.Maximum() + if present { panic("invalid max") } - ty = NewTableType(NewValType(KindF64), Limits{Min: 1, Max: 129}) + ty = NewTableType(NewValType(KindF64), 1, true, 129) if ty.Element().Kind() != KindF64 { panic("invalid kind") } - if ty.Limits().Min != 1 { + if ty.Minimum() != 1 { panic("invalid min") } - if ty.Limits().Max != 129 { + present, max := ty.Maximum() + if !present || max != 129 { panic("invalid max") }