mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[experiment] add alternative wasm sqlite3 implementation available via build-tag (#2863)
This allows for building GoToSocial with [SQLite transpiled to WASM](https://github.com/ncruces/go-sqlite3) and accessed through [Wazero](https://wazero.io/).
This commit is contained in:
100
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/code.go
generated
vendored
Normal file
100
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/code.go
generated
vendored
Normal file
@ -0,0 +1,100 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeCode(r *bytes.Reader, codeSectionStart uint64, ret *wasm.Code) (err error) {
|
||||
ss, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get the size of code: %w", err)
|
||||
}
|
||||
remaining := int64(ss)
|
||||
|
||||
// Parse #locals.
|
||||
ls, bytesRead, err := leb128.DecodeUint32(r)
|
||||
remaining -= int64(bytesRead)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get the size locals: %v", err)
|
||||
} else if remaining < 0 {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
// Validate the locals.
|
||||
bytesRead = 0
|
||||
var sum uint64
|
||||
for i := uint32(0); i < ls; i++ {
|
||||
num, n, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read n of locals: %v", err)
|
||||
} else if remaining < 0 {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
sum += uint64(num)
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read type of local: %v", err)
|
||||
}
|
||||
|
||||
bytesRead += n + 1
|
||||
switch vt := b; vt {
|
||||
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
|
||||
wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128:
|
||||
default:
|
||||
return fmt.Errorf("invalid local type: 0x%x", vt)
|
||||
}
|
||||
}
|
||||
|
||||
if sum > math.MaxUint32 {
|
||||
return fmt.Errorf("too many locals: %d", sum)
|
||||
}
|
||||
|
||||
// Rewind the buffer.
|
||||
_, err = r.Seek(-int64(bytesRead), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localTypes := make([]wasm.ValueType, 0, sum)
|
||||
for i := uint32(0); i < ls; i++ {
|
||||
num, bytesRead, err := leb128.DecodeUint32(r)
|
||||
remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte
|
||||
if err != nil {
|
||||
return fmt.Errorf("read n of locals: %v", err)
|
||||
} else if remaining < 0 {
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read type of local: %v", err)
|
||||
}
|
||||
|
||||
for j := uint32(0); j < num; j++ {
|
||||
localTypes = append(localTypes, b)
|
||||
}
|
||||
}
|
||||
|
||||
bodyOffsetInCodeSection := codeSectionStart - uint64(r.Len())
|
||||
body := make([]byte, remaining)
|
||||
if _, err = io.ReadFull(r, body); err != nil {
|
||||
return fmt.Errorf("read body: %w", err)
|
||||
}
|
||||
|
||||
if endIndex := len(body) - 1; endIndex < 0 || body[endIndex] != wasm.OpcodeEnd {
|
||||
return fmt.Errorf("expr not end with OpcodeEnd")
|
||||
}
|
||||
|
||||
ret.BodyOffsetInCodeSection = bodyOffsetInCodeSection
|
||||
ret.LocalTypes = localTypes
|
||||
ret.Body = body
|
||||
return nil
|
||||
}
|
105
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/const_expr.go
generated
vendored
Normal file
105
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/const_expr.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/ieee754"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeConstantExpression(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.ConstantExpression) error {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read opcode: %v", err)
|
||||
}
|
||||
|
||||
remainingBeforeData := int64(r.Len())
|
||||
offsetAtData := r.Size() - remainingBeforeData
|
||||
|
||||
opcode := b
|
||||
switch opcode {
|
||||
case wasm.OpcodeI32Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
_, _, err = leb128.DecodeInt32(r)
|
||||
case wasm.OpcodeI64Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
_, _, err = leb128.DecodeInt64(r)
|
||||
case wasm.OpcodeF32Const:
|
||||
buf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return fmt.Errorf("read f32 constant: %v", err)
|
||||
}
|
||||
_, err = ieee754.DecodeFloat32(buf)
|
||||
case wasm.OpcodeF64Const:
|
||||
buf := make([]byte, 8)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return fmt.Errorf("read f64 constant: %v", err)
|
||||
}
|
||||
_, err = ieee754.DecodeFloat64(buf)
|
||||
case wasm.OpcodeGlobalGet:
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeRefNull:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return fmt.Errorf("ref.null is not supported as %w", err)
|
||||
}
|
||||
reftype, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read reference type for ref.null: %w", err)
|
||||
} else if reftype != wasm.RefTypeFuncref && reftype != wasm.RefTypeExternref {
|
||||
return fmt.Errorf("invalid type for ref.null: 0x%x", reftype)
|
||||
}
|
||||
case wasm.OpcodeRefFunc:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return fmt.Errorf("ref.func is not supported as %w", err)
|
||||
}
|
||||
// Parsing index.
|
||||
_, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.OpcodeVecPrefix:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureSIMD); err != nil {
|
||||
return fmt.Errorf("vector instructions are not supported as %w", err)
|
||||
}
|
||||
opcode, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read vector instruction opcode suffix: %w", err)
|
||||
}
|
||||
|
||||
if opcode != wasm.OpcodeVecV128Const {
|
||||
return fmt.Errorf("invalid vector opcode for const expression: %#x", opcode)
|
||||
}
|
||||
|
||||
remainingBeforeData = int64(r.Len())
|
||||
offsetAtData = r.Size() - remainingBeforeData
|
||||
|
||||
n, err := r.Read(make([]byte, 16))
|
||||
if err != nil {
|
||||
return fmt.Errorf("read vector const instruction immediates: %w", err)
|
||||
} else if n != 16 {
|
||||
return fmt.Errorf("read vector const instruction immediates: needs 16 bytes but was %d bytes", n)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%v for const expression opt code: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("read value: %v", err)
|
||||
}
|
||||
|
||||
if b, err = r.ReadByte(); err != nil {
|
||||
return fmt.Errorf("look for end opcode: %v", err)
|
||||
}
|
||||
|
||||
if b != wasm.OpcodeEnd {
|
||||
return fmt.Errorf("constant expression has been not terminated")
|
||||
}
|
||||
|
||||
ret.Data = make([]byte, remainingBeforeData-int64(r.Len())-1)
|
||||
if _, err = r.ReadAt(ret.Data, offsetAtData); err != nil {
|
||||
return fmt.Errorf("error re-buffering ConstantExpression.Data")
|
||||
}
|
||||
ret.Opcode = opcode
|
||||
return nil
|
||||
}
|
22
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/custom.go
generated
vendored
Normal file
22
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/custom.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// decodeCustomSection deserializes the data **not** associated with the "name" key in SectionIDCustom.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#custom-section%E2%91%A0
|
||||
func decodeCustomSection(r *bytes.Reader, name string, limit uint64) (result *wasm.CustomSection, err error) {
|
||||
buf := make([]byte, limit)
|
||||
_, err = r.Read(buf)
|
||||
|
||||
result = &wasm.CustomSection{
|
||||
Name: name,
|
||||
Data: buf,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
79
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/data.go
generated
vendored
Normal file
79
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/data.go
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// dataSegmentPrefix represents three types of data segments.
|
||||
//
|
||||
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
|
||||
type dataSegmentPrefix = uint32
|
||||
|
||||
const (
|
||||
// dataSegmentPrefixActive is the prefix for the version 1.0 compatible data segment, which is classified as "active" in 2.0.
|
||||
dataSegmentPrefixActive dataSegmentPrefix = 0x0
|
||||
// dataSegmentPrefixPassive prefixes the "passive" data segment as in version 2.0 specification.
|
||||
dataSegmentPrefixPassive dataSegmentPrefix = 0x1
|
||||
// dataSegmentPrefixActiveWithMemoryIndex is the active prefix with memory index encoded which is defined for futur use as of 2.0.
|
||||
dataSegmentPrefixActiveWithMemoryIndex dataSegmentPrefix = 0x2
|
||||
)
|
||||
|
||||
func decodeDataSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.DataSegment) (err error) {
|
||||
dataSegmentPrefx, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read data segment prefix: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if dataSegmentPrefx != dataSegmentPrefixActive {
|
||||
if err = enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
err = fmt.Errorf("non-zero prefix for data segment is invalid as %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch dataSegmentPrefx {
|
||||
case dataSegmentPrefixActive,
|
||||
dataSegmentPrefixActiveWithMemoryIndex:
|
||||
// Active data segment as in
|
||||
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
|
||||
if dataSegmentPrefx == 0x2 {
|
||||
d, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read memory index: %v", err)
|
||||
} else if d != 0 {
|
||||
return fmt.Errorf("memory index must be zero but was %d", d)
|
||||
}
|
||||
}
|
||||
|
||||
err = decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpression)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read offset expression: %v", err)
|
||||
}
|
||||
case dataSegmentPrefixPassive:
|
||||
// Passive data segment doesn't need const expr nor memory index encoded.
|
||||
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#data-section
|
||||
ret.Passive = true
|
||||
default:
|
||||
err = fmt.Errorf("invalid data segment prefix: 0x%x", dataSegmentPrefx)
|
||||
return
|
||||
}
|
||||
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("get the size of vector: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
ret.Init = make([]byte, vs)
|
||||
if _, err = io.ReadFull(r, ret.Init); err != nil {
|
||||
err = fmt.Errorf("read bytes for init: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
193
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/decoder.go
generated
vendored
Normal file
193
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/decoder.go
generated
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/dwarf"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
||||
)
|
||||
|
||||
// DecodeModule implements wasm.DecodeModule for the WebAssembly 1.0 (20191205) Binary Format
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-format%E2%91%A0
|
||||
func DecodeModule(
|
||||
binary []byte,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
memoryLimitPages uint32,
|
||||
memoryCapacityFromMax,
|
||||
dwarfEnabled, storeCustomSections bool,
|
||||
) (*wasm.Module, error) {
|
||||
r := bytes.NewReader(binary)
|
||||
|
||||
// Magic number.
|
||||
buf := make([]byte, 4)
|
||||
if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, Magic) {
|
||||
return nil, ErrInvalidMagicNumber
|
||||
}
|
||||
|
||||
// Version.
|
||||
if _, err := io.ReadFull(r, buf); err != nil || !bytes.Equal(buf, version) {
|
||||
return nil, ErrInvalidVersion
|
||||
}
|
||||
|
||||
memSizer := newMemorySizer(memoryLimitPages, memoryCapacityFromMax)
|
||||
|
||||
m := &wasm.Module{}
|
||||
var info, line, str, abbrev, ranges []byte // For DWARF Data.
|
||||
for {
|
||||
// TODO: except custom sections, all others are required to be in order, but we aren't checking yet.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#modules%E2%91%A0%E2%93%AA
|
||||
sectionID, err := r.ReadByte()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("read section id: %w", err)
|
||||
}
|
||||
|
||||
sectionSize, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of section %s: %v", wasm.SectionIDName(sectionID), err)
|
||||
}
|
||||
|
||||
sectionContentStart := r.Len()
|
||||
switch sectionID {
|
||||
case wasm.SectionIDCustom:
|
||||
// First, validate the section and determine if the section for this name has already been set
|
||||
name, nameSize, decodeErr := decodeUTF8(r, "custom section name")
|
||||
if decodeErr != nil {
|
||||
err = decodeErr
|
||||
break
|
||||
} else if sectionSize < nameSize {
|
||||
err = fmt.Errorf("malformed custom section %s", name)
|
||||
break
|
||||
} else if name == "name" && m.NameSection != nil {
|
||||
err = fmt.Errorf("redundant custom section %s", name)
|
||||
break
|
||||
}
|
||||
|
||||
// Now, either decode the NameSection or CustomSection
|
||||
limit := sectionSize - nameSize
|
||||
|
||||
var c *wasm.CustomSection
|
||||
if name != "name" {
|
||||
if storeCustomSections || dwarfEnabled {
|
||||
c, err = decodeCustomSection(r, name, uint64(limit))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read custom section name[%s]: %w", name, err)
|
||||
}
|
||||
m.CustomSections = append(m.CustomSections, c)
|
||||
if dwarfEnabled {
|
||||
switch name {
|
||||
case ".debug_info":
|
||||
info = c.Data
|
||||
case ".debug_line":
|
||||
line = c.Data
|
||||
case ".debug_str":
|
||||
str = c.Data
|
||||
case ".debug_abbrev":
|
||||
abbrev = c.Data
|
||||
case ".debug_ranges":
|
||||
ranges = c.Data
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, err = io.CopyN(io.Discard, r, int64(limit)); err != nil {
|
||||
return nil, fmt.Errorf("failed to skip name[%s]: %w", name, err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m.NameSection, err = decodeNameSection(r, uint64(limit))
|
||||
}
|
||||
case wasm.SectionIDType:
|
||||
m.TypeSection, err = decodeTypeSection(enabledFeatures, r)
|
||||
case wasm.SectionIDImport:
|
||||
m.ImportSection, m.ImportPerModule, m.ImportFunctionCount, m.ImportGlobalCount, m.ImportMemoryCount, m.ImportTableCount, err = decodeImportSection(r, memSizer, memoryLimitPages, enabledFeatures)
|
||||
if err != nil {
|
||||
return nil, err // avoid re-wrapping the error.
|
||||
}
|
||||
case wasm.SectionIDFunction:
|
||||
m.FunctionSection, err = decodeFunctionSection(r)
|
||||
case wasm.SectionIDTable:
|
||||
m.TableSection, err = decodeTableSection(r, enabledFeatures)
|
||||
case wasm.SectionIDMemory:
|
||||
m.MemorySection, err = decodeMemorySection(r, enabledFeatures, memSizer, memoryLimitPages)
|
||||
case wasm.SectionIDGlobal:
|
||||
if m.GlobalSection, err = decodeGlobalSection(r, enabledFeatures); err != nil {
|
||||
return nil, err // avoid re-wrapping the error.
|
||||
}
|
||||
case wasm.SectionIDExport:
|
||||
m.ExportSection, m.Exports, err = decodeExportSection(r)
|
||||
case wasm.SectionIDStart:
|
||||
if m.StartSection != nil {
|
||||
return nil, errors.New("multiple start sections are invalid")
|
||||
}
|
||||
m.StartSection, err = decodeStartSection(r)
|
||||
case wasm.SectionIDElement:
|
||||
m.ElementSection, err = decodeElementSection(r, enabledFeatures)
|
||||
case wasm.SectionIDCode:
|
||||
m.CodeSection, err = decodeCodeSection(r)
|
||||
case wasm.SectionIDData:
|
||||
m.DataSection, err = decodeDataSection(r, enabledFeatures)
|
||||
case wasm.SectionIDDataCount:
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return nil, fmt.Errorf("data count section not supported as %v", err)
|
||||
}
|
||||
m.DataCountSection, err = decodeDataCountSection(r)
|
||||
default:
|
||||
err = ErrInvalidSectionID
|
||||
}
|
||||
|
||||
readBytes := sectionContentStart - r.Len()
|
||||
if err == nil && int(sectionSize) != readBytes {
|
||||
err = fmt.Errorf("invalid section length: expected to be %d but got %d", sectionSize, readBytes)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("section %s: %v", wasm.SectionIDName(sectionID), err)
|
||||
}
|
||||
}
|
||||
|
||||
if dwarfEnabled {
|
||||
d, _ := dwarf.New(abbrev, nil, nil, info, line, nil, ranges, str)
|
||||
m.DWARFLines = wasmdebug.NewDWARFLines(d)
|
||||
}
|
||||
|
||||
functionCount, codeCount := m.SectionElementCount(wasm.SectionIDFunction), m.SectionElementCount(wasm.SectionIDCode)
|
||||
if functionCount != codeCount {
|
||||
return nil, fmt.Errorf("function and code section have inconsistent lengths: %d != %d", functionCount, codeCount)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// memorySizer derives min, capacity and max pages from decoded wasm.
|
||||
type memorySizer func(minPages uint32, maxPages *uint32) (min uint32, capacity uint32, max uint32)
|
||||
|
||||
// newMemorySizer sets capacity to minPages unless max is defined and
|
||||
// memoryCapacityFromMax is true.
|
||||
func newMemorySizer(memoryLimitPages uint32, memoryCapacityFromMax bool) memorySizer {
|
||||
return func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) {
|
||||
if maxPages != nil {
|
||||
if memoryCapacityFromMax {
|
||||
return minPages, *maxPages, *maxPages
|
||||
}
|
||||
// This is an invalid value: let it propagate, we will fail later.
|
||||
if *maxPages > wasm.MemoryLimitPages {
|
||||
return minPages, minPages, *maxPages
|
||||
}
|
||||
// This is a valid value, but it goes over the run-time limit: return the limit.
|
||||
if *maxPages > memoryLimitPages {
|
||||
return minPages, minPages, memoryLimitPages
|
||||
}
|
||||
return minPages, minPages, *maxPages
|
||||
}
|
||||
if memoryCapacityFromMax {
|
||||
return minPages, memoryLimitPages, memoryLimitPages
|
||||
}
|
||||
return minPages, minPages, memoryLimitPages
|
||||
}
|
||||
}
|
269
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/element.go
generated
vendored
Normal file
269
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/element.go
generated
vendored
Normal file
@ -0,0 +1,269 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func ensureElementKindFuncRef(r *bytes.Reader) error {
|
||||
elemKind, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read element prefix: %w", err)
|
||||
}
|
||||
if elemKind != 0x0 { // ElemKind is fixed to 0x0 now: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
|
||||
return fmt.Errorf("element kind must be zero but was 0x%x", elemKind)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeElementInitValueVector(r *bytes.Reader) ([]wasm.Index, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
vec := make([]wasm.Index, vs)
|
||||
for i := range vec {
|
||||
u32, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read function index: %w", err)
|
||||
}
|
||||
|
||||
if u32 >= wasm.MaximumFunctionIndex {
|
||||
return nil, fmt.Errorf("too large function index in Element init: %d", u32)
|
||||
}
|
||||
vec[i] = u32
|
||||
}
|
||||
return vec, nil
|
||||
}
|
||||
|
||||
func decodeElementConstExprVector(r *bytes.Reader, elemType wasm.RefType, enabledFeatures api.CoreFeatures) ([]wasm.Index, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the size of constexpr vector: %w", err)
|
||||
}
|
||||
vec := make([]wasm.Index, vs)
|
||||
for i := range vec {
|
||||
var expr wasm.ConstantExpression
|
||||
err := decodeConstantExpression(r, enabledFeatures, &expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch expr.Opcode {
|
||||
case wasm.OpcodeRefFunc:
|
||||
if elemType != wasm.RefTypeFuncref {
|
||||
return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has funcref", wasm.RefTypeName(elemType))
|
||||
}
|
||||
v, _, _ := leb128.LoadUint32(expr.Data)
|
||||
if v >= wasm.MaximumFunctionIndex {
|
||||
return nil, fmt.Errorf("too large function index in Element init: %d", v)
|
||||
}
|
||||
vec[i] = v
|
||||
case wasm.OpcodeRefNull:
|
||||
if elemType != expr.Data[0] {
|
||||
return nil, fmt.Errorf("element type mismatch: want %s, but constexpr has %s",
|
||||
wasm.RefTypeName(elemType), wasm.RefTypeName(expr.Data[0]))
|
||||
}
|
||||
vec[i] = wasm.ElementInitNullReference
|
||||
case wasm.OpcodeGlobalGet:
|
||||
i32, _, _ := leb128.LoadInt32(expr.Data)
|
||||
// Resolving the reference type from globals is done at instantiation phase. See the comment on
|
||||
// wasm.elementInitImportedGlobalReferenceType.
|
||||
vec[i] = wasm.WrapGlobalIndexAsElementInit(wasm.Index(i32))
|
||||
default:
|
||||
return nil, fmt.Errorf("const expr must be either ref.null or ref.func but was %s", wasm.InstructionName(expr.Opcode))
|
||||
}
|
||||
}
|
||||
return vec, nil
|
||||
}
|
||||
|
||||
func decodeElementRefType(r *bytes.Reader) (ret wasm.RefType, err error) {
|
||||
ret, err = r.ReadByte()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read element ref type: %w", err)
|
||||
return
|
||||
}
|
||||
if ret != wasm.RefTypeFuncref && ret != wasm.RefTypeExternref {
|
||||
return 0, errors.New("ref type must be funcref or externref for element as of WebAssembly 2.0")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
// The prefix is explained at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
|
||||
|
||||
// elementSegmentPrefixLegacy is the legacy prefix and is only valid one before CoreFeatureBulkMemoryOperations.
|
||||
elementSegmentPrefixLegacy = iota
|
||||
// elementSegmentPrefixPassiveFuncrefValueVector is the passive element whose indexes are encoded as vec(varint), and reftype is fixed to funcref.
|
||||
elementSegmentPrefixPassiveFuncrefValueVector
|
||||
// elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex is the same as elementSegmentPrefixPassiveFuncrefValueVector but active and table index is encoded.
|
||||
elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex
|
||||
// elementSegmentPrefixDeclarativeFuncrefValueVector is the same as elementSegmentPrefixPassiveFuncrefValueVector but declarative.
|
||||
elementSegmentPrefixDeclarativeFuncrefValueVector
|
||||
// elementSegmentPrefixActiveFuncrefConstExprVector is active whoce reftype is fixed to funcref and indexes are encoded as vec(const_expr).
|
||||
elementSegmentPrefixActiveFuncrefConstExprVector
|
||||
// elementSegmentPrefixPassiveConstExprVector is passive whoce indexes are encoded as vec(const_expr), and reftype is encoded.
|
||||
elementSegmentPrefixPassiveConstExprVector
|
||||
// elementSegmentPrefixPassiveConstExprVector is active whoce indexes are encoded as vec(const_expr), and reftype and table index are encoded.
|
||||
elementSegmentPrefixActiveConstExprVector
|
||||
// elementSegmentPrefixDeclarativeConstExprVector is declarative whoce indexes are encoded as vec(const_expr), and reftype is encoded.
|
||||
elementSegmentPrefixDeclarativeConstExprVector
|
||||
)
|
||||
|
||||
func decodeElementSegment(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.ElementSegment) error {
|
||||
prefix, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read element prefix: %w", err)
|
||||
}
|
||||
|
||||
if prefix != elementSegmentPrefixLegacy {
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureBulkMemoryOperations); err != nil {
|
||||
return fmt.Errorf("non-zero prefix for element segment is invalid as %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Encoding depends on the prefix and described at https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/binary/modules.html#element-section
|
||||
switch prefix {
|
||||
case elementSegmentPrefixLegacy:
|
||||
// Legacy prefix which is WebAssembly 1.0 compatible.
|
||||
err = decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
ret.Init, err = decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret.Mode = wasm.ElementModeActive
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
return nil
|
||||
case elementSegmentPrefixPassiveFuncrefValueVector:
|
||||
// Prefix 1 requires funcref.
|
||||
if err = ensureElementKindFuncRef(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret.Init, err = decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Mode = wasm.ElementModePassive
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
return nil
|
||||
case elementSegmentPrefixActiveFuncrefValueVectorWithTableIndex:
|
||||
ret.TableIndex, _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
if ret.TableIndex != 0 {
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
|
||||
return fmt.Errorf("table index must be zero but was %d: %w", ret.TableIndex, err)
|
||||
}
|
||||
}
|
||||
|
||||
err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
// Prefix 2 requires funcref.
|
||||
if err = ensureElementKindFuncRef(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret.Init, err = decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret.Mode = wasm.ElementModeActive
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
return nil
|
||||
case elementSegmentPrefixDeclarativeFuncrefValueVector:
|
||||
// Prefix 3 requires funcref.
|
||||
if err = ensureElementKindFuncRef(r); err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Init, err = decodeElementInitValueVector(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
ret.Mode = wasm.ElementModeDeclarative
|
||||
return nil
|
||||
case elementSegmentPrefixActiveFuncrefConstExprVector:
|
||||
err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
ret.Init, err = decodeElementConstExprVector(r, wasm.RefTypeFuncref, enabledFeatures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Mode = wasm.ElementModeActive
|
||||
ret.Type = wasm.RefTypeFuncref
|
||||
return nil
|
||||
case elementSegmentPrefixPassiveConstExprVector:
|
||||
ret.Type, err = decodeElementRefType(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Mode = wasm.ElementModePassive
|
||||
return nil
|
||||
case elementSegmentPrefixActiveConstExprVector:
|
||||
ret.TableIndex, _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
if ret.TableIndex != 0 {
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
|
||||
return fmt.Errorf("table index must be zero but was %d: %w", ret.TableIndex, err)
|
||||
}
|
||||
}
|
||||
err := decodeConstantExpression(r, enabledFeatures, &ret.OffsetExpr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read expr for offset: %w", err)
|
||||
}
|
||||
|
||||
ret.Type, err = decodeElementRefType(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret.Mode = wasm.ElementModeActive
|
||||
return nil
|
||||
case elementSegmentPrefixDeclarativeConstExprVector:
|
||||
ret.Type, err = decodeElementRefType(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ret.Init, err = decodeElementConstExprVector(r, ret.Type, enabledFeatures)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ret.Mode = wasm.ElementModeDeclarative
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("invalid element segment prefix: 0x%x", prefix)
|
||||
}
|
||||
}
|
11
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/errors.go
generated
vendored
Normal file
11
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/errors.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
package binary
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrInvalidByte = errors.New("invalid byte")
|
||||
ErrInvalidMagicNumber = errors.New("invalid magic number")
|
||||
ErrInvalidVersion = errors.New("invalid version header")
|
||||
ErrInvalidSectionID = errors.New("invalid section id")
|
||||
ErrCustomSectionNotFound = errors.New("custom section not found")
|
||||
)
|
32
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/export.go
generated
vendored
Normal file
32
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/export.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeExport(r *bytes.Reader, ret *wasm.Export) (err error) {
|
||||
if ret.Name, _, err = decodeUTF8(r, "export name"); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error decoding export kind: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
ret.Type = b
|
||||
switch ret.Type {
|
||||
case wasm.ExternTypeFunc, wasm.ExternTypeTable, wasm.ExternTypeMemory, wasm.ExternTypeGlobal:
|
||||
if ret.Index, _, err = leb128.DecodeUint32(r); err != nil {
|
||||
err = fmt.Errorf("error decoding export index: %w", err)
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("%w: invalid byte for exportdesc: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
return
|
||||
}
|
56
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/function.go
generated
vendored
Normal file
56
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/function.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeFunctionType(enabledFeatures api.CoreFeatures, r *bytes.Reader, ret *wasm.FunctionType) (err error) {
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read leading byte: %w", err)
|
||||
}
|
||||
|
||||
if b != 0x60 {
|
||||
return fmt.Errorf("%w: %#x != 0x60", ErrInvalidByte, b)
|
||||
}
|
||||
|
||||
paramCount, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read parameter count: %w", err)
|
||||
}
|
||||
|
||||
paramTypes, err := decodeValueTypes(r, paramCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read parameter types: %w", err)
|
||||
}
|
||||
|
||||
resultCount, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read result count: %w", err)
|
||||
}
|
||||
|
||||
// Guard >1.0 feature multi-value
|
||||
if resultCount > 1 {
|
||||
if err = enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil {
|
||||
return fmt.Errorf("multiple result types invalid as %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
resultTypes, err := decodeValueTypes(r, resultCount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read result types: %w", err)
|
||||
}
|
||||
|
||||
ret.Params = paramTypes
|
||||
ret.Results = resultTypes
|
||||
|
||||
// cache the key for the function type
|
||||
_ = ret.String()
|
||||
|
||||
return nil
|
||||
}
|
50
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/global.go
generated
vendored
Normal file
50
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/global.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// decodeGlobal returns the api.Global decoded with the WebAssembly 1.0 (20191205) Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-global
|
||||
func decodeGlobal(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Global) (err error) {
|
||||
ret.Type, err = decodeGlobalType(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = decodeConstantExpression(r, enabledFeatures, &ret.Init)
|
||||
return
|
||||
}
|
||||
|
||||
// decodeGlobalType returns the wasm.GlobalType decoded with the WebAssembly 1.0 (20191205) Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-globaltype
|
||||
func decodeGlobalType(r *bytes.Reader) (wasm.GlobalType, error) {
|
||||
vt, err := decodeValueTypes(r, 1)
|
||||
if err != nil {
|
||||
return wasm.GlobalType{}, fmt.Errorf("read value type: %w", err)
|
||||
}
|
||||
|
||||
ret := wasm.GlobalType{
|
||||
ValType: vt[0],
|
||||
}
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
return wasm.GlobalType{}, fmt.Errorf("read mutablity: %w", err)
|
||||
}
|
||||
|
||||
switch mut := b; mut {
|
||||
case 0x00: // not mutable
|
||||
case 0x01: // mutable
|
||||
ret.Mutable = true
|
||||
default:
|
||||
return wasm.GlobalType{}, fmt.Errorf("%w for mutability: %#x != 0x00 or 0x01", ErrInvalidByte, mut)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
9
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/header.go
generated
vendored
Normal file
9
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/header.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package binary
|
||||
|
||||
// Magic is the 4 byte preamble (literally "\0asm") of the binary format
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-magic
|
||||
var Magic = []byte{0x00, 0x61, 0x73, 0x6D}
|
||||
|
||||
// version is format version and doesn't change between known specification versions
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-version
|
||||
var version = []byte{0x01, 0x00, 0x00, 0x00}
|
52
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/import.go
generated
vendored
Normal file
52
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/import.go
generated
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeImport(
|
||||
r *bytes.Reader,
|
||||
idx uint32,
|
||||
memorySizer memorySizer,
|
||||
memoryLimitPages uint32,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
ret *wasm.Import,
|
||||
) (err error) {
|
||||
if ret.Module, _, err = decodeUTF8(r, "import module"); err != nil {
|
||||
err = fmt.Errorf("import[%d] error decoding module: %w", idx, err)
|
||||
return
|
||||
}
|
||||
|
||||
if ret.Name, _, err = decodeUTF8(r, "import name"); err != nil {
|
||||
err = fmt.Errorf("import[%d] error decoding name: %w", idx, err)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := r.ReadByte()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("import[%d] error decoding type: %w", idx, err)
|
||||
return
|
||||
}
|
||||
ret.Type = b
|
||||
switch ret.Type {
|
||||
case wasm.ExternTypeFunc:
|
||||
ret.DescFunc, _, err = leb128.DecodeUint32(r)
|
||||
case wasm.ExternTypeTable:
|
||||
err = decodeTable(r, enabledFeatures, &ret.DescTable)
|
||||
case wasm.ExternTypeMemory:
|
||||
ret.DescMem, err = decodeMemory(r, enabledFeatures, memorySizer, memoryLimitPages)
|
||||
case wasm.ExternTypeGlobal:
|
||||
ret.DescGlobal, err = decodeGlobalType(r)
|
||||
default:
|
||||
err = fmt.Errorf("%w: invalid byte for importdesc: %#x", ErrInvalidByte, b)
|
||||
}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, wasm.ExternTypeName(ret.Type), ret.Module, ret.Name, err)
|
||||
}
|
||||
return
|
||||
}
|
47
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/limits.go
generated
vendored
Normal file
47
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/limits.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
)
|
||||
|
||||
// decodeLimitsType returns the `limitsType` (min, max) decoded with the WebAssembly 1.0 (20191205) Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#limits%E2%91%A6
|
||||
//
|
||||
// Extended in threads proposal: https://webassembly.github.io/threads/core/binary/types.html#limits
|
||||
func decodeLimitsType(r *bytes.Reader) (min uint32, max *uint32, shared bool, err error) {
|
||||
var flag byte
|
||||
if flag, err = r.ReadByte(); err != nil {
|
||||
err = fmt.Errorf("read leading byte: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch flag {
|
||||
case 0x00, 0x02:
|
||||
min, _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read min of limit: %v", err)
|
||||
}
|
||||
case 0x01, 0x03:
|
||||
min, _, err = leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("read min of limit: %v", err)
|
||||
return
|
||||
}
|
||||
var m uint32
|
||||
if m, _, err = leb128.DecodeUint32(r); err != nil {
|
||||
err = fmt.Errorf("read max of limit: %v", err)
|
||||
} else {
|
||||
max = &m
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("%v for limits: %#x not in (0x00, 0x01, 0x02, 0x03)", ErrInvalidByte, flag)
|
||||
}
|
||||
|
||||
shared = flag == 0x02 || flag == 0x03
|
||||
|
||||
return
|
||||
}
|
42
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/memory.go
generated
vendored
Normal file
42
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/memory.go
generated
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// decodeMemory returns the api.Memory decoded with the WebAssembly 1.0 (20191205) Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-memory
|
||||
func decodeMemory(
|
||||
r *bytes.Reader,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
memorySizer func(minPages uint32, maxPages *uint32) (min, capacity, max uint32),
|
||||
memoryLimitPages uint32,
|
||||
) (*wasm.Memory, error) {
|
||||
min, maxP, shared, err := decodeLimitsType(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if shared {
|
||||
if !enabledFeatures.IsEnabled(experimental.CoreFeaturesThreads) {
|
||||
return nil, fmt.Errorf("shared memory requested but threads feature not enabled")
|
||||
}
|
||||
|
||||
// This restriction may be lifted in the future.
|
||||
// https://webassembly.github.io/threads/core/binary/types.html#memory-types
|
||||
if maxP == nil {
|
||||
return nil, fmt.Errorf("shared memory requires a maximum size to be specified")
|
||||
}
|
||||
}
|
||||
|
||||
min, capacity, max := memorySizer(min, maxP)
|
||||
mem := &wasm.Memory{Min: min, Cap: capacity, Max: max, IsMaxEncoded: maxP != nil, IsShared: shared}
|
||||
|
||||
return mem, mem.Validate(memoryLimitPages)
|
||||
}
|
151
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/names.go
generated
vendored
Normal file
151
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/names.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
const (
|
||||
// subsectionIDModuleName contains only the module name.
|
||||
subsectionIDModuleName = uint8(0)
|
||||
// subsectionIDFunctionNames is a map of indices to function names, in ascending order by function index
|
||||
subsectionIDFunctionNames = uint8(1)
|
||||
// subsectionIDLocalNames contain a map of function indices to a map of local indices to their names, in ascending
|
||||
// order by function and local index
|
||||
subsectionIDLocalNames = uint8(2)
|
||||
)
|
||||
|
||||
// decodeNameSection deserializes the data associated with the "name" key in SectionIDCustom according to the
|
||||
// standard:
|
||||
//
|
||||
// * ModuleName decode from subsection 0
|
||||
// * FunctionNames decode from subsection 1
|
||||
// * LocalNames decode from subsection 2
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
|
||||
func decodeNameSection(r *bytes.Reader, limit uint64) (result *wasm.NameSection, err error) {
|
||||
// TODO: add leb128 functions that work on []byte and offset. While using a reader allows us to reuse reader-based
|
||||
// leb128 functions, it is less efficient, causes untestable code and in some cases more complex vs plain []byte.
|
||||
result = &wasm.NameSection{}
|
||||
|
||||
// subsectionID is decoded if known, and skipped if not
|
||||
var subsectionID uint8
|
||||
// subsectionSize is the length to skip when the subsectionID is unknown
|
||||
var subsectionSize uint32
|
||||
var bytesRead uint64
|
||||
for limit > 0 {
|
||||
if subsectionID, err = r.ReadByte(); err != nil {
|
||||
if err == io.EOF {
|
||||
return result, nil
|
||||
}
|
||||
// TODO: untestable as this can't fail for a reason beside EOF reading a byte from a buffer
|
||||
return nil, fmt.Errorf("failed to read a subsection ID: %w", err)
|
||||
}
|
||||
limit--
|
||||
|
||||
if subsectionSize, bytesRead, err = leb128.DecodeUint32(r); err != nil {
|
||||
return nil, fmt.Errorf("failed to read the size of subsection[%d]: %w", subsectionID, err)
|
||||
}
|
||||
limit -= bytesRead
|
||||
|
||||
switch subsectionID {
|
||||
case subsectionIDModuleName:
|
||||
if result.ModuleName, _, err = decodeUTF8(r, "module name"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case subsectionIDFunctionNames:
|
||||
if result.FunctionNames, err = decodeFunctionNames(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case subsectionIDLocalNames:
|
||||
if result.LocalNames, err = decodeLocalNames(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default: // Skip other subsections.
|
||||
// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
|
||||
if _, err = io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil {
|
||||
return nil, fmt.Errorf("failed to skip subsection[%d]: %w", subsectionID, err)
|
||||
}
|
||||
}
|
||||
limit -= uint64(subsectionSize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeFunctionNames(r *bytes.Reader) (wasm.NameMap, error) {
|
||||
functionCount, err := decodeFunctionCount(r, subsectionIDFunctionNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(wasm.NameMap, functionCount)
|
||||
for i := uint32(0); i < functionCount; i++ {
|
||||
functionIndex, err := decodeFunctionIndex(r, subsectionIDFunctionNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name, _, err := decodeUTF8(r, "function[%d] name", functionIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[i] = wasm.NameAssoc{Index: functionIndex, Name: name}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeLocalNames(r *bytes.Reader) (wasm.IndirectNameMap, error) {
|
||||
functionCount, err := decodeFunctionCount(r, subsectionIDLocalNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(wasm.IndirectNameMap, functionCount)
|
||||
for i := uint32(0); i < functionCount; i++ {
|
||||
functionIndex, err := decodeFunctionIndex(r, subsectionIDLocalNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localCount, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read the local count for function[%d]: %w", functionIndex, err)
|
||||
}
|
||||
|
||||
locals := make(wasm.NameMap, localCount)
|
||||
for j := uint32(0); j < localCount; j++ {
|
||||
localIndex, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read a local index of function[%d]: %w", functionIndex, err)
|
||||
}
|
||||
|
||||
name, _, err := decodeUTF8(r, "function[%d] local[%d] name", functionIndex, localIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locals[j] = wasm.NameAssoc{Index: localIndex, Name: name}
|
||||
}
|
||||
result[i] = wasm.NameMapAssoc{Index: functionIndex, NameMap: locals}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeFunctionIndex(r *bytes.Reader, subsectionID uint8) (uint32, error) {
|
||||
functionIndex, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to read a function index in subsection[%d]: %w", subsectionID, err)
|
||||
}
|
||||
return functionIndex, nil
|
||||
}
|
||||
|
||||
func decodeFunctionCount(r *bytes.Reader, subsectionID uint8) (uint32, error) {
|
||||
functionCount, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to read the function count of subsection[%d]: %w", subsectionID, err)
|
||||
}
|
||||
return functionCount, nil
|
||||
}
|
226
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/section.go
generated
vendored
Normal file
226
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/section.go
generated
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeTypeSection(enabledFeatures api.CoreFeatures, r *bytes.Reader) ([]wasm.FunctionType, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]wasm.FunctionType, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
if err = decodeFunctionType(enabledFeatures, r, &result[i]); err != nil {
|
||||
return nil, fmt.Errorf("read %d-th type: %v", i, err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// decodeImportSection decodes the decoded import segments plus the count per wasm.ExternType.
|
||||
func decodeImportSection(
|
||||
r *bytes.Reader,
|
||||
memorySizer memorySizer,
|
||||
memoryLimitPages uint32,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
) (result []wasm.Import,
|
||||
perModule map[string][]*wasm.Import,
|
||||
funcCount, globalCount, memoryCount, tableCount wasm.Index, err error,
|
||||
) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("get size of vector: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
perModule = make(map[string][]*wasm.Import)
|
||||
result = make([]wasm.Import, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
imp := &result[i]
|
||||
if err = decodeImport(r, i, memorySizer, memoryLimitPages, enabledFeatures, imp); err != nil {
|
||||
return
|
||||
}
|
||||
switch imp.Type {
|
||||
case wasm.ExternTypeFunc:
|
||||
imp.IndexPerType = funcCount
|
||||
funcCount++
|
||||
case wasm.ExternTypeGlobal:
|
||||
imp.IndexPerType = globalCount
|
||||
globalCount++
|
||||
case wasm.ExternTypeMemory:
|
||||
imp.IndexPerType = memoryCount
|
||||
memoryCount++
|
||||
case wasm.ExternTypeTable:
|
||||
imp.IndexPerType = tableCount
|
||||
tableCount++
|
||||
}
|
||||
perModule[imp.Module] = append(perModule[imp.Module], imp)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func decodeFunctionSection(r *bytes.Reader) ([]uint32, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]uint32, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
if result[i], _, err = leb128.DecodeUint32(r); err != nil {
|
||||
return nil, fmt.Errorf("get type index: %w", err)
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func decodeTableSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.Table, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading size")
|
||||
}
|
||||
if vs > 1 {
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
|
||||
return nil, fmt.Errorf("at most one table allowed in module as %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ret := make([]wasm.Table, vs)
|
||||
for i := range ret {
|
||||
err = decodeTable(r, enabledFeatures, &ret[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func decodeMemorySection(
|
||||
r *bytes.Reader,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
memorySizer memorySizer,
|
||||
memoryLimitPages uint32,
|
||||
) (*wasm.Memory, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading size")
|
||||
}
|
||||
if vs > 1 {
|
||||
return nil, fmt.Errorf("at most one memory allowed in module, but read %d", vs)
|
||||
} else if vs == 0 {
|
||||
// memory count can be zero.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return decodeMemory(r, enabledFeatures, memorySizer, memoryLimitPages)
|
||||
}
|
||||
|
||||
func decodeGlobalSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.Global, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]wasm.Global, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
if err = decodeGlobal(r, enabledFeatures, &result[i]); err != nil {
|
||||
return nil, fmt.Errorf("global[%d]: %w", i, err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeExportSection(r *bytes.Reader) ([]wasm.Export, map[string]*wasm.Export, error) {
|
||||
vs, _, sizeErr := leb128.DecodeUint32(r)
|
||||
if sizeErr != nil {
|
||||
return nil, nil, fmt.Errorf("get size of vector: %v", sizeErr)
|
||||
}
|
||||
|
||||
exportMap := make(map[string]*wasm.Export, vs)
|
||||
exportSection := make([]wasm.Export, vs)
|
||||
for i := wasm.Index(0); i < vs; i++ {
|
||||
export := &exportSection[i]
|
||||
err := decodeExport(r, export)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("read export: %w", err)
|
||||
}
|
||||
if _, ok := exportMap[export.Name]; ok {
|
||||
return nil, nil, fmt.Errorf("export[%d] duplicates name %q", i, export.Name)
|
||||
} else {
|
||||
exportMap[export.Name] = export
|
||||
}
|
||||
}
|
||||
return exportSection, exportMap, nil
|
||||
}
|
||||
|
||||
func decodeStartSection(r *bytes.Reader) (*wasm.Index, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get function index: %w", err)
|
||||
}
|
||||
return &vs, nil
|
||||
}
|
||||
|
||||
func decodeElementSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.ElementSegment, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]wasm.ElementSegment, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
if err = decodeElementSegment(r, enabledFeatures, &result[i]); err != nil {
|
||||
return nil, fmt.Errorf("read element: %w", err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeCodeSection(r *bytes.Reader) ([]wasm.Code, error) {
|
||||
codeSectionStart := uint64(r.Len())
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]wasm.Code, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
err = decodeCode(r, codeSectionStart, &result[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read %d-th code segment: %v", i, err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeDataSection(r *bytes.Reader, enabledFeatures api.CoreFeatures) ([]wasm.DataSegment, error) {
|
||||
vs, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get size of vector: %w", err)
|
||||
}
|
||||
|
||||
result := make([]wasm.DataSegment, vs)
|
||||
for i := uint32(0); i < vs; i++ {
|
||||
if err = decodeDataSegment(r, enabledFeatures, &result[i]); err != nil {
|
||||
return nil, fmt.Errorf("read data segment: %w", err)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func decodeDataCountSection(r *bytes.Reader) (count *uint32, err error) {
|
||||
v, _, err := leb128.DecodeUint32(r)
|
||||
if err != nil && err != io.EOF {
|
||||
// data count is optional, so EOF is fine.
|
||||
return nil, err
|
||||
}
|
||||
return &v, nil
|
||||
}
|
43
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/table.go
generated
vendored
Normal file
43
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/table.go
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// decodeTable returns the wasm.Table decoded with the WebAssembly 1.0 (20191205) Binary Format.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-table
|
||||
func decodeTable(r *bytes.Reader, enabledFeatures api.CoreFeatures, ret *wasm.Table) (err error) {
|
||||
ret.Type, err = r.ReadByte()
|
||||
if err != nil {
|
||||
return fmt.Errorf("read leading byte: %v", err)
|
||||
}
|
||||
|
||||
if ret.Type != wasm.RefTypeFuncref {
|
||||
if err = enabledFeatures.RequireEnabled(api.CoreFeatureReferenceTypes); err != nil {
|
||||
return fmt.Errorf("table type funcref is invalid: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var shared bool
|
||||
ret.Min, ret.Max, shared, err = decodeLimitsType(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read limits: %v", err)
|
||||
}
|
||||
if ret.Min > wasm.MaximumFunctionIndex {
|
||||
return fmt.Errorf("table min must be at most %d", wasm.MaximumFunctionIndex)
|
||||
}
|
||||
if ret.Max != nil {
|
||||
if *ret.Max < ret.Min {
|
||||
return fmt.Errorf("table size minimum must not be greater than maximum")
|
||||
}
|
||||
}
|
||||
if shared {
|
||||
return fmt.Errorf("tables cannot be marked as shared")
|
||||
}
|
||||
return
|
||||
}
|
60
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/value.go
generated
vendored
Normal file
60
vendor/github.com/tetratelabs/wazero/internal/wasm/binary/value.go
generated
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
package binary
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) {
|
||||
if num == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
ret := make([]wasm.ValueType, num)
|
||||
_, err := io.ReadFull(r, ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range ret {
|
||||
switch v {
|
||||
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeV128:
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid value type: %d", v)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// decodeUTF8 decodes a size prefixed string from the reader, returning it and the count of bytes read.
|
||||
// contextFormat and contextArgs apply an error format when present
|
||||
func decodeUTF8(r *bytes.Reader, contextFormat string, contextArgs ...interface{}) (string, uint32, error) {
|
||||
size, sizeOfSize, err := leb128.DecodeUint32(r)
|
||||
if err != nil {
|
||||
return "", 0, fmt.Errorf("failed to read %s size: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
|
||||
}
|
||||
|
||||
if size == 0 {
|
||||
return "", uint32(sizeOfSize), nil
|
||||
}
|
||||
|
||||
buf := make([]byte, size)
|
||||
if _, err = io.ReadFull(r, buf); err != nil {
|
||||
return "", 0, fmt.Errorf("failed to read %s: %w", fmt.Sprintf(contextFormat, contextArgs...), err)
|
||||
}
|
||||
|
||||
if !utf8.Valid(buf) {
|
||||
return "", 0, fmt.Errorf("%s is not valid UTF-8", fmt.Sprintf(contextFormat, contextArgs...))
|
||||
}
|
||||
|
||||
// TODO: use unsafe.String after flooring Go 1.20.
|
||||
ret := *(*string)(unsafe.Pointer(&buf))
|
||||
return ret, size + uint32(sizeOfSize), nil
|
||||
}
|
51
vendor/github.com/tetratelabs/wazero/internal/wasm/counts.go
generated
vendored
Normal file
51
vendor/github.com/tetratelabs/wazero/internal/wasm/counts.go
generated
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
package wasm
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SectionElementCount returns the count of elements in a given section ID
|
||||
//
|
||||
// For example...
|
||||
// * SectionIDType returns the count of FunctionType
|
||||
// * SectionIDCustom returns the count of CustomSections plus one if NameSection is present
|
||||
// * SectionIDHostFunction returns the count of HostFunctionSection
|
||||
// * SectionIDExport returns the count of unique export names
|
||||
func (m *Module) SectionElementCount(sectionID SectionID) uint32 { // element as in vector elements!
|
||||
switch sectionID {
|
||||
case SectionIDCustom:
|
||||
numCustomSections := uint32(len(m.CustomSections))
|
||||
if m.NameSection != nil {
|
||||
numCustomSections++
|
||||
}
|
||||
return numCustomSections
|
||||
case SectionIDType:
|
||||
return uint32(len(m.TypeSection))
|
||||
case SectionIDImport:
|
||||
return uint32(len(m.ImportSection))
|
||||
case SectionIDFunction:
|
||||
return uint32(len(m.FunctionSection))
|
||||
case SectionIDTable:
|
||||
return uint32(len(m.TableSection))
|
||||
case SectionIDMemory:
|
||||
if m.MemorySection != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case SectionIDGlobal:
|
||||
return uint32(len(m.GlobalSection))
|
||||
case SectionIDExport:
|
||||
return uint32(len(m.ExportSection))
|
||||
case SectionIDStart:
|
||||
if m.StartSection != nil {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case SectionIDElement:
|
||||
return uint32(len(m.ElementSection))
|
||||
case SectionIDCode:
|
||||
return uint32(len(m.CodeSection))
|
||||
case SectionIDData:
|
||||
return uint32(len(m.DataSection))
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: unknown section: %d", sectionID))
|
||||
}
|
||||
}
|
72
vendor/github.com/tetratelabs/wazero/internal/wasm/engine.go
generated
vendored
Normal file
72
vendor/github.com/tetratelabs/wazero/internal/wasm/engine.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
)
|
||||
|
||||
// Engine is a Store-scoped mechanism to compile functions declared or imported by a module.
|
||||
// This is a top-level type implemented by an interpreter or compiler.
|
||||
type Engine interface {
|
||||
// Close closes this engine, and releases all the compiled cache.
|
||||
Close() (err error)
|
||||
|
||||
// CompileModule implements the same method as documented on wasm.Engine.
|
||||
CompileModule(ctx context.Context, module *Module, listeners []experimental.FunctionListener, ensureTermination bool) error
|
||||
|
||||
// CompiledModuleCount is exported for testing, to track the size of the compilation cache.
|
||||
CompiledModuleCount() uint32
|
||||
|
||||
// DeleteCompiledModule releases compilation caches for the given module (source).
|
||||
// Note: it is safe to call this function for a module from which module instances are instantiated even when these
|
||||
// module instances have outstanding calls.
|
||||
DeleteCompiledModule(module *Module)
|
||||
|
||||
// NewModuleEngine compiles down the function instances in a module, and returns ModuleEngine for the module.
|
||||
//
|
||||
// * module is the source module from which moduleFunctions are instantiated. This is used for caching.
|
||||
// * instance is the *ModuleInstance which is created from `module`.
|
||||
//
|
||||
// Note: Input parameters must be pre-validated with wasm.Module Validate, to ensure no fields are invalid
|
||||
// due to reasons such as out-of-bounds.
|
||||
NewModuleEngine(module *Module, instance *ModuleInstance) (ModuleEngine, error)
|
||||
}
|
||||
|
||||
// ModuleEngine implements function calls for a given module.
|
||||
type ModuleEngine interface {
|
||||
// DoneInstantiation is called at the end of the instantiation of the module.
|
||||
DoneInstantiation()
|
||||
|
||||
// NewFunction returns an api.Function for the given function pointed by the given Index.
|
||||
NewFunction(index Index) api.Function
|
||||
|
||||
// ResolveImportedFunction is used to add imported functions needed to make this ModuleEngine fully functional.
|
||||
// - `index` is the function Index of this imported function.
|
||||
// - `indexInImportedModule` is the function Index of the imported function in the imported module.
|
||||
// - `importedModuleEngine` is the ModuleEngine for the imported ModuleInstance.
|
||||
ResolveImportedFunction(index, indexInImportedModule Index, importedModuleEngine ModuleEngine)
|
||||
|
||||
// ResolveImportedMemory is called when this module imports a memory from another module.
|
||||
ResolveImportedMemory(importedModuleEngine ModuleEngine)
|
||||
|
||||
// LookupFunction returns the FunctionModule and the Index of the function in the returned ModuleInstance at the given offset in the table.
|
||||
LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) (*ModuleInstance, Index)
|
||||
|
||||
// GetGlobalValue returns the value of the global variable at the given Index.
|
||||
// Only called when OwnsGlobals() returns true, and must not be called for imported globals
|
||||
GetGlobalValue(idx Index) (lo, hi uint64)
|
||||
|
||||
// SetGlobalValue sets the value of the global variable at the given Index.
|
||||
// Only called when OwnsGlobals() returns true, and must not be called for imported globals
|
||||
SetGlobalValue(idx Index, lo, hi uint64)
|
||||
|
||||
// OwnsGlobals returns true if this ModuleEngine owns the global variables. If true, wasm.GlobalInstance's Val,ValHi should
|
||||
// not be accessed directly.
|
||||
OwnsGlobals() bool
|
||||
|
||||
// FunctionInstanceReference returns Reference for the given Index for a FunctionInstance. The returned values are used by
|
||||
// the initialization via ElementSegment.
|
||||
FunctionInstanceReference(funcIndex Index) Reference
|
||||
}
|
2340
vendor/github.com/tetratelabs/wazero/internal/wasm/func_validation.go
generated
vendored
Normal file
2340
vendor/github.com/tetratelabs/wazero/internal/wasm/func_validation.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
188
vendor/github.com/tetratelabs/wazero/internal/wasm/function_definition.go
generated
vendored
Normal file
188
vendor/github.com/tetratelabs/wazero/internal/wasm/function_definition.go
generated
vendored
Normal file
@ -0,0 +1,188 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
||||
)
|
||||
|
||||
// ImportedFunctions returns the definitions of each imported function.
|
||||
//
|
||||
// Note: Unlike ExportedFunctions, there is no unique constraint on imports.
|
||||
func (m *Module) ImportedFunctions() (ret []api.FunctionDefinition) {
|
||||
for i := uint32(0); i < m.ImportFunctionCount; i++ {
|
||||
ret = append(ret, m.FunctionDefinition(i))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ExportedFunctions returns the definitions of each exported function.
|
||||
func (m *Module) ExportedFunctions() map[string]api.FunctionDefinition {
|
||||
ret := map[string]api.FunctionDefinition{}
|
||||
for i := range m.ExportSection {
|
||||
exp := &m.ExportSection[i]
|
||||
if exp.Type == ExternTypeFunc {
|
||||
d := m.FunctionDefinition(exp.Index)
|
||||
ret[exp.Name] = d
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// FunctionDefinition returns the FunctionDefinition for the given `index`.
|
||||
func (m *Module) FunctionDefinition(index Index) *FunctionDefinition {
|
||||
// TODO: function initialization is lazy, but bulk. Make it per function.
|
||||
m.buildFunctionDefinitions()
|
||||
return &m.FunctionDefinitionSection[index]
|
||||
}
|
||||
|
||||
// buildFunctionDefinitions generates function metadata that can be parsed from
|
||||
// the module. This must be called after all validation.
|
||||
func (m *Module) buildFunctionDefinitions() {
|
||||
m.functionDefinitionSectionInitOnce.Do(m.buildFunctionDefinitionsOnce)
|
||||
}
|
||||
|
||||
func (m *Module) buildFunctionDefinitionsOnce() {
|
||||
var moduleName string
|
||||
var functionNames NameMap
|
||||
var localNames, resultNames IndirectNameMap
|
||||
if m.NameSection != nil {
|
||||
moduleName = m.NameSection.ModuleName
|
||||
functionNames = m.NameSection.FunctionNames
|
||||
localNames = m.NameSection.LocalNames
|
||||
resultNames = m.NameSection.ResultNames
|
||||
}
|
||||
|
||||
importCount := m.ImportFunctionCount
|
||||
m.FunctionDefinitionSection = make([]FunctionDefinition, importCount+uint32(len(m.FunctionSection)))
|
||||
|
||||
importFuncIdx := Index(0)
|
||||
for i := range m.ImportSection {
|
||||
imp := &m.ImportSection[i]
|
||||
if imp.Type != ExternTypeFunc {
|
||||
continue
|
||||
}
|
||||
|
||||
def := &m.FunctionDefinitionSection[importFuncIdx]
|
||||
def.importDesc = imp
|
||||
def.index = importFuncIdx
|
||||
def.Functype = &m.TypeSection[imp.DescFunc]
|
||||
importFuncIdx++
|
||||
}
|
||||
|
||||
for codeIndex, typeIndex := range m.FunctionSection {
|
||||
code := &m.CodeSection[codeIndex]
|
||||
idx := importFuncIdx + Index(codeIndex)
|
||||
def := &m.FunctionDefinitionSection[idx]
|
||||
def.index = idx
|
||||
def.Functype = &m.TypeSection[typeIndex]
|
||||
def.goFunc = code.GoFunc
|
||||
}
|
||||
|
||||
n, nLen := 0, len(functionNames)
|
||||
for i := range m.FunctionDefinitionSection {
|
||||
d := &m.FunctionDefinitionSection[i]
|
||||
// The function name section begins with imports, but can be sparse.
|
||||
// This keeps track of how far in the name section we've searched.
|
||||
funcIdx := d.index
|
||||
var funcName string
|
||||
for ; n < nLen; n++ {
|
||||
next := &functionNames[n]
|
||||
if next.Index > funcIdx {
|
||||
break // we have function names, but starting at a later index.
|
||||
} else if next.Index == funcIdx {
|
||||
funcName = next.Name
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
d.moduleName = moduleName
|
||||
d.name = funcName
|
||||
d.Debugname = wasmdebug.FuncName(moduleName, funcName, funcIdx)
|
||||
d.paramNames = paramNames(localNames, funcIdx, len(d.Functype.Params))
|
||||
d.resultNames = paramNames(resultNames, funcIdx, len(d.Functype.Results))
|
||||
|
||||
for i := range m.ExportSection {
|
||||
e := &m.ExportSection[i]
|
||||
if e.Type == ExternTypeFunc && e.Index == funcIdx {
|
||||
d.exportNames = append(d.exportNames, e.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FunctionDefinition implements api.FunctionDefinition
|
||||
type FunctionDefinition struct {
|
||||
internalapi.WazeroOnlyType
|
||||
moduleName string
|
||||
index Index
|
||||
name string
|
||||
// Debugname is exported for testing purpose.
|
||||
Debugname string
|
||||
goFunc interface{}
|
||||
// Functype is exported for testing purpose.
|
||||
Functype *FunctionType
|
||||
importDesc *Import
|
||||
exportNames []string
|
||||
paramNames []string
|
||||
resultNames []string
|
||||
}
|
||||
|
||||
// ModuleName implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) ModuleName() string {
|
||||
return f.moduleName
|
||||
}
|
||||
|
||||
// Index implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) Index() uint32 {
|
||||
return f.index
|
||||
}
|
||||
|
||||
// Name implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// DebugName implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) DebugName() string {
|
||||
return f.Debugname
|
||||
}
|
||||
|
||||
// Import implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) Import() (moduleName, name string, isImport bool) {
|
||||
if f.importDesc != nil {
|
||||
importDesc := f.importDesc
|
||||
moduleName, name, isImport = importDesc.Module, importDesc.Name, true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ExportNames implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) ExportNames() []string {
|
||||
return f.exportNames
|
||||
}
|
||||
|
||||
// GoFunction implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) GoFunction() interface{} {
|
||||
return f.goFunc
|
||||
}
|
||||
|
||||
// ParamTypes implements api.FunctionDefinition ParamTypes.
|
||||
func (f *FunctionDefinition) ParamTypes() []ValueType {
|
||||
return f.Functype.Params
|
||||
}
|
||||
|
||||
// ParamNames implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) ParamNames() []string {
|
||||
return f.paramNames
|
||||
}
|
||||
|
||||
// ResultTypes implements api.FunctionDefinition ResultTypes.
|
||||
func (f *FunctionDefinition) ResultTypes() []ValueType {
|
||||
return f.Functype.Results
|
||||
}
|
||||
|
||||
// ResultNames implements the same method as documented on api.FunctionDefinition.
|
||||
func (f *FunctionDefinition) ResultNames() []string {
|
||||
return f.resultNames
|
||||
}
|
55
vendor/github.com/tetratelabs/wazero/internal/wasm/global.go
generated
vendored
Normal file
55
vendor/github.com/tetratelabs/wazero/internal/wasm/global.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
)
|
||||
|
||||
// constantGlobal wraps GlobalInstance to implement api.Global.
|
||||
type constantGlobal struct {
|
||||
internalapi.WazeroOnlyType
|
||||
g *GlobalInstance
|
||||
}
|
||||
|
||||
// Type implements api.Global.
|
||||
func (g constantGlobal) Type() api.ValueType {
|
||||
return g.g.Type.ValType
|
||||
}
|
||||
|
||||
// Get implements api.Global.
|
||||
func (g constantGlobal) Get() uint64 {
|
||||
ret, _ := g.g.Value()
|
||||
return ret
|
||||
}
|
||||
|
||||
// String implements api.Global.
|
||||
func (g constantGlobal) String() string {
|
||||
return g.g.String()
|
||||
}
|
||||
|
||||
// mutableGlobal extends constantGlobal to allow updates.
|
||||
type mutableGlobal struct {
|
||||
internalapi.WazeroOnlyType
|
||||
g *GlobalInstance
|
||||
}
|
||||
|
||||
// Type implements api.Global.
|
||||
func (g mutableGlobal) Type() api.ValueType {
|
||||
return g.g.Type.ValType
|
||||
}
|
||||
|
||||
// Get implements api.Global.
|
||||
func (g mutableGlobal) Get() uint64 {
|
||||
ret, _ := g.g.Value()
|
||||
return ret
|
||||
}
|
||||
|
||||
// String implements api.Global.
|
||||
func (g mutableGlobal) String() string {
|
||||
return g.g.String()
|
||||
}
|
||||
|
||||
// Set implements the same method as documented on api.MutableGlobal.
|
||||
func (g mutableGlobal) Set(v uint64) {
|
||||
g.g.SetValue(v, 0)
|
||||
}
|
279
vendor/github.com/tetratelabs/wazero/internal/wasm/gofunc.go
generated
vendored
Normal file
279
vendor/github.com/tetratelabs/wazero/internal/wasm/gofunc.go
generated
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
type paramsKind byte
|
||||
|
||||
const (
|
||||
paramsKindNoContext paramsKind = iota
|
||||
paramsKindContext
|
||||
paramsKindContextModule
|
||||
)
|
||||
|
||||
// Below are reflection code to get the interface type used to parse functions and set values.
|
||||
|
||||
var (
|
||||
moduleType = reflect.TypeOf((*api.Module)(nil)).Elem()
|
||||
goContextType = reflect.TypeOf((*context.Context)(nil)).Elem()
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
|
||||
// compile-time check to ensure reflectGoModuleFunction implements
|
||||
// api.GoModuleFunction.
|
||||
var _ api.GoModuleFunction = (*reflectGoModuleFunction)(nil)
|
||||
|
||||
type reflectGoModuleFunction struct {
|
||||
fn *reflect.Value
|
||||
params, results []ValueType
|
||||
}
|
||||
|
||||
// Call implements the same method as documented on api.GoModuleFunction.
|
||||
func (f *reflectGoModuleFunction) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
callGoFunc(ctx, mod, f.fn, stack)
|
||||
}
|
||||
|
||||
// EqualTo is exposed for testing.
|
||||
func (f *reflectGoModuleFunction) EqualTo(that interface{}) bool {
|
||||
if f2, ok := that.(*reflectGoModuleFunction); !ok {
|
||||
return false
|
||||
} else {
|
||||
// TODO compare reflect pointers
|
||||
return bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
|
||||
}
|
||||
}
|
||||
|
||||
// compile-time check to ensure reflectGoFunction implements api.GoFunction.
|
||||
var _ api.GoFunction = (*reflectGoFunction)(nil)
|
||||
|
||||
type reflectGoFunction struct {
|
||||
fn *reflect.Value
|
||||
pk paramsKind
|
||||
params, results []ValueType
|
||||
}
|
||||
|
||||
// EqualTo is exposed for testing.
|
||||
func (f *reflectGoFunction) EqualTo(that interface{}) bool {
|
||||
if f2, ok := that.(*reflectGoFunction); !ok {
|
||||
return false
|
||||
} else {
|
||||
// TODO compare reflect pointers
|
||||
return f.pk == f2.pk &&
|
||||
bytes.Equal(f.params, f2.params) && bytes.Equal(f.results, f2.results)
|
||||
}
|
||||
}
|
||||
|
||||
// Call implements the same method as documented on api.GoFunction.
|
||||
func (f *reflectGoFunction) Call(ctx context.Context, stack []uint64) {
|
||||
if f.pk == paramsKindNoContext {
|
||||
ctx = nil
|
||||
}
|
||||
callGoFunc(ctx, nil, f.fn, stack)
|
||||
}
|
||||
|
||||
// callGoFunc executes the reflective function by converting params to Go
|
||||
// types. The results of the function call are converted back to api.ValueType.
|
||||
func callGoFunc(ctx context.Context, mod api.Module, fn *reflect.Value, stack []uint64) {
|
||||
tp := fn.Type()
|
||||
|
||||
var in []reflect.Value
|
||||
pLen := tp.NumIn()
|
||||
if pLen != 0 {
|
||||
in = make([]reflect.Value, pLen)
|
||||
|
||||
i := 0
|
||||
if ctx != nil {
|
||||
in[0] = newContextVal(ctx)
|
||||
i++
|
||||
}
|
||||
if mod != nil {
|
||||
in[1] = newModuleVal(mod)
|
||||
i++
|
||||
}
|
||||
|
||||
for j := 0; i < pLen; i++ {
|
||||
next := tp.In(i)
|
||||
val := reflect.New(next).Elem()
|
||||
k := next.Kind()
|
||||
raw := stack[j]
|
||||
j++
|
||||
|
||||
switch k {
|
||||
case reflect.Float32:
|
||||
val.SetFloat(float64(math.Float32frombits(uint32(raw))))
|
||||
case reflect.Float64:
|
||||
val.SetFloat(math.Float64frombits(raw))
|
||||
case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
val.SetUint(raw)
|
||||
case reflect.Int32, reflect.Int64:
|
||||
val.SetInt(int64(raw))
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: param[%d] has an invalid type: %v", i, k))
|
||||
}
|
||||
in[i] = val
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the host function and push back the call result onto the stack.
|
||||
for i, ret := range fn.Call(in) {
|
||||
switch ret.Kind() {
|
||||
case reflect.Float32:
|
||||
stack[i] = uint64(math.Float32bits(float32(ret.Float())))
|
||||
case reflect.Float64:
|
||||
stack[i] = math.Float64bits(ret.Float())
|
||||
case reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
stack[i] = ret.Uint()
|
||||
case reflect.Int32, reflect.Int64:
|
||||
stack[i] = uint64(ret.Int())
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: result[%d] has an invalid type: %v", i, ret.Kind()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newContextVal(ctx context.Context) reflect.Value {
|
||||
val := reflect.New(goContextType).Elem()
|
||||
val.Set(reflect.ValueOf(ctx))
|
||||
return val
|
||||
}
|
||||
|
||||
func newModuleVal(m api.Module) reflect.Value {
|
||||
val := reflect.New(moduleType).Elem()
|
||||
val.Set(reflect.ValueOf(m))
|
||||
return val
|
||||
}
|
||||
|
||||
// MustParseGoReflectFuncCode parses Code from the go function or panics.
|
||||
//
|
||||
// Exposing this simplifies FunctionDefinition of host functions in built-in host
|
||||
// modules and tests.
|
||||
func MustParseGoReflectFuncCode(fn interface{}) Code {
|
||||
_, _, code, err := parseGoReflectFunc(fn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
func parseGoReflectFunc(fn interface{}) (params, results []ValueType, code Code, err error) {
|
||||
fnV := reflect.ValueOf(fn)
|
||||
p := fnV.Type()
|
||||
|
||||
if fnV.Kind() != reflect.Func {
|
||||
err = fmt.Errorf("kind != func: %s", fnV.Kind().String())
|
||||
return
|
||||
}
|
||||
|
||||
pk, kindErr := kind(p)
|
||||
if kindErr != nil {
|
||||
err = kindErr
|
||||
return
|
||||
}
|
||||
|
||||
pOffset := 0
|
||||
switch pk {
|
||||
case paramsKindNoContext:
|
||||
case paramsKindContext:
|
||||
pOffset = 1
|
||||
case paramsKindContextModule:
|
||||
pOffset = 2
|
||||
}
|
||||
|
||||
pCount := p.NumIn() - pOffset
|
||||
if pCount > 0 {
|
||||
params = make([]ValueType, pCount)
|
||||
}
|
||||
for i := 0; i < len(params); i++ {
|
||||
pI := p.In(i + pOffset)
|
||||
if t, ok := getTypeOf(pI.Kind()); ok {
|
||||
params[i] = t
|
||||
continue
|
||||
}
|
||||
|
||||
// Now, we will definitely err, decide which message is best
|
||||
var arg0Type reflect.Type
|
||||
if hc := pI.Implements(moduleType); hc {
|
||||
arg0Type = moduleType
|
||||
} else if gc := pI.Implements(goContextType); gc {
|
||||
arg0Type = goContextType
|
||||
}
|
||||
|
||||
if arg0Type != nil {
|
||||
err = fmt.Errorf("param[%d] is a %s, which may be defined only once as param[0]", i+pOffset, arg0Type)
|
||||
} else {
|
||||
err = fmt.Errorf("param[%d] is unsupported: %s", i+pOffset, pI.Kind())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
rCount := p.NumOut()
|
||||
if rCount > 0 {
|
||||
results = make([]ValueType, rCount)
|
||||
}
|
||||
for i := 0; i < len(results); i++ {
|
||||
rI := p.Out(i)
|
||||
if t, ok := getTypeOf(rI.Kind()); ok {
|
||||
results[i] = t
|
||||
continue
|
||||
}
|
||||
|
||||
// Now, we will definitely err, decide which message is best
|
||||
if rI.Implements(errorType) {
|
||||
err = fmt.Errorf("result[%d] is an error, which is unsupported", i)
|
||||
} else {
|
||||
err = fmt.Errorf("result[%d] is unsupported: %s", i, rI.Kind())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
code = Code{}
|
||||
if pk == paramsKindContextModule {
|
||||
code.GoFunc = &reflectGoModuleFunction{fn: &fnV, params: params, results: results}
|
||||
} else {
|
||||
code.GoFunc = &reflectGoFunction{pk: pk, fn: &fnV, params: params, results: results}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func kind(p reflect.Type) (paramsKind, error) {
|
||||
pCount := p.NumIn()
|
||||
if pCount > 0 && p.In(0).Kind() == reflect.Interface {
|
||||
p0 := p.In(0)
|
||||
if p0.Implements(moduleType) {
|
||||
return 0, errors.New("invalid signature: api.Module parameter must be preceded by context.Context")
|
||||
} else if p0.Implements(goContextType) {
|
||||
if pCount >= 2 && p.In(1).Implements(moduleType) {
|
||||
return paramsKindContextModule, nil
|
||||
}
|
||||
return paramsKindContext, nil
|
||||
}
|
||||
}
|
||||
// Without context param allows portability with reflective runtimes.
|
||||
// This allows people to more easily port to wazero.
|
||||
return paramsKindNoContext, nil
|
||||
}
|
||||
|
||||
func getTypeOf(kind reflect.Kind) (ValueType, bool) {
|
||||
switch kind {
|
||||
case reflect.Float64:
|
||||
return ValueTypeF64, true
|
||||
case reflect.Float32:
|
||||
return ValueTypeF32, true
|
||||
case reflect.Int32, reflect.Uint32:
|
||||
return ValueTypeI32, true
|
||||
case reflect.Int64, reflect.Uint64:
|
||||
return ValueTypeI64, true
|
||||
case reflect.Uintptr:
|
||||
return ValueTypeExternref, true
|
||||
default:
|
||||
return 0x00, false
|
||||
}
|
||||
}
|
179
vendor/github.com/tetratelabs/wazero/internal/wasm/host.go
generated
vendored
Normal file
179
vendor/github.com/tetratelabs/wazero/internal/wasm/host.go
generated
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasmdebug"
|
||||
)
|
||||
|
||||
type HostFuncExporter interface {
|
||||
ExportHostFunc(*HostFunc)
|
||||
}
|
||||
|
||||
// HostFunc is a function with an inlined type, used for NewHostModule.
|
||||
// Any corresponding FunctionType will be reused or added to the Module.
|
||||
type HostFunc struct {
|
||||
// ExportName is the only value returned by api.FunctionDefinition.
|
||||
ExportName string
|
||||
|
||||
// Name is equivalent to the same method on api.FunctionDefinition.
|
||||
Name string
|
||||
|
||||
// ParamTypes is equivalent to the same method on api.FunctionDefinition.
|
||||
ParamTypes []ValueType
|
||||
|
||||
// ParamNames is equivalent to the same method on api.FunctionDefinition.
|
||||
ParamNames []string
|
||||
|
||||
// ResultTypes is equivalent to the same method on api.FunctionDefinition.
|
||||
ResultTypes []ValueType
|
||||
|
||||
// ResultNames is equivalent to the same method on api.FunctionDefinition.
|
||||
ResultNames []string
|
||||
|
||||
// Code is the equivalent function in the SectionIDCode.
|
||||
Code Code
|
||||
}
|
||||
|
||||
// WithGoModuleFunc returns a copy of the function, replacing its Code.GoFunc.
|
||||
func (f *HostFunc) WithGoModuleFunc(fn api.GoModuleFunc) *HostFunc {
|
||||
ret := *f
|
||||
ret.Code.GoFunc = fn
|
||||
return &ret
|
||||
}
|
||||
|
||||
// NewHostModule is defined internally for use in WASI tests and to keep the code size in the root directory small.
|
||||
func NewHostModule(
|
||||
moduleName string,
|
||||
exportNames []string,
|
||||
nameToHostFunc map[string]*HostFunc,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
) (m *Module, err error) {
|
||||
if moduleName != "" {
|
||||
m = &Module{NameSection: &NameSection{ModuleName: moduleName}}
|
||||
} else {
|
||||
return nil, errors.New("a module name must not be empty")
|
||||
}
|
||||
|
||||
if exportCount := uint32(len(nameToHostFunc)); exportCount > 0 {
|
||||
m.ExportSection = make([]Export, 0, exportCount)
|
||||
m.Exports = make(map[string]*Export, exportCount)
|
||||
if err = addFuncs(m, exportNames, nameToHostFunc, enabledFeatures); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
m.IsHostModule = true
|
||||
// Uses the address of *wasm.Module as the module ID so that host functions can have each state per compilation.
|
||||
// Downside of this is that compilation cache on host functions (trampoline codes for Go functions and
|
||||
// Wasm codes for Wasm-implemented host functions) are not available and compiles each time. On the other hand,
|
||||
// compilation of host modules is not costly as it's merely small trampolines vs the real-world native Wasm binary.
|
||||
// TODO: refactor engines so that we can properly cache compiled machine codes for host modules.
|
||||
m.AssignModuleID([]byte(fmt.Sprintf("@@@@@@@@%p", m)), // @@@@@@@@ = any 8 bytes different from Wasm header.
|
||||
nil, false)
|
||||
return
|
||||
}
|
||||
|
||||
func addFuncs(
|
||||
m *Module,
|
||||
exportNames []string,
|
||||
nameToHostFunc map[string]*HostFunc,
|
||||
enabledFeatures api.CoreFeatures,
|
||||
) (err error) {
|
||||
if m.NameSection == nil {
|
||||
m.NameSection = &NameSection{}
|
||||
}
|
||||
moduleName := m.NameSection.ModuleName
|
||||
|
||||
for _, k := range exportNames {
|
||||
hf := nameToHostFunc[k]
|
||||
if hf.Name == "" {
|
||||
hf.Name = k // default name to export name
|
||||
}
|
||||
switch hf.Code.GoFunc.(type) {
|
||||
case api.GoModuleFunction, api.GoFunction:
|
||||
continue // already parsed
|
||||
}
|
||||
|
||||
// Resolve the code using reflection
|
||||
hf.ParamTypes, hf.ResultTypes, hf.Code, err = parseGoReflectFunc(hf.Code.GoFunc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("func[%s.%s] %w", moduleName, k, err)
|
||||
}
|
||||
|
||||
// Assign names to the function, if they exist.
|
||||
params := hf.ParamTypes
|
||||
if paramNames := hf.ParamNames; paramNames != nil {
|
||||
if paramNamesLen := len(paramNames); paramNamesLen != len(params) {
|
||||
return fmt.Errorf("func[%s.%s] has %d params, but %d params names", moduleName, k, paramNamesLen, len(params))
|
||||
}
|
||||
}
|
||||
|
||||
results := hf.ResultTypes
|
||||
if resultNames := hf.ResultNames; resultNames != nil {
|
||||
if resultNamesLen := len(resultNames); resultNamesLen != len(results) {
|
||||
return fmt.Errorf("func[%s.%s] has %d results, but %d results names", moduleName, k, resultNamesLen, len(results))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
funcCount := uint32(len(exportNames))
|
||||
m.NameSection.FunctionNames = make([]NameAssoc, 0, funcCount)
|
||||
m.FunctionSection = make([]Index, 0, funcCount)
|
||||
m.CodeSection = make([]Code, 0, funcCount)
|
||||
|
||||
idx := Index(0)
|
||||
for _, name := range exportNames {
|
||||
hf := nameToHostFunc[name]
|
||||
debugName := wasmdebug.FuncName(moduleName, name, idx)
|
||||
typeIdx, typeErr := m.maybeAddType(hf.ParamTypes, hf.ResultTypes, enabledFeatures)
|
||||
if typeErr != nil {
|
||||
return fmt.Errorf("func[%s] %v", debugName, typeErr)
|
||||
}
|
||||
m.FunctionSection = append(m.FunctionSection, typeIdx)
|
||||
m.CodeSection = append(m.CodeSection, hf.Code)
|
||||
|
||||
export := hf.ExportName
|
||||
m.ExportSection = append(m.ExportSection, Export{Type: ExternTypeFunc, Name: export, Index: idx})
|
||||
m.Exports[export] = &m.ExportSection[len(m.ExportSection)-1]
|
||||
m.NameSection.FunctionNames = append(m.NameSection.FunctionNames, NameAssoc{Index: idx, Name: hf.Name})
|
||||
|
||||
if len(hf.ParamNames) > 0 {
|
||||
localNames := NameMapAssoc{Index: idx}
|
||||
for i, n := range hf.ParamNames {
|
||||
localNames.NameMap = append(localNames.NameMap, NameAssoc{Index: Index(i), Name: n})
|
||||
}
|
||||
m.NameSection.LocalNames = append(m.NameSection.LocalNames, localNames)
|
||||
}
|
||||
if len(hf.ResultNames) > 0 {
|
||||
resultNames := NameMapAssoc{Index: idx}
|
||||
for i, n := range hf.ResultNames {
|
||||
resultNames.NameMap = append(resultNames.NameMap, NameAssoc{Index: Index(i), Name: n})
|
||||
}
|
||||
m.NameSection.ResultNames = append(m.NameSection.ResultNames, resultNames)
|
||||
}
|
||||
idx++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) maybeAddType(params, results []ValueType, enabledFeatures api.CoreFeatures) (Index, error) {
|
||||
if len(results) > 1 {
|
||||
// Guard >1.0 feature multi-value
|
||||
if err := enabledFeatures.RequireEnabled(api.CoreFeatureMultiValue); err != nil {
|
||||
return 0, fmt.Errorf("multiple result types invalid as %v", err)
|
||||
}
|
||||
}
|
||||
for i := range m.TypeSection {
|
||||
t := &m.TypeSection[i]
|
||||
if t.EqualsSignature(params, results) {
|
||||
return Index(i), nil
|
||||
}
|
||||
}
|
||||
|
||||
result := m.SectionElementCount(SectionIDType)
|
||||
m.TypeSection = append(m.TypeSection, FunctionType{Params: params, Results: results})
|
||||
return result, nil
|
||||
}
|
1866
vendor/github.com/tetratelabs/wazero/internal/wasm/instruction.go
generated
vendored
Normal file
1866
vendor/github.com/tetratelabs/wazero/internal/wasm/instruction.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
461
vendor/github.com/tetratelabs/wazero/internal/wasm/memory.go
generated
vendored
Normal file
461
vendor/github.com/tetratelabs/wazero/internal/wasm/memory.go
generated
vendored
Normal file
@ -0,0 +1,461 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
"github.com/tetratelabs/wazero/internal/wasmruntime"
|
||||
)
|
||||
|
||||
const (
|
||||
// MemoryPageSize is the unit of memory length in WebAssembly,
|
||||
// and is defined as 2^16 = 65536.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0
|
||||
MemoryPageSize = uint32(65536)
|
||||
// MemoryLimitPages is maximum number of pages defined (2^16).
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
|
||||
MemoryLimitPages = uint32(65536)
|
||||
// MemoryPageSizeInBits satisfies the relation: "1 << MemoryPageSizeInBits == MemoryPageSize".
|
||||
MemoryPageSizeInBits = 16
|
||||
)
|
||||
|
||||
// compile-time check to ensure MemoryInstance implements api.Memory
|
||||
var _ api.Memory = &MemoryInstance{}
|
||||
|
||||
type waiters struct {
|
||||
mux sync.Mutex
|
||||
l *list.List
|
||||
}
|
||||
|
||||
// MemoryInstance represents a memory instance in a store, and implements api.Memory.
|
||||
//
|
||||
// Note: In WebAssembly 1.0 (20191205), there may be up to one Memory per store, which means the precise memory is always
|
||||
// wasm.Store Memories index zero: `store.Memories[0]`
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0.
|
||||
type MemoryInstance struct {
|
||||
internalapi.WazeroOnlyType
|
||||
|
||||
Buffer []byte
|
||||
Min, Cap, Max uint32
|
||||
Shared bool
|
||||
// definition is known at compile time.
|
||||
definition api.MemoryDefinition
|
||||
|
||||
// Mux is used in interpreter mode to prevent overlapping calls to atomic instructions,
|
||||
// introduced with WebAssembly threads proposal.
|
||||
Mux sync.Mutex
|
||||
|
||||
// waiters implements atomic wait and notify. It is implemented similarly to golang.org/x/sync/semaphore,
|
||||
// with a fixed weight of 1 and no spurious notifications.
|
||||
waiters sync.Map
|
||||
|
||||
expBuffer experimental.LinearMemory
|
||||
}
|
||||
|
||||
// NewMemoryInstance creates a new instance based on the parameters in the SectionIDMemory.
|
||||
func NewMemoryInstance(memSec *Memory, allocator experimental.MemoryAllocator) *MemoryInstance {
|
||||
minBytes := MemoryPagesToBytesNum(memSec.Min)
|
||||
capBytes := MemoryPagesToBytesNum(memSec.Cap)
|
||||
maxBytes := MemoryPagesToBytesNum(memSec.Max)
|
||||
|
||||
var buffer []byte
|
||||
var expBuffer experimental.LinearMemory
|
||||
if allocator != nil {
|
||||
expBuffer = allocator.Allocate(capBytes, maxBytes)
|
||||
buffer = expBuffer.Reallocate(minBytes)
|
||||
} else if memSec.IsShared {
|
||||
// Shared memory needs a fixed buffer, so allocate with the maximum size.
|
||||
//
|
||||
// The rationale as to why we can simply use make([]byte) to a fixed buffer is that Go's GC is non-relocating.
|
||||
// That is not a part of Go spec, but is well-known thing in Go community (wazero's compiler heavily relies on it!)
|
||||
// * https://github.com/go4org/unsafe-assume-no-moving-gc
|
||||
//
|
||||
// Also, allocating Max here isn't harmful as the Go runtime uses mmap for large allocations, therefore,
|
||||
// the memory buffer allocation here is virtual and doesn't consume physical memory until it's used.
|
||||
// * https://github.com/golang/go/blob/8121604559035734c9677d5281bbdac8b1c17a1e/src/runtime/malloc.go#L1059
|
||||
// * https://github.com/golang/go/blob/8121604559035734c9677d5281bbdac8b1c17a1e/src/runtime/malloc.go#L1165
|
||||
buffer = make([]byte, minBytes, maxBytes)
|
||||
} else {
|
||||
buffer = make([]byte, minBytes, capBytes)
|
||||
}
|
||||
return &MemoryInstance{
|
||||
Buffer: buffer,
|
||||
Min: memSec.Min,
|
||||
Cap: memoryBytesNumToPages(uint64(cap(buffer))),
|
||||
Max: memSec.Max,
|
||||
Shared: memSec.IsShared,
|
||||
expBuffer: expBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
// Definition implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) Definition() api.MemoryDefinition {
|
||||
return m.definition
|
||||
}
|
||||
|
||||
// Size implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) Size() uint32 {
|
||||
return uint32(len(m.Buffer))
|
||||
}
|
||||
|
||||
// ReadByte implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) ReadByte(offset uint32) (byte, bool) {
|
||||
if !m.hasSize(offset, 1) {
|
||||
return 0, false
|
||||
}
|
||||
return m.Buffer[offset], true
|
||||
}
|
||||
|
||||
// ReadUint16Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) ReadUint16Le(offset uint32) (uint16, bool) {
|
||||
if !m.hasSize(offset, 2) {
|
||||
return 0, false
|
||||
}
|
||||
return binary.LittleEndian.Uint16(m.Buffer[offset : offset+2]), true
|
||||
}
|
||||
|
||||
// ReadUint32Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) ReadUint32Le(offset uint32) (uint32, bool) {
|
||||
return m.readUint32Le(offset)
|
||||
}
|
||||
|
||||
// ReadFloat32Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) ReadFloat32Le(offset uint32) (float32, bool) {
|
||||
v, ok := m.readUint32Le(offset)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return math.Float32frombits(v), true
|
||||
}
|
||||
|
||||
// ReadUint64Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) ReadUint64Le(offset uint32) (uint64, bool) {
|
||||
return m.readUint64Le(offset)
|
||||
}
|
||||
|
||||
// ReadFloat64Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) ReadFloat64Le(offset uint32) (float64, bool) {
|
||||
v, ok := m.readUint64Le(offset)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return math.Float64frombits(v), true
|
||||
}
|
||||
|
||||
// Read implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) Read(offset, byteCount uint32) ([]byte, bool) {
|
||||
if !m.hasSize(offset, uint64(byteCount)) {
|
||||
return nil, false
|
||||
}
|
||||
return m.Buffer[offset : offset+byteCount : offset+byteCount], true
|
||||
}
|
||||
|
||||
// WriteByte implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) WriteByte(offset uint32, v byte) bool {
|
||||
if !m.hasSize(offset, 1) {
|
||||
return false
|
||||
}
|
||||
m.Buffer[offset] = v
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteUint16Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) WriteUint16Le(offset uint32, v uint16) bool {
|
||||
if !m.hasSize(offset, 2) {
|
||||
return false
|
||||
}
|
||||
binary.LittleEndian.PutUint16(m.Buffer[offset:], v)
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteUint32Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) WriteUint32Le(offset, v uint32) bool {
|
||||
return m.writeUint32Le(offset, v)
|
||||
}
|
||||
|
||||
// WriteFloat32Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) WriteFloat32Le(offset uint32, v float32) bool {
|
||||
return m.writeUint32Le(offset, math.Float32bits(v))
|
||||
}
|
||||
|
||||
// WriteUint64Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) WriteUint64Le(offset uint32, v uint64) bool {
|
||||
return m.writeUint64Le(offset, v)
|
||||
}
|
||||
|
||||
// WriteFloat64Le implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) WriteFloat64Le(offset uint32, v float64) bool {
|
||||
return m.writeUint64Le(offset, math.Float64bits(v))
|
||||
}
|
||||
|
||||
// Write implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) Write(offset uint32, val []byte) bool {
|
||||
if !m.hasSize(offset, uint64(len(val))) {
|
||||
return false
|
||||
}
|
||||
copy(m.Buffer[offset:], val)
|
||||
return true
|
||||
}
|
||||
|
||||
// WriteString implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) WriteString(offset uint32, val string) bool {
|
||||
if !m.hasSize(offset, uint64(len(val))) {
|
||||
return false
|
||||
}
|
||||
copy(m.Buffer[offset:], val)
|
||||
return true
|
||||
}
|
||||
|
||||
// MemoryPagesToBytesNum converts the given pages into the number of bytes contained in these pages.
|
||||
func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
|
||||
return uint64(pages) << MemoryPageSizeInBits
|
||||
}
|
||||
|
||||
// Grow implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) Grow(delta uint32) (result uint32, ok bool) {
|
||||
currentPages := m.Pages()
|
||||
if delta == 0 {
|
||||
return currentPages, true
|
||||
}
|
||||
|
||||
// If exceeds the max of memory size, we push -1 according to the spec.
|
||||
newPages := currentPages + delta
|
||||
if newPages > m.Max || int32(delta) < 0 {
|
||||
return 0, false
|
||||
} else if m.expBuffer != nil {
|
||||
buffer := m.expBuffer.Reallocate(MemoryPagesToBytesNum(newPages))
|
||||
if m.Shared {
|
||||
if unsafe.SliceData(buffer) != unsafe.SliceData(m.Buffer) {
|
||||
panic("shared memory cannot move, this is a bug in the memory allocator")
|
||||
}
|
||||
// We assume grow is called under a guest lock.
|
||||
// But the memory length is accessed elsewhere,
|
||||
// so use atomic to make the new length visible across threads.
|
||||
atomicStoreLengthAndCap(&m.Buffer, uintptr(len(buffer)), uintptr(cap(buffer)))
|
||||
m.Cap = memoryBytesNumToPages(uint64(cap(buffer)))
|
||||
} else {
|
||||
m.Buffer = buffer
|
||||
m.Cap = newPages
|
||||
}
|
||||
return currentPages, true
|
||||
} else if newPages > m.Cap { // grow the memory.
|
||||
if m.Shared {
|
||||
panic("shared memory cannot be grown, this is a bug in wazero")
|
||||
}
|
||||
m.Buffer = append(m.Buffer, make([]byte, MemoryPagesToBytesNum(delta))...)
|
||||
m.Cap = newPages
|
||||
return currentPages, true
|
||||
} else { // We already have the capacity we need.
|
||||
if m.Shared {
|
||||
// We assume grow is called under a guest lock.
|
||||
// But the memory length is accessed elsewhere,
|
||||
// so use atomic to make the new length visible across threads.
|
||||
atomicStoreLength(&m.Buffer, uintptr(MemoryPagesToBytesNum(newPages)))
|
||||
} else {
|
||||
m.Buffer = m.Buffer[:MemoryPagesToBytesNum(newPages)]
|
||||
}
|
||||
return currentPages, true
|
||||
}
|
||||
}
|
||||
|
||||
// Pages implements the same method as documented on api.Memory.
|
||||
func (m *MemoryInstance) Pages() (result uint32) {
|
||||
return memoryBytesNumToPages(uint64(len(m.Buffer)))
|
||||
}
|
||||
|
||||
// PagesToUnitOfBytes converts the pages to a human-readable form similar to what's specified. e.g. 1 -> "64Ki"
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#memory-instances%E2%91%A0
|
||||
func PagesToUnitOfBytes(pages uint32) string {
|
||||
k := pages * 64
|
||||
if k < 1024 {
|
||||
return fmt.Sprintf("%d Ki", k)
|
||||
}
|
||||
m := k / 1024
|
||||
if m < 1024 {
|
||||
return fmt.Sprintf("%d Mi", m)
|
||||
}
|
||||
g := m / 1024
|
||||
if g < 1024 {
|
||||
return fmt.Sprintf("%d Gi", g)
|
||||
}
|
||||
return fmt.Sprintf("%d Ti", g/1024)
|
||||
}
|
||||
|
||||
// Below are raw functions used to implement the api.Memory API:
|
||||
|
||||
// Uses atomic write to update the length of a slice.
|
||||
func atomicStoreLengthAndCap(slice *[]byte, length uintptr, cap uintptr) {
|
||||
slicePtr := (*reflect.SliceHeader)(unsafe.Pointer(slice))
|
||||
capPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Cap))
|
||||
atomic.StoreUintptr(capPtr, cap)
|
||||
lenPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Len))
|
||||
atomic.StoreUintptr(lenPtr, length)
|
||||
}
|
||||
|
||||
// Uses atomic write to update the length of a slice.
|
||||
func atomicStoreLength(slice *[]byte, length uintptr) {
|
||||
slicePtr := (*reflect.SliceHeader)(unsafe.Pointer(slice))
|
||||
lenPtr := (*uintptr)(unsafe.Pointer(&slicePtr.Len))
|
||||
atomic.StoreUintptr(lenPtr, length)
|
||||
}
|
||||
|
||||
// memoryBytesNumToPages converts the given number of bytes into the number of pages.
|
||||
func memoryBytesNumToPages(bytesNum uint64) (pages uint32) {
|
||||
return uint32(bytesNum >> MemoryPageSizeInBits)
|
||||
}
|
||||
|
||||
// hasSize returns true if Len is sufficient for byteCount at the given offset.
|
||||
//
|
||||
// Note: This is always fine, because memory can grow, but never shrink.
|
||||
func (m *MemoryInstance) hasSize(offset uint32, byteCount uint64) bool {
|
||||
return uint64(offset)+byteCount <= uint64(len(m.Buffer)) // uint64 prevents overflow on add
|
||||
}
|
||||
|
||||
// readUint32Le implements ReadUint32Le without using a context. This is extracted as both ints and floats are stored in
|
||||
// memory as uint32le.
|
||||
func (m *MemoryInstance) readUint32Le(offset uint32) (uint32, bool) {
|
||||
if !m.hasSize(offset, 4) {
|
||||
return 0, false
|
||||
}
|
||||
return binary.LittleEndian.Uint32(m.Buffer[offset : offset+4]), true
|
||||
}
|
||||
|
||||
// readUint64Le implements ReadUint64Le without using a context. This is extracted as both ints and floats are stored in
|
||||
// memory as uint64le.
|
||||
func (m *MemoryInstance) readUint64Le(offset uint32) (uint64, bool) {
|
||||
if !m.hasSize(offset, 8) {
|
||||
return 0, false
|
||||
}
|
||||
return binary.LittleEndian.Uint64(m.Buffer[offset : offset+8]), true
|
||||
}
|
||||
|
||||
// writeUint32Le implements WriteUint32Le without using a context. This is extracted as both ints and floats are stored
|
||||
// in memory as uint32le.
|
||||
func (m *MemoryInstance) writeUint32Le(offset uint32, v uint32) bool {
|
||||
if !m.hasSize(offset, 4) {
|
||||
return false
|
||||
}
|
||||
binary.LittleEndian.PutUint32(m.Buffer[offset:], v)
|
||||
return true
|
||||
}
|
||||
|
||||
// writeUint64Le implements WriteUint64Le without using a context. This is extracted as both ints and floats are stored
|
||||
// in memory as uint64le.
|
||||
func (m *MemoryInstance) writeUint64Le(offset uint32, v uint64) bool {
|
||||
if !m.hasSize(offset, 8) {
|
||||
return false
|
||||
}
|
||||
binary.LittleEndian.PutUint64(m.Buffer[offset:], v)
|
||||
return true
|
||||
}
|
||||
|
||||
// Wait32 suspends the caller until the offset is notified by a different agent.
|
||||
func (m *MemoryInstance) Wait32(offset uint32, exp uint32, timeout int64, reader func(mem *MemoryInstance, offset uint32) uint32) uint64 {
|
||||
w := m.getWaiters(offset)
|
||||
w.mux.Lock()
|
||||
|
||||
cur := reader(m, offset)
|
||||
if cur != exp {
|
||||
w.mux.Unlock()
|
||||
return 1
|
||||
}
|
||||
|
||||
return m.wait(w, timeout)
|
||||
}
|
||||
|
||||
// Wait64 suspends the caller until the offset is notified by a different agent.
|
||||
func (m *MemoryInstance) Wait64(offset uint32, exp uint64, timeout int64, reader func(mem *MemoryInstance, offset uint32) uint64) uint64 {
|
||||
w := m.getWaiters(offset)
|
||||
w.mux.Lock()
|
||||
|
||||
cur := reader(m, offset)
|
||||
if cur != exp {
|
||||
w.mux.Unlock()
|
||||
return 1
|
||||
}
|
||||
|
||||
return m.wait(w, timeout)
|
||||
}
|
||||
|
||||
func (m *MemoryInstance) wait(w *waiters, timeout int64) uint64 {
|
||||
if w.l == nil {
|
||||
w.l = list.New()
|
||||
}
|
||||
|
||||
// The specification requires a trap if the number of existing waiters + 1 == 2^32, so we add a check here.
|
||||
// In practice, it is unlikely the application would ever accumulate such a large number of waiters as it
|
||||
// indicates several GB of RAM used just for the list of waiters.
|
||||
// https://github.com/WebAssembly/threads/blob/main/proposals/threads/Overview.md#wait
|
||||
if uint64(w.l.Len()+1) == 1<<32 {
|
||||
w.mux.Unlock()
|
||||
panic(wasmruntime.ErrRuntimeTooManyWaiters)
|
||||
}
|
||||
|
||||
ready := make(chan struct{})
|
||||
elem := w.l.PushBack(ready)
|
||||
w.mux.Unlock()
|
||||
|
||||
if timeout < 0 {
|
||||
<-ready
|
||||
return 0
|
||||
} else {
|
||||
select {
|
||||
case <-ready:
|
||||
return 0
|
||||
case <-time.After(time.Duration(timeout)):
|
||||
// While we could see if the channel completed by now and ignore the timeout, similar to x/sync/semaphore,
|
||||
// the Wasm spec doesn't specify this behavior, so we keep things simple by prioritizing the timeout.
|
||||
w.mux.Lock()
|
||||
w.l.Remove(elem)
|
||||
w.mux.Unlock()
|
||||
return 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MemoryInstance) getWaiters(offset uint32) *waiters {
|
||||
wAny, ok := m.waiters.Load(offset)
|
||||
if !ok {
|
||||
// The first time an address is waited on, simultaneous waits will cause extra allocations.
|
||||
// Further operations will be loaded above, which is also the general pattern of usage with
|
||||
// mutexes.
|
||||
wAny, _ = m.waiters.LoadOrStore(offset, &waiters{})
|
||||
}
|
||||
|
||||
return wAny.(*waiters)
|
||||
}
|
||||
|
||||
// Notify wakes up at most count waiters at the given offset.
|
||||
func (m *MemoryInstance) Notify(offset uint32, count uint32) uint32 {
|
||||
wAny, ok := m.waiters.Load(offset)
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
w := wAny.(*waiters)
|
||||
|
||||
w.mux.Lock()
|
||||
defer w.mux.Unlock()
|
||||
if w.l == nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
res := uint32(0)
|
||||
for num := w.l.Len(); num > 0 && res < count; num = w.l.Len() {
|
||||
w := w.l.Remove(w.l.Front()).(chan struct{})
|
||||
close(w)
|
||||
res++
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
128
vendor/github.com/tetratelabs/wazero/internal/wasm/memory_definition.go
generated
vendored
Normal file
128
vendor/github.com/tetratelabs/wazero/internal/wasm/memory_definition.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
)
|
||||
|
||||
// ImportedMemories implements the same method as documented on wazero.CompiledModule.
|
||||
func (m *Module) ImportedMemories() (ret []api.MemoryDefinition) {
|
||||
for i := range m.MemoryDefinitionSection {
|
||||
d := &m.MemoryDefinitionSection[i]
|
||||
if d.importDesc != nil {
|
||||
ret = append(ret, d)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ExportedMemories implements the same method as documented on wazero.CompiledModule.
|
||||
func (m *Module) ExportedMemories() map[string]api.MemoryDefinition {
|
||||
ret := map[string]api.MemoryDefinition{}
|
||||
for i := range m.MemoryDefinitionSection {
|
||||
d := &m.MemoryDefinitionSection[i]
|
||||
for _, e := range d.exportNames {
|
||||
ret[e] = d
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// BuildMemoryDefinitions generates memory metadata that can be parsed from
|
||||
// the module. This must be called after all validation.
|
||||
//
|
||||
// Note: This is exported for wazero.Runtime `CompileModule`.
|
||||
func (m *Module) BuildMemoryDefinitions() {
|
||||
var moduleName string
|
||||
if m.NameSection != nil {
|
||||
moduleName = m.NameSection.ModuleName
|
||||
}
|
||||
|
||||
memoryCount := m.ImportMemoryCount
|
||||
if m.MemorySection != nil {
|
||||
memoryCount++
|
||||
}
|
||||
|
||||
if memoryCount == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
m.MemoryDefinitionSection = make([]MemoryDefinition, 0, memoryCount)
|
||||
importMemIdx := Index(0)
|
||||
for i := range m.ImportSection {
|
||||
imp := &m.ImportSection[i]
|
||||
if imp.Type != ExternTypeMemory {
|
||||
continue
|
||||
}
|
||||
|
||||
m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, MemoryDefinition{
|
||||
importDesc: &[2]string{imp.Module, imp.Name},
|
||||
index: importMemIdx,
|
||||
memory: imp.DescMem,
|
||||
})
|
||||
importMemIdx++
|
||||
}
|
||||
|
||||
if m.MemorySection != nil {
|
||||
m.MemoryDefinitionSection = append(m.MemoryDefinitionSection, MemoryDefinition{
|
||||
index: importMemIdx,
|
||||
memory: m.MemorySection,
|
||||
})
|
||||
}
|
||||
|
||||
for i := range m.MemoryDefinitionSection {
|
||||
d := &m.MemoryDefinitionSection[i]
|
||||
d.moduleName = moduleName
|
||||
for i := range m.ExportSection {
|
||||
e := &m.ExportSection[i]
|
||||
if e.Type == ExternTypeMemory && e.Index == d.index {
|
||||
d.exportNames = append(d.exportNames, e.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MemoryDefinition implements api.MemoryDefinition
|
||||
type MemoryDefinition struct {
|
||||
internalapi.WazeroOnlyType
|
||||
moduleName string
|
||||
index Index
|
||||
importDesc *[2]string
|
||||
exportNames []string
|
||||
memory *Memory
|
||||
}
|
||||
|
||||
// ModuleName implements the same method as documented on api.MemoryDefinition.
|
||||
func (f *MemoryDefinition) ModuleName() string {
|
||||
return f.moduleName
|
||||
}
|
||||
|
||||
// Index implements the same method as documented on api.MemoryDefinition.
|
||||
func (f *MemoryDefinition) Index() uint32 {
|
||||
return f.index
|
||||
}
|
||||
|
||||
// Import implements the same method as documented on api.MemoryDefinition.
|
||||
func (f *MemoryDefinition) Import() (moduleName, name string, isImport bool) {
|
||||
if importDesc := f.importDesc; importDesc != nil {
|
||||
moduleName, name, isImport = importDesc[0], importDesc[1], true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ExportNames implements the same method as documented on api.MemoryDefinition.
|
||||
func (f *MemoryDefinition) ExportNames() []string {
|
||||
return f.exportNames
|
||||
}
|
||||
|
||||
// Min implements the same method as documented on api.MemoryDefinition.
|
||||
func (f *MemoryDefinition) Min() uint32 {
|
||||
return f.memory.Min
|
||||
}
|
||||
|
||||
// Max implements the same method as documented on api.MemoryDefinition.
|
||||
func (f *MemoryDefinition) Max() (max uint32, encoded bool) {
|
||||
max = f.memory.Max
|
||||
encoded = f.memory.IsMaxEncoded
|
||||
return
|
||||
}
|
1083
vendor/github.com/tetratelabs/wazero/internal/wasm/module.go
generated
vendored
Normal file
1083
vendor/github.com/tetratelabs/wazero/internal/wasm/module.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
251
vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance.go
generated
vendored
Normal file
251
vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance.go
generated
vendored
Normal file
@ -0,0 +1,251 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// FailIfClosed returns a sys.ExitError if CloseWithExitCode was called.
|
||||
func (m *ModuleInstance) FailIfClosed() (err error) {
|
||||
if closed := m.Closed.Load(); closed != 0 {
|
||||
switch closed & exitCodeFlagMask {
|
||||
case exitCodeFlagResourceClosed:
|
||||
case exitCodeFlagResourceNotClosed:
|
||||
// This happens when this module is closed asynchronously in CloseModuleOnCanceledOrTimeout,
|
||||
// and the closure of resources have been deferred here.
|
||||
_ = m.ensureResourcesClosed(context.Background())
|
||||
}
|
||||
return sys.NewExitError(uint32(closed >> 32)) // Unpack the high order bits as the exit code.
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseModuleOnCanceledOrTimeout take a context `ctx`, which might be a Cancel or Timeout context,
|
||||
// and spawns the Goroutine to check the context is canceled ot deadline exceeded. If it reaches
|
||||
// one of the conditions, it sets the appropriate exit code.
|
||||
//
|
||||
// Callers of this function must invoke the returned context.CancelFunc to release the spawned Goroutine.
|
||||
func (m *ModuleInstance) CloseModuleOnCanceledOrTimeout(ctx context.Context) context.CancelFunc {
|
||||
// Creating an empty channel in this case is a bit more efficient than
|
||||
// creating a context.Context and canceling it with the same effect. We
|
||||
// really just need to be notified when to stop listening to the users
|
||||
// context. Closing the channel will unblock the select in the goroutine
|
||||
// causing it to return an stop listening to ctx.Done().
|
||||
cancelChan := make(chan struct{})
|
||||
go m.closeModuleOnCanceledOrTimeout(ctx, cancelChan)
|
||||
return func() { close(cancelChan) }
|
||||
}
|
||||
|
||||
// closeModuleOnCanceledOrTimeout is extracted from CloseModuleOnCanceledOrTimeout for testing.
|
||||
func (m *ModuleInstance) closeModuleOnCanceledOrTimeout(ctx context.Context, cancelChan <-chan struct{}) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
select {
|
||||
case <-cancelChan:
|
||||
// In some cases by the time this goroutine is scheduled, the caller
|
||||
// has already closed both the context and the cancelChan. In this
|
||||
// case go will randomize which branch of the outer select to enter
|
||||
// and we don't want to close the module.
|
||||
default:
|
||||
// This is the same logic as CloseWithCtxErr except this calls closeWithExitCodeWithoutClosingResource
|
||||
// so that we can defer the resource closure in FailIfClosed.
|
||||
switch {
|
||||
case errors.Is(ctx.Err(), context.Canceled):
|
||||
// TODO: figure out how to report error here.
|
||||
_ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeContextCanceled)
|
||||
case errors.Is(ctx.Err(), context.DeadlineExceeded):
|
||||
// TODO: figure out how to report error here.
|
||||
_ = m.closeWithExitCodeWithoutClosingResource(sys.ExitCodeDeadlineExceeded)
|
||||
}
|
||||
}
|
||||
case <-cancelChan:
|
||||
}
|
||||
}
|
||||
|
||||
// CloseWithCtxErr closes the module with an exit code based on the type of
|
||||
// error reported by the context.
|
||||
//
|
||||
// If the context's error is unknown or nil, the module does not close.
|
||||
func (m *ModuleInstance) CloseWithCtxErr(ctx context.Context) {
|
||||
switch {
|
||||
case errors.Is(ctx.Err(), context.Canceled):
|
||||
// TODO: figure out how to report error here.
|
||||
_ = m.CloseWithExitCode(ctx, sys.ExitCodeContextCanceled)
|
||||
case errors.Is(ctx.Err(), context.DeadlineExceeded):
|
||||
// TODO: figure out how to report error here.
|
||||
_ = m.CloseWithExitCode(ctx, sys.ExitCodeDeadlineExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
// Name implements the same method as documented on api.Module
|
||||
func (m *ModuleInstance) Name() string {
|
||||
return m.ModuleName
|
||||
}
|
||||
|
||||
// String implements the same method as documented on api.Module
|
||||
func (m *ModuleInstance) String() string {
|
||||
return fmt.Sprintf("Module[%s]", m.Name())
|
||||
}
|
||||
|
||||
// Close implements the same method as documented on api.Module.
|
||||
func (m *ModuleInstance) Close(ctx context.Context) (err error) {
|
||||
return m.CloseWithExitCode(ctx, 0)
|
||||
}
|
||||
|
||||
// CloseWithExitCode implements the same method as documented on api.Module.
|
||||
func (m *ModuleInstance) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) {
|
||||
if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) {
|
||||
return nil // not an error to have already closed
|
||||
}
|
||||
_ = m.s.deleteModule(m)
|
||||
return m.ensureResourcesClosed(ctx)
|
||||
}
|
||||
|
||||
// IsClosed implements the same method as documented on api.Module.
|
||||
func (m *ModuleInstance) IsClosed() bool {
|
||||
return m.Closed.Load() != 0
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) closeWithExitCodeWithoutClosingResource(exitCode uint32) (err error) {
|
||||
if !m.setExitCode(exitCode, exitCodeFlagResourceNotClosed) {
|
||||
return nil // not an error to have already closed
|
||||
}
|
||||
_ = m.s.deleteModule(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// closeWithExitCode is the same as CloseWithExitCode besides this doesn't delete it from Store.moduleList.
|
||||
func (m *ModuleInstance) closeWithExitCode(ctx context.Context, exitCode uint32) (err error) {
|
||||
if !m.setExitCode(exitCode, exitCodeFlagResourceClosed) {
|
||||
return nil // not an error to have already closed
|
||||
}
|
||||
return m.ensureResourcesClosed(ctx)
|
||||
}
|
||||
|
||||
type exitCodeFlag = uint64
|
||||
|
||||
const exitCodeFlagMask = 0xff
|
||||
|
||||
const (
|
||||
// exitCodeFlagResourceClosed indicates that the module was closed and resources were already closed.
|
||||
exitCodeFlagResourceClosed = 1 << iota
|
||||
// exitCodeFlagResourceNotClosed indicates that the module was closed while resources are not closed yet.
|
||||
exitCodeFlagResourceNotClosed
|
||||
)
|
||||
|
||||
func (m *ModuleInstance) setExitCode(exitCode uint32, flag exitCodeFlag) bool {
|
||||
closed := flag | uint64(exitCode)<<32 // Store exitCode as high-order bits.
|
||||
return m.Closed.CompareAndSwap(0, closed)
|
||||
}
|
||||
|
||||
// ensureResourcesClosed ensures that resources assigned to ModuleInstance is released.
|
||||
// Only one call will happen per module, due to external atomic guards on Closed.
|
||||
func (m *ModuleInstance) ensureResourcesClosed(ctx context.Context) (err error) {
|
||||
if closeNotifier := m.CloseNotifier; closeNotifier != nil { // experimental
|
||||
closeNotifier.CloseNotify(ctx, uint32(m.Closed.Load()>>32))
|
||||
m.CloseNotifier = nil
|
||||
}
|
||||
|
||||
if sysCtx := m.Sys; sysCtx != nil { // nil if from HostModuleBuilder
|
||||
err = sysCtx.FS().Close()
|
||||
m.Sys = nil
|
||||
}
|
||||
|
||||
if mem := m.MemoryInstance; mem != nil {
|
||||
if mem.expBuffer != nil {
|
||||
mem.expBuffer.Free()
|
||||
mem.expBuffer = nil
|
||||
}
|
||||
}
|
||||
|
||||
if m.CodeCloser != nil {
|
||||
if e := m.CodeCloser.Close(ctx); err == nil {
|
||||
err = e
|
||||
}
|
||||
m.CodeCloser = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Memory implements the same method as documented on api.Module.
|
||||
func (m *ModuleInstance) Memory() api.Memory {
|
||||
return m.MemoryInstance
|
||||
}
|
||||
|
||||
// ExportedMemory implements the same method as documented on api.Module.
|
||||
func (m *ModuleInstance) ExportedMemory(name string) api.Memory {
|
||||
_, err := m.getExport(name, ExternTypeMemory)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// We Assume that we have at most one memory.
|
||||
return m.MemoryInstance
|
||||
}
|
||||
|
||||
// ExportedMemoryDefinitions implements the same method as documented on
|
||||
// api.Module.
|
||||
func (m *ModuleInstance) ExportedMemoryDefinitions() map[string]api.MemoryDefinition {
|
||||
// Special case as we currently only support one memory.
|
||||
if mem := m.MemoryInstance; mem != nil {
|
||||
// Now, find out if it is exported
|
||||
for name, exp := range m.Exports {
|
||||
if exp.Type == ExternTypeMemory {
|
||||
return map[string]api.MemoryDefinition{name: mem.definition}
|
||||
}
|
||||
}
|
||||
}
|
||||
return map[string]api.MemoryDefinition{}
|
||||
}
|
||||
|
||||
// ExportedFunction implements the same method as documented on api.Module.
|
||||
func (m *ModuleInstance) ExportedFunction(name string) api.Function {
|
||||
exp, err := m.getExport(name, ExternTypeFunc)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return m.Engine.NewFunction(exp.Index)
|
||||
}
|
||||
|
||||
// ExportedFunctionDefinitions implements the same method as documented on
|
||||
// api.Module.
|
||||
func (m *ModuleInstance) ExportedFunctionDefinitions() map[string]api.FunctionDefinition {
|
||||
result := map[string]api.FunctionDefinition{}
|
||||
for name, exp := range m.Exports {
|
||||
if exp.Type == ExternTypeFunc {
|
||||
result[name] = m.Source.FunctionDefinition(exp.Index)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GlobalVal is an internal hack to get the lower 64 bits of a global.
|
||||
func (m *ModuleInstance) GlobalVal(idx Index) uint64 {
|
||||
return m.Globals[idx].Val
|
||||
}
|
||||
|
||||
// ExportedGlobal implements the same method as documented on api.Module.
|
||||
func (m *ModuleInstance) ExportedGlobal(name string) api.Global {
|
||||
exp, err := m.getExport(name, ExternTypeGlobal)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
g := m.Globals[exp.Index]
|
||||
if g.Type.Mutable {
|
||||
return mutableGlobal{g: g}
|
||||
}
|
||||
return constantGlobal{g: g}
|
||||
}
|
||||
|
||||
// NumGlobal implements experimental.InternalModule.
|
||||
func (m *ModuleInstance) NumGlobal() int {
|
||||
return len(m.Globals)
|
||||
}
|
||||
|
||||
// Global implements experimental.InternalModule.
|
||||
func (m *ModuleInstance) Global(idx int) api.Global {
|
||||
return constantGlobal{g: m.Globals[idx]}
|
||||
}
|
73
vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance_lookup.go
generated
vendored
Normal file
73
vendor/github.com/tetratelabs/wazero/internal/wasm/module_instance_lookup.go
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
)
|
||||
|
||||
// LookupFunction looks up the table by the given index, and returns the api.Function implementation if found,
|
||||
// otherwise this panics according to the same semantics as call_indirect instruction.
|
||||
// Currently, this is only used by emscripten which needs to do call_indirect-like operation in the host function.
|
||||
func (m *ModuleInstance) LookupFunction(t *TableInstance, typeId FunctionTypeID, tableOffset Index) api.Function {
|
||||
fm, index := m.Engine.LookupFunction(t, typeId, tableOffset)
|
||||
if source := fm.Source; source.IsHostModule {
|
||||
// This case, the found function is a host function stored in the table. Generally, Engine.NewFunction are only
|
||||
// responsible for calling Wasm-defined functions (not designed for calling Go functions!). Hence we need to wrap
|
||||
// the host function as a special case.
|
||||
def := &source.FunctionDefinitionSection[index]
|
||||
goF := source.CodeSection[index].GoFunc
|
||||
switch typed := goF.(type) {
|
||||
case api.GoFunction:
|
||||
// GoFunction doesn't need looked up module.
|
||||
return &lookedUpGoFunction{def: def, g: goFunctionAsGoModuleFunction(typed)}
|
||||
case api.GoModuleFunction:
|
||||
return &lookedUpGoFunction{def: def, lookedUpModule: m, g: typed}
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected GoFunc type: %T", goF))
|
||||
}
|
||||
} else {
|
||||
return fm.Engine.NewFunction(index)
|
||||
}
|
||||
}
|
||||
|
||||
// lookedUpGoFunction implements lookedUpGoModuleFunction.
|
||||
type lookedUpGoFunction struct {
|
||||
internalapi.WazeroOnly
|
||||
def *FunctionDefinition
|
||||
// lookedUpModule is the *ModuleInstance from which this Go function is looked up, i.e. owner of the table.
|
||||
lookedUpModule *ModuleInstance
|
||||
g api.GoModuleFunction
|
||||
}
|
||||
|
||||
// goFunctionAsGoModuleFunction converts api.GoFunction to api.GoModuleFunction which ignores the api.Module argument.
|
||||
func goFunctionAsGoModuleFunction(g api.GoFunction) api.GoModuleFunction {
|
||||
return api.GoModuleFunc(func(ctx context.Context, _ api.Module, stack []uint64) {
|
||||
g.Call(ctx, stack)
|
||||
})
|
||||
}
|
||||
|
||||
// Definition implements api.Function.
|
||||
func (l *lookedUpGoFunction) Definition() api.FunctionDefinition { return l.def }
|
||||
|
||||
// Call implements api.Function.
|
||||
func (l *lookedUpGoFunction) Call(ctx context.Context, params ...uint64) ([]uint64, error) {
|
||||
typ := l.def.Functype
|
||||
stackSize := typ.ParamNumInUint64
|
||||
rn := typ.ResultNumInUint64
|
||||
if rn > stackSize {
|
||||
stackSize = rn
|
||||
}
|
||||
stack := make([]uint64, stackSize)
|
||||
copy(stack, params)
|
||||
return stack[:rn], l.CallWithStack(ctx, stack)
|
||||
}
|
||||
|
||||
// CallWithStack implements api.Function.
|
||||
func (l *lookedUpGoFunction) CallWithStack(ctx context.Context, stack []uint64) error {
|
||||
// The Go host function always needs to access caller's module, in this case the one holding the table.
|
||||
l.g.Call(ctx, l.lookedUpModule, stack)
|
||||
return nil
|
||||
}
|
668
vendor/github.com/tetratelabs/wazero/internal/wasm/store.go
generated
vendored
Normal file
668
vendor/github.com/tetratelabs/wazero/internal/wasm/store.go
generated
vendored
Normal file
@ -0,0 +1,668 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/expctxkeys"
|
||||
"github.com/tetratelabs/wazero/internal/internalapi"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
// nameToModuleShrinkThreshold is the size the nameToModule map can grow to
|
||||
// before it starts to be monitored for shrinking.
|
||||
// The capacity will never be smaller than this once the threshold is met.
|
||||
const nameToModuleShrinkThreshold = 100
|
||||
|
||||
type (
|
||||
// Store is the runtime representation of "instantiated" Wasm module and objects.
|
||||
// Multiple modules can be instantiated within a single store, and each instance,
|
||||
// (e.g. function instance) can be referenced by other module instances in a Store via Module.ImportSection.
|
||||
//
|
||||
// Every type whose name ends with "Instance" suffix belongs to exactly one store.
|
||||
//
|
||||
// Note that store is not thread (concurrency) safe, meaning that using single Store
|
||||
// via multiple goroutines might result in race conditions. In that case, the invocation
|
||||
// and access to any methods and field of Store must be guarded by mutex.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0
|
||||
Store struct {
|
||||
// moduleList ensures modules are closed in reverse initialization order.
|
||||
moduleList *ModuleInstance // guarded by mux
|
||||
|
||||
// nameToModule holds the instantiated Wasm modules by module name from Instantiate.
|
||||
// It ensures no race conditions instantiating two modules of the same name.
|
||||
nameToModule map[string]*ModuleInstance // guarded by mux
|
||||
|
||||
// nameToModuleCap tracks the growth of the nameToModule map in order to
|
||||
// track when to shrink it.
|
||||
nameToModuleCap int // guarded by mux
|
||||
|
||||
// EnabledFeatures are read-only to allow optimizations.
|
||||
EnabledFeatures api.CoreFeatures
|
||||
|
||||
// Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules.
|
||||
Engine Engine
|
||||
|
||||
// typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to
|
||||
// do type-checks on indirect function calls.
|
||||
typeIDs map[string]FunctionTypeID
|
||||
|
||||
// functionMaxTypes represents the limit on the number of function types in a store.
|
||||
// Note: this is fixed to 2^27 but have this a field for testability.
|
||||
functionMaxTypes uint32
|
||||
|
||||
// mux is used to guard the fields from concurrent access.
|
||||
mux sync.RWMutex
|
||||
}
|
||||
|
||||
// ModuleInstance represents instantiated wasm module.
|
||||
// The difference from the spec is that in wazero, a ModuleInstance holds pointers
|
||||
// to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst
|
||||
//
|
||||
// This implements api.Module.
|
||||
ModuleInstance struct {
|
||||
internalapi.WazeroOnlyType
|
||||
|
||||
ModuleName string
|
||||
Exports map[string]*Export
|
||||
Globals []*GlobalInstance
|
||||
MemoryInstance *MemoryInstance
|
||||
Tables []*TableInstance
|
||||
|
||||
// Engine implements function calls for this module.
|
||||
Engine ModuleEngine
|
||||
|
||||
// TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store.
|
||||
// This is necessary to achieve fast runtime type checking for indirect function calls at runtime.
|
||||
TypeIDs []FunctionTypeID
|
||||
|
||||
// DataInstances holds data segments bytes of the module.
|
||||
// This is only used by bulk memory operations.
|
||||
//
|
||||
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
|
||||
DataInstances []DataInstance
|
||||
|
||||
// ElementInstances holds the element instance, and each holds the references to either functions
|
||||
// or external objects (unimplemented).
|
||||
ElementInstances []ElementInstance
|
||||
|
||||
// Sys is exposed for use in special imports such as WASI, assemblyscript.
|
||||
//
|
||||
// # Notes
|
||||
//
|
||||
// - This is a part of ModuleInstance so that scope and Close is coherent.
|
||||
// - This is not exposed outside this repository (as a host function
|
||||
// parameter) because we haven't thought through capabilities based
|
||||
// security implications.
|
||||
Sys *internalsys.Context
|
||||
|
||||
// Closed is used both to guard moduleEngine.CloseWithExitCode and to store the exit code.
|
||||
//
|
||||
// The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed.
|
||||
//
|
||||
// Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations.
|
||||
// See /RATIONALE.md
|
||||
Closed atomic.Uint64
|
||||
|
||||
// CodeCloser is non-nil when the code should be closed after this module.
|
||||
CodeCloser api.Closer
|
||||
|
||||
// s is the Store on which this module is instantiated.
|
||||
s *Store
|
||||
// prev and next hold the nodes in the linked list of ModuleInstance held by Store.
|
||||
prev, next *ModuleInstance
|
||||
// Source is a pointer to the Module from which this ModuleInstance derives.
|
||||
Source *Module
|
||||
|
||||
// CloseNotifier is an experimental hook called once on close.
|
||||
CloseNotifier experimental.CloseNotifier
|
||||
}
|
||||
|
||||
// DataInstance holds bytes corresponding to the data segment in a module.
|
||||
//
|
||||
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances
|
||||
DataInstance = []byte
|
||||
|
||||
// GlobalInstance represents a global instance in a store.
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0
|
||||
GlobalInstance struct {
|
||||
Type GlobalType
|
||||
// Val holds a 64-bit representation of the actual value.
|
||||
// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
|
||||
Val uint64
|
||||
// ValHi is only used for vector type globals, and holds the higher bits of the vector.
|
||||
// If me is non-nil, the value will not be updated and the current value is stored in the module engine.
|
||||
ValHi uint64
|
||||
// Me is the module engine that owns this global instance.
|
||||
// The .Val and .ValHi fields are only valid when me is nil.
|
||||
// If me is non-nil, the value is stored in the module engine.
|
||||
Me ModuleEngine
|
||||
Index Index
|
||||
}
|
||||
|
||||
// FunctionTypeID is a uniquely assigned integer for a function type.
|
||||
// This is wazero specific runtime object and specific to a store,
|
||||
// and used at runtime to do type-checks on indirect function calls.
|
||||
FunctionTypeID uint32
|
||||
)
|
||||
|
||||
// The wazero specific limitations described at RATIONALE.md.
|
||||
const maximumFunctionTypes = 1 << 27
|
||||
|
||||
// GetFunctionTypeID is used by emscripten.
|
||||
func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) FunctionTypeID {
|
||||
id, err := m.s.GetFunctionTypeID(t)
|
||||
if err != nil {
|
||||
// This is not recoverable in practice since the only error GetFunctionTypeID returns is
|
||||
// when there's too many function types in the store.
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) {
|
||||
m.ElementInstances = make([][]Reference, len(elements))
|
||||
for i, elm := range elements {
|
||||
if elm.Type == RefTypeFuncref && elm.Mode == ElementModePassive {
|
||||
// Only passive elements can be access as element instances.
|
||||
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
|
||||
inits := elm.Init
|
||||
inst := make([]Reference, len(inits))
|
||||
m.ElementInstances[i] = inst
|
||||
for j, idx := range inits {
|
||||
if index, ok := unwrapElementInitGlobalReference(idx); ok {
|
||||
global := m.Globals[index]
|
||||
inst[j] = Reference(global.Val)
|
||||
} else {
|
||||
if idx != ElementInitNullReference {
|
||||
inst[j] = m.Engine.FunctionInstanceReference(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) applyElements(elems []ElementSegment) {
|
||||
for elemI := range elems {
|
||||
elem := &elems[elemI]
|
||||
if !elem.IsActive() ||
|
||||
// Per https://github.com/WebAssembly/spec/issues/1427 init can be no-op.
|
||||
len(elem.Init) == 0 {
|
||||
continue
|
||||
}
|
||||
var offset uint32
|
||||
if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
|
||||
// Ignore error as it's already validated.
|
||||
globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
|
||||
global := m.Globals[globalIdx]
|
||||
offset = uint32(global.Val)
|
||||
} else {
|
||||
// Ignore error as it's already validated.
|
||||
o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data)
|
||||
offset = uint32(o)
|
||||
}
|
||||
|
||||
table := m.Tables[elem.TableIndex]
|
||||
references := table.References
|
||||
if int(offset)+len(elem.Init) > len(references) {
|
||||
// ErrElementOffsetOutOfBounds is the error raised when the active element offset exceeds the table length.
|
||||
// Before CoreFeatureReferenceTypes, this was checked statically before instantiation, after the proposal,
|
||||
// this must be raised as runtime error (as in assert_trap in spectest), not even an instantiation error.
|
||||
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274
|
||||
//
|
||||
// In wazero, we ignore it since in any way, the instantiated module and engines are fine and can be used
|
||||
// for function invocations.
|
||||
return
|
||||
}
|
||||
|
||||
if table.Type == RefTypeExternref {
|
||||
for i := 0; i < len(elem.Init); i++ {
|
||||
references[offset+uint32(i)] = Reference(0)
|
||||
}
|
||||
} else {
|
||||
for i, init := range elem.Init {
|
||||
if init == ElementInitNullReference {
|
||||
continue
|
||||
}
|
||||
|
||||
var ref Reference
|
||||
if index, ok := unwrapElementInitGlobalReference(init); ok {
|
||||
global := m.Globals[index]
|
||||
ref = Reference(global.Val)
|
||||
} else {
|
||||
ref = m.Engine.FunctionInstanceReference(index)
|
||||
}
|
||||
references[offset+uint32(i)] = ref
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validateData ensures that data segments are valid in terms of memory boundary.
|
||||
// Note: this is used only when bulk-memory/reference type feature is disabled.
|
||||
func (m *ModuleInstance) validateData(data []DataSegment) (err error) {
|
||||
for i := range data {
|
||||
d := &data[i]
|
||||
if !d.IsPassive() {
|
||||
offset := int(executeConstExpressionI32(m.Globals, &d.OffsetExpression))
|
||||
ceil := offset + len(d.Init)
|
||||
if offset < 0 || ceil > len(m.MemoryInstance.Buffer) {
|
||||
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// applyData uses the given data segments and mutate the memory according to the initial contents on it
|
||||
// and populate the `DataInstances`. This is called after all the validation phase passes and out of
|
||||
// bounds memory access error here is not a validation error, but rather a runtime error.
|
||||
func (m *ModuleInstance) applyData(data []DataSegment) error {
|
||||
m.DataInstances = make([][]byte, len(data))
|
||||
for i := range data {
|
||||
d := &data[i]
|
||||
m.DataInstances[i] = d.Init
|
||||
if !d.IsPassive() {
|
||||
offset := executeConstExpressionI32(m.Globals, &d.OffsetExpression)
|
||||
if offset < 0 || int(offset)+len(d.Init) > len(m.MemoryInstance.Buffer) {
|
||||
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
|
||||
}
|
||||
copy(m.MemoryInstance.Buffer[offset:], d.Init)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExport returns an export of the given name and type or errs if not exported or the wrong type.
|
||||
func (m *ModuleInstance) getExport(name string, et ExternType) (*Export, error) {
|
||||
exp, ok := m.Exports[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%q is not exported in module %q", name, m.ModuleName)
|
||||
}
|
||||
if exp.Type != et {
|
||||
return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.ModuleName, ExternTypeName(exp.Type), ExternTypeName(et))
|
||||
}
|
||||
return exp, nil
|
||||
}
|
||||
|
||||
func NewStore(enabledFeatures api.CoreFeatures, engine Engine) *Store {
|
||||
return &Store{
|
||||
nameToModule: map[string]*ModuleInstance{},
|
||||
nameToModuleCap: nameToModuleShrinkThreshold,
|
||||
EnabledFeatures: enabledFeatures,
|
||||
Engine: engine,
|
||||
typeIDs: map[string]FunctionTypeID{},
|
||||
functionMaxTypes: maximumFunctionTypes,
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under
|
||||
// different names safely and concurrently.
|
||||
//
|
||||
// * ctx: the default context used for function calls.
|
||||
// * name: the name of the module.
|
||||
// * sys: the system context, which will be closed (SysContext.Close) on ModuleInstance.Close.
|
||||
//
|
||||
// Note: Module.Validate must be called prior to instantiation.
|
||||
func (s *Store) Instantiate(
|
||||
ctx context.Context,
|
||||
module *Module,
|
||||
name string,
|
||||
sys *internalsys.Context,
|
||||
typeIDs []FunctionTypeID,
|
||||
) (*ModuleInstance, error) {
|
||||
// Instantiate the module and add it to the store so that other modules can import it.
|
||||
m, err := s.instantiate(ctx, module, name, sys, typeIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Now that the instantiation is complete without error, add it.
|
||||
if err = s.registerModule(m); err != nil {
|
||||
_ = m.Close(ctx)
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (s *Store) instantiate(
|
||||
ctx context.Context,
|
||||
module *Module,
|
||||
name string,
|
||||
sysCtx *internalsys.Context,
|
||||
typeIDs []FunctionTypeID,
|
||||
) (m *ModuleInstance, err error) {
|
||||
m = &ModuleInstance{ModuleName: name, TypeIDs: typeIDs, Sys: sysCtx, s: s, Source: module}
|
||||
|
||||
m.Tables = make([]*TableInstance, int(module.ImportTableCount)+len(module.TableSection))
|
||||
m.Globals = make([]*GlobalInstance, int(module.ImportGlobalCount)+len(module.GlobalSection))
|
||||
m.Engine, err = s.Engine.NewModuleEngine(module, m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = m.resolveImports(module); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = m.buildTables(module,
|
||||
// As of reference-types proposal, boundary check must be done after instantiation.
|
||||
s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allocator, _ := ctx.Value(expctxkeys.MemoryAllocatorKey{}).(experimental.MemoryAllocator)
|
||||
|
||||
m.buildGlobals(module, m.Engine.FunctionInstanceReference)
|
||||
m.buildMemory(module, allocator)
|
||||
m.Exports = module.Exports
|
||||
for _, exp := range m.Exports {
|
||||
if exp.Type == ExternTypeTable {
|
||||
t := m.Tables[exp.Index]
|
||||
t.involvingModuleInstances = append(t.involvingModuleInstances, m)
|
||||
}
|
||||
}
|
||||
|
||||
// As of reference types proposal, data segment validation must happen after instantiation,
|
||||
// and the side effect must persist even if there's out of bounds error after instantiation.
|
||||
// https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L395-L405
|
||||
if !s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) {
|
||||
if err = m.validateData(module.DataSection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// After engine creation, we can create the funcref element instances and initialize funcref type globals.
|
||||
m.buildElementInstances(module.ElementSection)
|
||||
|
||||
// Now all the validation passes, we are safe to mutate memory instances (possibly imported ones).
|
||||
if err = m.applyData(module.DataSection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.applyElements(module.ElementSection)
|
||||
|
||||
m.Engine.DoneInstantiation()
|
||||
|
||||
// Execute the start function.
|
||||
if module.StartSection != nil {
|
||||
funcIdx := *module.StartSection
|
||||
ce := m.Engine.NewFunction(funcIdx)
|
||||
_, err = ce.Call(ctx)
|
||||
if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error!
|
||||
return nil, exitErr
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(SectionIDFunction, funcIdx), err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) resolveImports(module *Module) (err error) {
|
||||
for moduleName, imports := range module.ImportPerModule {
|
||||
var importedModule *ModuleInstance
|
||||
importedModule, err = m.s.module(moduleName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, i := range imports {
|
||||
var imported *Export
|
||||
imported, err = importedModule.getExport(i.Name, i.Type)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch i.Type {
|
||||
case ExternTypeFunc:
|
||||
expectedType := &module.TypeSection[i.DescFunc]
|
||||
src := importedModule.Source
|
||||
actual := src.typeOfFunction(imported.Index)
|
||||
if !actual.EqualsSignature(expectedType.Params, expectedType.Results) {
|
||||
err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual))
|
||||
return
|
||||
}
|
||||
|
||||
m.Engine.ResolveImportedFunction(i.IndexPerType, imported.Index, importedModule.Engine)
|
||||
case ExternTypeTable:
|
||||
expected := i.DescTable
|
||||
importedTable := importedModule.Tables[imported.Index]
|
||||
if expected.Type != importedTable.Type {
|
||||
err = errorInvalidImport(i, fmt.Errorf("table type mismatch: %s != %s",
|
||||
RefTypeName(expected.Type), RefTypeName(importedTable.Type)))
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Min > importedTable.Min {
|
||||
err = errorMinSizeMismatch(i, expected.Min, importedTable.Min)
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Max != nil {
|
||||
expectedMax := *expected.Max
|
||||
if importedTable.Max == nil {
|
||||
err = errorNoMax(i, expectedMax)
|
||||
return
|
||||
} else if expectedMax < *importedTable.Max {
|
||||
err = errorMaxSizeMismatch(i, expectedMax, *importedTable.Max)
|
||||
return
|
||||
}
|
||||
}
|
||||
m.Tables[i.IndexPerType] = importedTable
|
||||
importedTable.involvingModuleInstancesMutex.Lock()
|
||||
if len(importedTable.involvingModuleInstances) == 0 {
|
||||
panic("BUG: involvingModuleInstances must not be nil when it's imported")
|
||||
}
|
||||
importedTable.involvingModuleInstances = append(importedTable.involvingModuleInstances, m)
|
||||
importedTable.involvingModuleInstancesMutex.Unlock()
|
||||
case ExternTypeMemory:
|
||||
expected := i.DescMem
|
||||
importedMemory := importedModule.MemoryInstance
|
||||
|
||||
if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) {
|
||||
err = errorMinSizeMismatch(i, expected.Min, importedMemory.Min)
|
||||
return
|
||||
}
|
||||
|
||||
if expected.Max < importedMemory.Max {
|
||||
err = errorMaxSizeMismatch(i, expected.Max, importedMemory.Max)
|
||||
return
|
||||
}
|
||||
m.MemoryInstance = importedMemory
|
||||
m.Engine.ResolveImportedMemory(importedModule.Engine)
|
||||
case ExternTypeGlobal:
|
||||
expected := i.DescGlobal
|
||||
importedGlobal := importedModule.Globals[imported.Index]
|
||||
|
||||
if expected.Mutable != importedGlobal.Type.Mutable {
|
||||
err = errorInvalidImport(i, fmt.Errorf("mutability mismatch: %t != %t",
|
||||
expected.Mutable, importedGlobal.Type.Mutable))
|
||||
return
|
||||
}
|
||||
|
||||
if expected.ValType != importedGlobal.Type.ValType {
|
||||
err = errorInvalidImport(i, fmt.Errorf("value type mismatch: %s != %s",
|
||||
ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType)))
|
||||
return
|
||||
}
|
||||
m.Globals[i.IndexPerType] = importedGlobal
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func errorMinSizeMismatch(i *Import, expected, actual uint32) error {
|
||||
return errorInvalidImport(i, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual))
|
||||
}
|
||||
|
||||
func errorNoMax(i *Import, expected uint32) error {
|
||||
return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected))
|
||||
}
|
||||
|
||||
func errorMaxSizeMismatch(i *Import, expected, actual uint32) error {
|
||||
return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual))
|
||||
}
|
||||
|
||||
func errorInvalidImport(i *Import, err error) error {
|
||||
return fmt.Errorf("import %s[%s.%s]: %w", ExternTypeName(i.Type), i.Module, i.Name, err)
|
||||
}
|
||||
|
||||
// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32.
|
||||
// The validity of the expression is ensured when calling this function as this is only called
|
||||
// during instantiation phrase, and the validation happens in compilation (validateConstExpression).
|
||||
func executeConstExpressionI32(importedGlobals []*GlobalInstance, expr *ConstantExpression) (ret int32) {
|
||||
switch expr.Opcode {
|
||||
case OpcodeI32Const:
|
||||
ret, _, _ = leb128.LoadInt32(expr.Data)
|
||||
case OpcodeGlobalGet:
|
||||
id, _, _ := leb128.LoadUint32(expr.Data)
|
||||
g := importedGlobals[id]
|
||||
ret = int32(g.Val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initialize initializes the value of this global instance given the const expr and imported globals.
|
||||
// funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr.
|
||||
//
|
||||
// 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
|
||||
func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) {
|
||||
switch expr.Opcode {
|
||||
case OpcodeI32Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ := leb128.LoadInt32(expr.Data)
|
||||
g.Val = uint64(uint32(v))
|
||||
case OpcodeI64Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ := leb128.LoadInt64(expr.Data)
|
||||
g.Val = uint64(v)
|
||||
case OpcodeF32Const:
|
||||
g.Val = uint64(binary.LittleEndian.Uint32(expr.Data))
|
||||
case OpcodeF64Const:
|
||||
g.Val = binary.LittleEndian.Uint64(expr.Data)
|
||||
case OpcodeGlobalGet:
|
||||
id, _, _ := leb128.LoadUint32(expr.Data)
|
||||
importedG := importedGlobals[id]
|
||||
switch importedG.Type.ValType {
|
||||
case ValueTypeI32:
|
||||
g.Val = uint64(uint32(importedG.Val))
|
||||
case ValueTypeI64:
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeF32:
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeF64:
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeV128:
|
||||
g.Val, g.ValHi = importedG.Val, importedG.ValHi
|
||||
case ValueTypeFuncref, ValueTypeExternref:
|
||||
g.Val = importedG.Val
|
||||
}
|
||||
case OpcodeRefNull:
|
||||
switch expr.Data[0] {
|
||||
case ValueTypeExternref, ValueTypeFuncref:
|
||||
g.Val = 0 // Reference types are opaque 64bit pointer at runtime.
|
||||
}
|
||||
case OpcodeRefFunc:
|
||||
v, _, _ := leb128.LoadUint32(expr.Data)
|
||||
g.Val = uint64(funcRefResolver(v))
|
||||
case OpcodeVecV128Const:
|
||||
g.Val, g.ValHi = binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])
|
||||
}
|
||||
}
|
||||
|
||||
// String implements api.Global.
|
||||
func (g *GlobalInstance) String() string {
|
||||
switch g.Type.ValType {
|
||||
case ValueTypeI32, ValueTypeI64:
|
||||
return fmt.Sprintf("global(%d)", g.Val)
|
||||
case ValueTypeF32:
|
||||
return fmt.Sprintf("global(%f)", api.DecodeF32(g.Val))
|
||||
case ValueTypeF64:
|
||||
return fmt.Sprintf("global(%f)", api.DecodeF64(g.Val))
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: unknown value type %X", g.Type.ValType))
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GlobalInstance) Value() (uint64, uint64) {
|
||||
if g.Me != nil {
|
||||
return g.Me.GetGlobalValue(g.Index)
|
||||
}
|
||||
return g.Val, g.ValHi
|
||||
}
|
||||
|
||||
func (g *GlobalInstance) SetValue(lo, hi uint64) {
|
||||
if g.Me != nil {
|
||||
g.Me.SetGlobalValue(g.Index, lo, hi)
|
||||
} else {
|
||||
g.Val, g.ValHi = lo, hi
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) {
|
||||
ret := make([]FunctionTypeID, len(ts))
|
||||
for i := range ts {
|
||||
t := &ts[i]
|
||||
inst, err := s.GetFunctionTypeID(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret[i] = inst
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) {
|
||||
s.mux.RLock()
|
||||
key := t.key()
|
||||
id, ok := s.typeIDs[key]
|
||||
s.mux.RUnlock()
|
||||
if !ok {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
// Check again in case another goroutine has already added the type.
|
||||
if id, ok = s.typeIDs[key]; ok {
|
||||
return id, nil
|
||||
}
|
||||
l := len(s.typeIDs)
|
||||
if uint32(l) >= s.functionMaxTypes {
|
||||
return 0, fmt.Errorf("too many function types in a store")
|
||||
}
|
||||
id = FunctionTypeID(l)
|
||||
s.typeIDs[key] = id
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// CloseWithExitCode implements the same method as documented on wazero.Runtime.
|
||||
func (s *Store) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
// Close modules in reverse initialization order.
|
||||
for m := s.moduleList; m != nil; m = m.next {
|
||||
// If closing this module errs, proceed anyway to close the others.
|
||||
if e := m.closeWithExitCode(ctx, exitCode); e != nil && err == nil {
|
||||
// TODO: use multiple errors handling in Go 1.20.
|
||||
err = e // first error
|
||||
}
|
||||
}
|
||||
s.moduleList = nil
|
||||
s.nameToModule = nil
|
||||
s.nameToModuleCap = 0
|
||||
s.typeIDs = nil
|
||||
return
|
||||
}
|
97
vendor/github.com/tetratelabs/wazero/internal/wasm/store_module_list.go
generated
vendored
Normal file
97
vendor/github.com/tetratelabs/wazero/internal/wasm/store_module_list.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
)
|
||||
|
||||
// deleteModule makes the moduleName available for instantiation again.
|
||||
func (s *Store) deleteModule(m *ModuleInstance) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
// Remove this module name.
|
||||
if m.prev != nil {
|
||||
m.prev.next = m.next
|
||||
}
|
||||
if m.next != nil {
|
||||
m.next.prev = m.prev
|
||||
}
|
||||
if s.moduleList == m {
|
||||
s.moduleList = m.next
|
||||
}
|
||||
// Clear the m state so it does not enter any other branch
|
||||
// on subsequent calls to deleteModule.
|
||||
m.prev = nil
|
||||
m.next = nil
|
||||
|
||||
if m.ModuleName != "" {
|
||||
delete(s.nameToModule, m.ModuleName)
|
||||
|
||||
// Shrink the map if it's allocated more than twice the size of the list
|
||||
newCap := len(s.nameToModule)
|
||||
if newCap < nameToModuleShrinkThreshold {
|
||||
newCap = nameToModuleShrinkThreshold
|
||||
}
|
||||
if newCap*2 <= s.nameToModuleCap {
|
||||
nameToModule := make(map[string]*ModuleInstance, newCap)
|
||||
for k, v := range s.nameToModule {
|
||||
nameToModule[k] = v
|
||||
}
|
||||
s.nameToModule = nameToModule
|
||||
s.nameToModuleCap = newCap
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// module returns the module of the given name or error if not in this store
|
||||
func (s *Store) module(moduleName string) (*ModuleInstance, error) {
|
||||
s.mux.RLock()
|
||||
defer s.mux.RUnlock()
|
||||
m, ok := s.nameToModule[moduleName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("module[%s] not instantiated", moduleName)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// registerModule registers a ModuleInstance into the store.
|
||||
// This makes the ModuleInstance visible for import if it's not anonymous, and ensures it is closed when the store is.
|
||||
func (s *Store) registerModule(m *ModuleInstance) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
if s.nameToModule == nil {
|
||||
return errors.New("already closed")
|
||||
}
|
||||
|
||||
if m.ModuleName != "" {
|
||||
if _, ok := s.nameToModule[m.ModuleName]; ok {
|
||||
return fmt.Errorf("module[%s] has already been instantiated", m.ModuleName)
|
||||
}
|
||||
s.nameToModule[m.ModuleName] = m
|
||||
if len(s.nameToModule) > s.nameToModuleCap {
|
||||
s.nameToModuleCap = len(s.nameToModule)
|
||||
}
|
||||
}
|
||||
|
||||
// Add the newest node to the moduleNamesList as the head.
|
||||
m.next = s.moduleList
|
||||
if m.next != nil {
|
||||
m.next.prev = m
|
||||
}
|
||||
s.moduleList = m
|
||||
return nil
|
||||
}
|
||||
|
||||
// Module implements wazero.Runtime Module
|
||||
func (s *Store) Module(moduleName string) api.Module {
|
||||
m, err := s.module(moduleName)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return m
|
||||
}
|
339
vendor/github.com/tetratelabs/wazero/internal/wasm/table.go
generated
vendored
Normal file
339
vendor/github.com/tetratelabs/wazero/internal/wasm/table.go
generated
vendored
Normal file
@ -0,0 +1,339 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
)
|
||||
|
||||
// Table describes the limits of elements and its type in a table.
|
||||
type Table struct {
|
||||
Min uint32
|
||||
Max *uint32
|
||||
Type RefType
|
||||
}
|
||||
|
||||
// RefType is either RefTypeFuncref or RefTypeExternref as of WebAssembly core 2.0.
|
||||
type RefType = byte
|
||||
|
||||
const (
|
||||
// RefTypeFuncref represents a reference to a function.
|
||||
RefTypeFuncref = ValueTypeFuncref
|
||||
// RefTypeExternref represents a reference to a host object, which is not currently supported in wazero.
|
||||
RefTypeExternref = ValueTypeExternref
|
||||
)
|
||||
|
||||
func RefTypeName(t RefType) (ret string) {
|
||||
switch t {
|
||||
case RefTypeFuncref:
|
||||
ret = "funcref"
|
||||
case RefTypeExternref:
|
||||
ret = "externref"
|
||||
default:
|
||||
ret = fmt.Sprintf("unknown(0x%x)", t)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ElementMode represents a mode of element segment which is either active, passive or declarative.
|
||||
//
|
||||
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments
|
||||
type ElementMode = byte
|
||||
|
||||
const (
|
||||
// ElementModeActive is the mode which requires the runtime to initialize table with the contents in .Init field combined with OffsetExpr.
|
||||
ElementModeActive ElementMode = iota
|
||||
// ElementModePassive is the mode which doesn't require the runtime to initialize table, and only used with OpcodeTableInitName.
|
||||
ElementModePassive
|
||||
// ElementModeDeclarative is introduced in reference-types proposal which can be used to declare function indexes used by OpcodeRefFunc.
|
||||
ElementModeDeclarative
|
||||
)
|
||||
|
||||
// ElementSegment are initialization instructions for a TableInstance
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-elem
|
||||
type ElementSegment struct {
|
||||
// OffsetExpr returns the table element offset to apply to Init indices.
|
||||
// Note: This can be validated prior to instantiation unless it includes OpcodeGlobalGet (an imported global).
|
||||
OffsetExpr ConstantExpression
|
||||
|
||||
// TableIndex is the table's index to which this element segment is applied.
|
||||
// Note: This is used if and only if the Mode is active.
|
||||
TableIndex Index
|
||||
|
||||
// Followings are set/used regardless of the Mode.
|
||||
|
||||
// Init indices are (nullable) table elements where each index is the function index by which the module initialize the table.
|
||||
Init []Index
|
||||
|
||||
// Type holds the type of this element segment, which is the RefType in WebAssembly 2.0.
|
||||
Type RefType
|
||||
|
||||
// Mode is the mode of this element segment.
|
||||
Mode ElementMode
|
||||
}
|
||||
|
||||
const (
|
||||
// ElementInitNullReference represents the null reference in ElementSegment's Init.
|
||||
// In Wasm spec, an init item represents either Function's Index or null reference,
|
||||
// and in wazero, we limit the maximum number of functions available in a module to
|
||||
// MaximumFunctionIndex. Therefore, it is safe to use 1 << 31 to represent the null
|
||||
// reference in Element segments.
|
||||
ElementInitNullReference Index = 1 << 31
|
||||
// elementInitImportedGlobalReferenceType represents an init item which is resolved via an imported global constexpr.
|
||||
// The actual function reference stored at Global is only known at instantiation-time, so we set this flag
|
||||
// to items of ElementSegment.Init at binary decoding, and unwrap this flag at instantiation to resolve the value.
|
||||
//
|
||||
// This might collide the init element resolved via ref.func instruction which is resolved with the func index at decoding,
|
||||
// but in practice, that is not allowed in wazero thanks to our limit MaximumFunctionIndex. Thus, it is safe to set this flag
|
||||
// in init element to indicate as such.
|
||||
elementInitImportedGlobalReferenceType Index = 1 << 30
|
||||
)
|
||||
|
||||
// unwrapElementInitGlobalReference takes an item of the init vector of an ElementSegment,
|
||||
// and returns the Global index if it is supposed to get generated from a global.
|
||||
// ok is true if the given init item is as such.
|
||||
func unwrapElementInitGlobalReference(init Index) (_ Index, ok bool) {
|
||||
if init&elementInitImportedGlobalReferenceType == elementInitImportedGlobalReferenceType {
|
||||
return init &^ elementInitImportedGlobalReferenceType, true
|
||||
}
|
||||
return init, false
|
||||
}
|
||||
|
||||
// WrapGlobalIndexAsElementInit wraps the given index as an init item which is resolved via an imported global value.
|
||||
// See the comments on elementInitImportedGlobalReferenceType for more details.
|
||||
func WrapGlobalIndexAsElementInit(init Index) Index {
|
||||
return init | elementInitImportedGlobalReferenceType
|
||||
}
|
||||
|
||||
// IsActive returns true if the element segment is "active" mode which requires the runtime to initialize table
|
||||
// with the contents in .Init field.
|
||||
func (e *ElementSegment) IsActive() bool {
|
||||
return e.Mode == ElementModeActive
|
||||
}
|
||||
|
||||
// TableInstance represents a table of (RefTypeFuncref) elements in a module.
|
||||
//
|
||||
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#table-instances%E2%91%A0
|
||||
type TableInstance struct {
|
||||
// References holds references whose type is either RefTypeFuncref or RefTypeExternref (unsupported).
|
||||
//
|
||||
// Currently, only function references are supported.
|
||||
References []Reference
|
||||
|
||||
// Min is the minimum (function) elements in this table and cannot grow to accommodate ElementSegment.
|
||||
Min uint32
|
||||
|
||||
// Max if present is the maximum (function) elements in this table, or nil if unbounded.
|
||||
Max *uint32
|
||||
|
||||
// Type is either RefTypeFuncref or RefTypeExternRef.
|
||||
Type RefType
|
||||
|
||||
// The following is only used when the table is exported.
|
||||
|
||||
// involvingModuleInstances is a set of module instances which are involved in the table instance.
|
||||
// This is critical for safety purpose because once a table is imported, it can hold any reference to
|
||||
// any function in the owner and importing module instances. Therefore, these module instance,
|
||||
// transitively the compiled modules, must be alive as long as the table instance is alive.
|
||||
involvingModuleInstances []*ModuleInstance
|
||||
// involvingModuleInstancesMutex is a mutex to protect involvingModuleInstances.
|
||||
involvingModuleInstancesMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// ElementInstance represents an element instance in a module.
|
||||
//
|
||||
// See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#element-instances
|
||||
type ElementInstance = []Reference
|
||||
|
||||
// Reference is the runtime representation of RefType which is either RefTypeFuncref or RefTypeExternref.
|
||||
type Reference = uintptr
|
||||
|
||||
// validateTable ensures any ElementSegment is valid. This caches results via Module.validatedActiveElementSegments.
|
||||
// Note: limitsType are validated by decoders, so not re-validated here.
|
||||
func (m *Module) validateTable(enabledFeatures api.CoreFeatures, tables []Table, maximumTableIndex uint32) error {
|
||||
if len(tables) > int(maximumTableIndex) {
|
||||
return fmt.Errorf("too many tables in a module: %d given with limit %d", len(tables), maximumTableIndex)
|
||||
}
|
||||
|
||||
importedTableCount := m.ImportTableCount
|
||||
|
||||
// Create bounds checks as these can err prior to instantiation
|
||||
funcCount := m.ImportFunctionCount + m.SectionElementCount(SectionIDFunction)
|
||||
globalsCount := m.ImportGlobalCount + m.SectionElementCount(SectionIDGlobal)
|
||||
|
||||
// Now, we have to figure out which table elements can be resolved before instantiation and also fail early if there
|
||||
// are any imported globals that are known to be invalid by their declarations.
|
||||
for i := range m.ElementSection {
|
||||
elem := &m.ElementSection[i]
|
||||
idx := Index(i)
|
||||
initCount := uint32(len(elem.Init))
|
||||
|
||||
// Any offset applied is to the element, not the function index: validate here if the funcidx is sound.
|
||||
for ei, init := range elem.Init {
|
||||
if init == ElementInitNullReference {
|
||||
continue
|
||||
}
|
||||
index, ok := unwrapElementInitGlobalReference(init)
|
||||
if ok {
|
||||
if index >= globalsCount {
|
||||
return fmt.Errorf("%s[%d].init[%d] global index %d out of range", SectionIDName(SectionIDElement), idx, ei, index)
|
||||
}
|
||||
} else {
|
||||
if elem.Type == RefTypeExternref {
|
||||
return fmt.Errorf("%s[%d].init[%d] must be ref.null but was %d", SectionIDName(SectionIDElement), idx, ei, init)
|
||||
}
|
||||
if index >= funcCount {
|
||||
return fmt.Errorf("%s[%d].init[%d] func index %d out of range", SectionIDName(SectionIDElement), idx, ei, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if elem.IsActive() {
|
||||
if len(tables) <= int(elem.TableIndex) {
|
||||
return fmt.Errorf("unknown table %d as active element target", elem.TableIndex)
|
||||
}
|
||||
|
||||
t := tables[elem.TableIndex]
|
||||
if t.Type != elem.Type {
|
||||
return fmt.Errorf("element type mismatch: table has %s but element has %s",
|
||||
RefTypeName(t.Type), RefTypeName(elem.Type),
|
||||
)
|
||||
}
|
||||
|
||||
// global.get needs to be discovered during initialization
|
||||
oc := elem.OffsetExpr.Opcode
|
||||
if oc == OpcodeGlobalGet {
|
||||
globalIdx, _, err := leb128.LoadUint32(elem.OffsetExpr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s[%d] couldn't read global.get parameter: %w", SectionIDName(SectionIDElement), idx, err)
|
||||
} else if err = m.verifyImportGlobalI32(SectionIDElement, idx, globalIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if oc == OpcodeI32Const {
|
||||
// Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L117 we must pass if imported
|
||||
// table has set its min=0. Per https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L142, we
|
||||
// have to do fail if module-defined min=0.
|
||||
if !enabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) && elem.TableIndex >= importedTableCount {
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
o, _, err := leb128.LoadInt32(elem.OffsetExpr.Data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s[%d] couldn't read i32.const parameter: %w", SectionIDName(SectionIDElement), idx, err)
|
||||
}
|
||||
offset := Index(o)
|
||||
if err = checkSegmentBounds(t.Min, uint64(initCount)+uint64(offset), idx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("%s[%d] has an invalid const expression: %s", SectionIDName(SectionIDElement), idx, InstructionName(oc))
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// buildTable returns TableInstances if the module defines or imports a table.
|
||||
// - importedTables: returned as `tables` unmodified.
|
||||
// - importedGlobals: include all instantiated, imported globals.
|
||||
//
|
||||
// If the result `init` is non-nil, it is the `tableInit` parameter of Engine.NewModuleEngine.
|
||||
//
|
||||
// Note: An error is only possible when an ElementSegment.OffsetExpr is out of range of the TableInstance.Min.
|
||||
func (m *ModuleInstance) buildTables(module *Module, skipBoundCheck bool) (err error) {
|
||||
idx := module.ImportTableCount
|
||||
for i := range module.TableSection {
|
||||
tsec := &module.TableSection[i]
|
||||
// The module defining the table is the one that sets its Min/Max etc.
|
||||
m.Tables[idx] = &TableInstance{
|
||||
References: make([]Reference, tsec.Min), Min: tsec.Min, Max: tsec.Max,
|
||||
Type: tsec.Type,
|
||||
}
|
||||
idx++
|
||||
}
|
||||
|
||||
if !skipBoundCheck {
|
||||
for elemI := range module.ElementSection { // Do not loop over the value since elementSegments is a slice of value.
|
||||
elem := &module.ElementSection[elemI]
|
||||
table := m.Tables[elem.TableIndex]
|
||||
var offset uint32
|
||||
if elem.OffsetExpr.Opcode == OpcodeGlobalGet {
|
||||
// Ignore error as it's already validated.
|
||||
globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data)
|
||||
global := m.Globals[globalIdx]
|
||||
offset = uint32(global.Val)
|
||||
} else { // i32.const
|
||||
// Ignore error as it's already validated.
|
||||
o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data)
|
||||
offset = uint32(o)
|
||||
}
|
||||
|
||||
// Check to see if we are out-of-bounds
|
||||
initCount := uint64(len(elem.Init))
|
||||
if err = checkSegmentBounds(table.Min, uint64(offset)+initCount, Index(elemI)); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// checkSegmentBounds fails if the capacity needed for an ElementSegment.Init is larger than limitsType.Min
|
||||
//
|
||||
// WebAssembly 1.0 (20191205) doesn't forbid growing to accommodate element segments, and spectests are inconsistent.
|
||||
// For example, the spectests enforce elements within Table limitsType.Min, but ignore Import.DescTable min. What this
|
||||
// means is we have to delay offset checks on imported tables until we link to them.
|
||||
// e.g. https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L117 wants pass on min=0 for import
|
||||
// e.g. https://github.com/WebAssembly/spec/blob/wg-1.0/test/core/elem.wast#L142 wants fail on min=0 module-defined
|
||||
func checkSegmentBounds(min uint32, requireMin uint64, idx Index) error { // uint64 in case offset was set to -1
|
||||
if requireMin > uint64(min) {
|
||||
return fmt.Errorf("%s[%d].init exceeds min table size", SectionIDName(SectionIDElement), idx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Module) verifyImportGlobalI32(sectionID SectionID, sectionIdx Index, idx uint32) error {
|
||||
ig := uint32(math.MaxUint32) // +1 == 0
|
||||
for i := range m.ImportSection {
|
||||
imp := &m.ImportSection[i]
|
||||
if imp.Type == ExternTypeGlobal {
|
||||
ig++
|
||||
if ig == idx {
|
||||
if imp.DescGlobal.ValType != ValueTypeI32 {
|
||||
return fmt.Errorf("%s[%d] (global.get %d): import[%d].global.ValType != i32", SectionIDName(sectionID), sectionIdx, idx, i)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s[%d] (global.get %d): out of range of imported globals", SectionIDName(sectionID), sectionIdx, idx)
|
||||
}
|
||||
|
||||
// Grow appends the `initialRef` by `delta` times into the References slice.
|
||||
// Returns -1 if the operation is not valid, otherwise the old length of the table.
|
||||
//
|
||||
// https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/instructions.html#xref-syntax-instructions-syntax-instr-table-mathsf-table-grow-x
|
||||
func (t *TableInstance) Grow(delta uint32, initialRef Reference) (currentLen uint32) {
|
||||
currentLen = uint32(len(t.References))
|
||||
if delta == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if newLen := int64(currentLen) + int64(delta); // adding as 64bit ints to avoid overflow.
|
||||
newLen >= math.MaxUint32 || (t.Max != nil && newLen > int64(*t.Max)) {
|
||||
return 0xffffffff // = -1 in signed 32-bit integer.
|
||||
}
|
||||
t.References = append(t.References, make([]uintptr, delta)...)
|
||||
|
||||
// Uses the copy trick for faster filling the new region with the initial value.
|
||||
// https://gist.github.com/taylorza/df2f89d5f9ab3ffd06865062a4cf015d
|
||||
newRegion := t.References[currentLen:]
|
||||
newRegion[0] = initialRef
|
||||
for i := 1; i < len(newRegion); i *= 2 {
|
||||
copy(newRegion[i:], newRegion[:i])
|
||||
}
|
||||
return
|
||||
}
|
Reference in New Issue
Block a user