blob: afde353a098fe5aad93fdacb87fa548b4b029e60 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vom
import (
"strconv"
"v.io/v23/vdl"
)
// This file contains the ReadValue* methods. The semantics of these methods is
// the same as if StartValue, Decode*, FinishValue were called in sequence. The
// implementation is faster than actually calling that sequence, because we can
// avoid pushing and popping the decoder stack and also avoid unnecessary
// compatibility checks. We also get a minor improvement by avoiding extra
// method calls indirected through the Decoder interface.
//
// Each method has the same pattern:
//
// Check fastpath:
// If we've already determined from the parent type that we can use the
// fastpath, we simply decode the value, skipping both the StartValue logic as
// well as the conversion logic.
// StartValue:
// If IgnoreNextStartValue is set, the type is already on the stack.
// Otherwise setup the type to process Any and Optional headers. We pass nil
// to d.setupType to avoid the compatibility check, since the decode step will
// naturally let us perform that check.
// Decode:
// We implement common-case fastpaths; e.g. avoiding unnecessary conversions.
// FinishValue:
// Mirrors StartValue, only pop the stack if necessary.
// isFastReadParent returns true iff subtypes of tt can use the fastpath for the
// ReadValue* methods. By using the fastpath we can skip the expensive
// dfsNextType and setupType calls. We can't use the fastpath for:
// Any: since we always need to process the any header
// Enum: since ReadValueString won't know whether to decode a string or enum
// Byte: since ReadValueUint won't know whether to decode a uint or full byte
//
// REQUIRES: tt is identical to the want type that the user is decoding into,
// which ensures that we don't need to perform conversions.
func isFastReadParent(tt *vdl.Type) bool {
switch tt.Kind() {
case vdl.Array, vdl.List:
elem := tt.Elem().Kind()
return elem != vdl.Any && elem != vdl.Enum && elem != vdl.Byte
case vdl.Set:
key := tt.Key().Kind()
return key != vdl.Any && key != vdl.Enum && key != vdl.Byte
case vdl.Map:
key := tt.Key().Kind()
elem := tt.Elem().Kind()
return key != vdl.Any && key != vdl.Enum && key != vdl.Byte &&
elem != vdl.Any && elem != vdl.Enum && elem != vdl.Byte
case vdl.Struct, vdl.Union:
if !tt.ContainsKind(vdl.WalkAll, kkAnyEnumByte...) {
return true
}
for f := 0; f < tt.NumField(); f++ {
if k := tt.Field(f).Type.Kind(); k == vdl.Any || k == vdl.Enum || k == vdl.Byte {
return false
}
}
return true
default:
return false
}
}
var kkAnyEnumByte = []vdl.Kind{vdl.Any, vdl.Enum, vdl.Byte}
func (d *decoder81) ReadValueBool() (value bool, err error) {
top, isOnStack := d.top(), d.flag.IgnoreNextStartValue()
// Check fastpath
if top != nil && top.Flag.FastRead() {
value, err = binaryDecodeBool(d.buf)
} else {
// StartValue
var tt *vdl.Type
if isOnStack {
tt = top.Type
} else {
if tt, err = d.dfsNextType(); err != nil {
return false, err
}
if tt, _, _, err = d.setupType(tt, nil); err != nil {
return false, err
}
}
// Decode
switch tt.Kind() {
case vdl.Bool:
value, err = binaryDecodeBool(d.buf)
default:
return false, errIncompatibleDecode(tt, "bool")
}
}
// FinishValue
if isOnStack {
if err := d.FinishValue(); err != nil {
return false, err
}
} else {
d.flag = d.flag.Clear(decFlagFinishValue)
if top == nil {
if err := d.endMessage(); err != nil {
return false, err
}
}
}
return value, err
}
func (d *decoder81) ReadValueString() (value string, err error) {
top, isOnStack := d.top(), d.flag.IgnoreNextStartValue()
// Check fastpath
if top != nil && top.Flag.FastRead() {
value, err = binaryDecodeString(d.buf)
} else {
// StartValue
var tt *vdl.Type
if isOnStack {
tt = top.Type
} else {
if tt, err = d.dfsNextType(); err != nil {
return "", err
}
if tt, _, _, err = d.setupType(tt, nil); err != nil {
return "", err
}
}
// Decode
switch tt.Kind() {
case vdl.String:
value, err = binaryDecodeString(d.buf)
case vdl.Enum:
value, err = d.binaryDecodeEnum(tt)
default:
return "", errIncompatibleDecode(tt, "string")
}
}
// FinishValue
if isOnStack {
if err := d.FinishValue(); err != nil {
return "", err
}
} else {
d.flag = d.flag.Clear(decFlagFinishValue)
if top == nil {
if err := d.endMessage(); err != nil {
return "", err
}
}
}
return value, err
}
func (d *decoder81) ReadValueUint(bitlen int) (value uint64, err error) {
top, isOnStack := d.top(), d.flag.IgnoreNextStartValue()
// Check fastpath
if top != nil && top.Flag.FastRead() {
value, err = binaryDecodeUint(d.buf)
} else {
// StartValue
var tt *vdl.Type
if isOnStack {
tt = top.Type
} else {
if tt, err = d.dfsNextType(); err != nil {
return 0, err
}
if tt, _, _, err = d.setupType(tt, nil); err != nil {
return 0, err
}
}
// Decode, avoiding unnecessary number conversions.
switch kind := tt.Kind(); kind {
case vdl.Uint16, vdl.Uint32, vdl.Uint64:
if kind.BitLen() <= bitlen {
value, err = binaryDecodeUint(d.buf)
} else {
value, err = d.decodeUint(tt, uint(bitlen))
}
case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64, vdl.Float32, vdl.Float64:
value, err = d.decodeUint(tt, uint(bitlen))
case vdl.Byte:
var b byte
b, err = d.binaryDecodeByte()
value = uint64(b)
default:
return 0, errIncompatibleDecode(tt, "uint"+strconv.Itoa(bitlen))
}
}
// FinishValue
if isOnStack {
if err := d.FinishValue(); err != nil {
return 0, err
}
} else {
d.flag = d.flag.Clear(decFlagFinishValue)
if top == nil {
if err := d.endMessage(); err != nil {
return 0, err
}
}
}
return value, err
}
func (d *decoder81) ReadValueInt(bitlen int) (value int64, err error) {
top, isOnStack := d.top(), d.flag.IgnoreNextStartValue()
// Check fastpath
if top != nil && top.Flag.FastRead() {
value, err = binaryDecodeInt(d.buf)
} else {
// StartValue
var tt *vdl.Type
if isOnStack {
tt = top.Type
} else {
if tt, err = d.dfsNextType(); err != nil {
return 0, err
}
if tt, _, _, err = d.setupType(tt, nil); err != nil {
return 0, err
}
}
// Decode, avoiding unnecessary number conversions.
switch kind := tt.Kind(); kind {
case vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64:
if kind.BitLen() <= bitlen {
value, err = binaryDecodeInt(d.buf)
} else {
value, err = d.decodeInt(tt, uint(bitlen))
}
case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Float32, vdl.Float64:
value, err = d.decodeInt(tt, uint(bitlen))
default:
return 0, errIncompatibleDecode(tt, "int"+strconv.Itoa(bitlen))
}
}
// FinishValue
if isOnStack {
if err := d.FinishValue(); err != nil {
return 0, err
}
} else {
d.flag = d.flag.Clear(decFlagFinishValue)
if top == nil {
if err := d.endMessage(); err != nil {
return 0, err
}
}
}
return value, err
}
func (d *decoder81) ReadValueFloat(bitlen int) (value float64, err error) {
top, isOnStack := d.top(), d.flag.IgnoreNextStartValue()
// Check fastpath
if top != nil && top.Flag.FastRead() {
value, err = binaryDecodeFloat(d.buf)
} else {
// StartValue
var tt *vdl.Type
if isOnStack {
tt = top.Type
} else {
if tt, err = d.dfsNextType(); err != nil {
return 0, err
}
if tt, _, _, err = d.setupType(tt, nil); err != nil {
return 0, err
}
}
// Decode, avoiding unnecessary number conversions.
switch kind := tt.Kind(); kind {
case vdl.Float32, vdl.Float64:
if kind.BitLen() <= bitlen {
value, err = binaryDecodeFloat(d.buf)
} else {
value, err = d.decodeFloat(tt, uint(bitlen))
}
case vdl.Byte, vdl.Uint16, vdl.Uint32, vdl.Uint64, vdl.Int8, vdl.Int16, vdl.Int32, vdl.Int64:
value, err = d.decodeFloat(tt, uint(bitlen))
default:
return 0, errIncompatibleDecode(tt, "float"+strconv.Itoa(bitlen))
}
}
// FinishValue
if isOnStack {
if err := d.FinishValue(); err != nil {
return 0, err
}
} else {
d.flag = d.flag.Clear(decFlagFinishValue)
if top == nil {
if err := d.endMessage(); err != nil {
return 0, err
}
}
}
return value, err
}
func (d *decoder81) ReadValueTypeObject() (value *vdl.Type, err error) {
top, isOnStack := d.top(), d.flag.IgnoreNextStartValue()
// Check fastpath
if top != nil && top.Flag.FastRead() {
value, err = d.binaryDecodeType()
} else {
// StartValue
var tt *vdl.Type
if isOnStack {
tt = top.Type
} else {
if tt, err = d.dfsNextType(); err != nil {
return nil, err
}
if tt, _, _, err = d.setupType(tt, nil); err != nil {
return nil, err
}
}
// Decode
switch tt.Kind() {
case vdl.TypeObject:
value, err = d.binaryDecodeType()
default:
return nil, errIncompatibleDecode(tt, "typeobject")
}
}
// FinishValue
if isOnStack {
if err := d.FinishValue(); err != nil {
return nil, err
}
} else {
d.flag = d.flag.Clear(decFlagFinishValue)
if top == nil {
if err := d.endMessage(); err != nil {
return nil, err
}
}
}
return value, err
}
// ReadValueBytes is more complicated than the other ReadValue* methods, since
// []byte lists and [n]byte arrays aren't scalar, and may need more complicated
// conversions
//
// TODO(toddw): Implement fastpath for this?
func (d *decoder81) ReadValueBytes(fixedLen int, x *[]byte) (err error) {
// StartValue. Initialize tt and lenHint, and track whether the []byte type
// is already on the stack via isOnStack.
isOnStack := d.flag.IgnoreNextStartValue()
d.flag = d.flag.Clear(decFlagIgnoreNextStartValue)
var tt *vdl.Type
var lenHint int
if isOnStack {
top := d.top()
tt, lenHint = top.Type, top.LenHint
} else {
if tt, err = d.dfsNextType(); err != nil {
return err
}
var flag decStackFlag
if tt, lenHint, flag, err = d.setupType(tt, nil); err != nil {
return err
}
// If tt isn't []byte or [n]byte (or a named variant of these), we need to
// perform conversion byte-by-byte. This is complicated, and can't be
// really fast, so we just push an entry onto the stack and handle this via
// DecodeConvertedBytes below.
//
// We also need to perform the compatibility check, to make sure tt is
// compatible with []byte. The check is fairly expensive, so skipping it
// when tt is actually a bytes type makes the the common case faster.
if !tt.IsBytes() {
if !vdl.Compatible(tt, ttByteList) {
return errIncompatibleDecode(tt, "bytes")
}
d.stack = append(d.stack, decStackEntry{
Type: tt,
Index: -1,
LenHint: lenHint,
Flag: flag,
})
isOnStack = true
}
}
// Decode. The common-case fastpath reads directly from the buffer.
if tt.IsBytes() {
if err := d.decodeBytes(tt, lenHint, fixedLen, x); err != nil {
return err
}
} else {
if err := vdl.DecodeConvertedBytes(d, fixedLen, x); err != nil {
return err
}
}
// FinishValue
if isOnStack {
if err := d.FinishValue(); err != nil {
return err
}
} else {
d.flag = d.flag.Clear(decFlagIsParentBytes)
if len(d.stack) == 0 {
if err := d.endMessage(); err != nil {
return err
}
}
}
return nil
}
var ttByteList = vdl.ListType(vdl.ByteType)