| // Copyright 2023 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 coding_tables |
| |
| import ( |
| "fmt" |
| "math" |
| "strconv" |
| "strings" |
| "unicode" |
| |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| cpp "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen_cpp" |
| "golang.org/x/exp/slices" |
| ) |
| |
| type Root struct { |
| // Forward declarations of coding tables. |
| // Includes both our own and ones linked in from direct dependencies. |
| ForwardDecls []forwardDecl |
| |
| // Declarations (external linkage) |
| Bits []bits |
| Enums []enum |
| Structs []struct_ |
| Tables []table |
| Unions []union |
| |
| // Types used in declarations (internal linkage) |
| Arrays []array |
| Vectors []vector |
| Strings []string_ |
| Handles []handle |
| Boxes []box |
| } |
| |
| type forwardDecl struct { |
| Name string |
| CType string |
| StorageClass string |
| } |
| |
| type bits struct { |
| Name string |
| FidlName fidlgen.EncodedCompoundIdentifier |
| UnderlyingType string |
| Strictness string |
| Mask uint64 |
| } |
| |
| type enum struct { |
| Name string |
| FidlName fidlgen.EncodedCompoundIdentifier |
| UnderlyingType string |
| Strictness string |
| Validator string |
| Values []string |
| } |
| |
| type struct_ struct { |
| Name string |
| FidlName fidlgen.EncodedCompoundIdentifier |
| Emptiness string |
| InlineSize int |
| MembersName string |
| Members []structMember |
| } |
| |
| type structMember struct { |
| Offset int |
| // Set for non-padding members. |
| Type string |
| Resourceness string |
| // Set for padding members. |
| PaddingMaskBitWidth int |
| PaddingMask uint64 |
| } |
| |
| type table struct { |
| Name string |
| FidlName fidlgen.EncodedCompoundIdentifier |
| Resourceness string |
| MembersName string |
| Members []tableMember |
| } |
| |
| type tableMember struct { |
| Ordinal int |
| Type string |
| } |
| |
| type union struct { |
| Name string |
| FidlName fidlgen.EncodedCompoundIdentifier |
| NullableName string |
| Strictness string |
| Resourceness string |
| MembersName string |
| Members []unionMember |
| } |
| |
| type unionMember struct { |
| Type string |
| } |
| |
| type array struct { |
| Name string |
| ElementType string |
| InlineSize int |
| ElementSize int |
| } |
| |
| type vector struct { |
| Name string |
| ElementType string |
| MaxCount uint32 |
| Nullability string |
| ElementSize int |
| } |
| |
| type string_ struct { |
| Name string |
| MaxCount uint32 |
| Nullability string |
| } |
| |
| type handle struct { |
| Name string |
| ObjectType string |
| Rights string |
| Nullability string |
| } |
| |
| type box struct { |
| Name string |
| StructName string |
| } |
| |
| type compiler struct { |
| library fidlgen.EncodedLibraryIdentifier |
| decls fidlgen.DeclInfoMap |
| structs map[fidlgen.EncodedCompoundIdentifier]*fidlgen.Struct |
| seenTypes map[string]struct{} |
| out Root |
| } |
| |
| func (c *compiler) compileForwardDecl(name string, typ fidlgen.Type) forwardDecl { |
| storage := "static" |
| if typ.Kind == fidlgen.IdentifierType { |
| info, ok := c.decls[typ.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("identifier not in decl map: %s", typ.Identifier)) |
| } |
| if info.Type != fidlgen.ProtocolDeclType && !(info.Type == fidlgen.StructDeclType && typ.Nullable) { |
| if typ.Identifier.LibraryName() == c.library { |
| storage = "" |
| } else { |
| // Extern is the default, but we use it to emphasize that this |
| // coding table will be linked in from another translation unit. |
| storage = "extern" |
| } |
| } |
| } |
| return forwardDecl{ |
| Name: name, |
| CType: forwardDeclCType(typ, c.decls), |
| StorageClass: storage, |
| } |
| } |
| |
| func (c *compiler) compileDecl(decl fidlgen.Decl) { |
| // Skip decls from other libraries, e.g. from "external_structs". |
| if decl.GetName().LibraryName() != c.library { |
| return |
| } |
| switch decl := decl.(type) { |
| case *fidlgen.Bits: |
| c.out.Bits = append(c.out.Bits, c.compileBits(*decl)) |
| case *fidlgen.Enum: |
| c.out.Enums = append(c.out.Enums, c.compileEnum(*decl)) |
| case *fidlgen.Struct: |
| c.out.Structs = append(c.out.Structs, c.compileStruct(*decl)) |
| case *fidlgen.Table: |
| c.out.Tables = append(c.out.Tables, c.compileTable(*decl)) |
| case *fidlgen.Union: |
| c.out.Unions = append(c.out.Unions, c.compileUnion(*decl)) |
| case *fidlgen.Alias, *fidlgen.Const, *fidlgen.NewType, *fidlgen.Overlay, |
| *fidlgen.Protocol, *fidlgen.Resource, *fidlgen.Service: |
| // These declarations don't go in coding tables. |
| default: |
| panic(fmt.Sprintf("unexpected decl type: %T", decl)) |
| } |
| } |
| |
| func (c *compiler) compileBits(v fidlgen.Bits) bits { |
| mask, err := strconv.ParseUint(v.Mask, 10, 64) |
| if err != nil { |
| panic(fmt.Sprintf("cannot parse bits mask: %s", err)) |
| } |
| return bits{ |
| FidlName: v.Name, |
| Name: compileDeclName(v.Name), |
| UnderlyingType: compilePrimitiveSubtype(v.Type.PrimitiveSubtype), |
| Strictness: compileStrictness(v.Strictness), |
| Mask: mask, |
| } |
| } |
| |
| func (c *compiler) compileEnum(v fidlgen.Enum) enum { |
| var validator string |
| if v.IsStrict() { |
| validator = "EnumValidatorFor_" + nameDecl(v.Name) |
| } |
| var values []string |
| for _, m := range v.Members { |
| // TODO(https://fxbug.dev/42086098): Centralize C++ integer literal logic. |
| var expr string |
| if m.Value.Value == "-9223372036854775808" { |
| expr = "-9223372036854775807 - 1" |
| } else if strings.HasPrefix(m.Value.Value, "-") { |
| expr = m.Value.Value |
| } else { |
| expr = m.Value.Value + "u" |
| } |
| values = append(values, expr) |
| } |
| return enum{ |
| FidlName: v.Name, |
| Name: compileDeclName(v.Name), |
| UnderlyingType: compilePrimitiveSubtype(v.Type), |
| Strictness: compileStrictness(v.Strictness), |
| Validator: validator, |
| Values: values, |
| } |
| } |
| |
| func (c *compiler) compileStruct(v fidlgen.Struct) struct_ { |
| var members []structMember |
| c.fillStructNonPaddingMembers(v, 0, &members) |
| c.fillStructPaddingMembers(v, &members) |
| slices.SortFunc(members, func(a, b structMember) int { return a.Offset - b.Offset }) |
| return struct_{ |
| FidlName: v.Name, |
| Name: compileDeclName(v.Name), |
| Emptiness: compileEmptiness(v.Members), |
| InlineSize: v.TypeShapeV2.InlineSize, |
| MembersName: compileMembersName(v.Name, len(members)), |
| Members: members, |
| } |
| } |
| |
| func (c *compiler) fillStructNonPaddingMembers(v fidlgen.Struct, baseOffset int, members *[]structMember) { |
| for _, m := range v.Members { |
| offset := baseOffset + m.FieldShapeV2.Offset |
| if nested, ok := c.structs[m.Type.Identifier]; ok && !m.Type.Nullable { |
| c.fillStructNonPaddingMembers(*nested, offset, members) |
| } else if c.canCopyWithoutValidation(m.Type) { |
| // No coding table member is needed. |
| } else { |
| *members = append(*members, structMember{ |
| Offset: offset, |
| Type: c.compilePointerToType(m.Type), |
| Resourceness: compileResourceness(c.decls.LookupResourceness(m.Type)), |
| }) |
| } |
| } |
| } |
| |
| func (c *compiler) fillStructPaddingMembers(v fidlgen.Struct, members *[]structMember) { |
| markers := v.BuildPaddingMarkers(fidlgen.PaddingConfig{ |
| FlattenStructs: true, |
| ResolveStruct: func(eci fidlgen.EncodedCompoundIdentifier) *fidlgen.Struct { return c.structs[eci] }, |
| }) |
| for _, marker := range markers { |
| *members = append(*members, structMember{ |
| Offset: marker.Offset, |
| PaddingMaskBitWidth: marker.MaskBitWidth, |
| PaddingMask: marker.Mask, |
| }) |
| } |
| } |
| |
| func (c *compiler) canCopyWithoutValidation(typ fidlgen.Type) bool { |
| switch typ.Kind { |
| case fidlgen.PrimitiveType: |
| return typ.PrimitiveSubtype != fidlgen.Bool |
| case fidlgen.ArrayType: |
| return c.canCopyWithoutValidation(*typ.ElementType) |
| case fidlgen.IdentifierType: |
| if s, ok := c.structs[typ.Identifier]; ok && !typ.Nullable && !s.TypeShapeV2.HasPadding { |
| for _, m := range s.Members { |
| if !c.canCopyWithoutValidation(m.Type) { |
| return false |
| } |
| } |
| return true |
| } |
| } |
| return false |
| } |
| |
| func (c *compiler) compileTable(v fidlgen.Table) table { |
| var members []tableMember |
| for _, m := range v.Members { |
| members = append(members, tableMember{ |
| Ordinal: m.Ordinal, |
| Type: c.compilePointerToType(m.Type), |
| }) |
| } |
| return table{ |
| FidlName: v.Name, |
| Name: compileDeclName(v.Name), |
| Resourceness: compileResourceness(v.Resourceness), |
| MembersName: compileMembersName(v.Name, len(members)), |
| Members: members, |
| } |
| } |
| |
| func (c *compiler) compileUnion(v fidlgen.Union) union { |
| nullableType := fidlgen.Type{ |
| Kind: fidlgen.IdentifierType, |
| Identifier: v.Name, |
| Nullable: true, |
| } |
| var members []unionMember |
| for _, m := range v.Members { |
| for len(members) < m.Ordinal-1 { |
| members = append(members, unionMember{Type: "NULL"}) |
| } |
| members = append(members, unionMember{ |
| Type: c.compilePointerToType(m.Type), |
| }) |
| } |
| return union{ |
| FidlName: v.Name, |
| Name: compileDeclName(v.Name), |
| NullableName: compileTypeName(nullableType, c.decls), |
| Strictness: compileStrictness(v.Strictness), |
| Resourceness: compileResourceness(v.Resourceness), |
| MembersName: compileMembersName(v.Name, len(v.Members)), |
| Members: members, |
| } |
| } |
| |
| func (c *compiler) compilePointerToType(typ fidlgen.Type) string { |
| return "(const fidl_type_t*)&" + c.compilePointerToTypeWithoutCast(typ) |
| } |
| |
| func (c *compiler) compilePointerToTypeWithoutCast(typ fidlgen.Type) string { |
| name := compileTypeName(typ, c.decls) |
| if _, ok := c.seenTypes[name]; !ok { |
| c.seenTypes[name] = struct{}{} |
| c.compileType(name, typ) |
| } |
| return name |
| } |
| |
| func (c *compiler) compileType(name string, typ fidlgen.Type) { |
| // Skip the forward declaration for types from #include <lib/fidl/internal.h>. |
| if !strings.HasPrefix(name, "fidl_internal_k") { |
| c.out.ForwardDecls = append(c.out.ForwardDecls, c.compileForwardDecl(name, typ)) |
| } |
| switch typ.Kind { |
| case fidlgen.ArrayType: |
| c.out.Arrays = append(c.out.Arrays, c.compileArray(name, typ)) |
| case fidlgen.VectorType: |
| c.out.Vectors = append(c.out.Vectors, c.compileVector(name, typ)) |
| case fidlgen.StringType: |
| c.out.Strings = append(c.out.Strings, c.compileString(name, typ)) |
| case fidlgen.HandleType, fidlgen.RequestType: |
| c.out.Handles = append(c.out.Handles, c.compileHandle(name, typ)) |
| case fidlgen.IdentifierType: |
| info, ok := c.decls[typ.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("identifier not in decl map: %s", typ.Identifier)) |
| } |
| switch info.Type { |
| case fidlgen.StructDeclType: |
| if typ.Nullable { |
| c.out.Boxes = append(c.out.Boxes, c.compileBox(name, typ)) |
| } |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType, fidlgen.TableDeclType, fidlgen.UnionDeclType: |
| // Do nothing. All we need for these is the forward declaration above. |
| case fidlgen.ProtocolDeclType: |
| c.out.Handles = append(c.out.Handles, c.compileHandle(name, typ)) |
| default: |
| panic(fmt.Sprintf("identifier type refers to unexpected decl type: %s", info.Type)) |
| } |
| case fidlgen.PrimitiveType, fidlgen.InternalType: |
| // Do nothing. |
| case fidlgen.ZxExperimentalPointerType, fidlgen.StringArray: |
| panic(fmt.Sprintf("unsupported type kind for coding tables: %s", typ.Kind)) |
| default: |
| panic(fmt.Sprintf("unexpected type kind: %s", typ.Kind)) |
| } |
| } |
| |
| func (c *compiler) compileArray(name string, typ fidlgen.Type) array { |
| return array{ |
| Name: name, |
| ElementType: c.compilePointerToType(*typ.ElementType), |
| InlineSize: typ.TypeShapeV2.InlineSize, |
| ElementSize: typ.ElementType.TypeShapeV2.InlineSize, |
| } |
| } |
| |
| func (c *compiler) compileVector(name string, typ fidlgen.Type) vector { |
| return vector{ |
| Name: name, |
| ElementType: c.compilePointerToType(*typ.ElementType), |
| MaxCount: compileMaxCount(typ.ElementCount), |
| Nullability: compileNullability(typ.Nullable), |
| ElementSize: typ.ElementType.TypeShapeV2.InlineSize, |
| } |
| } |
| |
| func (c *compiler) compileString(name string, typ fidlgen.Type) string_ { |
| return string_{ |
| Name: name, |
| MaxCount: compileMaxCount(typ.ElementCount), |
| Nullability: compileNullability(typ.Nullable), |
| } |
| } |
| |
| func (c *compiler) compileHandle(name string, typ fidlgen.Type) handle { |
| info := cpp.FieldHandleInformation(typ, c.decls) |
| return handle{ |
| Name: name, |
| ObjectType: info.ObjectType, |
| Rights: info.Rights, |
| Nullability: compileNullability(typ.Nullable), |
| } |
| } |
| |
| func (c *compiler) compileBox(name string, typ fidlgen.Type) box { |
| return box{ |
| Name: name, |
| StructName: c.compilePointerToTypeWithoutCast(fidlgen.Type{ |
| Kind: fidlgen.IdentifierType, |
| Identifier: typ.Identifier, |
| Nullable: false, |
| }), |
| } |
| } |
| |
| func compileDeclName(eci fidlgen.EncodedCompoundIdentifier) string { |
| return nameDecl(eci) + "Table" |
| } |
| |
| func compileTypeName(typ fidlgen.Type, decls fidlgen.DeclInfoMap) string { |
| return nameType(typ, decls) + "Table" |
| } |
| |
| func compileMembersName(eci fidlgen.EncodedCompoundIdentifier, numMembers int) string { |
| if numMembers == 0 { |
| return "NULL" |
| } |
| return "Fields" + lengthPrefixed(nameDecl(eci)) |
| } |
| |
| func compileStrictness(s fidlgen.Strictness) string { |
| switch s { |
| case fidlgen.IsStrict: |
| return "kFidlStrictness_Strict" |
| case fidlgen.IsFlexible: |
| return "kFidlStrictness_Flexible" |
| default: |
| panic("unexpected strictness") |
| } |
| } |
| |
| func compileResourceness(r fidlgen.Resourceness) string { |
| switch fidlgen.Resourceness(r) { |
| case fidlgen.IsValueType: |
| return "kFidlIsResource_NotResource" |
| case fidlgen.IsResourceType: |
| return "kFidlIsResource_Resource" |
| default: |
| panic("unexpected resourceness") |
| } |
| } |
| |
| func compileNullability(nullable bool) string { |
| if nullable { |
| return "kFidlNullability_Nullable" |
| } |
| return "kFidlNullability_Nonnullable" |
| } |
| |
| func compilePrimitiveSubtype(p fidlgen.PrimitiveSubtype) string { |
| return "kFidlCodedPrimitiveSubtype_" + namePrimitiveSubtype(p) |
| } |
| |
| func compileEmptiness(members []fidlgen.StructMember) string { |
| if len(members) == 0 { |
| return "kFidlEmpty_IsEmpty" |
| } |
| return "kFidlEmpty_IsNotEmpty" |
| } |
| |
| func compileMaxCount(n *int) uint32 { |
| if n == nil { |
| return math.MaxUint32 |
| } |
| return uint32(*n) |
| } |
| |
| func forwardDeclCType(typ fidlgen.Type, decls fidlgen.DeclInfoMap) string { |
| switch typ.Kind { |
| case fidlgen.ArrayType: |
| return "FidlCodedArray" |
| case fidlgen.VectorType: |
| return "FidlCodedVector" |
| case fidlgen.StringType: |
| return "FidlCodedString" |
| case fidlgen.HandleType, fidlgen.RequestType: |
| return "FidlCodedHandle" |
| case fidlgen.IdentifierType: |
| info, ok := decls[typ.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("identifier not in decl map: %s", typ.Identifier)) |
| } |
| switch info.Type { |
| case fidlgen.BitsDeclType: |
| return "FidlCodedBits" |
| case fidlgen.EnumDeclType: |
| return "FidlCodedEnum" |
| case fidlgen.StructDeclType: |
| if typ.Nullable { |
| return "FidlCodedStructPointer" |
| } |
| return "FidlCodedStruct" |
| case fidlgen.TableDeclType: |
| return "FidlCodedTable" |
| case fidlgen.UnionDeclType: |
| return "FidlCodedUnion" |
| case fidlgen.ProtocolDeclType: |
| return "FidlCodedHandle" |
| default: |
| panic(fmt.Sprintf("identifier type refers to unexpected decl type: %s", info.Type)) |
| } |
| case fidlgen.PrimitiveType, fidlgen.InternalType: |
| panic(fmt.Sprintf("should not need to forward declare type kind: %s", typ.Kind)) |
| case fidlgen.ZxExperimentalPointerType, fidlgen.StringArray: |
| panic(fmt.Sprintf("unsupported type kind for coding tables: %s", typ.Kind)) |
| default: |
| panic(fmt.Sprintf("unexpected type kind: %s", typ.Kind)) |
| } |
| } |
| |
| func nameType(typ fidlgen.Type, decls fidlgen.DeclInfoMap) string { |
| switch typ.Kind { |
| case fidlgen.ArrayType: |
| return fmt.Sprintf("Array%s_%s", nameSize(typ.ElementCount), lengthPrefixed(nameType(*typ.ElementType, decls))) |
| case fidlgen.VectorType: |
| return fmt.Sprintf("Vector%s%s_%s", nameSize(typ.ElementCount), nameNullability(typ.Nullable), lengthPrefixed(nameType(*typ.ElementType, decls))) |
| case fidlgen.StringType: |
| return fmt.Sprintf("String%s%s", nameSize(typ.ElementCount), nameNullability(typ.Nullable)) |
| case fidlgen.HandleType: |
| return fmt.Sprintf("Handle%s%d%s", typ.HandleSubtype, typ.HandleRights, nameNullability(typ.Nullable)) |
| case fidlgen.RequestType: |
| return fmt.Sprintf("Request%s%s", lengthPrefixed(nameDecl(typ.Identifier)), nameNullability(typ.Nullable)) |
| case fidlgen.PrimitiveType: |
| return fmt.Sprintf("fidl_internal_k%s", namePrimitiveSubtype(typ.PrimitiveSubtype)) |
| case fidlgen.InternalType: |
| return fmt.Sprintf("fidl_internal_k%s", nameInternalSubtype(typ.InternalSubtype)) |
| case fidlgen.IdentifierType: |
| info, ok := decls[typ.Identifier] |
| if !ok { |
| panic(fmt.Sprintf("identifier not in decl map: %s", typ.Identifier)) |
| } |
| if typ.Identifier.LibraryName() == "zx" { |
| switch typ.Identifier { |
| // We emit zx_obj_type_t and zx_rights_t for these types in |
| // bindings, so treat them as such in coding tables as well. |
| case "zx/ObjType", "zx/Rights": |
| return nameType(fidlgen.Type{Kind: fidlgen.PrimitiveType, PrimitiveSubtype: fidlgen.Uint32}, decls) |
| default: |
| panic(fmt.Sprintf("unexpected zx type: %s", typ.Identifier)) |
| } |
| } |
| switch info.Type { |
| case fidlgen.BitsDeclType, fidlgen.EnumDeclType, fidlgen.TableDeclType: |
| return nameDecl(typ.Identifier) |
| case fidlgen.StructDeclType: |
| if typ.Nullable { |
| return fmt.Sprintf("Pointer%s", lengthPrefixed(nameDecl(typ.Identifier))) |
| } |
| return nameDecl(typ.Identifier) |
| case fidlgen.UnionDeclType: |
| if typ.Nullable { |
| return fmt.Sprintf("%sNullableRef", nameDecl(typ.Identifier)) |
| } |
| return nameDecl(typ.Identifier) |
| case fidlgen.ProtocolDeclType: |
| return fmt.Sprintf("Protocol%s%s", lengthPrefixed(nameDecl(typ.Identifier)), nameNullability(typ.Nullable)) |
| default: |
| panic(fmt.Sprintf("identifier type refers to unexpected decl type: %s", info.Type)) |
| } |
| case fidlgen.ZxExperimentalPointerType, fidlgen.StringArray: |
| panic(fmt.Sprintf("unsupported type kind for coding tables: %s", typ.Kind)) |
| default: |
| panic(fmt.Sprintf("unexpected type kind: %s", typ.Kind)) |
| } |
| } |
| |
| func nameDecl(eci fidlgen.EncodedCompoundIdentifier) string { |
| s := string(eci) |
| s = strings.Replace(s, "/", "_", 1) |
| s = strings.ReplaceAll(s, ".", "_") |
| return s |
| } |
| |
| func nameSize(n *int) string { |
| if n == nil { |
| return "unbounded" |
| } |
| return strconv.Itoa(*n) |
| } |
| |
| func nameNullability(nullable bool) string { |
| if nullable { |
| return "nullable" |
| } |
| return "notnullable" |
| } |
| |
| func namePrimitiveSubtype(t fidlgen.PrimitiveSubtype) string { |
| s := string(t) |
| return string(unicode.ToUpper(rune(s[0]))) + s[1:] |
| } |
| |
| func nameInternalSubtype(t fidlgen.InternalSubtype) string { |
| switch t { |
| case fidlgen.FrameworkErr: |
| return "FrameworkErr" |
| default: |
| panic(fmt.Sprintf("unexpected internal subtype: %s", t)) |
| } |
| } |
| |
| func lengthPrefixed(s string) string { |
| return fmt.Sprintf("%d%s", len(s), s) |
| } |
| |
| func Compile(r fidlgen.Root) Root { |
| c := compiler{ |
| library: r.Name, |
| decls: r.DeclInfo(), |
| structs: make(map[fidlgen.EncodedCompoundIdentifier]*fidlgen.Struct), |
| seenTypes: make(map[string]struct{}), |
| } |
| for i := range r.Structs { |
| s := &r.Structs[i] |
| c.structs[s.Name] = s |
| } |
| r.ForEachDecl(c.compileDecl) |
| return c.out |
| } |