This stops gathering `NonStaticLocals` during validation phase,
which was previously used to do the "fast pass" on variable search
by the frontend. However, it had no impact after the last mile refactoring
included in 1.7.0 and caused tons of allocations.
As as result, you can see the compilation perf improvements especially
around memory pressure without any impacts on the runtime perf
### Zig
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
│ old_zig.txt │ new_zig.txt │
│ sec/op │ sec/op vs base │
Zig/Compile/test-opt.wasm-10 3.769 ± 1% 3.761 ± 1% ~ (p=0.485 n=6)
Zig/Run/test-opt.wasm-10 18.78 ± 1% 18.74 ± 0% -0.21% (p=0.041 n=6)
Zig/Compile/test.wasm-10 4.677 ± 1% 4.515 ± 0% -3.48% (p=0.002 n=6)
Zig/Run/test.wasm-10 19.31 ± 1% 19.27 ± 1% ~ (p=1.000 n=6)
geomean 8.942 8.850 -1.04%
│ old_zig.txt │ new_zig.txt │
│ B/op │ B/op vs base │
Zig/Compile/test-opt.wasm-10 394.7Mi ± 0% 393.5Mi ± 0% -0.30% (p=0.002 n=6)
Zig/Run/test-opt.wasm-10 741.7Mi ± 0% 741.7Mi ± 0% ~ (p=0.621 n=6)
Zig/Compile/test.wasm-10 659.5Mi ± 0% 599.3Mi ± 0% -9.12% (p=0.002 n=6)
Zig/Run/test.wasm-10 1.296Gi ± 0% 1.296Gi ± 0% ~ (p=0.102 n=6)
geomean 711.5Mi 694.2Mi -2.44%
│ old_zig.txt │ new_zig.txt │
│ allocs/op │ allocs/op vs base │
Zig/Compile/test-opt.wasm-10 362.6k ± 0% 343.2k ± 0% -5.34% (p=0.002 n=6)
Zig/Run/test-opt.wasm-10 51.58k ± 0% 51.58k ± 0% ~ (p=0.978 n=6)
Zig/Compile/test.wasm-10 514.7k ± 0% 288.1k ± 0% -44.04% (p=0.002 n=6)
Zig/Run/test.wasm-10 2.156M ± 0% 2.156M ± 0% ~ (p=0.273 n=6)
geomean 379.5k 323.8k -14.69%
```
### TinyGo
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
│ old_tinygo.txt │ new_tinygo.txt │
│ sec/op │ sec/op vs base │
TinyGo/Compile/container_heap.test-10 410.8m ± 1% 399.8m ± 0% -2.69% (p=0.001 n=7)
TinyGo/Run/container_heap.test-10 14.41m ± 0% 14.29m ± 2% -0.77% (p=0.026 n=7)
TinyGo/Compile/container_list.test-10 410.5m ± 1% 398.1m ± 0% -3.02% (p=0.001 n=7)
TinyGo/Run/container_list.test-10 14.27m ± 2% 14.16m ± 1% ~ (p=0.073 n=7)
TinyGo/Compile/container_ring.test-10 403.7m ± 1% 392.5m ± 2% -2.77% (p=0.001 n=7)
TinyGo/Run/container_ring.test-10 14.24m ± 0% 14.27m ± 1% ~ (p=0.259 n=7)
TinyGo/Compile/crypto_des.test-10 418.8m ± 0% 408.1m ± 0% -2.56% (p=0.001 n=7)
TinyGo/Run/crypto_des.test-10 18.23m ± 0% 18.17m ± 1% ~ (p=0.456 n=7)
TinyGo/Compile/crypto_md5.test-10 417.3m ± 2% 406.1m ± 1% -2.68% (p=0.001 n=7)
TinyGo/Run/crypto_md5.test-10 20.50m ± 0% 20.45m ± 1% ~ (p=0.128 n=7)
TinyGo/Compile/crypto_rc4.test-10 402.2m ± 1% 390.5m ± 0% -2.90% (p=0.001 n=7)
TinyGo/Run/crypto_rc4.test-10 160.8m ± 0% 161.0m ± 1% ~ (p=1.000 n=7)
TinyGo/Compile/crypto_sha1.test-10 417.2m ± 1% 404.5m ± 1% -3.04% (p=0.001 n=7)
TinyGo/Run/crypto_sha1.test-10 15.93m ± 1% 15.90m ± 1% ~ (p=0.710 n=7)
TinyGo/Compile/crypto_sha256.test-10 423.4m ± 1% 412.4m ± 1% -2.60% (p=0.001 n=7)
TinyGo/Run/crypto_sha256.test-10 16.16m ± ∞ ¹ 16.05m ± ∞ ¹ ~ (p=0.381 n=2+5)
geomean 94.17m 92.70m -1.56%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_tinygo.txt │ new_tinygo.txt │
│ B/op │ B/op vs base │
TinyGo/Compile/container_heap.test-10 48.55Mi ± 0% 48.30Mi ± 0% -0.52% (p=0.001 n=7)
TinyGo/Run/container_heap.test-10 16.63Mi ± 0% 16.63Mi ± 0% ~ (p=0.557 n=7)
TinyGo/Compile/container_list.test-10 48.53Mi ± 0% 48.29Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/container_list.test-10 16.40Mi ± 0% 16.40Mi ± 0% ~ (p=0.364 n=7)
TinyGo/Compile/container_ring.test-10 47.78Mi ± 0% 47.53Mi ± 0% -0.52% (p=0.001 n=7)
TinyGo/Run/container_ring.test-10 16.30Mi ± 0% 16.30Mi ± 0% ~ (p=0.128 n=7)
TinyGo/Compile/crypto_des.test-10 48.67Mi ± 0% 48.42Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_des.test-10 16.76Mi ± 0% 16.76Mi ± 0% ~ (p=0.902 n=7)
TinyGo/Compile/crypto_md5.test-10 48.73Mi ± 0% 48.48Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_md5.test-10 44.97Mi ± 0% 44.97Mi ± 0% ~ (p=0.402 n=7)
TinyGo/Compile/crypto_rc4.test-10 47.76Mi ± 0% 47.52Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_rc4.test-10 29.28Mi ± 0% 29.28Mi ± 0% ~ (p=0.104 n=7)
TinyGo/Compile/crypto_sha1.test-10 48.97Mi ± 0% 48.72Mi ± 0% -0.52% (p=0.001 n=7)
TinyGo/Run/crypto_sha1.test-10 17.44Mi ± 0% 17.44Mi ± 0% ~ (p=1.000 n=7)
TinyGo/Compile/crypto_sha256.test-10 48.81Mi ± 0% 48.56Mi ± 0% -0.51% (p=0.001 n=7)
TinyGo/Run/crypto_sha256.test-10 17.53Mi ± ∞ ¹ 17.53Mi ± ∞ ¹ ~ (p=0.381 n=2+5)
geomean 31.45Mi 31.37Mi -0.26%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_tinygo.txt │ new_tinygo.txt │
│ allocs/op │ allocs/op vs base │
TinyGo/Compile/container_heap.test-10 83.67k ± 0% 83.46k ± 0% -0.25% (p=0.011 n=7)
TinyGo/Run/container_heap.test-10 374.9k ± 0% 374.9k ± 0% ~ (p=1.000 n=7)
TinyGo/Compile/container_list.test-10 83.34k ± 0% 83.19k ± 0% -0.19% (p=0.002 n=7)
TinyGo/Run/container_list.test-10 370.0k ± 0% 370.0k ± 0% ~ (p=0.674 n=7)
TinyGo/Compile/container_ring.test-10 83.26k ± 0% 83.08k ± 0% -0.22% (p=0.004 n=7)
TinyGo/Run/container_ring.test-10 367.6k ± 0% 367.6k ± 0% ~ (p=0.249 n=7)
TinyGo/Compile/crypto_des.test-10 83.68k ± 0% 83.53k ± 0% -0.18% (p=0.004 n=7)
TinyGo/Run/crypto_des.test-10 378.1k ± 0% 378.1k ± 0% ~ (p=0.437 n=7)
TinyGo/Compile/crypto_md5.test-10 83.86k ± 0% 83.67k ± 0% -0.23% (p=0.001 n=7)
TinyGo/Run/crypto_md5.test-10 393.3k ± 0% 393.3k ± 0% ~ (p=0.592 n=7)
TinyGo/Compile/crypto_rc4.test-10 83.32k ± 0% 83.20k ± 0% -0.14% (p=0.011 n=7)
TinyGo/Run/crypto_rc4.test-10 367.1k ± 0% 367.1k ± 0% ~ (p=0.102 n=7)
TinyGo/Compile/crypto_sha1.test-10 84.05k ± 0% 83.87k ± 0% -0.21% (p=0.002 n=7)
TinyGo/Run/crypto_sha1.test-10 392.7k ± 0% 392.7k ± 0% ~ (p=1.000 n=7)
TinyGo/Compile/crypto_sha256.test-10 83.86k ± 0% 83.67k ± 0% -0.24% (p=0.001 n=7)
TinyGo/Run/crypto_sha256.test-10 394.5k ± ∞ ¹ 394.5k ± ∞ ¹ ~ (p=0.952 n=2+5)
geomean 178.2k 178.0k -0.10%
```
### wasip1
```
goos: darwin
goarch: arm64
pkg: github.com/tetratelabs/wazero/internal/integration_test/stdlibs
│ old_wasip1.txt │ new_wasip1.txt │
│ sec/op │ sec/op vs base │
Wasip1/Compile/src_archive_tar.test-10 2.066 ± 1% 2.066 ± 1% ~ (p=1.000 n=7)
Wasip1/Run/src_archive_tar.test-10 398.9m ± 1% 398.9m ± 0% ~ (p=0.902 n=7)
Wasip1/Compile/src_bufio.test-10 1.405 ± 0% 1.405 ± 0% ~ (p=0.318 n=7)
Wasip1/Run/src_bufio.test-10 120.1m ± 0% 120.0m ± 0% ~ (p=0.456 n=7)
Wasip1/Compile/src_bytes.test-10 1.453 ± 0% 1.452 ± 0% ~ (p=0.383 n=7)
Wasip1/Run/src_bytes.test-10 468.9m ± 1% 467.7m ± 1% ~ (p=1.000 n=7)
Wasip1/Compile/src_context.test-10 1.565 ± 0% 1.562 ± 0% -0.18% (p=0.001 n=7)
Wasip1/Run/src_context.test-10 31.52m ± 1% 31.51m ± 1% ~ (p=0.620 n=7)
Wasip1/Compile/src_encoding_ascii85.test-10 1.262 ± ∞ ¹ 1.262 ± 0% ~ (p=0.889 n=2+7)
geomean 565.3m 564.9m -0.07%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_wasip1.txt │ new_wasip1.txt │
│ B/op │ B/op vs base │
Wasip1/Compile/src_archive_tar.test-10 93.16Mi ± 0% 92.70Mi ± 0% -0.50% (p=0.001 n=7)
Wasip1/Run/src_archive_tar.test-10 286.0Mi ± 0% 286.0Mi ± 0% ~ (p=0.246 n=7)
Wasip1/Compile/src_bufio.test-10 74.12Mi ± 0% 73.79Mi ± 0% -0.45% (p=0.001 n=7)
Wasip1/Run/src_bufio.test-10 105.3Mi ± 0% 105.3Mi ± 0% ~ (p=0.780 n=7)
Wasip1/Compile/src_bytes.test-10 75.32Mi ± 0% 74.96Mi ± 0% -0.47% (p=0.001 n=7)
Wasip1/Run/src_bytes.test-10 605.0Mi ± 0% 605.0Mi ± 0% ~ (p=1.000 n=7)
Wasip1/Compile/src_context.test-10 78.07Mi ± 0% 77.68Mi ± 0% -0.49% (p=0.001 n=7)
Wasip1/Run/src_context.test-10 71.52Mi ± 0% 71.52Mi ± 0% ~ (p=0.516 n=7)
Wasip1/Compile/src_encoding_ascii85.test-10 70.38Mi ± ∞ ¹ 70.08Mi ± 0% ~ (p=0.056 n=2+7)
geomean 115.7Mi 115.4Mi -0.26%
¹ need >= 6 samples for confidence interval at level 0.95
│ old_wasip1.txt │ new_wasip1.txt │
│ allocs/op │ allocs/op vs base │
Wasip1/Compile/src_archive_tar.test-10 265.0k ± 0% 256.1k ± 0% -3.37% (p=0.001 n=7)
Wasip1/Run/src_archive_tar.test-10 7.831k ± 0% 7.830k ± 0% ~ (p=0.592 n=7)
Wasip1/Compile/src_bufio.test-10 195.3k ± 0% 189.1k ± 0% -3.19% (p=0.001 n=7)
Wasip1/Run/src_bufio.test-10 3.728k ± 0% 3.728k ± 0% ~ (p=1.000 n=7) ¹
Wasip1/Compile/src_bytes.test-10 203.7k ± 0% 197.0k ± 0% -3.31% (p=0.001 n=7)
Wasip1/Run/src_bytes.test-10 6.377k ± 0% 6.377k ± 0% ~ (p=0.559 n=7)
Wasip1/Compile/src_context.test-10 221.4k ± 0% 214.2k ± 0% -3.29% (p=0.001 n=7)
Wasip1/Run/src_context.test-10 3.814k ± 1% 3.814k ± 0% ~ (p=0.192 n=7)
Wasip1/Compile/src_encoding_ascii85.test-10 182.3k ± ∞ ² 176.6k ± 0% ~ (p=0.056 n=2+7)
geomean 40.64k 39.90k -1.82%
```
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
1079 lines
37 KiB
Go
1079 lines
37 KiB
Go
package wasm
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/experimental"
|
|
"github.com/tetratelabs/wazero/internal/ieee754"
|
|
"github.com/tetratelabs/wazero/internal/leb128"
|
|
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
|
)
|
|
|
|
// Module is a WebAssembly binary representation.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A8
|
|
//
|
|
// Differences from the specification:
|
|
// * NameSection is the only key ("name") decoded from the SectionIDCustom.
|
|
// * ExportSection is represented as a map for lookup convenience.
|
|
// * Code.GoFunc is contains any go `func`. It may be present when Code.Body is not.
|
|
type Module struct {
|
|
// TypeSection contains the unique FunctionType of functions imported or defined in this module.
|
|
//
|
|
// Note: Currently, there is no type ambiguity in the index as WebAssembly 1.0 only defines function type.
|
|
// In the future, other types may be introduced to support CoreFeatures such as module linking.
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDType.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#types%E2%91%A0%E2%91%A0
|
|
TypeSection []FunctionType
|
|
|
|
// ImportSection contains imported functions, tables, memories or globals required for instantiation
|
|
// (Store.Instantiate).
|
|
//
|
|
// Note: there are no unique constraints relating to the two-level namespace of Import.Module and Import.Name.
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDImport.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#import-section%E2%91%A0
|
|
ImportSection []Import
|
|
// ImportFunctionCount ImportGlobalCount ImportMemoryCount, and ImportTableCount are
|
|
// the cached import count per ExternType set during decoding.
|
|
ImportFunctionCount,
|
|
ImportGlobalCount,
|
|
ImportMemoryCount,
|
|
ImportTableCount Index
|
|
// ImportPerModule maps a module name to the list of Import to be imported from the module.
|
|
// This is used to do fast import resolution during instantiation.
|
|
ImportPerModule map[string][]*Import
|
|
|
|
// FunctionSection contains the index in TypeSection of each function defined in this module.
|
|
//
|
|
// Note: The function Index space begins with imported functions and ends with those defined in this module.
|
|
// For example, if there are two imported functions and one defined in this module, the function Index 3 is defined
|
|
// in this module at FunctionSection[0].
|
|
//
|
|
// Note: FunctionSection is index correlated with the CodeSection. If given the same position, e.g. 2, a function
|
|
// type is at TypeSection[FunctionSection[2]], while its locals and body are at CodeSection[2].
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDFunction.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-section%E2%91%A0
|
|
FunctionSection []Index
|
|
|
|
// TableSection contains each table defined in this module.
|
|
//
|
|
// Note: The table Index space begins with imported tables and ends with those defined in this module.
|
|
// For example, if there are two imported tables and one defined in this module, the table Index 3 is defined in
|
|
// this module at TableSection[0].
|
|
//
|
|
// Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one table definition per module, so the
|
|
// length of the TableSection can be zero or one, and can only be one if there is no imported table.
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDTable.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-section%E2%91%A0
|
|
TableSection []Table
|
|
|
|
// MemorySection contains each memory defined in this module.
|
|
//
|
|
// Note: The memory Index space begins with imported memories and ends with those defined in this module.
|
|
// For example, if there are two imported memories and one defined in this module, the memory Index 3 is defined in
|
|
// this module at TableSection[0].
|
|
//
|
|
// Note: Version 1.0 (20191205) of the WebAssembly spec allows at most one memory definition per module, so the
|
|
// length of the MemorySection can be zero or one, and can only be one if there is no imported memory.
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDMemory.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-section%E2%91%A0
|
|
MemorySection *Memory
|
|
|
|
// GlobalSection contains each global defined in this module.
|
|
//
|
|
// Global indexes are offset by any imported globals because the global index begins with imports, followed by
|
|
// ones defined in this module. For example, if there are two imported globals and three defined in this module, the
|
|
// global at index 3 is defined in this module at GlobalSection[0].
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDGlobal.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-section%E2%91%A0
|
|
GlobalSection []Global
|
|
|
|
// ExportSection contains each export defined in this module.
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDExport.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#exports%E2%91%A0
|
|
ExportSection []Export
|
|
// Exports maps a name to Export, and is convenient for fast look up of exported instances at runtime.
|
|
// Each item of this map points to an element of ExportSection.
|
|
Exports map[string]*Export
|
|
|
|
// StartSection is the index of a function to call before returning from Store.Instantiate.
|
|
//
|
|
// Note: The index here is not the position in the FunctionSection, rather in the function index, which
|
|
// begins with imported functions.
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDStart.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#start-section%E2%91%A0
|
|
StartSection *Index
|
|
|
|
// Note: In the Binary Format, this is SectionIDElement.
|
|
ElementSection []ElementSegment
|
|
|
|
// CodeSection is index-correlated with FunctionSection and contains each
|
|
// function's locals and body.
|
|
//
|
|
// When present, the HostFunctionSection of the same index must be nil.
|
|
//
|
|
// Note: In the Binary Format, this is SectionIDCode.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0
|
|
CodeSection []Code
|
|
|
|
// Note: In the Binary Format, this is SectionIDData.
|
|
DataSection []DataSegment
|
|
|
|
// NameSection is set when the SectionIDCustom "name" was successfully decoded from the binary format.
|
|
//
|
|
// Note: This is the only SectionIDCustom defined in the WebAssembly 1.0 (20191205) Binary Format.
|
|
// Others are skipped as they are not used in wazero.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
|
NameSection *NameSection
|
|
|
|
// CustomSections are set when the SectionIDCustom other than "name" were successfully decoded from the binary format.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
|
CustomSections []*CustomSection
|
|
|
|
// DataCountSection is the optional section and holds the number of data segments in the data section.
|
|
//
|
|
// Note: This may exist in WebAssembly 2.0 or WebAssembly 1.0 with CoreFeatureBulkMemoryOperations.
|
|
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section
|
|
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions
|
|
DataCountSection *uint32
|
|
|
|
// ID is the sha256 value of the source wasm plus the configurations which affect the runtime representation of
|
|
// Wasm binary. This is only used for caching.
|
|
ID ModuleID
|
|
|
|
// IsHostModule true if this is the host module, false otherwise.
|
|
IsHostModule bool
|
|
|
|
// functionDefinitionSectionInitOnce guards FunctionDefinitionSection so that it is initialized exactly once.
|
|
functionDefinitionSectionInitOnce sync.Once
|
|
|
|
// FunctionDefinitionSection is a wazero-specific section.
|
|
FunctionDefinitionSection []FunctionDefinition
|
|
|
|
// MemoryDefinitionSection is a wazero-specific section.
|
|
MemoryDefinitionSection []MemoryDefinition
|
|
|
|
// DWARFLines is used to emit DWARF based stack trace. This is created from the multiple custom sections
|
|
// as described in https://yurydelendik.github.io/webassembly-dwarf/, though it is not specified in the Wasm
|
|
// specification: https://github.com/WebAssembly/debugging/issues/1
|
|
DWARFLines *wasmdebug.DWARFLines
|
|
}
|
|
|
|
// ModuleID represents sha256 hash value uniquely assigned to Module.
|
|
type ModuleID = [sha256.Size]byte
|
|
|
|
// The wazero specific limitation described at RATIONALE.md.
|
|
// TL;DR; We multiply by 8 (to get offsets in bytes) and the multiplication result must be less than 32bit max
|
|
const (
|
|
MaximumGlobals = uint32(1 << 27)
|
|
MaximumFunctionIndex = uint32(1 << 27)
|
|
MaximumTableIndex = uint32(1 << 27)
|
|
)
|
|
|
|
// AssignModuleID calculates a sha256 checksum on `wasm` and other args, and set Module.ID to the result.
|
|
// See the doc on Module.ID on what it's used for.
|
|
func (m *Module) AssignModuleID(wasm []byte, listeners []experimental.FunctionListener, withEnsureTermination bool) {
|
|
h := sha256.New()
|
|
h.Write(wasm)
|
|
// Use the pre-allocated space backed by m.ID below.
|
|
|
|
// Write the existence of listeners to the checksum per function.
|
|
for i, l := range listeners {
|
|
binary.LittleEndian.PutUint32(m.ID[:], uint32(i))
|
|
m.ID[4] = boolToByte(l != nil)
|
|
h.Write(m.ID[:5])
|
|
}
|
|
// Write the flag of ensureTermination to the checksum.
|
|
m.ID[0] = boolToByte(withEnsureTermination)
|
|
h.Write(m.ID[:1])
|
|
// Get checksum by passing the slice underlying m.ID.
|
|
h.Sum(m.ID[:0])
|
|
}
|
|
|
|
func boolToByte(b bool) (ret byte) {
|
|
if b {
|
|
ret = 1
|
|
}
|
|
return
|
|
}
|
|
|
|
// typeOfFunction returns the wasm.FunctionType for the given function space index or nil.
|
|
func (m *Module) typeOfFunction(funcIdx Index) *FunctionType {
|
|
typeSectionLength, importedFunctionCount := uint32(len(m.TypeSection)), m.ImportFunctionCount
|
|
if funcIdx < importedFunctionCount {
|
|
// Imports are not exclusively functions. This is the current function index in the loop.
|
|
cur := Index(0)
|
|
for i := range m.ImportSection {
|
|
imp := &m.ImportSection[i]
|
|
if imp.Type != ExternTypeFunc {
|
|
continue
|
|
}
|
|
if funcIdx == cur {
|
|
if imp.DescFunc >= typeSectionLength {
|
|
return nil
|
|
}
|
|
return &m.TypeSection[imp.DescFunc]
|
|
}
|
|
cur++
|
|
}
|
|
}
|
|
|
|
funcSectionIdx := funcIdx - m.ImportFunctionCount
|
|
if funcSectionIdx >= uint32(len(m.FunctionSection)) {
|
|
return nil
|
|
}
|
|
typeIdx := m.FunctionSection[funcSectionIdx]
|
|
if typeIdx >= typeSectionLength {
|
|
return nil
|
|
}
|
|
return &m.TypeSection[typeIdx]
|
|
}
|
|
|
|
func (m *Module) Validate(enabledFeatures api.CoreFeatures) error {
|
|
for i := range m.TypeSection {
|
|
tp := &m.TypeSection[i]
|
|
tp.CacheNumInUint64()
|
|
}
|
|
|
|
if err := m.validateStartSection(); err != nil {
|
|
return err
|
|
}
|
|
|
|
functions, globals, memory, tables, err := m.AllDeclarations()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = m.validateImports(enabledFeatures); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = m.validateGlobals(globals, uint32(len(functions)), MaximumGlobals); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = m.validateMemory(memory, globals, enabledFeatures); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = m.validateExports(enabledFeatures, functions, globals, memory, tables); err != nil {
|
|
return err
|
|
}
|
|
|
|
if m.CodeSection != nil {
|
|
if err = m.validateFunctions(enabledFeatures, functions, globals, memory, tables, MaximumFunctionIndex); err != nil {
|
|
return err
|
|
}
|
|
} // No need to validate host functions as NewHostModule validates
|
|
|
|
if err = m.validateTable(enabledFeatures, tables, MaximumTableIndex); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = m.validateDataCountSection(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Module) validateStartSection() error {
|
|
// Check the start function is valid.
|
|
// TODO: this should be verified during decode so that errors have the correct source positions
|
|
if m.StartSection != nil {
|
|
startIndex := *m.StartSection
|
|
ft := m.typeOfFunction(startIndex)
|
|
if ft == nil { // TODO: move this check to decoder so that a module can never be decoded invalidly
|
|
return fmt.Errorf("invalid start function: func[%d] has an invalid type", startIndex)
|
|
}
|
|
if len(ft.Params) > 0 || len(ft.Results) > 0 {
|
|
return fmt.Errorf("invalid start function: func[%d] must have an empty (nullary) signature: %s", startIndex, ft)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Module) validateGlobals(globals []GlobalType, numFuncts, maxGlobals uint32) error {
|
|
if uint32(len(globals)) > maxGlobals {
|
|
return fmt.Errorf("too many globals in a module")
|
|
}
|
|
|
|
// Global initialization constant expression can only reference the imported globals.
|
|
// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
|
|
importedGlobals := globals[:m.ImportGlobalCount]
|
|
for i := range m.GlobalSection {
|
|
g := &m.GlobalSection[i]
|
|
if err := validateConstExpression(importedGlobals, numFuncts, &g.Init, g.Type.ValType); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Module) validateFunctions(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table, maximumFunctionIndex uint32) error {
|
|
if uint32(len(functions)) > maximumFunctionIndex {
|
|
return fmt.Errorf("too many functions (%d) in a module", len(functions))
|
|
}
|
|
|
|
functionCount := m.SectionElementCount(SectionIDFunction)
|
|
codeCount := m.SectionElementCount(SectionIDCode)
|
|
if functionCount == 0 && codeCount == 0 {
|
|
return nil
|
|
}
|
|
|
|
typeCount := m.SectionElementCount(SectionIDType)
|
|
if codeCount != functionCount {
|
|
return fmt.Errorf("code count (%d) != function count (%d)", codeCount, functionCount)
|
|
}
|
|
|
|
declaredFuncIndexes, err := m.declaredFunctionIndexes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create bytes.Reader once as it causes allocation, and
|
|
// we frequently need it (e.g. on every If instruction).
|
|
br := bytes.NewReader(nil)
|
|
// Also, we reuse the stacks across multiple function validations to reduce allocations.
|
|
vs := &stacks{}
|
|
for idx, typeIndex := range m.FunctionSection {
|
|
if typeIndex >= typeCount {
|
|
return fmt.Errorf("invalid %s: type section index %d out of range", m.funcDesc(SectionIDFunction, Index(idx)), typeIndex)
|
|
}
|
|
c := &m.CodeSection[idx]
|
|
if c.GoFunc != nil {
|
|
continue
|
|
}
|
|
if err = m.validateFunction(vs, enabledFeatures, Index(idx), functions, globals, memory, tables, declaredFuncIndexes, br); err != nil {
|
|
return fmt.Errorf("invalid %s: %w", m.funcDesc(SectionIDFunction, Index(idx)), err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// declaredFunctionIndexes returns a set of function indexes that can be used as an immediate for OpcodeRefFunc instruction.
|
|
//
|
|
// The criteria for which function indexes can be available for that instruction is vague in the spec:
|
|
//
|
|
// - "References: the list of function indices that occur in the module outside functions and can hence be used to form references inside them."
|
|
// - https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/valid/conventions.html#contexts
|
|
// - "Ref is the set funcidx(module with functions=ε, start=ε) , i.e., the set of function indices occurring in the module, except in its functions or start function."
|
|
// - https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/valid/modules.html#valid-module
|
|
//
|
|
// To clarify, we reverse-engineer logic required to pass the WebAssembly Core specification 2.0 test suite:
|
|
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/ref_func.wast#L78-L115
|
|
//
|
|
// To summarize, the function indexes OpcodeRefFunc can refer include:
|
|
// - existing in an element section regardless of its mode (active, passive, declarative).
|
|
// - defined as globals whose value type is ValueRefFunc.
|
|
// - used as an exported function.
|
|
//
|
|
// See https://github.com/WebAssembly/reference-types/issues/31
|
|
// See https://github.com/WebAssembly/reference-types/issues/76
|
|
func (m *Module) declaredFunctionIndexes() (ret map[Index]struct{}, err error) {
|
|
ret = map[uint32]struct{}{}
|
|
|
|
for i := range m.ExportSection {
|
|
exp := &m.ExportSection[i]
|
|
if exp.Type == ExternTypeFunc {
|
|
ret[exp.Index] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for i := range m.GlobalSection {
|
|
g := &m.GlobalSection[i]
|
|
if g.Init.Opcode == OpcodeRefFunc {
|
|
var index uint32
|
|
index, _, err = leb128.LoadUint32(g.Init.Data)
|
|
if err != nil {
|
|
err = fmt.Errorf("%s[%d] failed to initialize: %w", SectionIDName(SectionIDGlobal), i, err)
|
|
return
|
|
}
|
|
ret[index] = struct{}{}
|
|
}
|
|
}
|
|
|
|
for i := range m.ElementSection {
|
|
elem := &m.ElementSection[i]
|
|
for _, index := range elem.Init {
|
|
if index != ElementInitNullReference {
|
|
ret[index] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (m *Module) funcDesc(sectionID SectionID, sectionIndex Index) string {
|
|
// Try to improve the error message by collecting any exports:
|
|
var exportNames []string
|
|
funcIdx := sectionIndex + m.ImportFunctionCount
|
|
for i := range m.ExportSection {
|
|
exp := &m.ExportSection[i]
|
|
if exp.Index == funcIdx && exp.Type == ExternTypeFunc {
|
|
exportNames = append(exportNames, fmt.Sprintf("%q", exp.Name))
|
|
}
|
|
}
|
|
sectionIDName := SectionIDName(sectionID)
|
|
if exportNames == nil {
|
|
return fmt.Sprintf("%s[%d]", sectionIDName, sectionIndex)
|
|
}
|
|
sort.Strings(exportNames) // go map keys do not iterate consistently
|
|
return fmt.Sprintf("%s[%d] export[%s]", sectionIDName, sectionIndex, strings.Join(exportNames, ","))
|
|
}
|
|
|
|
func (m *Module) validateMemory(memory *Memory, globals []GlobalType, _ api.CoreFeatures) error {
|
|
var activeElementCount int
|
|
for i := range m.DataSection {
|
|
d := &m.DataSection[i]
|
|
if !d.IsPassive() {
|
|
activeElementCount++
|
|
}
|
|
}
|
|
if activeElementCount > 0 && memory == nil {
|
|
return fmt.Errorf("unknown memory")
|
|
}
|
|
|
|
// Constant expression can only reference imported globals.
|
|
// https://github.com/WebAssembly/spec/blob/5900d839f38641989a9d8df2df4aee0513365d39/test/core/data.wast#L84-L91
|
|
importedGlobals := globals[:m.ImportGlobalCount]
|
|
for i := range m.DataSection {
|
|
d := &m.DataSection[i]
|
|
if !d.IsPassive() {
|
|
if err := validateConstExpression(importedGlobals, 0, &d.OffsetExpression, ValueTypeI32); err != nil {
|
|
return fmt.Errorf("calculate offset: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Module) validateImports(enabledFeatures api.CoreFeatures) error {
|
|
for i := range m.ImportSection {
|
|
imp := &m.ImportSection[i]
|
|
if imp.Module == "" {
|
|
return fmt.Errorf("import[%d] has an empty module name", i)
|
|
}
|
|
switch imp.Type {
|
|
case ExternTypeFunc:
|
|
if int(imp.DescFunc) >= len(m.TypeSection) {
|
|
return fmt.Errorf("invalid import[%q.%q] function: type index out of range", imp.Module, imp.Name)
|
|
}
|
|
case ExternTypeGlobal:
|
|
if !imp.DescGlobal.Mutable {
|
|
continue
|
|
}
|
|
if err := enabledFeatures.RequireEnabled(api.CoreFeatureMutableGlobal); err != nil {
|
|
return fmt.Errorf("invalid import[%q.%q] global: %w", imp.Module, imp.Name, err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Module) validateExports(enabledFeatures api.CoreFeatures, functions []Index, globals []GlobalType, memory *Memory, tables []Table) error {
|
|
for i := range m.ExportSection {
|
|
exp := &m.ExportSection[i]
|
|
index := exp.Index
|
|
switch exp.Type {
|
|
case ExternTypeFunc:
|
|
if index >= uint32(len(functions)) {
|
|
return fmt.Errorf("unknown function for export[%q]", exp.Name)
|
|
}
|
|
case ExternTypeGlobal:
|
|
if index >= uint32(len(globals)) {
|
|
return fmt.Errorf("unknown global for export[%q]", exp.Name)
|
|
}
|
|
if !globals[index].Mutable {
|
|
continue
|
|
}
|
|
if err := enabledFeatures.RequireEnabled(api.CoreFeatureMutableGlobal); err != nil {
|
|
return fmt.Errorf("invalid export[%q] global[%d]: %w", exp.Name, index, err)
|
|
}
|
|
case ExternTypeMemory:
|
|
if index > 0 || memory == nil {
|
|
return fmt.Errorf("memory for export[%q] out of range", exp.Name)
|
|
}
|
|
case ExternTypeTable:
|
|
if index >= uint32(len(tables)) {
|
|
return fmt.Errorf("table for export[%q] out of range", exp.Name)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateConstExpression(globals []GlobalType, numFuncs uint32, expr *ConstantExpression, expectedType ValueType) (err error) {
|
|
var actualType ValueType
|
|
switch expr.Opcode {
|
|
case OpcodeI32Const:
|
|
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
|
_, _, err = leb128.LoadInt32(expr.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("read i32: %w", err)
|
|
}
|
|
actualType = ValueTypeI32
|
|
case OpcodeI64Const:
|
|
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
|
_, _, err = leb128.LoadInt64(expr.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("read i64: %w", err)
|
|
}
|
|
actualType = ValueTypeI64
|
|
case OpcodeF32Const:
|
|
_, err = ieee754.DecodeFloat32(expr.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("read f32: %w", err)
|
|
}
|
|
actualType = ValueTypeF32
|
|
case OpcodeF64Const:
|
|
_, err = ieee754.DecodeFloat64(expr.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("read f64: %w", err)
|
|
}
|
|
actualType = ValueTypeF64
|
|
case OpcodeGlobalGet:
|
|
id, _, err := leb128.LoadUint32(expr.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("read index of global: %w", err)
|
|
}
|
|
if uint32(len(globals)) <= id {
|
|
return fmt.Errorf("global index out of range")
|
|
}
|
|
actualType = globals[id].ValType
|
|
case OpcodeRefNull:
|
|
if len(expr.Data) == 0 {
|
|
return fmt.Errorf("read reference type for ref.null: %w", io.ErrShortBuffer)
|
|
}
|
|
reftype := expr.Data[0]
|
|
if reftype != RefTypeFuncref && reftype != RefTypeExternref {
|
|
return fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
|
|
}
|
|
actualType = reftype
|
|
case OpcodeRefFunc:
|
|
index, _, err := leb128.LoadUint32(expr.Data)
|
|
if err != nil {
|
|
return fmt.Errorf("read i32: %w", err)
|
|
} else if index >= numFuncs {
|
|
return fmt.Errorf("ref.func index out of range [%d] with length %d", index, numFuncs-1)
|
|
}
|
|
actualType = ValueTypeFuncref
|
|
case OpcodeVecV128Const:
|
|
if len(expr.Data) != 16 {
|
|
return fmt.Errorf("%s needs 16 bytes but was %d bytes", OpcodeVecV128ConstName, len(expr.Data))
|
|
}
|
|
actualType = ValueTypeV128
|
|
default:
|
|
return fmt.Errorf("invalid opcode for const expression: 0x%x", expr.Opcode)
|
|
}
|
|
|
|
if actualType != expectedType {
|
|
return fmt.Errorf("const expression type mismatch expected %s but got %s",
|
|
ValueTypeName(expectedType), ValueTypeName(actualType))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *Module) validateDataCountSection() (err error) {
|
|
if m.DataCountSection != nil && int(*m.DataCountSection) != len(m.DataSection) {
|
|
err = fmt.Errorf("data count section (%d) doesn't match the length of data section (%d)",
|
|
*m.DataCountSection, len(m.DataSection))
|
|
}
|
|
return
|
|
}
|
|
|
|
func (m *ModuleInstance) buildGlobals(module *Module, funcRefResolver func(funcIndex Index) Reference) {
|
|
importedGlobals := m.Globals[:module.ImportGlobalCount]
|
|
|
|
me := m.Engine
|
|
engineOwnGlobal := me.OwnsGlobals()
|
|
for i := Index(0); i < Index(len(module.GlobalSection)); i++ {
|
|
gs := &module.GlobalSection[i]
|
|
g := &GlobalInstance{}
|
|
if engineOwnGlobal {
|
|
g.Me = me
|
|
g.Index = i + module.ImportGlobalCount
|
|
}
|
|
m.Globals[i+module.ImportGlobalCount] = g
|
|
g.Type = gs.Type
|
|
g.initialize(importedGlobals, &gs.Init, funcRefResolver)
|
|
}
|
|
}
|
|
|
|
func paramNames(localNames IndirectNameMap, funcIdx uint32, paramLen int) []string {
|
|
for i := range localNames {
|
|
nm := &localNames[i]
|
|
// Only build parameter names if we have one for each.
|
|
if nm.Index != funcIdx || len(nm.NameMap) < paramLen {
|
|
continue
|
|
}
|
|
|
|
ret := make([]string, paramLen)
|
|
for j := range nm.NameMap {
|
|
p := &nm.NameMap[j]
|
|
if int(p.Index) < paramLen {
|
|
ret[p.Index] = p.Name
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *ModuleInstance) buildMemory(module *Module, allocator experimental.MemoryAllocator) {
|
|
memSec := module.MemorySection
|
|
if memSec != nil {
|
|
m.MemoryInstance = NewMemoryInstance(memSec, allocator, m.Engine)
|
|
m.MemoryInstance.definition = &module.MemoryDefinitionSection[0]
|
|
}
|
|
}
|
|
|
|
// Index is the offset in an index, not necessarily an absolute position in a Module section. This is because
|
|
// indexs are often preceded by a corresponding type in the Module.ImportSection.
|
|
//
|
|
// For example, the function index starts with any ExternTypeFunc in the Module.ImportSection followed by
|
|
// the Module.FunctionSection
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-index
|
|
type Index = uint32
|
|
|
|
// FunctionType is a possibly empty function signature.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#function-types%E2%91%A0
|
|
type FunctionType struct {
|
|
// Params are the possibly empty sequence of value types accepted by a function with this signature.
|
|
Params []ValueType
|
|
|
|
// Results are the possibly empty sequence of value types returned by a function with this signature.
|
|
//
|
|
// Note: In WebAssembly 1.0 (20191205), there can be at most one result.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#result-types%E2%91%A0
|
|
Results []ValueType
|
|
|
|
// string is cached as it is used both for String and key
|
|
string string
|
|
|
|
// ParamNumInUint64 is the number of uint64 values requires to represent the Wasm param type.
|
|
ParamNumInUint64 int
|
|
|
|
// ResultsNumInUint64 is the number of uint64 values requires to represent the Wasm result type.
|
|
ResultNumInUint64 int
|
|
}
|
|
|
|
func (f *FunctionType) CacheNumInUint64() {
|
|
if f.ParamNumInUint64 == 0 {
|
|
for _, tp := range f.Params {
|
|
f.ParamNumInUint64++
|
|
if tp == ValueTypeV128 {
|
|
f.ParamNumInUint64++
|
|
}
|
|
}
|
|
}
|
|
|
|
if f.ResultNumInUint64 == 0 {
|
|
for _, tp := range f.Results {
|
|
f.ResultNumInUint64++
|
|
if tp == ValueTypeV128 {
|
|
f.ResultNumInUint64++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// EqualsSignature returns true if the function type has the same parameters and results.
|
|
func (f *FunctionType) EqualsSignature(params []ValueType, results []ValueType) bool {
|
|
return bytes.Equal(f.Params, params) && bytes.Equal(f.Results, results)
|
|
}
|
|
|
|
// key gets or generates the key for Store.typeIDs. e.g. "i32_v" for one i32 parameter and no (void) result.
|
|
func (f *FunctionType) key() string {
|
|
if f.string != "" {
|
|
return f.string
|
|
}
|
|
var ret string
|
|
for _, b := range f.Params {
|
|
ret += ValueTypeName(b)
|
|
}
|
|
if len(f.Params) == 0 {
|
|
ret += "v_"
|
|
} else {
|
|
ret += "_"
|
|
}
|
|
for _, b := range f.Results {
|
|
ret += ValueTypeName(b)
|
|
}
|
|
if len(f.Results) == 0 {
|
|
ret += "v"
|
|
}
|
|
f.string = ret
|
|
return ret
|
|
}
|
|
|
|
// String implements fmt.Stringer.
|
|
func (f *FunctionType) String() string {
|
|
return f.key()
|
|
}
|
|
|
|
// Import is the binary representation of an import indicated by Type
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-import
|
|
type Import struct {
|
|
Type ExternType
|
|
// Module is the possibly empty primary namespace of this import
|
|
Module string
|
|
// Module is the possibly empty secondary namespace of this import
|
|
Name string
|
|
// DescFunc is the index in Module.TypeSection when Type equals ExternTypeFunc
|
|
DescFunc Index
|
|
// DescTable is the inlined Table when Type equals ExternTypeTable
|
|
DescTable Table
|
|
// DescMem is the inlined Memory when Type equals ExternTypeMemory
|
|
DescMem *Memory
|
|
// DescGlobal is the inlined GlobalType when Type equals ExternTypeGlobal
|
|
DescGlobal GlobalType
|
|
// IndexPerType has the index of this import per ExternType.
|
|
IndexPerType Index
|
|
}
|
|
|
|
// Memory describes the limits of pages (64KB) in a memory.
|
|
type Memory struct {
|
|
Min, Cap, Max uint32
|
|
// IsMaxEncoded true if the Max is encoded in the original binary.
|
|
IsMaxEncoded bool
|
|
// IsShared true if the memory is shared for access from multiple agents.
|
|
IsShared bool
|
|
}
|
|
|
|
// Validate ensures values assigned to Min, Cap and Max are within valid thresholds.
|
|
func (m *Memory) Validate(memoryLimitPages uint32) error {
|
|
min, capacity, max := m.Min, m.Cap, m.Max
|
|
|
|
if max > memoryLimitPages {
|
|
return fmt.Errorf("max %d pages (%s) over limit of %d pages (%s)",
|
|
max, PagesToUnitOfBytes(max), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages))
|
|
} else if min > memoryLimitPages {
|
|
return fmt.Errorf("min %d pages (%s) over limit of %d pages (%s)",
|
|
min, PagesToUnitOfBytes(min), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages))
|
|
} else if min > max {
|
|
return fmt.Errorf("min %d pages (%s) > max %d pages (%s)",
|
|
min, PagesToUnitOfBytes(min), max, PagesToUnitOfBytes(max))
|
|
} else if capacity < min {
|
|
return fmt.Errorf("capacity %d pages (%s) less than minimum %d pages (%s)",
|
|
capacity, PagesToUnitOfBytes(capacity), min, PagesToUnitOfBytes(min))
|
|
} else if capacity > memoryLimitPages {
|
|
return fmt.Errorf("capacity %d pages (%s) over limit of %d pages (%s)",
|
|
capacity, PagesToUnitOfBytes(capacity), memoryLimitPages, PagesToUnitOfBytes(memoryLimitPages))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type GlobalType struct {
|
|
ValType ValueType
|
|
Mutable bool
|
|
}
|
|
|
|
type Global struct {
|
|
Type GlobalType
|
|
Init ConstantExpression
|
|
}
|
|
|
|
type ConstantExpression struct {
|
|
Opcode Opcode
|
|
Data []byte
|
|
}
|
|
|
|
// Export is the binary representation of an export indicated by Type
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-export
|
|
type Export struct {
|
|
Type ExternType
|
|
|
|
// Name is what the host refers to this definition as.
|
|
Name string
|
|
|
|
// Index is the index of the definition to export, the index is by Type
|
|
// e.g. If ExternTypeFunc, this is a position in the function index.
|
|
Index Index
|
|
}
|
|
|
|
// Code is an entry in the Module.CodeSection containing the locals and body of the function.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
|
|
type Code struct {
|
|
// LocalTypes are any function-scoped variables in insertion order.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-local
|
|
LocalTypes []ValueType
|
|
|
|
// Body is a sequence of expressions ending in OpcodeEnd
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-expr
|
|
Body []byte
|
|
|
|
// GoFunc is non-nil when IsHostFunction and defined in go, either
|
|
// api.GoFunction or api.GoModuleFunction. When present, LocalTypes and Body must
|
|
// be nil.
|
|
//
|
|
// Note: This has no serialization format, so is not encodable.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#host-functions%E2%91%A2
|
|
GoFunc interface{}
|
|
|
|
// BodyOffsetInCodeSection is the offset of the beginning of the body in the code section.
|
|
// This is used for DWARF based stack trace where a program counter represents an offset in code section.
|
|
BodyOffsetInCodeSection uint64
|
|
}
|
|
|
|
type DataSegment struct {
|
|
OffsetExpression ConstantExpression
|
|
Init []byte
|
|
Passive bool
|
|
}
|
|
|
|
// IsPassive returns true if this data segment is "passive" in the sense that memory offset and
|
|
// index is determined at runtime and used by OpcodeMemoryInitName instruction in the bulk memory
|
|
// operations proposal.
|
|
//
|
|
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions
|
|
func (d *DataSegment) IsPassive() bool {
|
|
return d.Passive
|
|
}
|
|
|
|
// NameSection represent the known custom name subsections defined in the WebAssembly Binary Format
|
|
//
|
|
// Note: This can be nil if no names were decoded for any reason including configuration.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#name-section%E2%91%A0
|
|
type NameSection struct {
|
|
// ModuleName is the symbolic identifier for a module. e.g. math
|
|
//
|
|
// Note: This can be empty for any reason including configuration.
|
|
ModuleName string
|
|
|
|
// FunctionNames is an association of a function index to its symbolic identifier. e.g. add
|
|
//
|
|
// * the key (idx) is in the function index, where module defined functions are preceded by imported ones.
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#functions%E2%91%A7
|
|
//
|
|
// For example, assuming the below text format is the second import, you would expect FunctionNames[1] = "mul"
|
|
// (import "Math" "Mul" (func $mul (param $x f32) (param $y f32) (result f32)))
|
|
//
|
|
// Note: FunctionNames are only used for debugging. At runtime, functions are called based on raw numeric index.
|
|
// Note: This can be nil for any reason including configuration.
|
|
FunctionNames NameMap
|
|
|
|
// LocalNames contains symbolic names for function parameters or locals that have one.
|
|
//
|
|
// Note: In the Text Format, function local names can inherit parameter
|
|
// names from their type. Here are some examples:
|
|
// * (module (import (func (param $x i32) (param i32))) (func (type 0))) = [{0, {x,0}}]
|
|
// * (module (import (func (param i32) (param $y i32))) (func (type 0) (local $z i32))) = [0, [{y,1},{z,2}]]
|
|
// * (module (func (param $x i32) (local $y i32) (local $z i32))) = [{x,0},{y,1},{z,2}]
|
|
//
|
|
// Note: LocalNames are only used for debugging. At runtime, locals are called based on raw numeric index.
|
|
// Note: This can be nil for any reason including configuration.
|
|
LocalNames IndirectNameMap
|
|
|
|
// ResultNames is a wazero-specific mechanism to store result names.
|
|
ResultNames IndirectNameMap
|
|
}
|
|
|
|
// CustomSection contains the name and raw data of a custom section.
|
|
type CustomSection struct {
|
|
Name string
|
|
Data []byte
|
|
}
|
|
|
|
// NameMap associates an index with any associated names.
|
|
//
|
|
// Note: Often the index bridges multiple sections. For example, the function index starts with any
|
|
// ExternTypeFunc in the Module.ImportSection followed by the Module.FunctionSection
|
|
//
|
|
// Note: NameMap is unique by NameAssoc.Index, but NameAssoc.Name needn't be unique.
|
|
// Note: When encoding in the Binary format, this must be ordered by NameAssoc.Index
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap
|
|
type NameMap []NameAssoc
|
|
|
|
type NameAssoc struct {
|
|
Index Index
|
|
Name string
|
|
}
|
|
|
|
// IndirectNameMap associates an index with an association of names.
|
|
//
|
|
// Note: IndirectNameMap is unique by NameMapAssoc.Index, but NameMapAssoc.NameMap needn't be unique.
|
|
// Note: When encoding in the Binary format, this must be ordered by NameMapAssoc.Index
|
|
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-indirectnamemap
|
|
type IndirectNameMap []NameMapAssoc
|
|
|
|
type NameMapAssoc struct {
|
|
Index Index
|
|
NameMap NameMap
|
|
}
|
|
|
|
// AllDeclarations returns all declarations for functions, globals, memories and tables in a module including imported ones.
|
|
func (m *Module) AllDeclarations() (functions []Index, globals []GlobalType, memory *Memory, tables []Table, err error) {
|
|
for i := range m.ImportSection {
|
|
imp := &m.ImportSection[i]
|
|
switch imp.Type {
|
|
case ExternTypeFunc:
|
|
functions = append(functions, imp.DescFunc)
|
|
case ExternTypeGlobal:
|
|
globals = append(globals, imp.DescGlobal)
|
|
case ExternTypeMemory:
|
|
memory = imp.DescMem
|
|
case ExternTypeTable:
|
|
tables = append(tables, imp.DescTable)
|
|
}
|
|
}
|
|
|
|
functions = append(functions, m.FunctionSection...)
|
|
for i := range m.GlobalSection {
|
|
g := &m.GlobalSection[i]
|
|
globals = append(globals, g.Type)
|
|
}
|
|
if m.MemorySection != nil {
|
|
if memory != nil { // shouldn't be possible due to Validate
|
|
err = errors.New("at most one table allowed in module")
|
|
return
|
|
}
|
|
memory = m.MemorySection
|
|
}
|
|
if m.TableSection != nil {
|
|
tables = append(tables, m.TableSection...)
|
|
}
|
|
return
|
|
}
|
|
|
|
// SectionID identifies the sections of a Module in the WebAssembly 1.0 (20191205) Binary Format.
|
|
//
|
|
// Note: these are defined in the wasm package, instead of the binary package, as a key per section is needed regardless
|
|
// of format, and deferring to the binary type avoids confusion.
|
|
//
|
|
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
|
|
type SectionID = byte
|
|
|
|
const (
|
|
// SectionIDCustom includes the standard defined NameSection and possibly others not defined in the standard.
|
|
SectionIDCustom SectionID = iota // don't add anything not in https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
|
|
SectionIDType
|
|
SectionIDImport
|
|
SectionIDFunction
|
|
SectionIDTable
|
|
SectionIDMemory
|
|
SectionIDGlobal
|
|
SectionIDExport
|
|
SectionIDStart
|
|
SectionIDElement
|
|
SectionIDCode
|
|
SectionIDData
|
|
|
|
// SectionIDDataCount may exist in WebAssembly 2.0 or WebAssembly 1.0 with CoreFeatureBulkMemoryOperations enabled.
|
|
//
|
|
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-count-section
|
|
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/appendix/changes.html#bulk-memory-and-table-instructions
|
|
SectionIDDataCount
|
|
)
|
|
|
|
// SectionIDName returns the canonical name of a module section.
|
|
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#sections%E2%91%A0
|
|
func SectionIDName(sectionID SectionID) string {
|
|
switch sectionID {
|
|
case SectionIDCustom:
|
|
return "custom"
|
|
case SectionIDType:
|
|
return "type"
|
|
case SectionIDImport:
|
|
return "import"
|
|
case SectionIDFunction:
|
|
return "function"
|
|
case SectionIDTable:
|
|
return "table"
|
|
case SectionIDMemory:
|
|
return "memory"
|
|
case SectionIDGlobal:
|
|
return "global"
|
|
case SectionIDExport:
|
|
return "export"
|
|
case SectionIDStart:
|
|
return "start"
|
|
case SectionIDElement:
|
|
return "element"
|
|
case SectionIDCode:
|
|
return "code"
|
|
case SectionIDData:
|
|
return "data"
|
|
case SectionIDDataCount:
|
|
return "data_count"
|
|
}
|
|
return "unknown"
|
|
}
|
|
|
|
// ValueType is an alias of api.ValueType defined to simplify imports.
|
|
type ValueType = api.ValueType
|
|
|
|
const (
|
|
ValueTypeI32 = api.ValueTypeI32
|
|
ValueTypeI64 = api.ValueTypeI64
|
|
ValueTypeF32 = api.ValueTypeF32
|
|
ValueTypeF64 = api.ValueTypeF64
|
|
// TODO: ValueTypeV128 is not exposed in the api pkg yet.
|
|
ValueTypeV128 ValueType = 0x7b
|
|
// TODO: ValueTypeFuncref is not exposed in the api pkg yet.
|
|
ValueTypeFuncref ValueType = 0x70
|
|
ValueTypeExternref = api.ValueTypeExternref
|
|
)
|
|
|
|
// ValueTypeName is an alias of api.ValueTypeName defined to simplify imports.
|
|
func ValueTypeName(t ValueType) string {
|
|
if t == ValueTypeFuncref {
|
|
return "funcref"
|
|
} else if t == ValueTypeV128 {
|
|
return "v128"
|
|
}
|
|
return api.ValueTypeName(t)
|
|
}
|
|
|
|
func isReferenceValueType(vt ValueType) bool {
|
|
return vt == ValueTypeExternref || vt == ValueTypeFuncref
|
|
}
|
|
|
|
// ExternType is an alias of api.ExternType defined to simplify imports.
|
|
type ExternType = api.ExternType
|
|
|
|
const (
|
|
ExternTypeFunc = api.ExternTypeFunc
|
|
ExternTypeFuncName = api.ExternTypeFuncName
|
|
ExternTypeTable = api.ExternTypeTable
|
|
ExternTypeTableName = api.ExternTypeTableName
|
|
ExternTypeMemory = api.ExternTypeMemory
|
|
ExternTypeMemoryName = api.ExternTypeMemoryName
|
|
ExternTypeGlobal = api.ExternTypeGlobal
|
|
ExternTypeGlobalName = api.ExternTypeGlobalName
|
|
)
|
|
|
|
// ExternTypeName is an alias of api.ExternTypeName defined to simplify imports.
|
|
func ExternTypeName(t ValueType) string {
|
|
return api.ExternTypeName(t)
|
|
}
|