blob: e7ffe722ea01604868874108b91478fa62ba47a8 [file] [log] [blame]
// Copyright 2016 The Chromium 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 cgen
import (
"fmt"
"log"
"path/filepath"
"sort"
"strconv"
"strings"
"mojom/generated/mojom_files"
"mojom/generated/mojom_types"
)
// Describes a field (such as a struct or a union member) to be printed.
// Used inside StructTemplate and UnionTemplate.
type FieldTemplate struct {
// Text contains both type and field name. We don't separate this into two
// fields, since a) there's no real need, b) bools have bit field specifiers.
Text string
// Optional comment.
Comment string
}
// Describes the entirety of a struct to be printed.
type StructTemplate struct {
// Generated C name of the struct.
Name string
// |fields| is in packing order, ready to be printed.
Fields []FieldTemplate
// Constants defined within this struct, in declaration order.
Constants []ConstantTemplate
// Enums defined within this struct, in declaration order.
Enums []EnumTemplate
}
type UnionTemplate struct {
// Generated C name of the union.
Name string
// just the field names, in tag order.
Fields []FieldTemplate
// the tags enum generated from the fields above.
TagsEnum EnumTemplate
}
type EnumValueTemplate struct {
// Generated C name of the enum name.
Name string
// Integer value of the enum; if the enum value was defined in the mojom IDL
// as a reference, the value generated here will be the reduced int value.
Value int64
}
type EnumTemplate struct {
Name string
// In declaration order.
Values []EnumValueTemplate
}
type InterfaceMessageTemplate struct {
Name string
// This is the ordinal name (denoted by '@x' in the IDL).
MessageOrdinal uint32
MinVersion uint32
RequestStruct StructTemplate
// ResponseStruct.Name == "" if response not defined.
ResponseStruct StructTemplate
}
type InterfaceTemplate struct {
Name string
ServiceName string
Version uint32
Messages []InterfaceMessageTemplate
Enums []EnumTemplate
Constants []ConstantTemplate
}
// The type, name and initial value of the constant.
// Describes mojom constants declared top level, in structs, and in
// interfaces.
type ConstantTemplate struct {
Type string
Name string
Value string
}
// Print things in roughly this order:
// - Imports
// - Struct forward decl
// - Union forward decl
// - Constants
// - Enum typedefs + constants for all enums
// - Union defs
// - Struct defs
// - Interface request+response defs
// - Type tables
type HeaderTemplate struct {
HeaderGuard string
Imports []string
Constants []ConstantTemplate
Enums []EnumTemplate
Structs []StructTemplate
Unions []UnionTemplate
Interfaces []InterfaceTemplate
TypeTable TypeTableTemplate
}
// The follow type allows us to sort fields in packing-order, which is defined
// by the byte+bit-offsets already provided to us by the mojom parser.
type MojomSortableStructFields []mojom_types.StructField
func (a MojomSortableStructFields) Len() int { return len(a) }
func (a MojomSortableStructFields) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (a MojomSortableStructFields) Less(i, j int) bool {
if a[i].Offset == a[j].Offset {
return a[i].Bit < a[j].Bit
}
return a[i].Offset < a[j].Offset
}
func NewEnumTemplate(fileGraph *mojom_files.MojomFileGraph, mojomEnum *mojom_types.MojomEnum) EnumTemplate {
enumName := mojomToCName(*mojomEnum.DeclData.FullIdentifier)
if isReservedKeyword(enumName) {
log.Fatalf("Generated name %s is a reserved C keyword", enumName)
}
var values []EnumValueTemplate
for _, enumVal := range mojomEnum.Values {
values = append(values, EnumValueTemplate{
Name: *enumVal.DeclData.ShortName,
Value: int64(enumVal.IntValue),
})
}
return EnumTemplate{
Name: enumName,
Values: values,
}
}
// Given a |mojom_types.Value|, returns the C mangled version of the value.
// - if a literal, returns the C equivalent
// - if a reference, returns the resolved, concrete value of the reference
// (enum value or constant). This avoids any circular-dependency issues.
func resolveValue(fileGraph *mojom_files.MojomFileGraph, value mojom_types.Value) string {
switch value.(type) {
case *mojom_types.ValueLiteralValue:
return mojomToCLiteral(value.Interface().(mojom_types.LiteralValue))
case *mojom_types.ValueConstantReference:
const_key := value.Interface().(mojom_types.ConstantReference).ConstantKey
if decl_const, exists := fileGraph.ResolvedConstants[const_key]; exists {
constValue := decl_const.Value
if decl_const.ResolvedConcreteValue != nil {
constValue = decl_const.ResolvedConcreteValue
}
return resolveValue(fileGraph, constValue)
}
log.Fatal("Unresolved constant:", value)
case *mojom_types.ValueEnumValueReference:
enumRef := value.Interface().(mojom_types.EnumValueReference)
if udt, exists := fileGraph.ResolvedTypes[enumRef.EnumTypeKey]; exists {
enumType := udt.Interface().(mojom_types.MojomEnum)
enumVal := enumType.Values[enumRef.EnumValueIndex]
if enumVal.InitializerValue == nil {
return strconv.FormatInt(int64(enumVal.IntValue), 10)
}
return resolveValue(fileGraph, enumVal.InitializerValue)
}
case *mojom_types.ValueBuiltinValue:
return mojomToCBuiltinValue(value.Interface().(mojom_types.BuiltinConstantValue))
default:
log.Fatal("Shouldn't be here. Unknown value type for:", value)
}
return ""
}
func NewConstantTemplate(fileGraph *mojom_files.MojomFileGraph, mojomConstant mojom_types.DeclaredConstant) ConstantTemplate {
var type_text string
switch mojomConstant.Type.(type) {
// We can't type string constants as a 'union MojomStringHeaderPtr' since it
// involves some setup and prevents immediate consumption. const char* should
// suffice.
case *mojom_types.TypeStringType:
type_text = "char*"
default:
type_text = mojomToCType(mojomConstant.Type, fileGraph)
}
name := mojomToCName(*mojomConstant.DeclData.FullIdentifier)
if isReservedKeyword(name) {
log.Fatalf("Generated name %s is a reserved C keyword", name)
}
val := resolveValue(fileGraph, mojomConstant.Value)
return ConstantTemplate{
Type: type_text,
Name: name,
Value: val,
}
}
func NewUnionTemplate(fileGraph *mojom_files.MojomFileGraph, mojomUnion *mojom_types.MojomUnion) UnionTemplate {
union_name := mojomToCName(*mojomUnion.DeclData.FullIdentifier)
if isReservedKeyword(union_name) {
log.Fatalf("Generated name %s is a reserved C keyword", union_name)
}
var fields []FieldTemplate
var tag_enums []EnumValueTemplate
for field_i, mojomField := range mojomUnion.Fields {
field_name := *mojomField.DeclData.ShortName
var field_text string
if mojomTypeIsBool(mojomField.Type) {
field_text = fmt.Sprintf("bool %s : 1", field_name)
} else {
field_type_text := mojomToCType(mojomField.Type, fileGraph)
if mojomTypeIsUnion(mojomField.Type, fileGraph) {
// Unions within unions are pointers/references, not inlined.
// This will transform: "struct MyUnion" -> "union MyUnionPtr"
// TODO(vardhan): This feels hacky..
field_type_text = strings.Replace(field_type_text, "struct ", "union ", 1)
field_type_text += "Ptr"
}
field_text = fmt.Sprintf("%s f_%s", field_type_text, field_name)
}
fields = append(fields, FieldTemplate{
Text: field_text,
})
tag_enums = append(tag_enums, EnumValueTemplate{
Name: field_name,
Value: int64(field_i),
})
}
return UnionTemplate{
Name: union_name,
Fields: fields,
TagsEnum: EnumTemplate{
Name: union_name + "_Tag",
Values: tag_enums,
},
}
}
func generateContainedDeclarations(fileGraph *mojom_files.MojomFileGraph,
decls *mojom_types.ContainedDeclarations) (constants []ConstantTemplate, enums []EnumTemplate) {
if decls.Constants != nil {
for _, mojomConstKey := range *decls.Constants {
constants = append(constants,
NewConstantTemplate(fileGraph, fileGraph.ResolvedConstants[mojomConstKey]))
}
}
if decls.Enums != nil {
for _, mojomEnumKey := range *decls.Enums {
mojom_enum := fileGraph.ResolvedTypes[mojomEnumKey].Interface().(mojom_types.MojomEnum)
enums = append(enums, NewEnumTemplate(fileGraph, &mojom_enum))
}
}
return
}
func NewInterfaceTemplate(fileGraph *mojom_files.MojomFileGraph, mojomInterface *mojom_types.MojomInterface) InterfaceTemplate {
interface_name := mojomToCName(*mojomInterface.DeclData.FullIdentifier)
var service_name string
if mojomInterface.ServiceName != nil {
service_name = *mojomInterface.ServiceName
}
// Generate templates for the containing constants and enums
var constants []ConstantTemplate
var enums []EnumTemplate
if decls := mojomInterface.DeclData.ContainedDeclarations; decls != nil {
constants, enums = generateContainedDeclarations(fileGraph, decls)
}
var msgs []InterfaceMessageTemplate
for _, mojomMethod := range mojomInterface.Methods {
req_struct := NewStructTemplate(fileGraph, &mojomMethod.Parameters)
req_struct.Name = requestMethodToCName(mojomInterface, &mojomMethod.Parameters)
var resp_struct StructTemplate
if mojomMethod.ResponseParams != nil {
resp_struct = NewStructTemplate(fileGraph, mojomMethod.ResponseParams)
resp_struct.Name = responseMethodToCName(mojomInterface, mojomMethod.ResponseParams)
}
msgs = append(msgs, InterfaceMessageTemplate{
Name: *mojomMethod.DeclData.ShortName,
MessageOrdinal: mojomMethod.Ordinal,
MinVersion: mojomMethod.MinVersion,
RequestStruct: req_struct,
ResponseStruct: resp_struct,
})
}
return InterfaceTemplate{
Name: interface_name,
ServiceName: service_name,
Version: mojomInterface.CurrentVersion,
Enums: enums,
Constants: constants,
Messages: msgs,
}
}
// TODO(vardhan): We can make this function, along with all other New*Template()
// functions, be a method for InterfaceTemplate so that we don't have to pass
// around |fileGraph| all over the place.
func NewStructTemplate(fileGraph *mojom_files.MojomFileGraph,
mojomStruct *mojom_types.MojomStruct) StructTemplate {
// Sort fields by packing order (by offset,bit).
sortedFields := mojomStruct.Fields
sort.Sort(MojomSortableStructFields(sortedFields))
// Generate the printable fields.
var fields []FieldTemplate
for i, mojomField := range sortedFields {
field_name := *mojomField.DeclData.ShortName
if isReservedKeyword(field_name) {
log.Fatalf("Generated field name %s is a reserved C keyword", field_name)
}
var field_text string
if mojomTypeIsBool(mojomField.Type) {
field_text = fmt.Sprintf("bool %s : 1", field_name)
} else {
field_text = fmt.Sprintf("%s %s",
mojomToCType(mojomField.Type, fileGraph), field_name)
}
fields = append(fields, FieldTemplate{
Text: field_text,
Comment: fmt.Sprintf("offset,bit = %d,%d", mojomField.Offset, mojomField.Bit),
})
// Compute the padding between this field and the next.
if padding := getPaddingAfter(sortedFields, i, fileGraph); padding > 0 {
fields = append(fields, FieldTemplate{
Text: fmt.Sprintf("uint8_t pad%d_[%d]", i, padding),
Comment: "padding"})
}
}
// Generate templates for the containing constants and enums
var constants []ConstantTemplate
var enums []EnumTemplate
if decls := mojomStruct.DeclData.ContainedDeclarations; decls != nil {
constants, enums = generateContainedDeclarations(fileGraph, decls)
}
// FullIdentifier may not exist if this struct is an interface method params struct.
var name string
if mojomStruct.DeclData.FullIdentifier != nil {
name = mojomToCName(*mojomStruct.DeclData.FullIdentifier)
} else {
name = mojomToCName(*mojomStruct.DeclData.ShortName)
}
if isReservedKeyword(name) {
log.Fatalf("Generated struct name %s is a reserved C keyword", name)
}
return StructTemplate{
Name: name,
Fields: fields,
Constants: constants,
Enums: enums,
}
}
func NewHeaderTemplate(fileGraph *mojom_files.MojomFileGraph, file *mojom_files.MojomFile, srcRootPath string) HeaderTemplate {
var relGenPath string
var err error
if relGenPath, err = filepath.Rel(srcRootPath, file.FileName); err != nil {
log.Fatal(err)
}
var imports []string
if file.Imports != nil {
for _, import_str := range *file.Imports {
imports = append(imports, mojomToCFilePath(srcRootPath, import_str))
}
}
var constants []ConstantTemplate
if file.DeclaredMojomObjects.TopLevelConstants != nil {
for _, mojomConstKey := range *(file.DeclaredMojomObjects.TopLevelConstants) {
mojomConst := fileGraph.ResolvedConstants[mojomConstKey]
constants = append(constants, NewConstantTemplate(fileGraph, mojomConst))
}
}
var enums []EnumTemplate
if file.DeclaredMojomObjects.TopLevelEnums != nil {
for _, mojomEnumKey := range *(file.DeclaredMojomObjects.TopLevelEnums) {
mojomEnum := fileGraph.ResolvedTypes[mojomEnumKey].Interface().(mojom_types.MojomEnum)
enums = append(enums, NewEnumTemplate(fileGraph, &mojomEnum))
}
}
var unions []UnionTemplate
if file.DeclaredMojomObjects.Unions != nil {
for _, mojomUnionKey := range *(file.DeclaredMojomObjects.Unions) {
mojomUnion := fileGraph.ResolvedTypes[mojomUnionKey].Interface().(mojom_types.MojomUnion)
unions = append(unions, NewUnionTemplate(fileGraph, &mojomUnion))
}
}
var structs []StructTemplate
if file.DeclaredMojomObjects.Structs != nil {
for _, mojomStructKey := range *(file.DeclaredMojomObjects.Structs) {
mojomStruct := fileGraph.ResolvedTypes[mojomStructKey].Interface().(mojom_types.MojomStruct)
structs = append(structs, NewStructTemplate(fileGraph, &mojomStruct))
}
}
var interfaces []InterfaceTemplate
if file.DeclaredMojomObjects.Interfaces != nil {
for _, mojomIfaceKey := range *(file.DeclaredMojomObjects.Interfaces) {
mojomIface := fileGraph.ResolvedTypes[mojomIfaceKey].Interface().(mojom_types.MojomInterface)
interfaces = append(interfaces, NewInterfaceTemplate(fileGraph, &mojomIface))
}
}
return HeaderTemplate{
HeaderGuard: toHeaderGuard(relGenPath),
Imports: imports,
Constants: constants,
Structs: structs,
Unions: unions,
Enums: enums,
Interfaces: interfaces,
TypeTable: NewTypeTableTemplate(fileGraph, file),
}
}
func getPaddingAfter(fields []mojom_types.StructField, i int, fileGraph *mojom_files.MojomFileGraph) uint32 {
// Must be a power of 2 for the remaining computation to work
const kAlignment uint32 = 8
// Calculate the remaining padding for the last field
if i == len(fields)-1 {
// m = (field offset + field size) % kAlignment
m := (fields[i].Offset + mojomTypeSize(fields[i].Type, fileGraph)) & (kAlignment - 1)
if m != 0 {
return kAlignment - m
}
return 0
}
// (next element's offset)
// - (current element's offset + current element's size)
diff := int64(fields[i+1].Offset) - (int64(fields[i].Offset) + int64(mojomTypeSize(fields[i].Type, fileGraph)))
if diff <= 0 {
return 0
}
return uint32(diff)
}