blob: 86871fb89537124bc19ac7fcc80e85f01f4dc972 [file] [log] [blame]
// Copyright 2018 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_cpp
import (
"bytes"
"fmt"
"sort"
"strings"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
type Attributes struct {
fidlgen.Attributes
}
// Docs returns C++ documentation comments.
func (a Attributes) Docs() string {
var buf bytes.Buffer
for _, c := range a.DocComments() {
buf.WriteString("\n///")
buf.WriteString(c)
}
return buf.String()
}
type TypeShape struct {
fidlgen.TypeShape
}
func (ts TypeShape) MaxTotalSize() int {
return fidlAlign(ts.TypeShape.InlineSize) + fidlAlign(ts.TypeShape.MaxOutOfLine)
}
func (ts TypeShape) HasPointer() bool {
return ts.TypeShape.Depth > 0
}
type declKind namespacedEnumMember
type declKinds struct {
Bits declKind
Const declKind
Enum declKind
Protocol declKind
Service declKind
Struct declKind
Table declKind
Union declKind
}
// Kinds are the different kinds of FIDL declarations. They are used in
// header/impl templates to select the correct decl-specific template.
var Kinds = namespacedEnum(declKinds{}).(declKinds)
// A Kinded value is a declaration in FIDL, for which we would like to
// generate some corresponding C++ code.
type Kinded interface {
Kind() declKind
}
type familyKind namespacedEnumMember
type familyKinds struct {
// TrivialCopy identifies values for whom a copy is trivial (like integers)
TrivialCopy familyKind
// Reference identifies values with a non trivial copy for which we use a
// reference on the caller argument.
Reference familyKind
// String identifies string values for which we can use a const reference
// and for which we can optimize the field construction.
String familyKind
// Vector identifies vector values for which we can use a reference and for
// which we can optimize the field construction.
Vector familyKind
}
// FamilyKinds are general categories identifying what operation we should use
// to pass a value without a move (LLCPP). It also defines the way we should
// initialize a field.
var FamilyKinds = namespacedEnum(familyKinds{}).(familyKinds)
type typeKind namespacedEnumMember
type typeKinds struct {
Array typeKind
Vector typeKind
String typeKind
Handle typeKind
Request typeKind
Primitive typeKind
Bits typeKind
Enum typeKind
Const typeKind
Struct typeKind
Table typeKind
Union typeKind
Protocol typeKind
}
// TypeKinds are the kinds of C++ types (arrays, primitives, structs, ...).
var TypeKinds = namespacedEnum(typeKinds{}).(typeKinds)
type Type struct {
nameVariants
WirePointer bool
// Defines what operation we should use to pass a value without a move (LLCPP). It also
// defines the way we should initialize a field.
WireFamily familyKind
// NeedsDtor indicates whether this type needs to be destructed explicitely
// or not.
NeedsDtor bool
Kind typeKind
IsResource bool
Nullable bool
DeclarationName fidlgen.EncodedCompoundIdentifier
// Set iff IsArray || IsVector
ElementType *Type
// Valid iff IsArray
ElementCount int
InlineInEnvelope bool
}
// IsPrimitiveType returns true if this type is primitive.
func (t *Type) IsPrimitiveType() bool {
return t.Kind == TypeKinds.Primitive || t.Kind == TypeKinds.Bits || t.Kind == TypeKinds.Enum
}
// WireArgumentDeclaration returns the argument declaration for this type for the wire variant.
func (t *Type) WireArgumentDeclaration(n string) string {
switch t.WireFamily {
case FamilyKinds.TrivialCopy:
return t.String() + " " + n
case FamilyKinds.Reference, FamilyKinds.Vector:
return t.String() + "& " + n
case FamilyKinds.String:
return "const " + t.String() + "& " + n
default:
panic(fmt.Sprintf("Unknown wire family kind %v", t.WireFamily))
}
}
// WireInitMessage returns message field initialization for the wire variant.
func (t *Type) WireInitMessage(n string) string {
switch t.WireFamily {
case FamilyKinds.TrivialCopy:
return fmt.Sprintf("%s(%s)", n, n)
case FamilyKinds.Reference:
return fmt.Sprintf("%s(std::move(%s))", n, n)
case FamilyKinds.String:
return fmt.Sprintf("%s(%s)", n, n)
case FamilyKinds.Vector:
return fmt.Sprintf("%s(%s)", n, n)
default:
panic(fmt.Sprintf("Unknown wire family kind %v", t.WireFamily))
}
}
type namingContextKey = string
// toKey converts a naming context into a value that can be used as a map key
// (i.e. with equality), such that no different naming contexts produce the same
// key. A naming context is a stack of strings identifying the naming scopes
// that something is defined in; see naming-context in the IR and
// https://fuchsia.dev/fuchsia-src/contribute/governance/rfcs/0050_syntax_revamp?hl=en#layout-naming-contexts
// for details.
func toKey(idents []string) namingContextKey {
// relies on the fact that '$' is not a valid part of an identifier
return strings.Join(idents, "$")
}
// ScopedLayout represents the definition of a scoped name for an anonymous
// layout. It consists of the scoped name (defined within the parent layout),
// and the flattened name (defined at the top level)
type ScopedLayout struct {
scopedName stringNamePart
flattenedName nameVariants
}
func (s ScopedLayout) ScopedName() string {
return s.scopedName.String()
}
func (s ScopedLayout) FlattenedName() string {
return s.flattenedName.NoLeading()
}
type Member interface {
NameAndType() (string, Type)
}
type Root struct {
HandleTypes []string
Library fidlgen.LibraryIdentifier
Decls []Kinded
Dependencies []fidlgen.LibraryIdentifier
}
func (r *Root) declsOfKind(kind declKind) []Kinded {
ds := []Kinded{}
for _, d := range r.Decls {
if d.Kind() == kind {
ds = append(ds, d)
}
}
return ds
}
func (r *Root) Bits() []Kinded {
return r.declsOfKind(Kinds.Bits)
}
func (r *Root) Consts() []Kinded {
return r.declsOfKind(Kinds.Const)
}
func (r *Root) Enums() []Kinded {
return r.declsOfKind(Kinds.Enum)
}
func (r *Root) Protocols() []Kinded {
return r.declsOfKind(Kinds.Protocol)
}
func (r *Root) Services() []Kinded {
return r.declsOfKind(Kinds.Service)
}
func (r *Root) Structs() []Kinded {
return r.declsOfKind(Kinds.Struct)
}
func (r *Root) Tables() []Kinded {
return r.declsOfKind(Kinds.Table)
}
func (r *Root) Unions() []Kinded {
return r.declsOfKind(Kinds.Union)
}
func (r *Root) ProtocolsForTransport() func(string) []*Protocol {
return func(t string) []*Protocol {
var ps []*Protocol
for _, k := range r.Protocols() {
p := k.(*Protocol)
_, ok := p.Transports()[t]
if ok {
ps = append(ps, p)
}
}
return ps
}
}
func (r *Root) LegacyIncludeDir() string {
return formatLibraryLegacyPath(r.Library) + "/cpp"
}
func (r *Root) UnifiedIncludeDir() string {
return formatLibraryUnifiedPath(r.Library) + "/cpp"
}
// SingleComponentLibraryName returns if the FIDL library name only consists of
// a single identifier (e.g. "library foo;"). This is significant because the
// unified namespace and the natural namespace are identical when the library
// only has one component.
func (r *Root) SingleComponentLibraryName() bool {
return len(r.Library) == 1
}
// Namespace returns the C++ namespace for generated protocol types this FIDL
// library.
func (r *Root) Namespace() namespace {
switch currentVariant {
case noVariant:
fidlgen.TemplateFatalf("Called Root.Namespace() when currentVariant isn't set.\n")
case hlcppVariant:
return naturalNamespace(r.Library)
case unifiedVariant, wireVariant:
return unifiedNamespace(r.Library)
}
panic("not reached")
}
// Result holds information about error results on methods.
type Result struct {
ValueMembers []Parameter
ResultDecl nameVariants
ErrorDecl nameVariants
Error Type
ValueDecl name
ValueStructDecl nameVariants
ValueTupleDecl name
Value Type
}
func (r Result) ValueArity() int {
return len(r.ValueMembers)
}
var primitiveTypes = map[fidlgen.PrimitiveSubtype]string{
fidlgen.Bool: "bool",
fidlgen.Int8: "int8_t",
fidlgen.Int16: "int16_t",
fidlgen.Int32: "int32_t",
fidlgen.Int64: "int64_t",
fidlgen.Uint8: "uint8_t",
fidlgen.Uint16: "uint16_t",
fidlgen.Uint32: "uint32_t",
fidlgen.Uint64: "uint64_t",
fidlgen.Float32: "float",
fidlgen.Float64: "double",
}
// NameVariantsForPrimitive returns the C++ name of a FIDL primitive type.
func NameVariantsForPrimitive(val fidlgen.PrimitiveSubtype) nameVariants {
if t, ok := primitiveTypes[val]; ok {
return primitiveNameVariants(t)
}
panic(fmt.Sprintf("unknown primitive type: %v", val))
}
type identifierTransform bool
const (
keepPartIfReserved identifierTransform = false
changePartIfReserved identifierTransform = true
)
func libraryParts(library fidlgen.LibraryIdentifier, identifierTransform identifierTransform) []string {
parts := []string{}
for _, part := range library {
if identifierTransform == changePartIfReserved {
parts = append(parts, changeIfReserved(string(part), nsComponentContext))
} else {
parts = append(parts, string(part))
}
}
return parts
}
func formatLibraryPrefix(library fidlgen.LibraryIdentifier) string {
return formatLibrary(library, "_", keepPartIfReserved)
}
func formatLibraryLegacyPath(library fidlgen.LibraryIdentifier) string {
return formatLibrary(library, "/", keepPartIfReserved)
}
func formatLibraryUnifiedPath(library fidlgen.LibraryIdentifier) string {
return fmt.Sprintf("fidl/%s", formatLibrary(library, ".", keepPartIfReserved))
}
func formatLibraryPath(library fidlgen.LibraryIdentifier) string {
if currentVariant == wireVariant {
return formatLibraryUnifiedPath(library)
}
return formatLibraryLegacyPath(library)
}
func codingTableName(ident fidlgen.EncodedCompoundIdentifier) string {
ci := fidlgen.ParseCompoundIdentifier(ident)
return formatLibrary(ci.Library, "_", keepPartIfReserved) + "_" + string(ci.Name) + string(ci.Member)
}
type compiler struct {
symbolPrefix string
decls fidlgen.DeclInfoMap
library fidlgen.LibraryIdentifier
handleTypes map[fidlgen.HandleSubtype]struct{}
resultForUnion map[fidlgen.EncodedCompoundIdentifier]*Result
requestResponsePayload map[fidlgen.EncodedCompoundIdentifier]fidlgen.Struct
// anonymousChildren maps a layout (defined by its naming context key) to
// the anonymous layouts defined directly within that layout. We opt to flatten
// the naming context and use a map rather than a trie like structure for
// simplicity.
anonymousChildren map[namingContextKey][]ScopedLayout
}
func (c *compiler) isInExternalLibrary(ci fidlgen.CompoundIdentifier) bool {
if len(ci.Library) != len(c.library) {
return true
}
for i, part := range c.library {
if ci.Library[i] != part {
return true
}
}
return false
}
func (c *compiler) compileNameVariants(eci fidlgen.EncodedCompoundIdentifier) nameVariants {
ci := fidlgen.ParseCompoundIdentifier(eci)
declInfo, ok := c.decls[ci.EncodeDecl()]
if !ok {
panic(fmt.Sprintf("unknown identifier: %v", eci))
}
ctx := declContext(declInfo.Type)
name := ctx.transform(ci) // Note: does not handle ci.Member
if len(ci.Member) == 0 {
return name
}
member := memberNameContext(declInfo.Type).transform(ci.Member)
return name.nestVariants(member)
}
func (c *compiler) compileCodingTableType(eci fidlgen.EncodedCompoundIdentifier) string {
return fmt.Sprintf("%s_%sTable", c.symbolPrefix, fidlgen.ParseCompoundIdentifier(eci).Name)
}
func (c *compiler) compileType(val fidlgen.Type) Type {
r := Type{}
r.Nullable = val.Nullable
r.InlineInEnvelope = val.TypeShapeV2.InlineSize <= 4
switch val.Kind {
case fidlgen.ArrayType:
t := c.compileType(*val.ElementType)
// Because the unified bindings alias types from the natural domain objects,
// the name _transformation_ would be identical between natural and unified,
// here and below. We reserve the flexibility to specify different names
// in the future.
r.nameVariants = nameVariants{
HLCPP: makeName("std::array").arrayTemplate(t.HLCPP, *val.ElementCount),
Unified: makeName("std::array").arrayTemplate(t.Unified, *val.ElementCount),
Wire: makeName("fidl::Array").arrayTemplate(t.Wire, *val.ElementCount),
}
r.WirePointer = t.WirePointer
r.WireFamily = FamilyKinds.Reference
r.NeedsDtor = true
r.Kind = TypeKinds.Array
r.IsResource = t.IsResource
r.ElementType = &t
r.ElementCount = *val.ElementCount
case fidlgen.VectorType:
t := c.compileType(*val.ElementType)
if val.Nullable {
r.nameVariants.HLCPP = makeName("fidl::VectorPtr").template(t.HLCPP)
r.nameVariants.Unified = makeName("fidl::VectorPtr").template(t.Unified)
} else {
r.nameVariants.HLCPP = makeName("std::vector").template(t.HLCPP)
r.nameVariants.Unified = makeName("std::vector").template(t.Unified)
}
r.nameVariants.Wire = makeName("fidl::VectorView").template(t.Wire)
r.WireFamily = FamilyKinds.Vector
r.WirePointer = t.WirePointer
r.NeedsDtor = true
r.Kind = TypeKinds.Vector
r.IsResource = t.IsResource
r.ElementType = &t
case fidlgen.StringType:
if val.Nullable {
r.HLCPP = makeName("fidl::StringPtr")
} else {
r.HLCPP = makeName("std::string")
}
r.Unified = r.HLCPP
r.Wire = makeName("fidl::StringView")
r.WireFamily = FamilyKinds.String
r.NeedsDtor = true
r.Kind = TypeKinds.String
case fidlgen.HandleType:
c.handleTypes[val.HandleSubtype] = struct{}{}
r.nameVariants = nameVariantsForHandle(val.HandleSubtype)
r.WireFamily = FamilyKinds.Reference
r.NeedsDtor = true
r.Kind = TypeKinds.Handle
r.IsResource = true
case fidlgen.RequestType:
p := c.compileNameVariants(val.RequestSubtype)
r.nameVariants = nameVariants{
HLCPP: makeName("fidl::InterfaceRequest").template(p.HLCPP),
Unified: makeName("fidl::InterfaceRequest").template(p.Unified),
Wire: makeName("fidl::ServerEnd").template(p.Wire),
}
r.WireFamily = FamilyKinds.Reference
r.NeedsDtor = true
r.Kind = TypeKinds.Request
r.IsResource = true
case fidlgen.PrimitiveType:
r.nameVariants = NameVariantsForPrimitive(val.PrimitiveSubtype)
r.WireFamily = FamilyKinds.TrivialCopy
r.Kind = TypeKinds.Primitive
case fidlgen.IdentifierType:
name := c.compileNameVariants(val.Identifier)
declInfo, ok := c.decls[val.Identifier]
if !ok {
panic(fmt.Sprintf("unknown identifier: %v", val.Identifier))
}
declType := declInfo.Type
if declType == fidlgen.ProtocolDeclType {
r.nameVariants = nameVariants{
HLCPP: makeName("fidl::InterfaceHandle").template(name.HLCPP),
Unified: makeName("fidl::InterfaceHandle").template(name.Unified),
Wire: makeName("fidl::ClientEnd").template(name.Wire),
}
r.WireFamily = FamilyKinds.Reference
r.NeedsDtor = true
r.Kind = TypeKinds.Protocol
r.IsResource = true
} else {
switch declType {
case fidlgen.BitsDeclType:
r.Kind = TypeKinds.Bits
r.WireFamily = FamilyKinds.TrivialCopy
case fidlgen.EnumDeclType:
r.Kind = TypeKinds.Enum
r.WireFamily = FamilyKinds.TrivialCopy
case fidlgen.ConstDeclType:
r.Kind = TypeKinds.Const
r.WireFamily = FamilyKinds.Reference
case fidlgen.StructDeclType:
r.Kind = TypeKinds.Struct
r.DeclarationName = val.Identifier
r.WireFamily = FamilyKinds.Reference
r.WirePointer = val.Nullable
r.IsResource = declInfo.IsResourceType()
case fidlgen.TableDeclType:
r.Kind = TypeKinds.Table
r.DeclarationName = val.Identifier
r.WireFamily = FamilyKinds.Reference
r.WirePointer = val.Nullable
r.IsResource = declInfo.IsResourceType()
case fidlgen.UnionDeclType:
r.Kind = TypeKinds.Union
r.DeclarationName = val.Identifier
r.WireFamily = FamilyKinds.Reference
r.IsResource = declInfo.IsResourceType()
default:
panic(fmt.Sprintf("unknown declaration type: %v", declType))
}
if val.Nullable {
r.nameVariants.HLCPP = makeName("std::unique_ptr").template(name.HLCPP)
r.nameVariants.Unified = makeName("std::unique_ptr").template(name.Unified)
if declType == fidlgen.UnionDeclType {
r.nameVariants.Wire = name.Wire
} else {
r.nameVariants.Wire = makeName("fidl::ObjectView").template(name.Wire)
}
r.NeedsDtor = true
} else {
r.nameVariants = name
r.NeedsDtor = true
}
}
default:
panic(fmt.Sprintf("unknown type kind: %v", val.Kind))
}
return r
}
func (c *compiler) getAnonymousChildren(layout fidlgen.Layout) []ScopedLayout {
return c.anonymousChildren[toKey(layout.NamingContext)]
}
func compile(r fidlgen.Root) *Root {
root := Root{
Library: fidlgen.ParseLibraryName(r.Name),
}
c := compiler{
symbolPrefix: formatLibraryPrefix(root.Library),
decls: r.DeclsWithDependencies(),
library: fidlgen.ParseLibraryName(r.Name),
handleTypes: make(map[fidlgen.HandleSubtype]struct{}),
resultForUnion: make(map[fidlgen.EncodedCompoundIdentifier]*Result),
requestResponsePayload: make(map[fidlgen.EncodedCompoundIdentifier]fidlgen.Struct),
anonymousChildren: make(map[namingContextKey][]ScopedLayout),
}
addAnonymousLayouts := func(layout fidlgen.Layout) {
if !layout.IsAnonymous() {
return
}
// given a naming context ["foo", "bar", "baz"], we mark that the layout
// at context ["foo", "bar"] has a child "baz"
key := toKey(layout.NamingContext[:len(layout.NamingContext)-1])
c.anonymousChildren[key] = append(c.anonymousChildren[key], ScopedLayout{
// TODO(fxbug.dev/60240): change this when other bindings use name transforms
scopedName: stringNamePart(fidlgen.ToUpperCamelCase(layout.NamingContext[len(layout.NamingContext)-1])),
flattenedName: c.compileNameVariants(layout.GetName()),
})
}
for _, v := range r.Bits {
addAnonymousLayouts(v.Layout)
}
for _, v := range r.Enums {
addAnonymousLayouts(v.Layout)
}
for _, v := range r.Unions {
addAnonymousLayouts(v.Layout)
}
for _, v := range r.Tables {
addAnonymousLayouts(v.Layout)
}
for _, v := range r.Structs {
addAnonymousLayouts(v.Layout)
}
decls := make(map[fidlgen.EncodedCompoundIdentifier]Kinded)
for _, v := range r.Bits {
decls[v.Name] = c.compileBits(v)
}
for _, v := range r.Consts {
decls[v.Name] = c.compileConst(v)
}
for _, v := range r.Enums {
decls[v.Name] = c.compileEnum(v)
}
// Note: for results calculation, we must first compile unions, and structs.
for _, v := range r.Unions {
decls[v.Name] = c.compileUnion(v)
}
for _, v := range r.Structs {
if v.IsRequestOrResponse {
c.requestResponsePayload[v.Name] = v
}
decls[v.Name] = c.compileStruct(v)
}
for _, v := range r.Protocols {
for _, m := range v.Methods {
if m.HasError {
var s *Struct
valueTypeDecl, ok := decls[m.ValueType.Identifier]
if ok {
s = valueTypeDecl.(*Struct)
} else {
// If we are unable to look up the struct, this implies that
// this is an externally defined struct. In this case, the
// IR exposes the declaration.
s = c.compileStruct(*m.ValueStruct)
}
result := c.compileResult(s, &m)
if ok {
s.Result = result
if resultTypeDecl, ok := decls[m.ResultType.Identifier]; !ok {
panic(fmt.Sprintf("success struct %s in library, but result union %s is not",
m.ValueType.Identifier, m.ResultType.Identifier))
} else {
u := resultTypeDecl.(*Union)
u.Result = result
}
}
}
}
}
for _, v := range r.Tables {
decls[v.Name] = c.compileTable(v)
}
for _, v := range r.Protocols {
decls[v.Name] = c.compileProtocol(v)
}
for _, v := range r.Services {
decls[v.Name] = c.compileService(v)
}
for _, v := range r.DeclOrder {
// We process only a subset of declarations mentioned in the declaration
// order, ignore those we do not support.
if d, known := decls[v]; known {
root.Decls = append(root.Decls, d)
}
}
for _, l := range r.Libraries {
if l.Name == r.Name {
// We don't need to include our own header.
continue
}
root.Dependencies = append(root.Dependencies, fidlgen.ParseLibraryName(l.Name))
}
// zx::channel is always referenced by the protocols in llcpp bindings API
if len(r.Protocols) > 0 {
c.handleTypes["channel"] = struct{}{}
}
// find all unique handle types referenced by the library
var handleTypes []string
for k := range c.handleTypes {
handleTypes = append(handleTypes, string(k))
}
sort.Sort(sort.StringSlice(handleTypes))
root.HandleTypes = handleTypes
return &root
}
func compileFor(r fidlgen.Root, n string) *Root {
return compile(r.ForBindings(n))
}