124 lines
3.6 KiB
Go
124 lines
3.6 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, codeSectionStart uint64) (*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)
|
|
}
|
|
}
|
|
|
|
bodyOffsetInCodeSection := codeSectionStart - uint64(r.Len())
|
|
body := make([]byte, remaining)
|
|
if _, err = io.ReadFull(r, body); err != nil {
|
|
return nil, fmt.Errorf("read body: %w", err)
|
|
}
|
|
|
|
if endIndex := len(body) - 1; endIndex < 0 || body[endIndex] != wasm.OpcodeEnd {
|
|
return nil, fmt.Errorf("expr not end with OpcodeEnd")
|
|
}
|
|
|
|
return &wasm.Code{Body: body, LocalTypes: localTypes, BodyOffsetInCodeSection: bodyOffsetInCodeSection}, 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 {
|
|
if c.GoFunc != nil {
|
|
panic("BUG: GoFunction is not encodable")
|
|
}
|
|
|
|
// 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...)
|
|
}
|