blob: c7691f5483255fa55cac15fbf318cd602eec77ac [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 measurer
import (
"fmt"
)
// CodeGenerator represents a code generator which takes a graph of
// `MeasureTape` and creates all the needed methods to measure their
// size.
type CodeGenerator struct {
toProcess []*MeasuringTape
done map[*MeasuringTape]struct{}
}
// NewCodeGenerator creates a new code generator.
func NewCodeGenerator(mt *MeasuringTape) *CodeGenerator {
return &CodeGenerator{
toProcess: []*MeasuringTape{mt},
done: make(map[*MeasuringTape]struct{}),
}
}
// Generate generates and optimizes the code.
//
// This should be called once, and the result saved for further processing or
// printing.
func (cg *CodeGenerator) Generate() map[MethodID]*Method {
allMethods := cg.genAllMethods()
pruneEmptyMethods(allMethods)
return allMethods
}
func (cg *CodeGenerator) genAllMethods() map[MethodID]*Method {
allMethods := make(map[MethodID]*Method)
for {
mt := cg.nextMt()
if mt == nil {
return allMethods
}
// TODO(fxbug.dev/51368): Variable naming should be defered to printing.
local := exprLocal("value", mt.kind, false)
for _, m := range []*Method{
cg.newMeasureMethod(mt, local),
cg.newMeasureOutOfLineMethod(mt, local),
cg.newMeasureHandlesMethod(mt, local),
} {
allMethods[m.ID] = m
}
}
}
func (cg *CodeGenerator) add(mt *MeasuringTape) {
if _, ok := cg.done[mt]; !ok {
cg.toProcess = append(cg.toProcess, mt)
}
}
func (cg *CodeGenerator) nextMt() *MeasuringTape {
for {
if len(cg.toProcess) == 0 {
return nil
}
var mt *MeasuringTape
mt, cg.toProcess = cg.toProcess[0], cg.toProcess[1:]
if _, ok := cg.done[mt]; !ok {
return mt
}
}
}
func (mt *MeasuringTape) assertOnlyStructUnionTable() {
switch mt.kind {
case Union:
return
case Struct:
return
case Table:
return
default:
panic(fmt.Sprintf("should not be reachable for kind %v", mt.kind))
}
}
func (cg *CodeGenerator) newMeasureMethod(mt *MeasuringTape, expr Expression) *Method {
mt.assertOnlyStructUnionTable()
var body Block
switch mt.kind {
case Union:
body.emitAddNumBytes(exprNum(mt.inlineNumBytes))
case Struct:
body.emitAddNumBytes(exprFidlAlign(exprNum(mt.inlineNumBytes)))
if mt.hasHandles {
body.emitInvoke(mt.methodIDOf(MeasureHandles), expr)
}
case Table:
body.emitAddNumBytes(exprNum(mt.inlineNumBytes))
}
body.emitInvoke(mt.methodIDOf(MeasureOutOfLine), expr)
return newMethod(mt.methodIDOf(Measure), expr, &body)
}
func (cg *CodeGenerator) newMeasureOutOfLineMethod(mt *MeasuringTape, expr Expression) *Method {
mt.assertOnlyStructUnionTable()
var body Block
switch mt.kind {
case Union:
cg.writeUnionOutOfLine(mt, expr, &body)
case Struct:
cg.writeStructOutOfLine(mt, expr, &body)
case Table:
cg.writeTableOutOfLine(mt, expr, &body)
}
return newMethod(mt.methodIDOf(MeasureOutOfLine), expr, &body)
}
func (cg *CodeGenerator) newMeasureHandlesMethod(mt *MeasuringTape, expr Expression) *Method {
mt.assertOnlyStructUnionTable()
var body Block
if mt.hasHandles {
switch mt.kind {
case Struct:
for _, member := range mt.members {
if member.mt.kind == Handle {
// TODO(fxbug.dev/49488): Conditionally increase for nullable handles.
body.emitAddNumHandles(exprNum(1))
} else if member.mt.hasHandles {
body.emitInvoke(
member.mt.methodIDOf(MeasureHandles),
exprMemberOf(expr, member.name, member.mt.kind, member.mt.nullable))
}
}
}
}
return newMethod(mt.methodIDOf(MeasureHandles), expr, &body)
}
type invokeKind int
const (
_ invokeKind = iota
inlineAndOutOfLine
outOfLineOnly
)
func guardNullableAccess(member measuringTapeMember, expr Expression, body *Block, fn func(*Block)) {
if member.mt.nullable {
var guardBody Block
body.emitGuard(expr, &guardBody)
fn(&guardBody)
} else {
fn(body)
}
}
func (cg *CodeGenerator) writeInvoke(member measuringTapeMember, expr Expression, body *Block, mode invokeKind) {
switch member.mt.kind {
case String:
if mode == inlineAndOutOfLine {
body.emitAddNumBytes(exprNum(16))
}
guardNullableAccess(member, expr, body, func(guardBody *Block) {
guardBody.emitAddNumBytes(exprFidlAlign(exprLength(expr)))
})
case Vector:
if mode == inlineAndOutOfLine {
body.emitAddNumBytes(exprNum(16))
}
guardNullableAccess(member, expr, body, func(guardBody *Block) {
if mode == inlineAndOutOfLine {
if member.mt.elementMt.kind == Handle ||
(!member.mt.elementMt.hasOutOfLine && member.mt.elementMt.hasHandles) {
// TODO(fxbug.dev/49488): Conditionally increase for nullable handles.
guardBody.emitAddNumHandles(
exprMult(
exprLength(expr),
exprNum(member.mt.elementMt.inlineNumHandles)))
}
}
if member.mt.elementMt.hasOutOfLine {
memberMt := measuringTapeMember{
name: fmt.Sprintf("%s_elem", member.name),
mt: member.mt.elementMt,
}
var iterateBody Block
local := exprLocal(memberMt.name, memberMt.mt.kind, memberMt.mt.nullable)
guardBody.emitIterate(
local, expr,
&iterateBody)
cg.writeInvoke(
memberMt, local, &iterateBody, inlineAndOutOfLine)
} else {
guardBody.emitAddNumBytes(
exprFidlAlign(
exprMult(
exprLength(expr),
exprNum(member.mt.elementMt.inlineNumBytes))))
}
})
case Array:
if mode == inlineAndOutOfLine {
body.emitAddNumBytes(exprFidlAlign(exprNum(member.mt.inlineNumBytes)))
if member.mt.elementMt.kind == Handle || (!member.mt.hasOutOfLine && member.mt.hasHandles) {
// TODO(fxbug.dev/49488): Conditionally increase for nullable handles.
body.emitAddNumHandles(exprNum(member.mt.inlineNumHandles))
}
}
if member.mt.hasOutOfLine {
memberMt := measuringTapeMember{
name: fmt.Sprintf("%s_elem", member.name),
mt: member.mt.elementMt,
}
var iterateBody Block
local := exprLocal(memberMt.name, memberMt.mt.kind, memberMt.mt.nullable)
body.emitIterate(
local, expr,
&iterateBody)
cg.writeInvoke(
memberMt, local, &iterateBody, outOfLineOnly)
}
case Primitive:
if mode == inlineAndOutOfLine {
body.emitAddNumBytes(exprNum(8))
}
case Handle:
if mode == inlineAndOutOfLine {
body.emitAddNumBytes(exprNum(8))
// TODO(fxbug.dev/49488): Conditionally increase for nullable handles.
body.emitAddNumHandles(exprNum(1))
}
default:
cg.add(member.mt)
guardNullableAccess(member, expr, body, func(guardBody *Block) {
switch mode {
case inlineAndOutOfLine:
guardBody.emitInvoke(member.mt.methodIDOf(Measure), expr)
case outOfLineOnly:
guardBody.emitInvoke(member.mt.methodIDOf(MeasureOutOfLine), expr)
}
})
}
}
func (cg *CodeGenerator) writeStructOutOfLine(mt *MeasuringTape, expr Expression, body *Block) {
for _, member := range mt.members {
cg.writeInvoke(
member,
exprMemberOf(expr, member.name, member.mt.kind, member.mt.nullable),
body, outOfLineOnly)
}
}
func (cg *CodeGenerator) writeUnionOutOfLine(mt *MeasuringTape, expr Expression, body *Block) {
variants := make(map[string]LocalWithBlock)
// known
for _, member := range mt.members {
var variantBody Block
local := exprLocal("_"+member.name, member.mt.kind, member.mt.nullable)
variants[member.name] = LocalWithBlock{
Local: local,
Body: &variantBody,
}
cg.writeInvoke(member, local, &variantBody, inlineAndOutOfLine)
}
// unknown
if mt.isFlexible {
var variantBody Block
variantBody.emitMaxOut()
variants[UnknownVariant] = LocalWithBlock{
Body: &variantBody,
}
}
body.emitSelectVariant(expr, mt.name, variants)
}
func (cg *CodeGenerator) writeTableOutOfLine(mt *MeasuringTape, expr Expression, body *Block) {
maxOrdinalLocal := body.emitDeclareMaxOrdinal()
for _, member := range mt.members {
var guardBody Block
body.emitGuard(
exprHasMember(expr, member.name),
&guardBody)
cg.writeInvoke(
member,
exprMemberOf(expr, member.name, member.mt.kind, member.mt.nullable),
&guardBody, inlineAndOutOfLine)
guardBody.emitSetMaxOrdinal(maxOrdinalLocal, member.ordinal)
}
body.emitAddNumBytes(exprMult(exprNum(16), maxOrdinalLocal))
}