Files
wazero/internal/wasm/binary/code.go
Takeshi Yoneda 064bcdddc6 Implements v128.const and adds support for vector value type. (#556)
This commit implements the v128.const, i32x4.add and i64x2.add in
interpreter mode and this adds support for the vector value types in the
locals and globals.

Notably, the vector type values can be passed and returned by exported functions
as well as host functions via two-uint64 encodings as described in #484 (comment).

Note: implementation of these instructions on JIT will be done in subsequent PR.

part of #484

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
2022-05-16 13:17:26 +09:00

122 lines
3.5 KiB
Go

package binary
import (
"bytes"
"fmt"
"io"
"math"
"github.com/tetratelabs/wazero/internal/leb128"
"github.com/tetratelabs/wazero/internal/wasm"
)
func decodeCode(r *bytes.Reader) (*wasm.Code, error) {
ss, _, err := leb128.DecodeUint32(r)
if err != nil {
return nil, 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 nil, fmt.Errorf("get the size locals: %v", err)
} else if remaining < 0 {
return nil, io.EOF
}
var nums []uint64
var types []wasm.ValueType
var sum uint64
var n uint32
for i := uint32(0); i < ls; i++ {
n, bytesRead, err = leb128.DecodeUint32(r)
remaining -= int64(bytesRead) + 1 // +1 for the subsequent ReadByte
if err != nil {
return nil, fmt.Errorf("read n of locals: %v", err)
} else if remaining < 0 {
return nil, io.EOF
}
sum += uint64(n)
nums = append(nums, uint64(n))
b, err := r.ReadByte()
if err != nil {
return nil, fmt.Errorf("read type of local: %v", err)
}
switch vt := b; vt {
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64,
wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeV128:
types = append(types, vt)
default:
return nil, fmt.Errorf("invalid local type: 0x%x", vt)
}
}
if sum > math.MaxUint32 {
return nil, fmt.Errorf("too many locals: %d", sum)
}
var localTypes []wasm.ValueType
for i, num := range nums {
t := types[i]
for j := uint64(0); j < num; j++ {
localTypes = append(localTypes, t)
}
}
body := make([]byte, remaining)
if _, err = io.ReadFull(r, body); err != nil {
return nil, fmt.Errorf("read body: %w", err)
}
// TODO: Is there a way to efficiently break if FeatureSignExtensionOps=false and
// OpcodeI32Extend8S <= op && op <= OpcodeI64Extend32S?
if body[len(body)-1] != wasm.OpcodeEnd {
return nil, fmt.Errorf("expr not end with OpcodeEnd")
}
return &wasm.Code{Body: body, LocalTypes: localTypes}, nil
}
// encodeCode returns the wasm.Code encoded in WebAssembly 1.0 (20191205) Binary Format.
//
// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-code
func encodeCode(c *wasm.Code) []byte {
// local blocks compress locals while preserving index order by grouping locals of the same type.
// https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#code-section%E2%91%A0
localBlockCount := uint32(0) // how many blocks of locals with the same type (types can repeat!)
var localBlocks []byte
localTypeLen := len(c.LocalTypes)
if localTypeLen > 0 {
i := localTypeLen - 1
var runCount uint32 // count of the same type
var lastValueType wasm.ValueType // initialize to an invalid type 0
// iterate backwards so it is easier to size prefix
for ; i >= 0; i-- {
vt := c.LocalTypes[i]
if lastValueType != vt {
if runCount != 0 { // Only on the first iteration, this is zero when vt is compared against invalid
localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...)
}
lastValueType = vt
localBlocks = append(leb128.EncodeUint32(uint32(vt)), localBlocks...) // reuse the EncodeUint32 cache
localBlockCount++
runCount = 1
} else {
runCount++
}
}
localBlocks = append(leb128.EncodeUint32(runCount), localBlocks...)
localBlocks = append(leb128.EncodeUint32(localBlockCount), localBlocks...)
} else {
localBlocks = leb128.EncodeUint32(0)
}
code := append(localBlocks, c.Body...)
return append(leb128.EncodeUint32(uint32(len(code))), code...)
}