blob: 8df02d9dbfacf3c802ae5cafe3409073e5e0d209 [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 cpp
import (
"fmt"
"log"
"sort"
"strings"
"fidl/compiler/backend/common"
"fidl/compiler/backend/types"
)
// This value needs to be kept in sync with the one defined in
// zircon/system/ulib/fidl/include/lib/fidl/llcpp/sync_call.h
const llcppMaxStackAllocSize = 512
// These are used in header/impl templates to select the correct type-specific template
type bitsKind struct{}
type constKind struct{}
type enumKind struct{}
type protocolKind struct{}
type serviceKind struct{}
type structKind struct{}
type tableKind struct{}
type unionKind struct{}
var Kinds = struct {
Const constKind
Bits bitsKind
Enum enumKind
Protocol protocolKind
Service serviceKind
Struct structKind
Table tableKind
Union unionKind
}{}
type Decl interface{}
type FamilyKind string
const (
// TrivialCopy identifies values for whom a copy is trivial (like integers)
TrivialCopy FamilyKind = "TrivialCopy"
// Reference identifies values with a non trivial copy for which we use a reference on the
// caller argument.
Reference FamilyKind = "Reference"
// String identifies string values for which we can use a const reference and for which we can
// optimize the field construction.
String FamilyKind = "String"
// Vector identifies vector values for which we can use a reference and for which we can
// optimize the field construction.
Vector FamilyKind = "Vector"
)
type Type struct {
// Use Type.Decl when you want to _declare_ a class/struct, e.g. "class Foo { … }". If you need
// to reference a class by its name (e.g. "new Foo"), use the Type.Identifier() method instead.
// Identifier() will add a type qualifier to the class name so that the compiler will resolve
// the class, even if any locally non-type declarations are present (e.g. "enum Foo"). Google
// for "C++ elaborated type specifier" for more details.
Decl string
FullDecl string // Decl but with full type name
LLDecl string
// Defines what operation we should use to pass a value without a move (LLCPP). It also
// defines the way we should initialize a field.
LLFamily FamilyKind
// NeedsDtor indicates whether this type needs to be destructed explicitely
// or not.
NeedsDtor bool
DeclType types.DeclType
IsPrimitive bool
IsArray bool
IsVector bool
IsTable bool
// Set iff IsArray || IsVector
ElementType *Type
// Valid iff IsArray
ElementCount int
}
func (t Type) Identifier() string {
// TODO(FIDL-762): The logic to determine whether the type qualifier is necessary in this method
// probably isn't correct in all cases due to the complexity of C++'s grammar rules, and could
// be improved.
// Don't prepend type qualifiers to fully-qualified class names, which will begin with "::"
// (e.g. "::fidl::namespace:ClassName"): they can't be hidden by local declarations.
if t.IsPrimitive || strings.HasPrefix(t.Decl, "::") {
return t.Decl
}
return "class " + t.Decl
}
type Const struct {
types.Attributes
Extern bool
Decorator string
Type Type
Name string
Value string
Kind constKind
}
type Bits struct {
types.Attributes
Namespace string
Type string
Name string
Mask string
MaskName string
Members []BitsMember
Kind bitsKind
}
type BitsMember struct {
types.Attributes
Name string
Value string
}
type Enum struct {
types.Attributes
Namespace string
Type string
Name string
Members []EnumMember
Kind enumKind
}
type EnumMember struct {
types.Attributes
Name string
Value string
}
type Union struct {
types.Attributes
Namespace string
Name string
TableType string
Members []UnionMember
InlineSize int
MaxHandles int
MaxOutOfLine int
Result *Result
HasPointer bool
IsResource bool
Kind unionKind
types.Strictness
}
type UnionMember struct {
types.Attributes
Ordinal uint64
Type Type
Name string
StorageName string
TagName string
Offset int
}
func (um UnionMember) UpperCamelCaseName() string {
return common.ToUpperCamelCase(um.Name)
}
type TableFrameItem struct {
LLDecl string // "void" if reserved
Name string
}
type Table struct {
types.Attributes
Namespace string
Name string
TableType string
Members []TableMember
InlineSize int
BiggestOrdinal int
MaxHandles int
MaxOutOfLine int
HasPointer bool
IsResource bool
Kind tableKind
// Types of the members in ordinal order, "void" for reserved.
FrameItems []TableFrameItem
}
type TableMember struct {
types.Attributes
Type Type
Name string
DefaultValue string
Ordinal int
FieldPresenceName string
FieldDataName string
MethodHasName string
MethodClearName string
ValueUnionName string
}
type Struct struct {
types.Attributes
Namespace string
Name string
TableType string
Members []StructMember
InlineSize int
MaxHandles int
MaxOutOfLine int
HasPadding bool
IsResultValue bool
HasPointer bool
IsResource bool
Result *Result
Kind structKind
// Full decls needed to check if a type is memcpy compatible.
// Only set if it may be possible for a type to be memcpy compatible,
// e.g. has no padding.
// See the struct template for usage.
FullDeclMemcpyCompatibleDeps []string
}
type StructMember struct {
types.Attributes
Type Type
Name string
DefaultValue string
Offset int
}
type Protocol struct {
types.Attributes
Namespace string
Name string
ClassName string
ServiceName string
ProxyName string
StubName string
EventSenderName string
SyncName string
SyncProxyName string
RequestEncoderName string
RequestDecoderName string
ResponseEncoderName string
ResponseDecoderName string
Methods []Method
HasEvents bool
Kind protocolKind
}
type Service struct {
types.Attributes
Namespace string
Name string
ServiceName string
Members []ServiceMember
Kind serviceKind
}
type ServiceMember struct {
types.Attributes
ProtocolType string
Name string
MethodName string
}
// TODO: There are common fields between Request and Response; consider factoring them out.
type Method struct {
types.Attributes
Name string
NameInLowerSnakeCase string
Ordinal uint64
// The name of a constant that defines the ordinal value.
OrdinalName string
HasRequest bool
Request []Parameter
RequestSize int
RequestTypeName string
RequestMaxHandles int
RequestMaxOutOfLine int
RequestPadding bool
RequestFlexible bool
RequestHasPointer bool
RequestIsResource bool
HasResponse bool
Response []Parameter
ResponseSize int
ResponseTypeName string
ResponseMaxHandles int
ResponseMaxOutOfLine int
ResponsePadding bool
ResponseFlexible bool
ResponseHasPointer bool
ResponseIsResource bool
CallbackType string
ResponseHandlerType string
ResponderType string
Transitional bool
Result *Result
LLProps LLProps
}
// LLContextProps contain context-dependent properties of a method specific to llcpp.
// Context is client (write request and read response) or server (read request and write response).
type LLContextProps struct {
// Should the request be allocated on the stack, in the managed flavor.
StackAllocRequest bool
// Should the response be allocated on the stack, in the managed flavor.
StackAllocResponse bool
// Total number of bytes of stack used for storing the request.
StackUseRequest int
// Total number of bytes of stack used for storing the response.
StackUseResponse int
}
// LLProps contain properties of a method specific to llcpp
type LLProps struct {
ProtocolName string
LinearizeRequest bool
LinearizeResponse bool
ClientContext LLContextProps
ServerContext LLContextProps
}
type Parameter struct {
Type Type
Name string
Offset int
}
type Root struct {
PrimaryHeader string
Headers []string
LLHeaders []string
LFHeaders []string
HandleTypes []string
Library types.LibraryIdentifier
LibraryReversed types.LibraryIdentifier
Decls []Decl
}
// Holds information about error results on methods
type Result struct {
ValueMembers []StructMember
ResultDecl string
ErrorDecl string
ValueDecl string
ValueStructDecl string
ValueTupleDecl string
}
func (r Result) ValueArity() int {
return len(r.ValueMembers)
}
func (m *Method) CallbackWrapper() string {
return "fit::function"
}
var reservedWords = map[string]bool{
"alignas": true,
"alignof": true,
"and": true,
"and_eq": true,
"asm": true,
"assert": true,
"atomic_cancel": true,
"atomic_commit": true,
"atomic_noexcept": true,
"auto": true,
"bitand": true,
"bitor": true,
"bool": true,
"break": true,
"case": true,
"catch": true,
"char": true,
"char16_t": true,
"char32_t": true,
"class": true,
"compl": true,
"concept": true,
"const": true,
"constexpr": true,
"const_cast": true,
"continue": true,
"co_await": true,
"co_return": true,
"co_yield": true,
"decltype": true,
"default": true,
"delete": true,
"do": true,
"double": true,
"dynamic_cast": true,
"else": true,
"enum": true,
"explicit": true,
"export": true,
"extern": true,
"false": true,
"float": true,
"for": true,
"friend": true,
"goto": true,
"if": true,
"import": true,
"inline": true,
"int": true,
"long": true,
"module": true,
"mutable": true,
"namespace": true,
"new": true,
"noexcept": true,
"not": true,
"not_eq": true,
"NULL": true,
"nullptr": true,
"offsetof": true,
"operator": true,
"or": true,
"or_eq": true,
"private": true,
"protected": true,
"public": true,
"register": true,
"reinterpret_cast": true,
"requires": true,
"return": true,
"short": true,
"signed": true,
"sizeof": true,
"static": true,
"static_assert": true,
"static_cast": true,
"struct": true,
"switch": true,
"synchronized": true,
"template": true,
"this": true,
"thread_local": true,
"throw": true,
"true": true,
"try": true,
"typedef": true,
"typeid": true,
"typename": true,
"union": true,
"unsigned": true,
"using": true,
"virtual": true,
"void": true,
"volatile": true,
"wchar_t": true,
"while": true,
"xor": true,
"xor_eq": true,
"xunion": true,
// names used in specific contexts e.g. union accessors
"FidlType": true,
"New": true,
"Tag": true,
"Which": true,
"has_invalid_tag": true,
"which": true,
"Unknown": true,
"unknown": true,
"IsEmpty": true,
"HandleEvents": true,
// TODO(ianloic) add: "Clone"
// There are Clone methods on a couple of protocols that are used
// across layers so this will be a breaking change.
// FIDL-461
}
var primitiveTypes = map[types.PrimitiveSubtype]string{
types.Bool: "bool",
types.Int8: "int8_t",
types.Int16: "int16_t",
types.Int32: "int32_t",
types.Int64: "int64_t",
types.Uint8: "uint8_t",
types.Uint16: "uint16_t",
types.Uint32: "uint32_t",
types.Uint64: "uint64_t",
types.Float32: "float",
types.Float64: "double",
}
func isReservedWord(str string) bool {
_, ok := reservedWords[str]
return ok
}
func changeIfReserved(i types.Identifier, ext string) string {
str := string(i) + ext
if isReservedWord(str) {
return str + "_"
}
return str
}
func formatLibrary(library types.LibraryIdentifier, sep string) string {
parts := []string{}
for _, part := range library {
parts = append(parts, string(part))
}
name := strings.Join(parts, sep)
return changeIfReserved(types.Identifier(name), "")
}
func formatNamespace(library types.LibraryIdentifier, appendNamespace string) string {
ns := "::" + formatLibrary(library, "::")
if len(appendNamespace) > 0 {
ns = ns + "::" + appendNamespace
}
return ns
}
func formatLLNamespace(library types.LibraryIdentifier, appendNamespace string) string {
// Avoid user-defined llcpp library colliding with the llcpp namespace, by appending underscore.
if len(library) > 0 && library[0] == "llcpp" {
libraryRenamed := make([]types.Identifier, len(library))
copy(libraryRenamed, library)
libraryRenamed[0] = "llcpp_"
library = libraryRenamed
}
return "::llcpp" + formatNamespace(library, appendNamespace)
}
func formatLibraryPrefix(library types.LibraryIdentifier) string {
return formatLibrary(library, "_")
}
func formatLibraryPath(library types.LibraryIdentifier) string {
return formatLibrary(library, "/")
}
type compiler struct {
namespace string
symbolPrefix string
decls types.DeclMap
library types.LibraryIdentifier
handleTypes map[types.HandleSubtype]bool
namespaceFormatter func(types.LibraryIdentifier, string) string
resultForStruct map[types.EncodedCompoundIdentifier]*Result
resultForUnion map[types.EncodedCompoundIdentifier]*Result
}
func (c *compiler) isInExternalLibrary(ci types.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) compileCompoundIdentifier(eci types.EncodedCompoundIdentifier, ext, appendNamespace string, fullName bool) string {
val := types.ParseCompoundIdentifier(eci)
strs := []string{}
if fullName || c.isInExternalLibrary(val) {
strs = append(strs, c.namespaceFormatter(val.Library, appendNamespace))
}
strs = append(strs, changeIfReserved(val.Name, ext))
if len(val.Member) != 0 {
strs = append(strs, changeIfReserved(val.Member, ext))
}
return strings.Join(strs, "::")
}
func (c *compiler) compileTableType(eci types.EncodedCompoundIdentifier) string {
val := types.ParseCompoundIdentifier(eci)
if c.isInExternalLibrary(val) {
log.Fatal("Can't create table type for external identifier: ", val)
}
return fmt.Sprintf("%s_%sTable", c.symbolPrefix, val.Name)
}
func (c *compiler) compileLiteral(val types.Literal, typ types.Type) string {
switch val.Kind {
case types.StringLiteral:
return fmt.Sprintf("%q", val.Value)
case types.NumericLiteral:
if val.Value == "-9223372036854775808" || val.Value == "0x8000000000000000" {
// C++ only supports nonnegative literals and a value this large in absolute
// value cannot be represented as a nonnegative number in 64-bits.
return "(-9223372036854775807ll-1)"
}
// TODO(FIDL-486): Once we expose resolved constants for defaults, e.g.
// in structs, we will not need ignore hex and binary values.
if strings.HasPrefix(val.Value, "0x") || strings.HasPrefix(val.Value, "0b") {
return val.Value
}
// No special handling of floats.
if strings.ContainsRune(val.Value, '.') {
return val.Value
}
if typ.Kind == types.PrimitiveType &&
(typ.PrimitiveSubtype == types.Float32 || typ.PrimitiveSubtype == types.Float64) {
return val.Value
}
if !strings.HasPrefix(val.Value, "-") {
return fmt.Sprintf("%su", val.Value)
}
return val.Value
case types.TrueLiteral:
return "true"
case types.FalseLiteral:
return "false"
case types.DefaultLiteral:
return "default"
default:
log.Fatal("Unknown literal kind: ", val.Kind)
return ""
}
}
func (c *compiler) compileConstant(val types.Constant, t *Type, typ types.Type, appendNamespace string) string {
switch val.Kind {
case types.IdentifierConstant:
return c.compileCompoundIdentifier(val.Identifier, "", appendNamespace, false)
case types.LiteralConstant:
return c.compileLiteral(val.Literal, typ)
default:
log.Fatal("Unknown constant kind: ", val.Kind)
return ""
}
}
func (c *compiler) compilePrimitiveSubtype(val types.PrimitiveSubtype) string {
if t, ok := primitiveTypes[val]; ok {
return t
}
log.Fatal("Unknown primitive type: ", val)
return ""
}
func (c *compiler) compileType(val types.Type) Type {
r := Type{}
switch val.Kind {
case types.ArrayType:
t := c.compileType(*val.ElementType)
r.Decl = fmt.Sprintf("::std::array<%s, %v>", t.Decl, *val.ElementCount)
r.FullDecl = fmt.Sprintf("::std::array<%s, %v>", t.FullDecl, *val.ElementCount)
r.LLDecl = fmt.Sprintf("::fidl::Array<%s, %v>", t.LLDecl, *val.ElementCount)
r.LLFamily = Reference
r.NeedsDtor = true
r.IsArray = true
r.ElementType = &t
r.ElementCount = *val.ElementCount
case types.VectorType:
t := c.compileType(*val.ElementType)
r.LLDecl = fmt.Sprintf("::fidl::VectorView<%s>", t.LLDecl)
r.LLFamily = Vector
if val.Nullable {
r.Decl = fmt.Sprintf("::fidl::VectorPtr<%s>", t.Decl)
r.FullDecl = fmt.Sprintf("::fidl::VectorPtr<%s>", t.FullDecl)
} else {
r.Decl = fmt.Sprintf("::std::vector<%s>", t.Decl)
r.FullDecl = fmt.Sprintf("::std::vector<%s>", t.FullDecl)
}
r.NeedsDtor = true
r.IsVector = true
r.ElementType = &t
case types.StringType:
r.LLDecl = "::fidl::StringView"
r.LLFamily = String
if val.Nullable {
r.Decl = "::fidl::StringPtr"
} else {
r.Decl = "::std::string"
}
r.FullDecl = r.Decl
r.NeedsDtor = true
case types.HandleType:
c.handleTypes[val.HandleSubtype] = true
r.Decl = fmt.Sprintf("::zx::%s", val.HandleSubtype)
r.FullDecl = r.Decl
r.LLDecl = r.Decl
r.LLFamily = Reference
r.NeedsDtor = true
case types.RequestType:
r.Decl = fmt.Sprintf("::fidl::InterfaceRequest<%s>",
c.compileCompoundIdentifier(val.RequestSubtype, "", "", false))
r.FullDecl = fmt.Sprintf("::fidl::InterfaceRequest<%s>",
c.compileCompoundIdentifier(val.RequestSubtype, "", "", true))
r.LLDecl = "::zx::channel"
r.LLFamily = Reference
r.NeedsDtor = true
case types.PrimitiveType:
r.Decl = c.compilePrimitiveSubtype(val.PrimitiveSubtype)
r.FullDecl = r.Decl
r.LLDecl = r.Decl
r.LLFamily = TrivialCopy
r.IsPrimitive = true
case types.IdentifierType:
t := c.compileCompoundIdentifier(val.Identifier, "", "", false)
ft := c.compileCompoundIdentifier(val.Identifier, "", "", true)
declType, ok := c.decls[val.Identifier]
if !ok {
log.Fatal("Unknown identifier: ", val.Identifier)
}
switch declType {
case types.BitsDeclType:
fallthrough
case types.EnumDeclType:
fallthrough
case types.ConstDeclType:
fallthrough
case types.StructDeclType:
fallthrough
case types.TableDeclType:
fallthrough
case types.UnionDeclType:
if declType.IsPrimitive() {
r.IsPrimitive = true
}
if declType == types.TableDeclType {
r.IsTable = true
}
if val.Nullable {
r.Decl = fmt.Sprintf("::std::unique_ptr<%s>", t)
r.FullDecl = fmt.Sprintf("::std::unique_ptr<%s>", ft)
if declType == types.UnionDeclType {
r.LLDecl = fmt.Sprintf("%s", ft)
} else {
r.LLDecl = fmt.Sprintf("::fidl::tracking_ptr<%s>", ft)
}
r.NeedsDtor = true
} else {
r.Decl = t
r.FullDecl = ft
r.LLDecl = ft
r.NeedsDtor = true
}
if declType.IsPrimitive() {
r.LLFamily = TrivialCopy
} else {
r.LLFamily = Reference
}
case types.ProtocolDeclType:
r.Decl = fmt.Sprintf("::fidl::InterfaceHandle<class %s>", t)
r.FullDecl = fmt.Sprintf("::fidl::InterfaceHandle<class %s>", ft)
r.LLDecl = "::zx::channel"
r.LLFamily = Reference
r.NeedsDtor = true
default:
log.Fatal("Unknown declaration type: ", declType)
}
r.DeclType = declType
default:
log.Fatal("Unknown type kind: ", val.Kind)
}
return r
}
func (c *compiler) compileBits(val types.Bits, appendNamespace string) Bits {
r := Bits{
Attributes: val.Attributes,
Namespace: c.namespace,
Type: c.compileType(val.Type).Decl,
Name: c.compileCompoundIdentifier(val.Name, "", appendNamespace, false),
Mask: val.Mask,
MaskName: c.compileCompoundIdentifier(val.Name, "Mask", appendNamespace, false),
}
for _, v := range val.Members {
r.Members = append(r.Members, BitsMember{
v.Attributes,
changeIfReserved(v.Name, ""),
c.compileConstant(v.Value, nil, val.Type, appendNamespace),
})
}
return r
}
func (c *compiler) compileConst(val types.Const, appendNamespace string) Const {
if val.Type.Kind == types.StringType {
return Const{
Attributes: val.Attributes,
Extern: true,
Decorator: "const",
Type: Type{
Decl: "char",
LLDecl: "char",
},
Name: c.compileCompoundIdentifier(val.Name, "[]", appendNamespace, false),
Value: c.compileConstant(val.Value, nil, val.Type, appendNamespace),
}
} else {
t := c.compileType(val.Type)
return Const{
Attributes: val.Attributes,
Extern: false,
Decorator: "constexpr",
Type: t,
Name: c.compileCompoundIdentifier(val.Name, "", appendNamespace, false),
Value: c.compileConstant(val.Value, &t, val.Type, appendNamespace),
}
}
}
func (c *compiler) compileEnum(val types.Enum, appendNamespace string) Enum {
r := Enum{
Attributes: val.Attributes,
Namespace: c.namespace,
Type: c.compilePrimitiveSubtype(val.Type),
Name: c.compileCompoundIdentifier(val.Name, "", appendNamespace, false),
Members: []EnumMember{},
}
for _, v := range val.Members {
r.Members = append(r.Members, EnumMember{
Attributes: v.Attributes,
Name: changeIfReserved(v.Name, ""),
// TODO(FIDL-324): When we expose types consistently in the IR, we
// will not need to plug this here.
Value: c.compileConstant(v.Value, nil, types.Type{
Kind: types.PrimitiveType,
PrimitiveSubtype: val.Type,
}, appendNamespace),
})
}
return r
}
func (c *compiler) compileParameterArray(val []types.Parameter) []Parameter {
var params []Parameter = []Parameter{}
for _, v := range val {
params = append(params, Parameter{
Type: c.compileType(v.Type),
Name: changeIfReserved(v.Name, ""),
Offset: v.FieldShapeV1.Offset,
})
}
return params
}
// LLContext indicates where the request/response is used.
// The allocation strategies differ for client and server contexts.
type LLContext int
const (
clientContext LLContext = iota
serverContext LLContext = iota
)
func (m Method) NewLLContextProps(context LLContext) LLContextProps {
stackAllocRequest := false
stackAllocResponse := false
if context == clientContext {
stackAllocRequest = len(m.Request) == 0 || (m.RequestSize+m.RequestMaxOutOfLine) < llcppMaxStackAllocSize
stackAllocResponse = len(m.Response) == 0 || (!m.ResponseFlexible && (m.ResponseSize+m.ResponseMaxOutOfLine) < llcppMaxStackAllocSize)
} else {
stackAllocRequest = len(m.Request) == 0 || (!m.RequestFlexible && (m.RequestSize+m.RequestMaxOutOfLine) < llcppMaxStackAllocSize)
stackAllocResponse = len(m.Response) == 0 || (m.ResponseSize+m.ResponseMaxOutOfLine) < llcppMaxStackAllocSize
}
stackUseRequest := 0
stackUseResponse := 0
if stackAllocRequest {
stackUseRequest = m.RequestSize + m.RequestMaxOutOfLine
}
if stackAllocResponse {
stackUseResponse = m.ResponseSize + m.ResponseMaxOutOfLine
}
return LLContextProps{
StackAllocRequest: stackAllocRequest,
StackAllocResponse: stackAllocResponse,
StackUseRequest: stackUseRequest,
StackUseResponse: stackUseResponse,
}
}
func (m Method) NewLLProps(r Protocol, reqTypeShape types.TypeShape, respTypeShape types.TypeShape) LLProps {
return LLProps{
ProtocolName: r.Name,
LinearizeRequest: len(m.Request) > 0 && reqTypeShape.Depth > 0,
LinearizeResponse: len(m.Response) > 0 && respTypeShape.Depth > 0,
ClientContext: m.NewLLContextProps(clientContext),
ServerContext: m.NewLLContextProps(serverContext),
}
}
func (c *compiler) compileProtocol(val types.Protocol) Protocol {
r := Protocol{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: c.compileCompoundIdentifier(val.Name, "", "", false),
ClassName: c.compileCompoundIdentifier(val.Name, "_clazz", "", false),
ServiceName: val.GetServiceName(),
ProxyName: c.compileCompoundIdentifier(val.Name, "_Proxy", "", false),
StubName: c.compileCompoundIdentifier(val.Name, "_Stub", "", false),
EventSenderName: c.compileCompoundIdentifier(val.Name, "_EventSender", "", false),
SyncName: c.compileCompoundIdentifier(val.Name, "_Sync", "", false),
SyncProxyName: c.compileCompoundIdentifier(val.Name, "_SyncProxy", "", false),
RequestEncoderName: c.compileCompoundIdentifier(val.Name, "_RequestEncoder", "", false),
RequestDecoderName: c.compileCompoundIdentifier(val.Name, "_RequestDecoder", "", false),
ResponseEncoderName: c.compileCompoundIdentifier(val.Name, "_ResponseEncoder", "", false),
ResponseDecoderName: c.compileCompoundIdentifier(val.Name, "_ResponseDecoder", "", false),
}
hasEvents := false
for _, v := range val.Methods {
name := changeIfReserved(v.Name, "")
callbackType := ""
if v.HasResponse {
callbackType = changeIfReserved(v.Name, "Callback")
}
responseTypeNameSuffix := "ResponseTable"
if !v.HasRequest {
responseTypeNameSuffix = "EventTable"
hasEvents = true
}
var result *Result
if v.HasResponse && len(v.Response) == 1 && v.Response[0].Name == "result" {
result = c.resultForUnion[v.Response[0].Type.Identifier]
}
m := Method{
Attributes: v.Attributes,
Name: name,
NameInLowerSnakeCase: common.ToSnakeCase(name),
Ordinal: v.Ordinal,
OrdinalName: fmt.Sprintf("k%s_%s_Ordinal", r.Name, v.Name),
HasRequest: v.HasRequest,
Request: c.compileParameterArray(v.Request),
RequestSize: v.RequestTypeShapeV1.InlineSize,
RequestTypeName: fmt.Sprintf("%s_%s%sRequestTable", c.symbolPrefix, r.Name, v.Name),
RequestMaxHandles: v.RequestTypeShapeV1.MaxHandles,
RequestMaxOutOfLine: v.RequestTypeShapeV1.MaxOutOfLine,
RequestPadding: v.RequestTypeShapeV1.HasPadding,
RequestFlexible: v.RequestTypeShapeV1.HasFlexibleEnvelope,
RequestHasPointer: v.RequestTypeShapeV1.Depth > 0,
RequestIsResource: v.RequestTypeShapeV1.IsResource,
HasResponse: v.HasResponse,
Response: c.compileParameterArray(v.Response),
ResponseSize: v.ResponseTypeShapeV1.InlineSize,
ResponseTypeName: fmt.Sprintf("%s_%s%s%s", c.symbolPrefix, r.Name, v.Name, responseTypeNameSuffix),
ResponseMaxHandles: v.ResponseTypeShapeV1.MaxHandles,
ResponseMaxOutOfLine: v.ResponseTypeShapeV1.MaxOutOfLine,
ResponsePadding: v.ResponseTypeShapeV1.HasPadding,
ResponseFlexible: v.ResponseTypeShapeV1.HasFlexibleEnvelope,
ResponseHasPointer: v.ResponseTypeShapeV1.Depth > 0,
ResponseIsResource: v.ResponseTypeShapeV1.IsResource,
CallbackType: callbackType,
ResponseHandlerType: fmt.Sprintf("%s_%s_ResponseHandler", r.Name, v.Name),
ResponderType: fmt.Sprintf("%s_%s_Responder", r.Name, v.Name),
Transitional: v.IsTransitional(),
Result: result,
}
m.LLProps = m.NewLLProps(r, v.RequestTypeShapeV1, v.ResponseTypeShapeV1)
r.Methods = append(r.Methods, m)
}
r.HasEvents = hasEvents
return r
}
func (c *compiler) compileService(val types.Service) Service {
s := Service{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: c.compileCompoundIdentifier(val.Name, "", "", false),
ServiceName: val.GetServiceName(),
}
for _, v := range val.Members {
s.Members = append(s.Members, c.compileServiceMember(v))
}
return s
}
func (c *compiler) compileServiceMember(val types.ServiceMember) ServiceMember {
return ServiceMember{
Attributes: val.Attributes,
ProtocolType: c.compileCompoundIdentifier(val.Type.Identifier, "", "", false),
Name: string(val.Name),
MethodName: changeIfReserved(val.Name, ""),
}
}
func (c *compiler) compileStructMember(val types.StructMember, appendNamespace string) StructMember {
t := c.compileType(val.Type)
defaultValue := ""
if val.MaybeDefaultValue != nil {
defaultValue = c.compileConstant(*val.MaybeDefaultValue, &t, val.Type, appendNamespace)
}
return StructMember{
Attributes: val.Attributes,
Type: t,
Name: changeIfReserved(val.Name, ""),
DefaultValue: defaultValue,
Offset: val.FieldShapeV1.Offset,
}
}
func (c *compiler) compileStruct(val types.Struct, appendNamespace string) Struct {
name := c.compileCompoundIdentifier(val.Name, "", appendNamespace, false)
tableType := c.compileTableType(val.Name)
r := Struct{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: name,
TableType: tableType,
Members: []StructMember{},
InlineSize: val.TypeShapeV1.InlineSize,
MaxHandles: val.TypeShapeV1.MaxHandles,
MaxOutOfLine: val.TypeShapeV1.MaxOutOfLine,
HasPadding: val.TypeShapeV1.HasPadding,
HasPointer: val.TypeShapeV1.Depth > 0,
IsResource: val.TypeShapeV1.IsResource,
}
for _, v := range val.Members {
r.Members = append(r.Members, c.compileStructMember(v, appendNamespace))
}
result := c.resultForStruct[val.Name]
if result != nil {
(*result).ValueMembers = r.Members
memberTypeDecls := []string{}
for _, m := range r.Members {
memberTypeDecls = append(memberTypeDecls, m.Type.Decl)
}
(*result).ValueTupleDecl = fmt.Sprintf("std::tuple<%s>", strings.Join(memberTypeDecls, ", "))
if len(r.Members) == 0 {
(*result).ValueDecl = "void"
} else if len(r.Members) == 1 {
(*result).ValueDecl = r.Members[0].Type.Decl
} else {
(*result).ValueDecl = (*result).ValueTupleDecl
}
r.IsResultValue = true
r.Result = result
}
if len(r.Members) == 0 {
r.Members = []StructMember{
c.compileStructMember(types.EmptyStructMember("__reserved"), appendNamespace),
}
}
// Construct a deduped list of decls for IsMemcpyCompatible template definitions.
memcpyCompatibleDepMap := map[string]bool{}
for _, member := range r.Members {
// The dangerous identifiers test package contains identifiers that won't compile.
// e.g. ::fidl::test::dangerous::struct::types::camel::Interface gives an
// "expected unqualified-id" error because of "struct".
// There isn't an easily accessible dangerous identifiers list to replace identifiers.
if strings.Contains(member.Type.FullDecl, "::fidl::test::dangerous::") {
memcpyCompatibleDepMap = nil
break
}
memcpyCompatibleDepMap[member.Type.FullDecl] = true
}
for decl := range memcpyCompatibleDepMap {
r.FullDeclMemcpyCompatibleDeps = append(r.FullDeclMemcpyCompatibleDeps, decl)
}
sort.Strings(r.FullDeclMemcpyCompatibleDeps)
return r
}
func (c *compiler) compileTableMember(val types.TableMember, appendNamespace string) TableMember {
t := c.compileType(val.Type)
defaultValue := ""
if val.MaybeDefaultValue != nil {
defaultValue = c.compileConstant(*val.MaybeDefaultValue, &t, val.Type, appendNamespace)
}
return TableMember{
Attributes: val.Attributes,
Type: t,
Name: changeIfReserved(val.Name, ""),
DefaultValue: defaultValue,
Ordinal: val.Ordinal,
FieldPresenceName: fmt.Sprintf("has_%s_", val.Name),
FieldDataName: fmt.Sprintf("%s_value_", val.Name),
MethodHasName: fmt.Sprintf("has_%s", val.Name),
MethodClearName: fmt.Sprintf("clear_%s", val.Name),
ValueUnionName: fmt.Sprintf("ValueUnion_%s", val.Name),
}
}
func (c *compiler) compileTable(val types.Table, appendNamespace string) Table {
name := c.compileCompoundIdentifier(val.Name, "", appendNamespace, false)
tableType := c.compileTableType(val.Name)
r := Table{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: name,
TableType: tableType,
Members: nil,
InlineSize: val.TypeShapeV1.InlineSize,
BiggestOrdinal: 0,
MaxHandles: val.TypeShapeV1.MaxHandles,
MaxOutOfLine: val.TypeShapeV1.MaxOutOfLine,
HasPointer: val.TypeShapeV1.Depth > 0,
IsResource: val.TypeShapeV1.IsResource,
}
for _, v := range val.SortedMembersNoReserved() {
m := c.compileTableMember(v, appendNamespace)
if m.Ordinal > r.BiggestOrdinal {
r.BiggestOrdinal = m.Ordinal
}
r.Members = append(r.Members, m)
}
r.FrameItems = make([]TableFrameItem, r.BiggestOrdinal)
for i := 0; i < len(r.FrameItems); i++ {
r.FrameItems[i] = TableFrameItem{
LLDecl: "void",
Name: fmt.Sprintf("reserved_%d", i),
}
}
for _, member := range r.Members {
r.FrameItems[member.Ordinal-1] = TableFrameItem{
LLDecl: member.Type.LLDecl,
Name: member.Name,
}
}
return r
}
func (c *compiler) compileUnionMember(val types.UnionMember) UnionMember {
n := changeIfReserved(val.Name, "")
return UnionMember{
Attributes: val.Attributes,
Ordinal: uint64(val.Ordinal),
Type: c.compileType(val.Type),
Name: n,
StorageName: changeIfReserved(val.Name, "_"),
TagName: fmt.Sprintf("k%s", common.ToUpperCamelCase(n)),
Offset: val.Offset,
}
}
func (c *compiler) compileUnion(val types.Union) Union {
name := c.compileCompoundIdentifier(val.Name, "", "", false)
tableType := c.compileTableType(val.Name)
r := Union{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: name,
TableType: tableType,
InlineSize: val.TypeShapeV1.InlineSize,
MaxHandles: val.TypeShapeV1.MaxHandles,
MaxOutOfLine: val.TypeShapeV1.MaxOutOfLine,
Strictness: val.Strictness,
HasPointer: val.TypeShapeV1.Depth > 0,
IsResource: val.TypeShapeV1.IsResource,
}
for _, v := range val.Members {
if v.Reserved {
continue
}
r.Members = append(r.Members, c.compileUnionMember(v))
}
if val.Attributes.HasAttribute("Result") {
if len(r.Members) != 2 {
log.Fatal("A Result union must have two members: ", val.Name)
}
if val.Members[0].Type.Kind != types.IdentifierType {
log.Fatal("Value member of result union must be an identifier", val.Name)
}
valueStructDeclType, ok := c.decls[val.Members[0].Type.Identifier]
if !ok {
log.Fatal("Unknown identifier: ", val.Members[0].Type.Identifier)
}
if valueStructDeclType != "struct" {
log.Fatal("First member of result union not a struct: ", val.Name)
}
result := Result{
ResultDecl: r.Name,
ValueStructDecl: r.Members[0].Type.Decl,
ErrorDecl: r.Members[1].Type.Decl,
}
c.resultForStruct[val.Members[0].Type.Identifier] = &result
c.resultForUnion[val.Name] = &result
r.Result = &result
}
return r
}
func compile(r types.Root, namespaceFormatter func(types.LibraryIdentifier, string) string) Root {
root := Root{}
library := make(types.LibraryIdentifier, 0)
raw_library := make(types.LibraryIdentifier, 0)
for _, identifier := range types.ParseLibraryName(r.Name) {
safe_name := changeIfReserved(identifier, "")
library = append(library, types.Identifier(safe_name))
raw_library = append(raw_library, identifier)
}
c := compiler{
namespaceFormatter(library, ""),
formatLibraryPrefix(raw_library),
r.DeclsWithDependencies(),
types.ParseLibraryName(r.Name),
make(map[types.HandleSubtype]bool),
namespaceFormatter,
make(map[types.EncodedCompoundIdentifier]*Result),
make(map[types.EncodedCompoundIdentifier]*Result),
}
root.Library = library
libraryReversed := make(types.LibraryIdentifier, len(library))
for i, j := 0, len(library)-1; i < len(library); i, j = i+1, j-1 {
libraryReversed[i] = library[j]
}
for i, identifier := range library {
libraryReversed[len(libraryReversed)-i-1] = identifier
}
root.LibraryReversed = libraryReversed
decls := map[types.EncodedCompoundIdentifier]Decl{}
for _, v := range r.Bits {
d := c.compileBits(v, "")
decls[v.Name] = &d
}
for _, v := range r.Consts {
d := c.compileConst(v, "")
decls[v.Name] = &d
}
for _, v := range r.Enums {
d := c.compileEnum(v, "")
decls[v.Name] = &d
}
// Note: for Result calculation unions must be compiled before structs.
for _, v := range r.Unions {
d := c.compileUnion(v)
decls[v.Name] = d
}
for _, v := range r.Structs {
// TODO(7704) remove once anonymous structs are supported
if v.Anonymous {
continue
}
d := c.compileStruct(v, "")
decls[v.Name] = &d
}
for _, v := range r.Tables {
d := c.compileTable(v, "")
decls[v.Name] = &d
}
for _, v := range r.Protocols {
d := c.compileProtocol(v)
decls[v.Name] = &d
}
for _, v := range r.Services {
d := c.compileService(v)
decls[v.Name] = &d
}
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
}
libraryIdent := types.ParseLibraryName(l.Name)
root.Headers = append(root.Headers, fmt.Sprintf("%s/cpp/fidl.h", formatLibraryPath(libraryIdent)))
root.LFHeaders = append(root.LFHeaders, fmt.Sprintf("%s/cpp/libfuzzer.h", formatLibraryPath(libraryIdent)))
root.LLHeaders = append(root.LLHeaders, fmt.Sprintf("%s/llcpp/fidl.h", formatLibraryPath(libraryIdent)))
}
// zx::channel is always referenced by the protocols in llcpp bindings API
if len(r.Protocols) > 0 {
c.handleTypes["channel"] = true
}
// 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 CompileHL(r types.Root) Root {
return compile(r.ForBindings("cpp"), formatNamespace)
}
func CompileLL(r types.Root) Root {
return compile(r.ForBindings("llcpp"), formatLLNamespace)
}
func CompileLibFuzzer(r types.Root) Root {
return compile(r.ForBindings("libfuzzer"), formatNamespace)
}