blob: 7947e1298aab79cf5ab6cb2de2faccb429a04740 [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 zither contains abstractions and utilities shared by the various backends.
package zither
import (
"fmt"
"math/bits"
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen_cpp"
)
// DeclOrder represents the ordering policy for declarations as they are
// provided to the backend.
type DeclOrder int
const (
// SourceDeclOrder orders declarations as they were in source:
// lexicographically by filename across FIDL files and then by location
// within files.
SourceDeclOrder DeclOrder = iota
// DependencyDeclOrder gives a topological sorting of declarations by
// dependency, so that a declaration is always preceded by the declarations
// figuring into its definition. Dependency alone, however, gives a partial
// order; this order is fixed in that it further orders declarations with
// no interdependencies by source (as above).
//
// An order topologically sorted on dependency is a general requirement for
// any backend generating C-family code. While technically speaking
// forward-declarations could be used to rearrange the definitions of a
// number of things, this is not an option with nested struct or union
// definitions, as the nested layout types would need to be 'complete' in
// that scope. Accordingly, it is simpler just to deal in full topological
// sorts.
DependencyDeclOrder
)
// Element represents a summarized FIDL element (i.e., a declaration or one of
// its members).
type Element interface {
GetComments() []string
}
var _ = []Element{
(*Alias)(nil),
(*Bits)(nil),
(*BitsMember)(nil),
(*Const)(nil),
(*Enum)(nil),
(*EnumMember)(nil),
(*Handle)(nil),
(*OverlayVariant)(nil),
(*Struct)(nil),
(*StructMember)(nil),
(*Syscall)(nil),
(*SyscallFamily)(nil),
(*SyscallParameter)(nil),
}
// Decl represents a summarized FIDL declaration.
type Decl interface {
Element
GetName() fidlgen.Name
}
var _ = []Decl{
(*Alias)(nil),
(*Const)(nil),
(*Enum)(nil),
(*Handle)(nil),
(*Overlay)(nil),
(*Struct)(nil),
(*SyscallFamily)(nil),
}
type decl struct {
Comments []string
Name fidlgen.Name
}
func (d decl) GetComments() []string {
return d.Comments
}
func (d decl) GetName() fidlgen.Name {
return d.Name
}
func newDecl(d fidlgen.Decl) decl {
attrs := d.GetAttributes()
var comments []string
if !attrs.HasAttribute("no_doc") {
comments = attrs.DocComments()
}
return decl{
Name: fidlgen.MustReadName(string(d.GetName())),
Comments: comments,
}
}
// Member represents a summarized member of a FIDL layout declaration.
type Member interface {
Element
GetName() string
}
var _ = []Member{
(*BitsMember)(nil),
(*EnumMember)(nil),
(*OverlayVariant)(nil),
(*StructMember)(nil),
(*Syscall)(nil),
(*SyscallParameter)(nil),
}
type member struct {
Comments []string
Name string
}
func (m member) GetComments() []string {
return m.Comments
}
func (m member) GetName() string {
return m.Name
}
func newMember(m fidlgen.Member) member {
attrs := m.GetAttributes()
var comments []string
if !attrs.HasAttribute("no_doc") {
comments = attrs.DocComments()
}
return member{
Name: string(m.GetName()),
Comments: comments,
}
}
// DeclWrapper represents an abstract (summarized) FIDL declaration, meant for
// use in template logic and featuring thin wrappers around type assertions for
// deriving concrete types. In normal go code, we would do the type assertions
// directly, but no can do in templates.
type DeclWrapper struct {
value interface{}
}
func (decl DeclWrapper) Name() fidlgen.Name {
return decl.AsDecl().GetName()
}
func (decl DeclWrapper) AsDecl() Decl {
return decl.value.(Decl)
}
func (decl DeclWrapper) IsConst() bool {
_, ok := decl.value.(*Const)
return ok
}
func (decl DeclWrapper) AsConst() Const {
return *decl.value.(*Const)
}
func (decl DeclWrapper) IsEnum() bool {
_, ok := decl.value.(*Enum)
return ok
}
func (decl DeclWrapper) AsEnum() Enum {
return *decl.value.(*Enum)
}
func (decl DeclWrapper) IsBits() bool {
_, ok := decl.value.(*Bits)
return ok
}
func (decl DeclWrapper) AsBits() Bits {
return *decl.value.(*Bits)
}
func (decl DeclWrapper) IsStruct() bool {
_, ok := decl.value.(*Struct)
return ok
}
func (decl DeclWrapper) AsStruct() Struct {
return *decl.value.(*Struct)
}
func (decl DeclWrapper) IsOverlay() bool {
_, ok := decl.value.(*Overlay)
return ok
}
func (decl DeclWrapper) AsOverlay() Overlay {
return *decl.value.(*Overlay)
}
func (decl DeclWrapper) IsAlias() bool {
_, ok := decl.value.(*Alias)
return ok
}
func (decl DeclWrapper) IsHandle() bool {
_, ok := decl.value.(*Handle)
return ok
}
func (decl DeclWrapper) IsSyscallFamily() bool {
_, ok := decl.value.(*SyscallFamily)
return ok
}
func (decl DeclWrapper) AsAlias() Alias {
return *decl.value.(*Alias)
}
func (decl DeclWrapper) AsHandle() Handle {
return *decl.value.(*Handle)
}
func (decl DeclWrapper) AsSyscallFamily() SyscallFamily {
return *decl.value.(*SyscallFamily)
}
// FileSummary is a summarized representation of a FIDL source file.
type FileSummary struct {
// Library is the associated FIDL library.
Library fidlgen.LibraryName
// Source gives the associated source file.
Source string
// The contained declarations.
Decls []DeclWrapper
// See Deps().
deps map[string]struct{}
// See TypeKinds().
typeKinds map[TypeKind]struct{}
}
// Name is the extension-less basename of the file.
func (summary FileSummary) Name() string {
name := filepath.Base(summary.Source)
name = strings.TrimSuffix(name, ".test.fidl")
name = strings.TrimSuffix(name, ".fidl")
name = strings.ReplaceAll(name, ".", "_")
return name
}
// Deps records the (extension-less) file names that this file depends on; we
// say a file "depends" on another if the former has a declaration that depends
// on a declaration in the latter.
func (summary FileSummary) Deps() []string {
var deps []string
for dep := range summary.deps {
deps = append(deps, dep)
}
// Sort to account for map access nondeterminism.
sort.Strings(deps)
return deps
}
// TypeKinds gives the kinds of types contained in this file's declarations.
// This is useful for knowing the precise set of imports to make in the code
// generated from this file.
func (summary FileSummary) TypeKinds() []TypeKind {
var kinds []TypeKind
for kind := range summary.typeKinds {
kinds = append(kinds, kind)
}
// Sort to account for map access nondeterminism.
sort.Slice(kinds, func(i, j int) bool {
return strings.Compare(string(kinds[i]), string(kinds[j])) < 0
})
return kinds
}
type LibrarySummary struct {
// Library is the associated FIDL library.
Library fidlgen.LibraryName
// Documentation gives newline-separated library-level documentation.
Documentation []string
Files []FileSummary
}
type declMap map[string]Decl
type memberMap map[string]Member
// Summarize creates FIDL file summaries from FIDL IR. Within each file
// summary, declarations are ordered according to `order`. Further, a callback
// may be provided for extra bookkeeping, which will be called on each
// zither-summarized Decl in topological 'dependency' order (à la
// DependencyDeclOrder), irrespective of the provided `order`.
func Summarize(ir fidlgen.Root, sourceDir string, order DeclOrder, cb func(Decl)) (*LibrarySummary, error) {
libName, err := fidlgen.ReadLibraryName(string(ir.Name))
if err != nil {
return nil, err
}
// We will process declarations in topological order (with respect to
// dependency) and record declarations as we go; that way, we when we
// process a particular declaration we will have full knowledge of is
// dependencies, and by extension itself. This ordering exists just for
// ease of processing and is independent of that prescribed by `order`.
g := fidlgen_cpp.NewDeclDepGraph(ir)
fidlDecls := g.SortedDecls()
locations := make(map[string]fidlgen.Location)
processedDecls := make(declMap)
processedConstMembers := make(memberMap)
filesByName := make(map[string]*FileSummary)
getFile := func(location fidlgen.Location) *FileSummary {
src, err := filepath.Rel(sourceDir, location.Filename)
if err != nil {
panic(fmt.Sprintf("could not relativize %s against source directory %s: %s", location.Filename, sourceDir, err))
}
file, ok := filesByName[location.Filename]
if !ok {
file = &FileSummary{
Library: libName,
Source: src,
deps: make(map[string]struct{}),
typeKinds: make(map[TypeKind]struct{}),
}
filesByName[location.Filename] = file
}
return file
}
for _, fidlDecl := range fidlDecls {
typeKinds := make(map[TypeKind]struct{})
var decl Decl
var constMembers []Member
var err error
switch fidlDecl := fidlDecl.(type) {
case *fidlgen.Const:
decl, err = newConst(*fidlDecl, processedDecls, processedConstMembers)
if err == nil {
typeKinds[decl.(*Const).Kind] = struct{}{}
}
case *fidlgen.Enum:
decl, err = newEnum(*fidlDecl)
if err == nil {
for _, m := range decl.(*Enum).Members {
constMembers = append(constMembers, Member(m))
}
typeKinds[TypeKindInteger] = struct{}{}
}
case *fidlgen.Bits:
decl, err = newBits(*fidlDecl)
if err == nil {
for _, m := range decl.(*Bits).Members {
constMembers = append(constMembers, Member(m))
}
typeKinds[TypeKindInteger] = struct{}{}
}
case *fidlgen.Struct:
decl, err = newStruct(*fidlDecl, processedDecls, typeKinds)
if err == nil {
typeKinds[TypeKindStruct] = struct{}{}
}
case *fidlgen.Overlay:
decl, err = newOverlay(*fidlDecl, processedDecls, typeKinds)
if err == nil {
typeKinds[TypeKindOverlay] = struct{}{}
}
case *fidlgen.Alias:
decl, err = newAlias(*fidlDecl, processedDecls, typeKinds)
if err == nil {
typeKinds[TypeKindAlias] = struct{}{}
}
case *fidlgen.Resource:
decl, err = newHandle(*fidlDecl, typeKinds)
if err == nil {
typeKinds[TypeKindHandle] = struct{}{}
}
case *fidlgen.Protocol:
decl, err = newSyscallFamily(*fidlDecl, processedDecls)
}
if err != nil {
return nil, err
}
// A nil `decl` means that it corresponds to an unknown FIDL
// declaration. Moreover, if `decl` wraps a nil (which does not imply
// that it is nil itself), then that means that it is defined in terms
// of unknown elements. In either case, we cannot make sense of the
// definition and so we skip it.
//
// TODO(https://fxbug.dev/42057859): We do not want to silently ignore things
// that a user might expect to generate results.
if decl == nil || reflect.ValueOf(decl).IsNil() {
// Before skipping, we should still record the file that the
// unrecognized declaration came from (e.g., for determinism in
// the case where we wish the have the generated set of files
// match the incoming source file names); calling getFile() has
// this side-effect.
getFile(fidlDecl.GetLocation())
continue
}
file := getFile(fidlDecl.GetLocation())
file.Decls = append(file.Decls, DeclWrapper{decl})
for kind := range typeKinds {
file.typeKinds[kind] = struct{}{}
}
// Now go back and record the dependents' dependency on this declaration.
dependents, ok := g.GetDirectDependents(fidlDecl.GetName())
if !ok {
panic(fmt.Sprintf("%s not found in declaration graph", decl.GetName()))
}
for _, dependent := range dependents {
dependentFile := getFile(dependent.GetLocation())
if dependentFile.Source != file.Source {
dependentFile.deps[file.Name()] = struct{}{}
}
}
declName := decl.GetName().String()
locations[declName] = fidlDecl.GetLocation()
processedDecls[declName] = decl
for _, m := range constMembers {
processedConstMembers[declName+"."+m.GetName()] = m
}
cb(decl)
}
var files []FileSummary
for _, file := range filesByName {
// Now reorder declarations in the order expected by the backends.
switch order {
case SourceDeclOrder:
sort.Slice(file.Decls, func(i, j int) bool {
locI := locations[file.Decls[i].Name().String()]
locJ := locations[file.Decls[j].Name().String()]
return fidlgen.LocationCmp(locI, locJ)
})
case DependencyDeclOrder:
// Already in this order.
default:
panic(fmt.Sprintf("unknown declaration order: %v", order))
}
files = append(files, *file)
}
return &LibrarySummary{
Library: libName,
Documentation: ir.Attributes.DocComments(),
Files: files,
}, nil
}
// TypeKind gives a rough categorization of FIDL primitive and declaration types.
type TypeKind string
const (
TypeKindBool TypeKind = "bool"
TypeKindInteger TypeKind = "integer"
TypeKindSize TypeKind = "size"
TypeKindString TypeKind = "string"
TypeKindEnum TypeKind = "enum"
TypeKindBits TypeKind = "bits"
TypeKindArray TypeKind = "array"
TypeKindStringArray TypeKind = "string_array"
TypeKindStruct TypeKind = "struct"
TypeKindOverlay TypeKind = "overlay"
TypeKindAlias TypeKind = "alias" // Not a type per se, but conveniently regarded as such.
// TODO(https://fxbug.dev/42061412): These kinds exist only for the sake of the
// interim, v2 form of FIDL library zx.
TypeKindPointer TypeKind = "pointer"
TypeKindVoidPointer TypeKind = "voidptr"
TypeKindVector TypeKind = "vector" // pointer + size_t
TypeKindVector32 TypeKind = "vector32" // pointer + uint32_t
TypeKindVoidVector TypeKind = "void_vector" // void* + size_t
TypeKindVoidVector32 TypeKind = "void_vector32" // void* + uint32_t
TypeKindHandle TypeKind = "handle"
)
func (kind TypeKind) IsPointerLike() bool {
return kind == TypeKindPointer || kind == TypeKindVoidPointer
}
func (kind TypeKind) IsVectorLike() bool {
return kind == TypeKindVector || kind == TypeKindVector32 || kind == TypeKindVoidVector || kind == TypeKindVoidVector32
}
func (kind TypeKind) IsVector32Like() bool {
return kind == TypeKindVector32 || kind == TypeKindVoidVector32
}
func (kind TypeKind) IsVoidVectorLike() bool {
return kind == TypeKindVoidVector || kind == TypeKindVoidVector32
}
// Const is a representation of a constant FIDL declaration.
type Const struct {
decl
// Kind is the kind of the constant's type.
Kind TypeKind
// Type is the FIDL type of the constant. If Kind is TypeKindEnum or
// TypeKindBits, then this field encodes a full declaration name of
// the associated enum or bits.
Type string
// Value is the constant's value in string form.
Value string
// Element gives whether this constant was defined in terms of another FIDL
// element.
Element *ConstElementValue
// Expression is the original FIDL expression given for the value,
// included only when it meaningfully differs from the value.
Expression string
}
func newConst(c fidlgen.Const, decls declMap, members memberMap) (*Const, error) {
var kind TypeKind
var typ string
switch c.Type.Kind {
case fidlgen.PrimitiveType:
if c.Type.PrimitiveSubtype.IsFloat() {
return nil, fmt.Errorf("floats are unsupported")
}
typ = string(c.Type.PrimitiveSubtype)
switch typ {
case string(fidlgen.Bool):
kind = TypeKindBool
case string(fidlgen.ZxExperimentalUsize64):
kind = TypeKindSize
default:
kind = TypeKindInteger
}
case fidlgen.StringType:
typ = string(fidlgen.StringType)
kind = TypeKindString
case fidlgen.IdentifierType:
typ = string(c.Type.Identifier)
switch decls[typ].(type) {
case *Enum:
kind = TypeKindEnum
case *Bits:
kind = TypeKindBits
default:
return nil, fmt.Errorf("%v has unsupported constant type: %s", c.Name, reflect.TypeOf(decls[typ]).Name())
}
default:
return nil, fmt.Errorf("%v has unsupported constant type: %s", c.Name, c.Type.Kind)
}
val := getConstantValue(c.Value, kind)
expr := c.Value.Expression
var elVal *ConstElementValue
switch c.Value.Kind {
case fidlgen.LiteralConstant:
// If the value matches the expression, there is no value in passing
// the latter along.
if expr == val || (kind == TypeKindString && expr == fmt.Sprintf("%q", val)) {
expr = ""
}
case fidlgen.IdentifierConstant:
valName, err := fidlgen.ReadName(string(c.Value.Identifier))
if err != nil {
return nil, err
}
declName, memberName := valName.SplitMember()
elVal = &ConstElementValue{
Decl: decls[declName.String()],
}
if memberName != "" {
elVal.Member = members[valName.String()]
}
// The original expression doesn't matter if it gave another identifier.
expr = ""
case fidlgen.BinaryOperator:
decl, ok := decls[typ]
if !ok {
break
}
elVal = &ConstElementValue{Decl: decl}
}
return &Const{
decl: newDecl(c),
Kind: kind,
Type: typ,
Value: val,
Element: elVal,
Expression: expr,
}, nil
}
// Gives the desired expression of a constant.
func getConstantValue(c fidlgen.Constant, kind TypeKind) string {
// In the integer case, the original expression conveys more
// information than the equivalent decimal value: if the author
// intended for the number to be understood in binary or hex, then the
// generated code should preserve that.
if c.Kind == fidlgen.LiteralConstant && (kind == TypeKindInteger || kind == TypeKindSize) {
return c.Expression
}
return c.Value
}
// ConstElementValue represents a constant value given by another FIDL element.
type ConstElementValue struct {
// Decl either gives either another constant or the parent layout of an
// enum or bits member.
Decl
// Member - if non-nil - gives the member of Decl() defining the associated
// constant.
Member
}
// Enum represents an FIDL enum declaration.
type Enum struct {
decl
// The primitive subtype underlying the Enum.
Subtype fidlgen.PrimitiveSubtype
// Members is the list of member values of the enum.
Members []EnumMember
}
// EnumMember represents a FIDL enum value.
type EnumMember struct {
member
// Value is the member's value.
Value string
// Expression is the original FIDL expression given for the value,
// included only when it meaningfully differs from the value.
Expression string
}
func newEnum(enum fidlgen.Enum) (*Enum, error) {
e := &Enum{
decl: newDecl(enum),
Subtype: enum.Type,
}
for _, member := range enum.Members {
val := getConstantValue(member.Value, TypeKindInteger)
// If the value matches the expression, there is no value in passing
// the latter along.
expr := member.Value.Expression
if expr == val {
expr = ""
}
e.Members = append(e.Members, EnumMember{
member: newMember(member),
Value: val,
Expression: expr,
})
}
return e, nil
}
// Bits represents an FIDL bitset declaration.
type Bits struct {
decl
// The primitive subtype underlying the bitset.
Subtype fidlgen.PrimitiveSubtype
// Members is the list of member values of the bitset.
Members []BitsMember
}
// BitsMember represents a FIDL enum value.
type BitsMember struct {
member
// Name is the name of the member.
Name string
// Index is the associated bit index.
Index int
}
func newBits(bits fidlgen.Bits) (*Bits, error) {
b := &Bits{
decl: newDecl(bits),
Subtype: bits.Type.PrimitiveSubtype,
}
for _, member := range bits.Members {
val, err := strconv.ParseUint(member.Value.Value, 10, 64)
if err != nil {
panic(fmt.Sprintf("%v member %s has bad value %q: %v", b.Name, member.Name, member.Value.Value, err))
}
b.Members = append(b.Members, BitsMember{
member: newMember(member),
Index: log2(val),
})
}
return b, nil
}
func log2(n uint64) int {
if bits.OnesCount64(n) != 1 {
panic(fmt.Sprintf("%d is not a power of two", n))
}
return bits.TrailingZeros64(n)
}
// TypeDescriptor gives a straightforward encoding of a type, accounting for
// any array nesting with a recursive pointer to a descriptor describing the
// element type.
type TypeDescriptor struct {
// Type gives the full name of the type, except in the case of an array:
// in that case, the array's element type is given by `.ElementType` and
// its size is given by `.ElementCount`.
Type string
// Decl gives the associated declaration, if one exists.
Decl Decl
// Kind is the kind of the type.
Kind TypeKind
// ElementType gives the underlying element type in the case of an array.
ElementType *TypeDescriptor
// ElementCount gives the size of the associated array.
ElementCount *int
// The wire size of the type.
Size int
// Mutable gives whether this type is mutable.
Mutable bool
}
// Represents an recursively-defined type, effectively abstracting
// fidlgen.Type and fidlgen.PartialTypeConstructor.
type recursiveType interface {
GetKind() fidlgen.TypeKind
// TODO(https://fxbug.dev/42057022): The presence of the declMap in the signature is
// to account for yet another type alias IR deficiency: the IR loses
// type information (e.g., size) about the value of the right-hand side, so
// we use the map to look up previously processed declarations to make a
// size determination.
GetSize(decls declMap) int
GetPrimitiveSubtype() fidlgen.PrimitiveSubtype
GetIdentifierType() fidlgen.EncodedCompoundIdentifier
GetElementType() recursiveType
GetElementCount() *int
}
// Resolves a recursively-defined type into a type descriptor. This process
// requires a map of previously processed declarations for consulting, as well
// as map of type kinds to record those seen during the resolution.
func resolveType(typ recursiveType, attrs fidlgen.Attributes, decls declMap, typeKinds map[TypeKind]struct{}) (*TypeDescriptor, error) {
desc := TypeDescriptor{Size: typ.GetSize(decls)}
switch typ.GetKind() {
case fidlgen.PrimitiveType:
if typ.GetPrimitiveSubtype().IsFloat() {
return nil, fmt.Errorf("floats are unsupported")
}
desc.Type = string(typ.GetPrimitiveSubtype())
switch desc.Type {
case string(fidlgen.Bool):
desc.Kind = TypeKindBool
case string(fidlgen.ZxExperimentalUsize64):
desc.Kind = TypeKindSize
default:
desc.Kind = TypeKindInteger
}
case fidlgen.StringType:
return nil, fmt.Errorf("strings are only supported as constants")
case fidlgen.IdentifierType:
desc.Type = string(typ.GetIdentifierType())
desc.Decl = decls[desc.Type]
switch desc.Decl.(type) {
case *Enum:
desc.Kind = TypeKindEnum
case *Bits:
desc.Kind = TypeKindBits
case *Struct:
desc.Kind = TypeKindStruct
case *Overlay:
desc.Kind = TypeKindOverlay
case *Alias:
desc.Kind = TypeKindAlias
default: // TODO(https://fxbug.dev/42057859): Skip if unknown.
return nil, nil
}
case fidlgen.ArrayType:
desc.Kind = TypeKindArray
desc.ElementCount = typ.GetElementCount()
nested, err := resolveType(typ.GetElementType(), fidlgen.Attributes{}, decls, typeKinds)
if err != nil {
return nil, err
}
if nested == nil { // TODO(https://fxbug.dev/42057859): Skip if unknown.
return nil, nil
}
desc.ElementType = nested
case fidlgen.StringArray:
desc.Kind = TypeKindStringArray
desc.ElementCount = typ.GetElementCount()
case fidlgen.ZxExperimentalPointerType, fidlgen.VectorType:
if typ.GetKind() == fidlgen.ZxExperimentalPointerType {
desc.Kind = TypeKindPointer
} else {
desc.Kind = TypeKindVector
}
// TODO(https://fxbug.dev/42057022): Temporary contriving of alias name currently
// lost in the IR.
//
// See the definition of @embedded_alias in //zircon/vdso/README.md for
// more detail.
elementType := typ.GetElementType()
if attr, ok := attrs.LookupAttribute("embedded_alias"); ok {
elementType = fidlgenType(fidlgen.Type{
Kind: fidlgen.IdentifierType,
Identifier: fidlgen.EncodedCompoundIdentifier(attr.Args[0].ValueString()),
})
}
nested, err := resolveType(elementType, fidlgen.Attributes{}, decls, typeKinds)
if err != nil {
return nil, err
}
if nested == nil { // TODO(https://fxbug.dev/42057859): Skip if unknown.
return nil, nil
}
desc.ElementType = nested
if attrs.HasAttribute("voidptr") {
if nested.Type != string(fidlgen.Uint8) {
return nil, fmt.Errorf("@voidptr may only annotate `experimental_pointer` for a pointee type of `uint8`")
}
if desc.Kind == TypeKindPointer {
desc.Kind = TypeKindVoidPointer
} else {
desc.Kind = TypeKindVoidVector
}
}
if attrs.HasAttribute("size32") {
if desc.Kind == TypeKindVector {
desc.Kind = TypeKindVector32
} else if desc.Kind == TypeKindVoidVector {
desc.Kind = TypeKindVoidVector32
} else {
return nil, fmt.Errorf("@size32 may only annotate `vector`s")
}
}
case fidlgen.HandleType:
desc.Kind = TypeKindHandle
desc.Type = string(typ.GetIdentifierType())
desc.Decl = decls[desc.Type]
default: // TODO(https://fxbug.dev/42057859): Skip if unknown.
return nil, nil
}
typeKinds[desc.Kind] = struct{}{}
return &desc, nil
}
// A thin wrapper implementing `recursiveType`.
type fidlgenType fidlgen.Type
func (typ fidlgenType) GetKind() fidlgen.TypeKind { return typ.Kind }
func (typ fidlgenType) GetSize(_ declMap) int { return typ.TypeShapeV2.InlineSize }
func (typ fidlgenType) GetPrimitiveSubtype() fidlgen.PrimitiveSubtype {
return typ.PrimitiveSubtype
}
func (typ fidlgenType) GetIdentifierType() fidlgen.EncodedCompoundIdentifier {
if typ.ResourceIdentifier != "" {
return fidlgen.EncodedCompoundIdentifier(typ.ResourceIdentifier)
}
return typ.Identifier
}
func (typ fidlgenType) GetElementType() recursiveType {
if typ.PointeeType != nil {
return fidlgenType(*typ.PointeeType)
}
return fidlgenType(*typ.ElementType)
}
func (typ fidlgenType) GetElementCount() *int { return typ.ElementCount }
// Struct represents a FIDL struct declaration.
type Struct struct {
decl
// Size is the size of the struct, including padding.
Size int
// Members is the list of the members of the layout.
Members []StructMember
// HasPadding gives whether the struct has any implicit padding on the wire.
HasPadding bool
// Whether the struct was formally declared; if false, it was synthesized
// or implicitly declared (as is in the case of a protocol method
// request/response payload).
synthesized bool
// TODO(https://fxbug.dev/42061412): wrappedReturn indicates that this struct defines
// the singleton, response body of a protocol method used to define a
// syscall, and further that the return type of that syscall is actually
// the type of the wrapped parameter. The reason for this workaround is
// that protocol methods must return a struct.
//
// See the definition of @wrapped_return in //zircon/vdso/README.md
wrappedReturn bool
}
// StructMember represents a FIDL struct member.
type StructMember struct {
member
// Type describes the type of the member.
Type TypeDescriptor
// Offset is the offset of the field.
Offset int
// TODO(https://fxbug.dev/42061412): The following attributes may annotate "syscall"
// request and response structs. While we have to synthesize syscall
// information manually, we record these values here during member
// processing for later syscall processing.
//
// See the definitions of @inout, @out, and @release in
// //zircon/vdso/README.md.
inout bool
out bool
release bool
}
func newStruct(strct fidlgen.Struct, decls declMap, typeKinds map[TypeKind]struct{}) (*Struct, error) {
s := &Struct{
decl: newDecl(strct),
Size: strct.TypeShapeV2.InlineSize,
HasPadding: strct.TypeShapeV2.HasPadding,
synthesized: strct.IsAnonymous(),
wrappedReturn: strct.GetAttributes().HasAttribute("wrapped_return"),
}
for _, member := range strct.Members {
attrs := member.GetAttributes()
// TODO(https://fxbug.dev/42057022): For struct members, we have the `MaybeAlias`
// field as a workaround for recovering any alias name.
memberType := recursiveType(fidlgenType(member.Type))
if member.MaybeAlias != nil {
memberType = recursiveType(fidlgenTypeCtor(*member.MaybeAlias))
}
typ, err := resolveType(memberType, attrs, decls, typeKinds)
if err != nil {
return nil, fmt.Errorf("%s.%s: failed to derive type: %w", s.Name, member.Name, err)
}
if typ == nil { // TODO(https://fxbug.dev/42057859): Skip if unknown.
continue
}
s.Members = append(s.Members, StructMember{
member: newMember(member),
Type: *typ,
Offset: member.FieldShapeV2.Offset,
// TODO(https://fxbug.dev/42061412): Eventually the IR will effectively
// surface this information directly in the syscall-related
// elements: at that point this can be removed.
inout: attrs.HasAttribute("inout"),
out: attrs.HasAttribute("out"),
release: attrs.HasAttribute("release"),
})
}
// Syscall parameters are encoded in synthesized/anonymous structs. While
// we do not want conventional bindings for these structs, we do want to
// make them available to later syscall processing: record them here but
// return nothing.
if s.synthesized {
decls[s.Name.String()] = s
return nil, nil
}
return s, nil
}
// Overlay represents a FIDL overlay declaration.
type Overlay struct {
decl
// MaxVariantSize is the maximum wire size of the overlay's variant types,
// including padding.
MaxVariantSize int
Variants []OverlayVariant
}
// Size gives the wire size of the overlay.
func (o Overlay) Size() int {
return 8 + o.MaxVariantSize // sizeof(discriminant) + max. variant size.
}
// OverlayVariant represents a FIDL overlay variant (i.e., member).
type OverlayVariant struct {
member
Type TypeDescriptor
// Discriminant is the ordinal uniquely identifying the variant.
Discriminant int
}
func newOverlay(overlay fidlgen.Overlay, decls declMap, typeKinds map[TypeKind]struct{}) (*Overlay, error) {
o := &Overlay{
decl: newDecl(overlay),
MaxVariantSize: overlay.TypeShapeV2.InlineSize - 8,
Variants: make([]OverlayVariant, len(overlay.Members)),
}
for i, member := range overlay.Members {
attrs := member.GetAttributes()
// TODO(https://fxbug.dev/42057022): For overlay members, we have the
// `MaybeAlias` field as a workaround for recovering any alias name.
memberType := recursiveType(fidlgenType(member.Type))
if member.MaybeAlias != nil {
memberType = recursiveType(fidlgenTypeCtor(*member.MaybeAlias))
}
typ, err := resolveType(memberType, attrs, decls, typeKinds)
if err != nil {
return nil, fmt.Errorf("%s.%s: failed to derive type: %w", o.Name, member.Name, err)
}
if err != nil || typ == nil {
return nil, fmt.Errorf("%s.%s: failed to derive type: %w", o.Name, member.Name, err)
}
o.Variants[i] = OverlayVariant{
member: newMember(member),
Type: *typ,
Discriminant: member.Ordinal,
}
}
return o, nil
}
// Alias represents a FIDL type alias declaration.
type Alias struct {
decl
// Value is the type under alias (i.e., the right-hand side of the
// declaration).
Value TypeDescriptor
}
func newAlias(alias fidlgen.Alias, decls declMap, typeKinds map[TypeKind]struct{}) (*Alias, error) {
unresolved := fidlgenTypeCtor(alias.PartialTypeConstructor)
typ, err := resolveType(unresolved, alias.GetAttributes(), decls, typeKinds)
if err != nil {
return nil, err
}
if typ == nil { // TODO(https://fxbug.dev/42057859): Skip if unknown.
return nil, nil
}
return &Alias{
decl: newDecl(alias),
Value: *typ,
}, nil
}
// An implementation of `recursiveType`.
type fidlgenTypeCtor fidlgen.PartialTypeConstructor
func (ctor fidlgenTypeCtor) GetKind() fidlgen.TypeKind {
if _, err := fidlgen.ReadName(string(ctor.Name)); err == nil {
return fidlgen.IdentifierType
}
switch string(ctor.Name) {
case string(fidlgen.Bool),
string(fidlgen.Int8),
string(fidlgen.Int16),
string(fidlgen.Int32),
string(fidlgen.Int64),
string(fidlgen.Uint8),
string(fidlgen.Uint16),
string(fidlgen.Uint32),
string(fidlgen.Uint64),
string(fidlgen.Float32),
string(fidlgen.Float64),
string(fidlgen.ZxExperimentalUchar),
string(fidlgen.ZxExperimentalUsize64),
string(fidlgen.ZxExperimentalUintptr64):
return fidlgen.PrimitiveType
case "array":
return fidlgen.ArrayType
default:
panic(fmt.Sprintf("unsupported type: %s", ctor.Name))
}
}
func (ctor fidlgenTypeCtor) GetSize(decls declMap) int {
switch ctor.GetKind() {
case fidlgen.IdentifierType:
d := decls[string(ctor.Name)]
switch d.(type) {
case *Enum:
return primitiveWireSize(d.(*Enum).Subtype)
case *Bits:
return primitiveWireSize(d.(*Bits).Subtype)
case *Struct:
return d.(*Struct).Size
case *Alias:
return d.(*Alias).Value.Size
case *Handle:
return 4
default:
// The above declarations are the only ones that should appear as
// an alias value.
panic(fmt.Sprintf("unknown alias value kind %s: %#v", ctor.GetKind(), ctor))
}
case fidlgen.PrimitiveType:
return primitiveWireSize(ctor.GetPrimitiveSubtype())
case fidlgen.ArrayType:
return *ctor.GetElementCount() * ctor.GetElementType().GetSize(decls)
default:
panic(fmt.Sprintf("unsupported type: %s", ctor.Name))
}
}
func (ctor fidlgenTypeCtor) GetPrimitiveSubtype() fidlgen.PrimitiveSubtype {
return fidlgen.PrimitiveSubtype(ctor.Name)
}
func (ctor fidlgenTypeCtor) GetIdentifierType() fidlgen.EncodedCompoundIdentifier {
return ctor.Name
}
func (ctor fidlgenTypeCtor) GetElementType() recursiveType {
if len(ctor.Args) == 0 {
return nil
}
// TODO(https://fxbug.dev/42156522): This list appears to always be empty or a
// singleton (and its unclear what further arguments would mean).
return fidlgenTypeCtor(ctor.Args[0])
}
func (ctor fidlgenTypeCtor) GetElementCount() *int {
if ctor.MaybeSize == nil {
return nil
}
count, err := strconv.Atoi(ctor.MaybeSize.Value)
if err != nil {
panic(fmt.Sprintf("could not interpret %s as an int", ctor.MaybeSize.Value))
}
return &count
}
func primitiveWireSize(typ fidlgen.PrimitiveSubtype) int {
switch typ {
case fidlgen.Bool, fidlgen.Int8, fidlgen.Uint8,
fidlgen.ZxExperimentalUchar:
return 1
case fidlgen.Int16, fidlgen.Uint16:
return 2
case fidlgen.Int32, fidlgen.Uint32:
return 4
case fidlgen.Int64, fidlgen.Uint64,
fidlgen.ZxExperimentalUintptr64, fidlgen.ZxExperimentalUsize64:
return 8
default:
panic(fmt.Sprintf("unknown primitive type: %s", typ))
}
}
// Handle represents a Zircon handle, as represented by a FIDL resource declaration.
//
// There is little value in modeling things in terms of generic resources, as
// the construction is already arbitrarily constrained to handle-like things
// and handles are currently the only use-case.
type Handle struct {
decl
}
func newHandle(resource fidlgen.Resource, typeKinds map[TypeKind]struct{}) (*Handle, error) {
name := fidlgen.MustReadName(string(resource.Name)).DeclarationName()
if name != "Handle" {
return nil, fmt.Errorf("resource declarations must be named \"Handle\", not %s", name)
}
typeKinds[TypeKindInteger] = struct{}{}
return &Handle{decl: newDecl(resource)}, nil
}
// SyscallFamily represents a logical family of syscalls, usually corresponding
// to a kernel object and the noun phrase namespacing in
// `zx_${noun_phrase}_${verb_phrase}`.
//
// The interpretation here relies upon the conventions detailed in
// //zircon/vdso/README.md.
type SyscallFamily struct {
decl
// Syscalls gives the syscalls that comprise the family.
Syscalls []Syscall
}
// SyscallCategory gives a categorization of syscalls.
type SyscallCategory string
const (
// SyscallCategoryBasic indicates the basic, unqualified category of
// syscalls (which is the majority).
SyscallCategoryBasic SyscallCategory = ""
// SyscallCategoryInternal indicates that the syscall is not part of public
// ABI.
//
// See the definition of @internal in //zircon/vdso/README.md.
SyscallCategoryInternal SyscallCategory = "internal"
// SyscallCategoryVdsocall indicates that the syscall is not a true
// syscall and is actually implemented within the vDSO.
//
// See the definition of @vdsocall in //zircon/vdso/README.md.
SyscallCategoryVdsoCall SyscallCategory = "vdsocall"
// SyscallCategoryNext indicates that the syscall is in the process of
// being stabilized and is not yet a part of public ABI.
//
// See the definition of @next in //zircon/vdso/README.md.
SyscallCategoryNext SyscallCategory = "next"
// SyscallCategoryTest1 is a category relevant only to testing.
//
// See the definition of @test_category1 in //zircon/vdso/README.md.
SyscallCategoryTest1 SyscallCategory = "test_category1"
// SyscallCategoryTest2 is a category relevant only to testing.
//
// See the definition of @test_category2 in //zircon/vdso/README.md.
SyscallCategoryTest2 SyscallCategory = "test_category2"
)
// Syscall corresponds to an individual syscall.
//
// `Name` includes the family namespacing of the syscall (i.e., "PortWait"
// instead of just "Wait").
type Syscall struct {
member
// Category gives the syscall's category.
Category SyscallCategory
// Blocking indicates a blocking call.
//
// See the definition of @blocking in //zircon/vdso/README.md.
Blocking bool
// NoReturn indicates that the sycall does not return.
//
// See the definition of @noreturn in //zircon/vdso/README.md.
NoReturn bool
// Const, which only applies to SyscallCategoryVdsoCall syscalls, indicates
// that the function is "const" in the sense of
// `__attribute__((__const__))`.
//
// See the definition of @const in //zircon/vdso/README.md.
Const bool
// Whether the syscall is defined solely for tests.
//
// See the definition of @testonly in //zircon/vdso/README.md.
Testonly bool
// Parameters gives the list of parameters in the C vDSO signature of the
// syscall in order.
Parameters []SyscallParameter
// ReturnType gives the return type of the C vDSO signature.
ReturnType *TypeDescriptor
}
func (syscall Syscall) IsInternal() bool { return syscall.Category == SyscallCategoryInternal }
func (syscall Syscall) IsVdsoCall() bool { return syscall.Category == SyscallCategoryVdsoCall }
func (syscall Syscall) IsNext() bool { return syscall.Category == SyscallCategoryNext }
func (syscall Syscall) IsTestCategory1() bool { return syscall.Category == SyscallCategoryTest1 }
func (syscall Syscall) IsTestCategory2() bool { return syscall.Category == SyscallCategoryTest2 }
// ParameterOrientation describes whether the parameter serves as input,
// output, or both.
type ParameterOrientation int
const (
ParameterOrientationIn ParameterOrientation = iota
ParameterOrientationOut
ParameterOrientationInOut
)
// ParameterTag gives additional, miscellaneous information about a syscall
// parameter, informing how it should be processed.
type ParameterTag int
const (
// ParameterTagDecayedFromVector gives whether this parameter derives
// from one given originally as a `vector`. This should only be the case
// when the associated parameter gives a 'pointer' or a 'length'.
ParameterTagDecayedFromVector ParameterTag = iota
// ParameterTagUncheckedHandle indicates that associated handle parameter
// may be left in a state (e.g., consumed) dependent on the success of the
// syscall.
ParameterTagUncheckedHandle
// ParameterTagReleasedHandle indicates that the associated handle
// parameter is consumed by its syscall.
ParameterTagReleasedHandle
)
// SyscallParameter represents a C syscall parameter.
type SyscallParameter struct {
member
// Type gives the type of the parameter.
Type TypeDescriptor
// Orientation gives whether this is an in, out, or in-out parameter.
Orientation ParameterOrientation
// Size is the size of the parameter.
Size int
// Tags gives additional metadata about the parameter.
Tags map[ParameterTag]struct{}
}
// HasTag checks whether the parameter has a given tag. This method is a safety
// and convenience accessor around SyscallParameter.Tags that handles the case
// of an uninitialized nil value.
func (param SyscallParameter) HasTag(tag ParameterTag) bool {
if param.Tags == nil {
return false
}
_, ok := param.Tags[tag]
return ok
}
// SetTag sets a given tag on the parameter. This method is a safety and
// convenience accessor around SyscallParameter.Tags that handles the case of
// an uninitialized nil value.
func (param *SyscallParameter) SetTag(tag ParameterTag) {
if param.Tags == nil {
param.Tags = make(map[ParameterTag]struct{})
}
param.Tags[tag] = struct{}{}
}
func (param SyscallParameter) IsStrictInput() bool {
return param.Orientation == ParameterOrientationIn
}
func (param SyscallParameter) IsStrictOutput() bool {
return param.Orientation == ParameterOrientationOut
}
func newSyscallFamily(protocol fidlgen.Protocol, decls declMap) (*SyscallFamily, error) {
if protocol.OverTransport() != "Syscall" {
return nil, nil
}
family := &SyscallFamily{decl: newDecl(protocol)}
// This gives whether the family protocol name actually meaningfully
// signals the official namespacing of the syscalls. If the attribute is
// present, the name is arbitrary and the syscall member names already
// include the proper namespacing.
//
// See @no_protocol_prefix in //zircon/vdso/README.md
_, noProtocolPrefix := protocol.LookupAttribute("no_protocol_prefix")
for _, method := range protocol.Methods {
syscall := Syscall{
member: newMember(method),
Category: SyscallCategoryBasic,
}
if !noProtocolPrefix {
syscall.member.Name = family.Name.DeclarationName() + syscall.member.Name
}
for _, cat := range []SyscallCategory{
SyscallCategoryInternal,
SyscallCategoryVdsoCall,
SyscallCategoryNext,
SyscallCategoryTest1,
SyscallCategoryTest2,
} {
if _, ok := method.LookupAttribute(fidlgen.Identifier(cat)); !ok {
continue
}
if syscall.Category != SyscallCategoryBasic {
return nil, fmt.Errorf("syscall %s cannot be annotated with both @%s and @%s", syscall.Name, syscall.Category, cat)
}
syscall.Category = cat
}
_, syscall.Blocking = method.LookupAttribute("blocking")
_, syscall.NoReturn = method.LookupAttribute("noreturn")
_, syscall.Const = method.LookupAttribute("const")
_, syscall.Testonly = method.LookupAttribute("testonly")
// @const must be paired with @vdsocall.
if syscall.Const && !syscall.IsVdsoCall() {
return nil, fmt.Errorf("annotation @const on syscall %s must be paired with @vdsocall", syscall.Name)
}
// @test_category{1,2} must be paired with @testonly
if (syscall.IsTestCategory1() || syscall.IsTestCategory2()) && !syscall.Testonly {
return nil, fmt.Errorf("annotation @%s on syscall %s must be paired with @testonly", syscall.Category, syscall.Name)
}
// If the method declares an error type, then that must be our C return
// type.
if method.ErrorType != nil {
var err error
syscall.ReturnType, err = resolveType(fidlgenType(*method.ErrorType), fidlgen.Attributes{}, decls, make(map[TypeKind]struct{}))
if err != nil {
return nil, err
}
// TODO(https://fxbug.dev/42057022, https://fxbug.dev/42065140): The name of an aliased
// error type does not yet survive into the IR (just the full
// resolution). So, to account for the major case of wanting to use
// `zx/Status` as an error type - while in its alias form - we
// hackily replace any int32 error specifications with a `zx/Status`
// int32 alias if present in the library.
//
// Once one of these bugs is fixed, this workaround can be removed.
if syscall.ReturnType.Kind == TypeKindInteger && syscall.ReturnType.Type == string(fidlgen.Int32) {
if status, ok := decls["zx/Status"]; ok {
int32Type := TypeDescriptor{
Type: string(fidlgen.Int32),
Kind: TypeKindInteger,
Size: 4,
}
if alias, ok := status.(*Alias); ok && alias.Value == int32Type {
syscall.ReturnType = &TypeDescriptor{
Type: "zx/Status",
Kind: TypeKindAlias,
Decl: status,
Size: 4,
}
}
}
}
}
// Aggregate parameters from both the request and response structs, as
// 'out' parameters may appear in the request struct. With request
// struct members first, the default ordering results in the desired
// order of parameters in the vDSO intertace.
processParams := func(typ fidlgen.Type, request bool) {
what := "request"
if !request {
what = "response"
}
if typ.Kind != fidlgen.IdentifierType {
panic(fmt.Sprintf("method %s type is not given by an indentifier", what))
}
ident, ok := decls[string(typ.Identifier)]
if !ok {
panic(fmt.Sprintf("unknown method %s type: %s", what, typ.Identifier))
}
s, ok := ident.(*Struct)
if !ok {
panic(fmt.Sprintf("method %s type %s is not a struct", what, typ.Identifier))
}
if s.wrappedReturn {
if request {
panic(fmt.Sprintf("@wrapped_return cannot annotate a request payload: %s", syscall.Name))
}
if syscall.ReturnType != nil {
panic(fmt.Sprintf("@wrapped_return cannot be paired with `error`: %s", syscall.Name))
}
if len(s.Members) != 1 {
panic(fmt.Sprintf("@wrapped_return must annotate a singleton struct: %s", syscall.Name))
}
syscall.ReturnType = &s.Members[0].Type
return
}
// If we are (a) processing the outputs, (b) the syscall has no
// return error type, and (c) we have formally declared the struct
// of outputs as a proper declaration, then that declared output
// struct should be regarded as the syscall's return type.
if !request && syscall.ReturnType == nil && !s.synthesized {
syscall.ReturnType = &TypeDescriptor{
Type: string(typ.Identifier),
Kind: TypeKindStruct,
Decl: ident,
Size: s.Size,
}
return
}
for _, m := range s.Members {
param := SyscallParameter{
member: m.member,
Type: m.Type,
Orientation: ParameterOrientationIn,
}
if !request || m.out {
param.Orientation = ParameterOrientationOut
} else if m.inout {
param.Orientation = ParameterOrientationInOut
}
kind := param.Type.Kind
if !param.IsStrictInput() && (kind.IsVectorLike() || kind.IsPointerLike()) {
param.Type.ElementType.Mutable = true
}
switch kind {
case TypeKindHandle, TypeKindPointer, TypeKindVector, TypeKindVector32:
if kind != TypeKindHandle && param.Type.ElementType.Kind != TypeKindHandle {
break
}
if method.GetAttributes().HasAttribute("handle_unchecked") {
param.SetTag(ParameterTagUncheckedHandle)
} else if m.release {
param.SetTag(ParameterTagReleasedHandle)
}
}
// Vector parameters decay into separate pointer and length
// parameters.
if kind.IsVectorLike() {
pointer := param
pointer.Type.Size = 8
pointer.SetTag(ParameterTagDecayedFromVector)
if kind.IsVoidVectorLike() {
pointer.Type.Kind = TypeKindVoidPointer
} else {
pointer.Type.Kind = TypeKindPointer
}
length := SyscallParameter{
member: m.member,
// We always regard lengths as inputs.
Orientation: ParameterOrientationIn,
}
length.SetTag(ParameterTagDecayedFromVector)
// Usually arrays with names that end in "s" are plurals,
// and usually arrays that refer to a plurality of objects
// deal in counts and not sizes. This is a cheesy
// heuristic, but it matches our reality close enough.
if strings.HasSuffix(param.Name, "s") {
length.Name = "num_" + length.Name
} else {
length.Name += "_size"
}
if kind.IsVector32Like() {
length.Type = TypeDescriptor{
Kind: TypeKindInteger,
Type: string(fidlgen.Uint32),
Size: 4,
}
} else {
length.Type = TypeDescriptor{
Kind: TypeKindSize,
Type: string(fidlgen.ZxExperimentalUsize64),
Size: 8,
}
}
syscall.Parameters = append(syscall.Parameters, pointer, length)
} else {
syscall.Parameters = append(syscall.Parameters, param)
}
}
}
if method.RequestPayload != nil {
processParams(*method.RequestPayload, true)
}
// `ValueType` gives the desired output struct when `error` is
// specified (with `ResponsePayload` giving a union of both that and
// the error type).
if method.ValueType != nil {
processParams(*method.ValueType, false)
} else if method.ResponsePayload != nil {
processParams(*method.ResponsePayload, false)
}
family.Syscalls = append(family.Syscalls, syscall)
}
return family, nil
}