Skip to content

Commit

Permalink
Add encoding/decoding dictionary static type info
Browse files Browse the repository at this point in the history
  • Loading branch information
SupunS committed Jun 30, 2021
1 parent 8b62e52 commit dc72ec5
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 14 deletions.
74 changes: 69 additions & 5 deletions runtime/interpreter/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -1868,11 +1868,7 @@ func decodeArrayElements(array *ArrayValue, elementContent []byte) error {
return nil
}

func decodeDictionaryEntries(v *DictionaryValue, content []byte) error {
if v.encodingVersion == 4 {
return decodeDictionaryEntriesV4(v, content)
}

func decodeDictionaryMetaInfo(v *DictionaryValue, content []byte) error {
d, err := NewByteDecoder(content, v.Owner, v.encodingVersion, v.decodeCallback)
if err != nil {
return err
Expand Down Expand Up @@ -1901,6 +1897,74 @@ func decodeDictionaryEntries(v *DictionaryValue, content []byte) error {
)
}

// Lazily decode keys

var keysContent []byte
if d.isByteDecoder {
// Use the zero-copy method if available, for better performance.
keysContent, err = d.decoder.DecodeRawBytesZeroCopy()
} else {
keysContent, err = d.decoder.DecodeRawBytes()
}

if err != nil {
if e, ok := err.(*cbor.WrongTypeError); ok {
return fmt.Errorf(
"invalid dictionary keys encoding (@ %s): %s",
strings.Join(v.valuePath, "."),
e.ActualType.String(),
)
}
return err
}

// Lazily decode values

var valuesContent []byte
if d.isByteDecoder {
// Use the zero-copy method if available, for better performance.
valuesContent, err = d.decoder.DecodeRawBytesZeroCopy()
} else {
valuesContent, err = d.decoder.DecodeRawBytes()
}

if err != nil {
if e, ok := err.(*cbor.WrongTypeError); ok {
return fmt.Errorf(
"invalid dictionary values encoding (@ %s): %s",
strings.Join(v.valuePath, "."),
e.ActualType.String(),
)
}
return err
}

// Decode type
// TODO: store dictionary type
// Option 1: convert to sema type. - Don't have the interpreter
// Option 2: Store static type in array
_, err = d.decodeStaticType()
if err != nil {
return err
}

//nolint:gocritic
v.entriesContent = append(keysContent, valuesContent...)
v.Type = nil

return nil
}

func decodeDictionaryEntries(v *DictionaryValue, content []byte) error {
if v.encodingVersion == 4 {
return decodeDictionaryEntriesV4(v, content)
}

d, err := NewByteDecoder(content, v.Owner, v.encodingVersion, v.decodeCallback)
if err != nil {
return err
}

// Decode keys at array index encodedDictionaryValueKeysFieldKey
//nolint:gocritic
keysPath := append(v.valuePath, dictionaryKeyPathPrefix)
Expand Down
12 changes: 10 additions & 2 deletions runtime/interpreter/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -855,12 +855,13 @@ func (e *EncoderV5) encodeArray(
const (
encodedDictionaryValueKeysFieldKey uint64 = 0
encodedDictionaryValueEntriesFieldKey uint64 = 1
encodedDictionaryValueTypeFieldKey uint64 = 2

// !!! *WARNING* !!!
//
// encodedDictionaryValueLength MUST be updated when new element is added.
// It is used to verify encoded dictionaries length during decoding.
encodedDictionaryValueLength = 2
encodedDictionaryValueLength = 3
)

const dictionaryKeyPathPrefix = "k"
Expand All @@ -872,6 +873,7 @@ const dictionaryValuePathPrefix = "v"
// Content: cborArray{
// encodedDictionaryValueKeysFieldKey: []interface{}(keys),
// encodedDictionaryValueEntriesFieldKey: []interface{}(entries),
// encodedDictionaryValueTypeFieldKey: []interface{}(type),
// },
// }
func (e *EncoderV5) encodeDictionaryValue(
Expand Down Expand Up @@ -901,7 +903,7 @@ func (e *EncoderV5) encodeDictionaryValue(
// Encode array head
err = e.enc.EncodeRawBytes([]byte{
// array, 2 items follow
0x82,
0x83,
})
if err != nil {
return err
Expand Down Expand Up @@ -1009,6 +1011,12 @@ func (e *EncoderV5) encodeDictionaryValue(
}
}

// Encode dictionary static type at array index encodedDictionaryValueTypeFieldKey
err = e.encodeStaticType(v.StaticType())
if err != nil {
return err
}

return nil
}

Expand Down
24 changes: 20 additions & 4 deletions runtime/interpreter/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5113,6 +5113,14 @@ func TestEncodeDecodeDictionaryDeferred(t *testing.T) {
0xf5,
// array, 0 items follow
0x80,
// dictionary type tag
0xd8, cborTagDictionaryStaticType,
// array, 2 items follow
0x82,
// key type
0xd8, cborTagPrimitiveStaticType, byte(PrimitiveStaticTypeAnyStruct),
// value type
0xd8, cborTagPrimitiveStaticType, byte(PrimitiveStaticTypeAnyResource),
}

version3Encoded := []byte{
Expand Down Expand Up @@ -5183,8 +5191,8 @@ func TestEncodeDecodeDictionaryDeferred(t *testing.T) {
encoded := []byte{
// tag
0xd8, cborTagDictionaryValue,
// array, 2 items follow
0x82,
// array, 3 items follow
0x83,

// cbor Array Value tag
0xd8, cborTagArrayValue,
Expand All @@ -5211,6 +5219,14 @@ func TestEncodeDecodeDictionaryDeferred(t *testing.T) {
0x78, 0x79, 0x7a,
// false
0xf4,
// dictionary type tag
0xd8, cborTagDictionaryStaticType,
// array, 2 items follow
0x82,
// key type
0xd8, cborTagPrimitiveStaticType, byte(PrimitiveStaticTypeAnyStruct),
// value type
0xd8, cborTagPrimitiveStaticType, byte(PrimitiveStaticTypeAnyStruct),
}

version3Encoded := []byte{
Expand All @@ -5220,8 +5236,8 @@ func TestEncodeDecodeDictionaryDeferred(t *testing.T) {
0xa2,
// key 0
0x0,
// array, 2 items follow
0x82,
// array, 3 items follow
0x83,
// UTF-8 string, length 4
0x64,
// t, e, s, t
Expand Down
33 changes: 30 additions & 3 deletions runtime/interpreter/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -7225,6 +7225,10 @@ type DictionaryValue struct {
// Only available for decoded values who's entries are not loaded yet.
content []byte

// Raw content cache for entries.
// Only available for decoded values who's entries are not loaded yet.
entriesContent []byte

// Value's path to be used during decoding.
// Only available for decoded values who's entries are not loaded yet.
valuePath []string
Expand Down Expand Up @@ -7882,19 +7886,42 @@ func (v *DictionaryValue) Equal(other Value, interpreter *Interpreter, loadDefer
return true
}

// ensureMetaInfoLoaded ensures loading the meta information of this dictionary value.
// If the meta info is already loaded, then calling this function won't have any effect.
// Otherwise, the values are decoded form the cached raw-content.
//
// Meta info includes:
// - static type
//
func (v *DictionaryValue) ensureMetaInfoLoaded() {
if v.content == nil {
return
}

err := decodeDictionaryMetaInfo(v, v.content)
if err != nil {
panic(err)
}

// Raw content is no longer needed. Clear the cache and free-up the memory.
v.content = nil
}

// ensureLoaded ensures the entries of this dictionary value are loaded.
func (v *DictionaryValue) ensureLoaded() {
if v.content == nil {
v.ensureMetaInfoLoaded()

if v.entriesContent == nil {
return
}

err := decodeDictionaryEntries(v, v.content)
err := decodeDictionaryEntries(v, v.entriesContent)
if err != nil {
panic(err)
}

// Reset the cache
v.content = nil
v.entriesContent = nil
v.valuePath = nil
v.decodeCallback = nil
v.encodingVersion = 0
Expand Down

0 comments on commit dc72ec5

Please sign in to comment.