blob: c88183d7ba70a4f85e5e59d5950affa1d33d8af6 [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 ir
import (
"flag"
"fmt"
"log"
"math"
"sort"
"strings"
"fidl/compiler/backend/common"
"fidl/compiler/backend/types"
)
var legacyCallbacks = flag.Bool("cpp-legacy-callbacks", false,
"use std::function instead of fit::function in C++ callbacks")
const llcppMaxStackAllocSize = 512
// These are used in header/impl templates to select the correct type-specific template
type constKind struct{}
type enumKind struct{}
type interfaceKind struct{}
type structKind struct{}
type tableKind struct{}
type unionKind struct{}
type xunionKind struct{}
var Kinds = struct {
Const constKind
Enum enumKind
Interface interfaceKind
Struct structKind
Table tableKind
Union unionKind
XUnion xunionKind
}{}
type Decl interface{}
type Type struct {
Decl string
LLDecl string
Dtor string
DeclType types.DeclType
}
type Const struct {
types.Attributes
Extern bool
Decorator string
Type Type
Name string
Value string
Kind constKind
}
type Enum struct {
Namespace string
Type string
Name string
Members []EnumMember
Kind enumKind
}
type EnumMember struct {
Name string
Value string
}
type Union struct {
types.Attributes
Namespace string
Name string
TableType string
Members []UnionMember
Size int
MaxHandles int
MaxOutOfLine int
Kind unionKind
}
type UnionMember struct {
types.Attributes
Type Type
Name string
StorageName string
TagName string
Offset int
}
type XUnion struct {
types.Attributes
Namespace string
Name string
TableType string
Members []XUnionMember
Size int
MaxHandles int
MaxOutOfLine int
Kind xunionKind
}
type XUnionMember struct {
types.Attributes
Ordinal int
Type Type
Name string
StorageName string
TagName string
Offset int
}
type Table struct {
types.Attributes
Namespace string
Name string
TableType string
Members []TableMember
Size int
BiggestOrdinal int
MaxHandles int
MaxOutOfLine int
Kind tableKind
}
type TableMember struct {
types.Attributes
Type Type
Name string
DefaultValue string
Ordinal int
FieldPresenceName string
FieldDataName string
MethodHasName string
MethodClearName string
ValueUnionName string
ValueXUnionName string
}
type Struct struct {
types.Attributes
Namespace string
Name string
TableType string
Members []StructMember
Size int
MaxHandles int
MaxOutOfLine int
Kind structKind
}
type StructMember struct {
types.Attributes
Type Type
Name string
DefaultValue string
Offset int
}
func (s Struct) NeedsEncodeDecode() bool {
return s.MaxHandles > 0 || s.MaxOutOfLine > 0
}
type Interface struct {
types.Attributes
Namespace string
Name string
ClassName string
ServiceName string
ProxyName string
StubName string
EventSenderName string
SyncName string
SyncProxyName string
Methods []Method
Kind interfaceKind
}
type Method struct {
types.Attributes
Ordinal types.Ordinal
OrdinalName string
GenOrdinal types.Ordinal
GenOrdinalName string
Name string
HasRequest bool
Request []Parameter
RequestSize int
RequestTypeName string
RequestMaxHandles int
RequestMaxOutOfLine int
HasResponse bool
Response []Parameter
ResponseSize int
ResponseTypeName string
ResponseMaxHandles int
ResponseMaxOutOfLine int
CallbackType string
ResponseHandlerType string
ResponderType string
Transitional bool
LLProps LLProps
}
// LLProps contain properties of a method specific to llcpp
type LLProps struct {
InterfaceName string
CBindingCompatible bool
NeedToLinearize bool
StackAllocRequest bool
StackAllocResponse bool
EncodeRequest bool
DecodeResponse bool
}
type Parameter struct {
Type Type
Name string
Offset int
}
type Root struct {
PrimaryHeader string
Headers []string
LLHeaders []string
HandleTypes []string
Library types.LibraryIdentifier
LibraryReversed types.LibraryIdentifier
Decls []Decl
}
func (m *Method) CallbackWrapper() string {
if *legacyCallbacks {
return "std::function"
}
return "fit::function"
}
var reservedWords = map[string]bool{
"alignas": true,
"alignof": true,
"and": true,
"and_eq": true,
"asm": 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,
"nullptr": 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
"which": true,
"has_invalid_tag": true,
}
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))
}
return changeIfReserved(types.Identifier(strings.Join(parts, sep)), "")
}
func formatNamespace(library types.LibraryIdentifier) string {
return "::" + formatLibrary(library, "::")
}
func formatLibraryPrefix(library types.LibraryIdentifier) string {
return formatLibrary(library, "_")
}
func formatLibraryPath(library types.LibraryIdentifier) string {
return formatLibrary(library, "/")
}
func formatDestructor(eci types.EncodedCompoundIdentifier) string {
val := types.ParseCompoundIdentifier(eci)
return fmt.Sprintf("~%s", changeIfReserved(val.Name, ""))
}
type compiler struct {
namespace string
symbolPrefix string
decls *types.DeclMap
library types.LibraryIdentifier
handleTypes map[types.HandleSubtype]bool
}
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 string) string {
val := types.ParseCompoundIdentifier(eci)
strs := []string{}
if c.isInExternalLibrary(val) {
strs = append(strs, formatNamespace(val.Library))
}
strs = append(strs, changeIfReserved(val.Name, ext))
return strings.Join(strs, "::")
}
func (c *compiler) compileLiteral(val types.Literal) string {
switch val.Kind {
case types.StringLiteral:
return fmt.Sprintf("%q", val.Value)
case types.NumericLiteral:
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) string {
switch val.Kind {
case types.IdentifierConstant:
v := c.compileCompoundIdentifier(val.Identifier, "")
if t != nil && t.DeclType == types.EnumDeclType {
v = fmt.Sprintf("%s::%s", t.Decl, v)
}
return v
case types.LiteralConstant:
return c.compileLiteral(val.Literal)
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("::fidl::Array<%s, %v>", t.Decl, *val.ElementCount)
r.LLDecl = fmt.Sprintf("::fidl::ArrayWrapper<%s, %v>", t.LLDecl, *val.ElementCount)
r.Dtor = fmt.Sprintf("~Array")
case types.VectorType:
t := c.compileType(*val.ElementType)
r.LLDecl = fmt.Sprintf("::fidl::VectorView<%s>", t.LLDecl)
if val.Nullable {
r.Decl = fmt.Sprintf("::fidl::VectorPtr<%s>", t.Decl)
r.Dtor = fmt.Sprintf("~VectorPtr")
} else {
r.Decl = fmt.Sprintf("::std::vector<%s>", t.Decl)
}
case types.StringType:
r.LLDecl = "::fidl::StringView"
if val.Nullable {
r.Decl = "::fidl::StringPtr"
r.Dtor = "~StringPtr"
} else {
r.Decl = "::std::string"
}
case types.HandleType:
c.handleTypes[val.HandleSubtype] = true
r.Decl = fmt.Sprintf("::zx::%s", val.HandleSubtype)
r.LLDecl = r.Decl
r.Dtor = fmt.Sprintf("~%s", val.HandleSubtype)
case types.RequestType:
t := c.compileCompoundIdentifier(val.RequestSubtype, "")
r.Decl = fmt.Sprintf("::fidl::InterfaceRequest<%s>", t)
r.LLDecl = r.Decl
r.Dtor = "~InterfaceRequest"
case types.PrimitiveType:
r.Decl = c.compilePrimitiveSubtype(val.PrimitiveSubtype)
r.LLDecl = r.Decl
case types.IdentifierType:
t := c.compileCompoundIdentifier(val.Identifier, "")
declType, ok := (*c.decls)[val.Identifier]
if !ok {
log.Fatal("Unknown identifier: ", val.Identifier)
}
switch declType {
case types.ConstDeclType:
fallthrough
case types.EnumDeclType:
fallthrough
case types.StructDeclType:
fallthrough
case types.TableDeclType:
fallthrough
case types.UnionDeclType:
fallthrough
case types.XUnionDeclType:
if val.Nullable {
r.Decl = fmt.Sprintf("::std::unique_ptr<%s>", t)
r.LLDecl = fmt.Sprintf("%s*", t)
r.Dtor = fmt.Sprintf("~unique_ptr")
} else {
r.Decl = t
r.LLDecl = r.Decl
r.Dtor = formatDestructor(val.Identifier)
}
case types.InterfaceDeclType:
r.Decl = fmt.Sprintf("::fidl::InterfaceHandle<%s>", t)
r.LLDecl = r.Decl
r.Dtor = fmt.Sprintf("~InterfaceHandle")
default:
log.Fatal("Unknown declaration type: ", declType)
}
r.DeclType = declType
default:
log.Fatal("Unknown type kind: ", val.Kind)
}
return r
}
func (c *compiler) compileConst(val types.Const) Const {
if val.Type.Kind == types.StringType {
return Const{
Attributes: val.Attributes,
Extern: true,
Decorator: "const",
Type: Type{
Decl: "char",
},
Name: c.compileCompoundIdentifier(val.Name, "[]"),
Value: c.compileConstant(val.Value, nil),
}
} else {
t := c.compileType(val.Type)
return Const{
Attributes: val.Attributes,
Extern: false,
Decorator: "constexpr",
Type: t,
Name: c.compileCompoundIdentifier(val.Name, ""),
Value: c.compileConstant(val.Value, &t),
}
}
}
func (c *compiler) compileEnum(val types.Enum) Enum {
r := Enum{
Namespace: c.namespace,
Type: c.compilePrimitiveSubtype(val.Type),
Name: c.compileCompoundIdentifier(val.Name, ""),
Members: []EnumMember{},
}
for _, v := range val.Members {
r.Members = append(r.Members, EnumMember{
changeIfReserved(v.Name, ""),
c.compileConstant(v.Value, nil),
})
}
return r
}
func (c *compiler) compileParameterArray(val []types.Parameter) []Parameter {
r := []Parameter{}
for _, v := range val {
p := Parameter{
c.compileType(v.Type),
changeIfReserved(v.Name, ""),
v.Offset,
}
r = append(r, p)
}
return r
}
func (c *compiler) maxHandlesFromParameterArray(val []types.Parameter) int {
numHandles := int64(0)
for _, v := range val {
numHandles += int64(v.MaxHandles)
}
if numHandles > math.MaxUint32 {
return math.MaxUint32
} else {
return int(numHandles)
}
}
func (c *compiler) maxOutOfLineFromParameterArray(val []types.Parameter) int {
maxOutOfLine := int64(0)
for _, v := range val {
maxOutOfLine += int64(v.MaxOutOfLine)
}
if maxOutOfLine > math.MaxUint32 {
return math.MaxUint32
} else {
return int(maxOutOfLine)
}
}
func (m Method) NewLLProps(r Interface) LLProps {
return LLProps{
InterfaceName: r.Name,
// If the response is not inline, then we cannot generate an out-parameter-style binding,
// because the out-of-line pointers would outlive their underlying managed storage.
CBindingCompatible: m.ResponseMaxOutOfLine == 0,
NeedToLinearize: len(m.Request) > 0 && m.RequestMaxOutOfLine > 0,
StackAllocRequest: len(m.Request) == 0 || (m.RequestSize + m.RequestMaxOutOfLine) < llcppMaxStackAllocSize,
StackAllocResponse: len(m.Response) == 0 || (m.ResponseSize + m.ResponseMaxOutOfLine) < llcppMaxStackAllocSize,
EncodeRequest: m.RequestMaxOutOfLine > 0 || m.RequestMaxHandles > 0,
DecodeResponse: m.ResponseMaxOutOfLine > 0 || m.ResponseMaxHandles > 0,
}
}
func (c *compiler) compileInterface(val types.Interface) Interface {
r := Interface{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: c.compileCompoundIdentifier(val.Name, ""),
ClassName: c.compileCompoundIdentifier(val.Name, "_clazz"),
ServiceName: val.GetServiceName(),
ProxyName: c.compileCompoundIdentifier(val.Name, "_Proxy"),
StubName: c.compileCompoundIdentifier(val.Name, "_Stub"),
EventSenderName: c.compileCompoundIdentifier(val.Name, "_EventSender"),
SyncName: c.compileCompoundIdentifier(val.Name, "_Sync"),
SyncProxyName: c.compileCompoundIdentifier(val.Name, "_SyncProxy"),
Methods: []Method{},
}
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"
}
_, transitional := v.LookupAttribute("Transitional")
m := Method{
Attributes: v.Attributes,
Ordinal: v.Ordinal,
OrdinalName: fmt.Sprintf("k%s_%s_Ordinal", r.Name, v.Name),
GenOrdinal: v.GenOrdinal,
GenOrdinalName: fmt.Sprintf("k%s_%s_GenOrdinal", r.Name, v.Name),
Name: name,
HasRequest: v.HasRequest,
Request: c.compileParameterArray(v.Request),
RequestSize: v.RequestSize,
RequestTypeName: fmt.Sprintf("%s_%s%sRequestTable", c.symbolPrefix, r.Name, v.Name),
RequestMaxHandles: c.maxHandlesFromParameterArray(v.Request),
RequestMaxOutOfLine: c.maxOutOfLineFromParameterArray(v.Request),
HasResponse: v.HasResponse,
Response: c.compileParameterArray(v.Response),
ResponseSize: v.ResponseSize,
ResponseTypeName: fmt.Sprintf("%s_%s%s%s", c.symbolPrefix, r.Name, v.Name, responseTypeNameSuffix),
ResponseMaxHandles: c.maxHandlesFromParameterArray(v.Response),
ResponseMaxOutOfLine: c.maxOutOfLineFromParameterArray(v.Response),
CallbackType: callbackType,
ResponseHandlerType: fmt.Sprintf("%s_%s_ResponseHandler", r.Name, v.Name),
ResponderType: fmt.Sprintf("%s_%s_Responder", r.Name, v.Name),
Transitional: transitional,
}
m.LLProps = m.NewLLProps(r)
r.Methods = append(r.Methods, m)
}
return r
}
func (c *compiler) compileStructMember(val types.StructMember) StructMember {
t := c.compileType(val.Type)
defaultValue := ""
if val.MaybeDefaultValue != nil {
defaultValue = c.compileConstant(*val.MaybeDefaultValue, &t)
}
return StructMember{
val.Attributes,
t,
changeIfReserved(val.Name, ""),
defaultValue,
val.Offset,
}
}
func (c *compiler) compileStruct(val types.Struct) Struct {
name := c.compileCompoundIdentifier(val.Name, "")
r := Struct{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: name,
TableType: fmt.Sprintf("%s_%sTable", c.symbolPrefix, name),
Members: []StructMember{},
Size: val.Size,
MaxHandles: val.MaxHandles,
MaxOutOfLine: val.MaxOutOfLine,
}
for _, v := range val.Members {
r.Members = append(r.Members, c.compileStructMember(v))
}
if len(r.Members) == 0 {
r.Members = []StructMember{
c.compileStructMember(types.EmptyStructMember("__reserved")),
}
}
return r
}
func (c *compiler) compileTableMember(val types.TableMember) TableMember {
t := c.compileType(val.Type)
defaultValue := ""
if val.MaybeDefaultValue != nil {
defaultValue = c.compileConstant(*val.MaybeDefaultValue, &t)
}
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_", val.Name),
MethodHasName: fmt.Sprintf("has_%s", val.Name),
MethodClearName: fmt.Sprintf("clear_%s", val.Name),
ValueUnionName: fmt.Sprintf("ValueUnion_%s", val.Name),
}
}
type byOrdinal []TableMember
func (m byOrdinal) Len() int {
return len(m)
}
func (m byOrdinal) Swap(i, j int) {
m[i], m[j] = m[j], m[i]
}
func (m byOrdinal) Less(i, j int) bool {
return m[i].Ordinal < m[j].Ordinal
}
func (c *compiler) compileTable(val types.Table) Table {
name := c.compileCompoundIdentifier(val.Name, "")
r := Table{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: name,
TableType: fmt.Sprintf("%s_%sTable", c.symbolPrefix, name),
Members: nil,
Size: val.Size,
BiggestOrdinal: 0,
}
for _, v := range val.Members {
if v.Reserved {
continue
}
m := c.compileTableMember(v)
r.BiggestOrdinal = m.Ordinal
r.Members = append(r.Members, m)
}
sort.Sort(byOrdinal(r.Members))
return r
}
func (c *compiler) compileUnionMember(val types.UnionMember) UnionMember {
n := changeIfReserved(val.Name, "")
return UnionMember{
Attributes: val.Attributes,
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, "")
r := Union{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: name,
TableType: fmt.Sprintf("%s_%sTable", c.symbolPrefix, name),
Members: []UnionMember{},
Size: val.Size,
MaxHandles: val.MaxHandles,
MaxOutOfLine: val.MaxOutOfLine,
}
for _, v := range val.Members {
r.Members = append(r.Members, c.compileUnionMember(v))
}
return r
}
func (c *compiler) compileXUnionMember(val types.XUnionMember) XUnionMember {
n := changeIfReserved(val.Name, "")
return XUnionMember{
Attributes: val.Attributes,
Ordinal: 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) compileXUnion(val types.XUnion) XUnion {
name := c.compileCompoundIdentifier(val.Name, "")
r := XUnion{
Attributes: val.Attributes,
Namespace: c.namespace,
Name: name,
TableType: fmt.Sprintf("%s_%sTable", c.symbolPrefix, name),
Size: val.Size,
MaxHandles: val.MaxHandles,
MaxOutOfLine: val.MaxOutOfLine,
}
for _, v := range val.Members {
r.Members = append(r.Members, c.compileXUnionMember(v))
}
return r
}
func Compile(r types.Root) Root {
root := Root{}
library := types.ParseLibraryName(r.Name)
c := compiler{
formatNamespace(library),
formatLibraryPrefix(library),
&r.Decls,
types.ParseLibraryName(r.Name),
make(map[types.HandleSubtype]bool),
}
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.Consts {
d := c.compileConst(v)
decls[v.Name] = &d
}
for _, v := range r.Enums {
d := c.compileEnum(v)
decls[v.Name] = &d
}
for _, v := range r.Interfaces {
d := c.compileInterface(v)
decls[v.Name] = &d
}
for _, v := range r.Structs {
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.Unions {
d := c.compileUnion(v)
decls[v.Name] = &d
}
for _, v := range r.XUnions {
d := c.compileXUnion(v)
decls[v.Name] = &d
}
for _, v := range r.DeclOrder {
d := decls[v]
if d == nil {
log.Fatal("Unknown declaration: ", v)
}
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)
h := fmt.Sprintf("%s/cpp/fidl.h", formatLibraryPath(libraryIdent))
root.Headers = append(root.Headers, h)
h = fmt.Sprintf("%s/llcpp/fidl.h", formatLibraryPath(libraryIdent))
root.LLHeaders = append(root.LLHeaders, h)
}
// zx::channel is always referenced by the interfaces in llcpp bindings API
if len(r.Interfaces) > 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
}