blob: 6466aeae7eb247943fb1bb9cc94c0754b0b2b05e [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 fidlgen
import (
"bytes"
"encoding/binary"
"fmt"
)
type PaddingMarker struct {
// Offset into the struct in bytes (0 is the start of the struct).
Offset int
// Width of the mask in bits. Either 16, 32, or 64.
MaskBitWidth int
// Little endian mask with 0x00 for non-padding and 0xff for padding.
// Only the lower MaskBitWidth bits are used. The higher bits are 0.
Mask uint64
}
type PaddingConfig struct {
FlattenStructs bool
// Only allowed when FlattenStructs is true.
FlattenArrays bool
// Must be provided if FlattenStructs is true.
ResolveStruct func(identifier EncodedCompoundIdentifier) *Struct
}
func (s Struct) BuildPaddingMarkers(config PaddingConfig) []PaddingMarker {
if config.FlattenArrays && !config.FlattenStructs {
panic("flattening arrays but not structs is pointless: arrays of non-structs have no padding")
}
var paddingMarkers []PaddingMarker
// Construct a mask across the whole struct with 0xff bytes where there is padding.
fullStructMask := make([]byte, s.TypeShapeV2.InlineSize)
populateFullStructMaskForStruct(fullStructMask, config, s)
// Split up the mask into aligned integer mask segments.
// Only the sections needing padding are outputted.
// e.g. 00 00 00 00 00 00 00 00 00 ff ff 00 ff ff ff ff
// -> 0000000000000000, 00ffff00ffffffff
// -> []PaddingMarker{{Offset: 8, MaskBitWidth: 64, Mask: binary.LittleEndian.Uint64([]byte{0x00, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, 0xff})}}
// -> []PaddingMarker{{Offset: 8, MaskBitWidth: 64, Mask: 0xff_ff_ff_ff_00_ff_ff_00)}}
extractNonzeroSliceOffsets := func(stride int) []int {
var offsets []int
for endi := stride - 1; endi < len(fullStructMask); endi += stride {
i := endi - (stride - 1)
if bytes.Contains(fullStructMask[i:i+stride], []byte{0xff}) {
offsets = append(offsets, i)
}
}
return offsets
}
littleEndian := func(b []byte, size int) uint64 {
switch size {
case 8:
return binary.LittleEndian.Uint64(b)
case 4:
return uint64(binary.LittleEndian.Uint32(b))
case 2:
return uint64(binary.LittleEndian.Uint16(b))
default:
panic("unexpected size")
}
}
for _, size := range []int{8, 4, 2} {
if s.TypeShapeV2.Alignment < size {
continue
}
for _, i := range extractNonzeroSliceOffsets(size) {
s := fullStructMask[i : i+size]
paddingMarkers = append(paddingMarkers, PaddingMarker{
Offset: i,
MaskBitWidth: size * 8,
Mask: littleEndian(s, size),
})
// Zero out the region to avoid including it in smaller masks.
for j := range s {
s[j] = 0
}
}
}
if bytes.Contains(fullStructMask, []byte{0xff}) {
// This shouldn't be possible because it requires an alignment 1 struct to have padding.
panic(fmt.Sprintf("expected mask to be zero, was %v", fullStructMask))
}
return paddingMarkers
}
func populateFullStructMaskForStruct(mask []byte, config PaddingConfig, s Struct) {
paddingEnd := s.TypeShapeV2.InlineSize - 1
for i := len(s.Members) - 1; i >= 0; i-- {
member := s.Members[i]
fieldShape := member.FieldShapeV2
populateFullStructMaskForType(mask[fieldShape.Offset:paddingEnd+1], config, &member.Type)
for j := 0; j < fieldShape.Padding; j++ {
mask[paddingEnd-j] = 0xff
}
paddingEnd = fieldShape.Offset - 1
}
}
func populateFullStructMaskForType(mask []byte, config PaddingConfig, typ *Type) {
if typ.Nullable {
return
}
switch typ.Kind {
case ArrayType:
if config.FlattenArrays {
elemByteSize := len(mask) / *typ.ElementCount
for i := 0; i < *typ.ElementCount; i++ {
populateFullStructMaskForType(mask[i*elemByteSize:(i+1)*elemByteSize], config, typ.ElementType)
}
}
case IdentifierType:
if config.FlattenStructs {
if s := config.ResolveStruct(typ.Identifier); s != nil {
populateFullStructMaskForStruct(mask, config, *s)
}
}
}
}