blob: 7c03ed64bac2130098eb2b274b307725cb5c2b6f [file] [log] [blame]
// Copyright 2020 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 hlcpp
import (
"bytes"
"encoding/hex"
"fmt"
"strconv"
"strings"
gidlir "go.fuchsia.dev/fuchsia/tools/fidl/gidl/ir"
gidlmixer "go.fuchsia.dev/fuchsia/tools/fidl/gidl/mixer"
"go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen"
)
func escapeStr(value string) string {
if fidlgen.PrintableASCII(value) {
return strconv.Quote(value)
}
var (
buf bytes.Buffer
src = []byte(value)
dstLen = hex.EncodedLen(len(src))
dst = make([]byte, dstLen)
)
hex.Encode(dst, src)
buf.WriteRune('"')
for i := 0; i < dstLen; i += 2 {
buf.WriteString("\\x")
buf.WriteByte(dst[i])
buf.WriteByte(dst[i+1])
}
buf.WriteRune('"')
return buf.String()
}
func buildHandleDef(def gidlir.HandleDef) string {
switch def.Subtype {
case fidlgen.Channel:
return fmt.Sprintf("fidl::test::util::CreateChannel(%d)", def.Rights)
case fidlgen.Event:
return fmt.Sprintf("fidl::test::util::CreateEvent(%d)", def.Rights)
default:
panic(fmt.Sprintf("unsupported handle subtype: %s", def.Subtype))
}
}
func handleType(subtype fidlgen.HandleSubtype) string {
switch subtype {
case fidlgen.Channel:
return "ZX_OBJ_TYPE_CHANNEL"
case fidlgen.Event:
return "ZX_OBJ_TYPE_EVENT"
default:
panic(fmt.Sprintf("unsupported handle subtype: %s", subtype))
}
}
func BuildHandleDefs(defs []gidlir.HandleDef) string {
if len(defs) == 0 {
return ""
}
var builder strings.Builder
builder.WriteString("std::vector<zx_handle_t>{\n")
for i, d := range defs {
builder.WriteString(buildHandleDef(d))
// Write indices corresponding to the .gidl file handle_defs block.
builder.WriteString(fmt.Sprintf(", // #%d\n", i))
}
builder.WriteString("}")
return builder.String()
}
func BuildHandleInfoDefs(defs []gidlir.HandleDef) string {
if len(defs) == 0 {
return ""
}
var builder strings.Builder
builder.WriteString("std::vector<zx_handle_info_t>{\n")
for i, d := range defs {
builder.WriteString(fmt.Sprintf(`
// #%d
zx_handle_info_t{
.handle = %s,
.type = %s,
.rights = %d,
.unused = 0u,
},
`, i, buildHandleDef(d), handleType(d.Subtype), d.Rights))
}
builder.WriteString("}")
return builder.String()
}
func newCppValueBuilder() cppValueBuilder {
return cppValueBuilder{}
}
type cppValueBuilder struct {
strings.Builder
varidx int
handleExtractOp string
}
func (b *cppValueBuilder) newVar() string {
b.varidx++
return fmt.Sprintf("v%d", b.varidx)
}
func (b *cppValueBuilder) visit(value interface{}, decl gidlmixer.Declaration) string {
switch value := value.(type) {
case bool:
return fmt.Sprintf("%t", value)
case int64:
intString := fmt.Sprintf("%dll", value)
if value == -9223372036854775808 {
intString = "-9223372036854775807ll - 1"
}
switch decl := decl.(type) {
case *gidlmixer.IntegerDecl:
return intString
case *gidlmixer.BitsDecl:
return fmt.Sprintf("%s(%s)", typeName(decl), intString)
case *gidlmixer.EnumDecl:
return fmt.Sprintf("%s(%s)", typeName(decl), intString)
}
case uint64:
switch decl := decl.(type) {
case *gidlmixer.IntegerDecl:
return fmt.Sprintf("%dull", value)
case *gidlmixer.BitsDecl:
return fmt.Sprintf("%s(%dull)", typeName(decl), value)
case *gidlmixer.EnumDecl:
return fmt.Sprintf("%s(%dull)", typeName(decl), value)
}
case float64:
switch decl := decl.(type) {
case *gidlmixer.FloatDecl:
switch decl.Subtype() {
case fidlgen.Float32:
s := fmt.Sprintf("%g", value)
if strings.Contains(s, ".") {
return fmt.Sprintf("%sf", s)
} else {
return s
}
case fidlgen.Float64:
return fmt.Sprintf("%g", value)
}
}
case gidlir.RawFloat:
switch decl.(*gidlmixer.FloatDecl).Subtype() {
case fidlgen.Float32:
return fmt.Sprintf("([] { uint32_t u = %#b; float f; memcpy(&f, &u, 4); return f; })()", value)
case fidlgen.Float64:
return fmt.Sprintf("([] { uint64_t u = %#b; double d; memcpy(&d, &u, 8); return d; })()", value)
}
case string:
return fmt.Sprintf("%s(%s, %d)", typeName(decl), escapeStr(value), len(value))
case gidlir.HandleWithRights:
return fmt.Sprintf("%s(handle_defs[%d]%s)", typeName(decl), value.Handle, b.handleExtractOp)
case gidlir.Record:
return b.visitRecord(value, decl.(gidlmixer.RecordDeclaration))
case []interface{}:
switch decl := decl.(type) {
case *gidlmixer.ArrayDecl:
return b.visitArray(value, decl)
case *gidlmixer.VectorDecl:
return b.visitVector(value, decl)
}
case nil:
return fmt.Sprintf("%s()", typeName(decl))
}
panic(fmt.Sprintf("not implemented: %T", value))
}
func (b *cppValueBuilder) visitRecord(value gidlir.Record, decl gidlmixer.RecordDeclaration) string {
containerVar := b.newVar()
nullable := decl.IsNullable()
if nullable {
b.Builder.WriteString(fmt.Sprintf(
"%s %s = std::make_unique<%s>();\n", typeName(decl), containerVar, declName(decl)))
} else {
b.Builder.WriteString(fmt.Sprintf("%s %s;\n", typeName(decl), containerVar))
}
_, isTable := decl.(*gidlmixer.TableDecl)
for _, field := range value.Fields {
accessor := "."
if nullable {
accessor = "->"
}
b.Builder.WriteString("\n")
if field.Key.IsUnknown() {
if isTable {
unknownData := field.Value.(gidlir.UnknownData)
if decl.IsResourceType() {
b.Builder.WriteString(fmt.Sprintf(`::fidl::UnknownData _data = {
.bytes=%s,
.handles=%s
};
%s%sSetUnknownDataEntry(%dlu, std::move(_data));
`, BuildBytes(unknownData.Bytes), buildHandles(unknownData.Handles, b.handleExtractOp), containerVar, accessor, field.Key.UnknownOrdinal))
} else {
b.Builder.WriteString(fmt.Sprintf(
"%s%sSetUnknownDataEntry(%dlu, %s);\n",
containerVar, accessor, field.Key.UnknownOrdinal, BuildBytes(unknownData.Bytes)))
}
} else {
unknownData := field.Value.(gidlir.UnknownData)
if decl.IsResourceType() {
b.Builder.WriteString(fmt.Sprintf(
"%s%sSetUnknownData(static_cast<fidl_xunion_tag_t>(%dlu), %s, %s);\n",
containerVar, accessor, field.Key.UnknownOrdinal, BuildBytes(unknownData.Bytes),
buildHandles(unknownData.Handles, b.handleExtractOp)))
} else {
b.Builder.WriteString(fmt.Sprintf(
"%s%sSetUnknownData(static_cast<fidl_xunion_tag_t>(%dlu), %s);\n",
containerVar, accessor, field.Key.UnknownOrdinal, BuildBytes(unknownData.Bytes)))
}
}
continue
}
fieldDecl, ok := decl.Field(field.Key.Name)
if !ok {
panic(fmt.Sprintf("field %s not found", field.Key.Name))
}
fieldVar := b.visit(field.Value, fieldDecl)
switch decl.(type) {
case *gidlmixer.StructDecl:
b.Builder.WriteString(fmt.Sprintf(
"%s%s%s = %s;\n", containerVar, accessor, field.Key.Name, fieldVar))
default:
b.Builder.WriteString(fmt.Sprintf(
"%s%sset_%s(%s);\n", containerVar, accessor, field.Key.Name, fieldVar))
}
}
return fmt.Sprintf("std::move(%s)", containerVar)
}
func (b *cppValueBuilder) visitArray(value []interface{}, decl *gidlmixer.ArrayDecl) string {
var elements []string
elemDecl := decl.Elem()
for _, item := range value {
elements = append(elements, fmt.Sprintf("%s", b.visit(item, elemDecl)))
}
// Populate the array using aggregate initialization.
return fmt.Sprintf("%s{%s}",
typeName(decl), strings.Join(elements, ", "))
}
func (b *cppValueBuilder) visitVector(value []interface{}, decl *gidlmixer.VectorDecl) string {
elemDecl := decl.Elem()
var elements []string
for _, item := range value {
elements = append(elements, b.visit(item, elemDecl))
}
if _, ok := elemDecl.(gidlmixer.PrimitiveDeclaration); ok {
// Populate the vector using aggregate initialization.
return fmt.Sprintf("%s{%s}",
typeName(decl), strings.Join(elements, ", "))
}
vectorVar := b.newVar()
// Populate the vector using push_back. We can't use an initializer list
// because they always copy, which breaks if the element is a unique_ptr.
b.Builder.WriteString(fmt.Sprintf("%s %s;\n", typeName(decl), vectorVar))
for _, element := range elements {
b.Builder.WriteString(fmt.Sprintf("%s.push_back(%s);\n", vectorVar, element))
}
return fmt.Sprintf("std::move(%s)", vectorVar)
}
func typeName(decl gidlmixer.Declaration) string {
switch decl := decl.(type) {
case gidlmixer.PrimitiveDeclaration:
return primitiveTypeName(decl.Subtype())
case gidlmixer.NamedDeclaration:
if decl.IsNullable() {
return fmt.Sprintf("std::unique_ptr<%s>", declName(decl))
}
return declName(decl)
case *gidlmixer.StringDecl:
if decl.IsNullable() {
return "::fidl::StringPtr"
}
return "std::string"
case *gidlmixer.ArrayDecl:
return fmt.Sprintf("std::array<%s, %d>", typeName(decl.Elem()), decl.Size())
case *gidlmixer.VectorDecl:
if decl.IsNullable() {
return fmt.Sprintf("::fidl::VectorPtr<%s>", typeName(decl.Elem()))
}
return fmt.Sprintf("std::vector<%s>", typeName(decl.Elem()))
case *gidlmixer.HandleDecl:
switch decl.Subtype() {
case fidlgen.Handle:
return "zx::handle"
case fidlgen.Channel:
return "zx::channel"
case fidlgen.Event:
return "zx::event"
default:
panic(fmt.Sprintf("Handle subtype not supported %s", decl.Subtype()))
}
default:
panic("unhandled case")
}
}
func declName(decl gidlmixer.NamedDeclaration) string {
parts := strings.Split(decl.Name(), "/")
return strings.Join(parts, "::")
}
func primitiveTypeName(subtype fidlgen.PrimitiveSubtype) string {
switch subtype {
case fidlgen.Bool:
return "bool"
case fidlgen.Uint8, fidlgen.Uint16, fidlgen.Uint32, fidlgen.Uint64,
fidlgen.Int8, fidlgen.Int16, fidlgen.Int32, fidlgen.Int64:
return fmt.Sprintf("%s_t", subtype)
case fidlgen.Float32:
return "float"
case fidlgen.Float64:
return "double"
default:
panic(fmt.Sprintf("unexpected subtype %s", subtype))
}
}
func BuildBytes(bytes []byte) string {
var builder strings.Builder
builder.WriteString("std::vector<uint8_t>{")
for i, b := range bytes {
if i%8 == 0 {
builder.WriteString("\n")
}
builder.WriteString(fmt.Sprintf("0x%02x,", b))
}
builder.WriteString("\n}")
return builder.String()
}
func buildRawHandleImpl(handles []gidlir.Handle, handleType, handleExtractOp string) string {
if len(handles) == 0 {
return fmt.Sprintf("std::vector<%s>{}", handleType)
}
var builder strings.Builder
builder.WriteString(fmt.Sprintf("std::vector<%s>{\n", handleType))
for i, h := range handles {
builder.WriteString(fmt.Sprintf("handle_defs[%d]%s,", h, handleExtractOp))
if i%8 == 7 {
builder.WriteString("\n")
}
}
builder.WriteString("}")
return builder.String()
}
func BuildRawHandles(handles []gidlir.Handle) string {
return buildRawHandleImpl(handles, "zx_handle_t", "")
}
func BuildRawHandlesFromHandleInfos(handles []gidlir.Handle) string {
return buildRawHandleImpl(handles, "zx_handle_t", ".handle")
}
func BuildRawHandleInfos(handles []gidlir.Handle) string {
return buildRawHandleImpl(handles, "zx_handle_info_t", "")
}
func BuildRawHandleDispositions(handle_dispositions []gidlir.HandleDisposition) string {
if len(handle_dispositions) == 0 {
return fmt.Sprintf("std::vector<zx_handle_disposition_t>{}")
}
var builder strings.Builder
builder.WriteString(fmt.Sprintf("std::vector<zx_handle_disposition_t>{"))
for _, h := range handle_dispositions {
builder.WriteString(fmt.Sprintf(`
{
.operation = ZX_HANDLE_OP_MOVE,
.handle = handle_defs[%d],
.type = %d,
.rights = %d,
.result = ZX_OK,
},`, h.Handle, h.Type, h.Rights))
}
builder.WriteString("}")
return builder.String()
}
func buildHandles(handles []gidlir.Handle, handleExtractOp string) string {
if len(handles) == 0 {
return "std::vector<zx::handle>{}"
}
var builder strings.Builder
// Initializer-list vectors only work for copyable types. zx::handle has no
// copy constructor, so we use an immediately-invoked lambda instead.
builder.WriteString("([&handle_defs] {\n")
builder.WriteString("std::vector<zx::handle> v;\n")
for _, h := range handles {
builder.WriteString(fmt.Sprintf("v.emplace_back(handle_defs[%d]%s);\n", h, handleExtractOp))
}
builder.WriteString("return v;\n")
builder.WriteString("})()")
return builder.String()
}