Files
wazero/internal/wasm/binary/value.go
Crypt Keeper cfb11f352a Adds ability to disable mutable globals and improves decode perf (#315)
This adds `RuntimeConfig.WithFeatureMutableGlobal(enabled bool)`, which
allows disabling of mutable globals. When disabled, any attempt to add a
mutable global, either explicitly or implicitly via decoding wasm will
fail.

To support this, there's a new `Features` bitflag that can allow up to
63 feature toggles without passing structs.

While here, I fixed a significant performance problem in decoding
binary:

Before
```
BenchmarkCodecExample/binary.DecodeModule-16         	  184243	      5623 ns/op	    3848 B/op	     184 allocs/op
```

Now
```
BenchmarkCodecExample/binary.DecodeModule-16         	  294084	      3520 ns/op	    2176 B/op	      91 allocs/op

```

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-03-03 10:31:10 +08:00

86 lines
2.6 KiB
Go

package binary
import (
"bytes"
"fmt"
"io"
"unicode/utf8"
"github.com/tetratelabs/wazero/internal/leb128"
wasm "github.com/tetratelabs/wazero/internal/wasm"
)
var noValType = []byte{0}
// encodedValTypes is a cache of size prefixed binary encoding of known val types.
var encodedValTypes = map[wasm.ValueType][]byte{
wasm.ValueTypeI32: {1, wasm.ValueTypeI32},
wasm.ValueTypeI64: {1, wasm.ValueTypeI64},
wasm.ValueTypeF32: {1, wasm.ValueTypeF32},
wasm.ValueTypeF64: {1, wasm.ValueTypeF64},
}
// encodeValTypes fast paths binary encoding of common value type lengths
func encodeValTypes(vt []wasm.ValueType) []byte {
// Special case nullary and parameter lengths of wasi_snapshot_preview1 to avoid excess allocations
switch uint32(len(vt)) {
case 0: // nullary
return noValType
case 1: // ex $wasi.fd_close or any result
if encoded, ok := encodedValTypes[vt[0]]; ok {
return encoded
}
case 2: // ex $wasi.environ_sizes_get
return []byte{2, vt[0], vt[1]}
case 4: // ex $wasi.fd_write
return []byte{4, vt[0], vt[1], vt[2], vt[3]}
case 9: // ex $wasi.fd_write
return []byte{9, vt[0], vt[1], vt[2], vt[3], vt[4], vt[5], vt[6], vt[7], vt[8]}
}
// Slow path others until someone complains with a valid signature
count := leb128.EncodeUint32(uint32(len(vt)))
return append(count, vt...)
}
func decodeValueTypes(r *bytes.Reader, num uint32) ([]wasm.ValueType, error) {
if num == 0 {
return nil, nil
}
ret := make([]wasm.ValueType, num)
buf := make([]wasm.ValueType, num)
_, err := io.ReadFull(r, buf)
if err != nil {
return nil, err
}
for i, v := range buf {
switch v {
case wasm.ValueTypeI32, wasm.ValueTypeF32, wasm.ValueTypeI64, wasm.ValueTypeF64:
ret[i] = v
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)
}
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...))
}
return string(buf), size + uint32(sizeOfSize), nil
}