| // 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" |
| "sort" |
| |
| "go.fuchsia.dev/fuchsia/tools/fidl/lib/fidlgen" |
| ) |
| |
| type statementKind int |
| |
| const ( |
| _ statementKind = iota |
| |
| addNumBytes |
| addNumHandles |
| iterate |
| invoke |
| guard |
| selectVariant |
| maxOut |
| declareMaxOrdinal |
| setMaxOrdinal |
| ) |
| |
| // statement describes an operation that needs to be done in order to calculate |
| // the size of a FIDL value. Statements are high level operations, describing |
| // steps which can be taken within a measuring tape. (Speficially, they are not |
| // meant to be general purpose operations like one would find in general |
| // purpose programming languages' IR). |
| // |
| // A statement can be one of: |
| // |
| // * AddNumBytes(E): add E to the num bytes counter. |
| // |
| // * AddNumHandles(E): add E to the num handles counter. |
| // |
| // * Iterate(L, E, B): iterate over E, and run block B for each element. Each |
| // element is referred to by L within block B. |
| // |
| // * Invoke(Id, E): invoke method identified as Id with value E. |
| // |
| // * Guard(M, B): run block B if member M is present. |
| // |
| // * SelectVariant(E, map[variant]B): run block B depending on the selected |
| // variant of E. E must be a union. |
| // |
| // * MaxOut: max out both bytes and handles counters. |
| // |
| // * DeclareMaxOrdinal: declare a local meant to hold the max ordinal value. |
| // |
| // * SetMaxOrdinal(E): update the max ordinal local to E. |
| type Statement struct { |
| kind statementKind |
| id MethodID |
| args []Expression |
| body *Block |
| targetType fidlgen.Name |
| variants map[string]LocalWithBlock |
| |
| deleted bool |
| } |
| |
| type StatementFormatter interface { |
| CaseMaxOut() |
| CaseAddNumBytes(expr Expression) |
| CaseAddNumHandles(expr Expression) |
| CaseInvoke(id MethodID, expr Expression) |
| CaseGuard(cond Expression, body *Block) |
| CaseIterate(local, expr Expression, body *Block) |
| CaseSelectVariant(expr Expression, targetType fidlgen.Name, variants map[string]LocalWithBlock) |
| CaseDeclareMaxOrdinal(local Expression) |
| CaseSetMaxOrdinal(local, ordinal Expression) |
| } |
| |
| func (stmt *Statement) Visit(formatter StatementFormatter) { |
| switch stmt.kind { |
| case addNumBytes: |
| formatter.CaseAddNumBytes(stmt.args[0]) |
| case addNumHandles: |
| formatter.CaseAddNumHandles(stmt.args[0]) |
| case iterate: |
| formatter.CaseIterate(stmt.args[0], stmt.args[1], stmt.body) |
| case invoke: |
| formatter.CaseInvoke(stmt.id, stmt.args[0]) |
| case guard: |
| formatter.CaseGuard(stmt.args[0], stmt.body) |
| case selectVariant: |
| formatter.CaseSelectVariant(stmt.args[0], stmt.targetType, stmt.variants) |
| case maxOut: |
| formatter.CaseMaxOut() |
| case declareMaxOrdinal: |
| formatter.CaseDeclareMaxOrdinal(stmt.args[0]) |
| case setMaxOrdinal: |
| formatter.CaseSetMaxOrdinal(stmt.args[0], stmt.args[1]) |
| default: |
| panic(fmt.Sprintf("unexpected statementKind %v", stmt.kind)) |
| } |
| } |
| |
| type MethodKind int |
| |
| const ( |
| _ MethodKind = iota |
| |
| Measure |
| MeasureOutOfLine |
| MeasureHandles |
| ) |
| |
| type MethodID struct { |
| Kind MethodKind |
| TargetType fidlgen.Name |
| } |
| |
| type ByTargetTypeThenKind []MethodID |
| |
| var _ sort.Interface = ByTargetTypeThenKind(nil) |
| |
| func (s ByTargetTypeThenKind) Less(i, j int) bool { |
| lhs, rhs := s[i], s[j] |
| if lhs.TargetType.FullyQualifiedName() < rhs.TargetType.FullyQualifiedName() { |
| return true |
| } else if lhs.TargetType == rhs.TargetType { |
| return lhs.Kind < rhs.Kind |
| } |
| return false |
| } |
| |
| func (s ByTargetTypeThenKind) Len() int { |
| return len(s) |
| } |
| |
| func (s ByTargetTypeThenKind) Swap(i, j int) { |
| s[j], s[i] = s[i], s[j] |
| } |
| |
| func (mt *MeasuringTape) methodIDOf(kind MethodKind) MethodID { |
| mt.assertOnlyStructUnionTable() |
| |
| return MethodID{ |
| Kind: kind, |
| TargetType: mt.name, |
| } |
| } |
| |
| type Block struct { |
| stmts []Statement |
| } |
| |
| func (b *Block) emitAddNumBytes(expr Expression) { |
| b.stmts = append(b.stmts, Statement{ |
| kind: addNumBytes, |
| args: []Expression{expr}, |
| }) |
| } |
| |
| func (b *Block) emitAddNumHandles(expr Expression) { |
| b.stmts = append(b.stmts, Statement{ |
| kind: addNumHandles, |
| args: []Expression{expr}, |
| }) |
| } |
| |
| func (b *Block) emitInvoke(id MethodID, expr Expression) { |
| b.stmts = append(b.stmts, Statement{ |
| kind: invoke, |
| id: id, |
| args: []Expression{expr}, |
| }) |
| } |
| |
| func (b *Block) emitIterate(local Expression, value Expression, body *Block) { |
| b.stmts = append(b.stmts, Statement{ |
| kind: iterate, |
| args: []Expression{local, value}, |
| body: body, |
| }) |
| } |
| |
| func (b *Block) emitGuard(cond Expression, body *Block) { |
| b.stmts = append(b.stmts, Statement{ |
| kind: guard, |
| args: []Expression{cond}, |
| body: body, |
| }) |
| } |
| |
| func (b *Block) emitMaxOut() { |
| b.stmts = append(b.stmts, Statement{ |
| kind: maxOut, |
| }) |
| } |
| |
| // UnknownVariant represents the unknown variant case in a select variant |
| // statement. |
| const UnknownVariant = "" |
| |
| type LocalWithBlock struct { |
| Local Expression |
| Body *Block |
| } |
| |
| func (b *Block) emitSelectVariant(expr Expression, targetType fidlgen.Name, variants map[string]LocalWithBlock) { |
| b.stmts = append(b.stmts, Statement{ |
| kind: selectVariant, |
| args: []Expression{expr}, |
| targetType: targetType, |
| variants: variants, |
| }) |
| } |
| |
| func (b *Block) emitDeclareMaxOrdinal() Expression { |
| local := exprLocal("max_ordinal", Primitive, false) |
| b.stmts = append(b.stmts, Statement{ |
| kind: declareMaxOrdinal, |
| args: []Expression{local}, |
| }) |
| return local |
| } |
| |
| func (b *Block) emitSetMaxOrdinal(local Expression, ordinal int) { |
| b.stmts = append(b.stmts, Statement{ |
| kind: setMaxOrdinal, |
| args: []Expression{local, exprNum(ordinal)}, |
| }) |
| } |
| |
| func (b *Block) ForAllStatements(fn func(stmt *Statement)) { |
| for i := 0; i < len(b.stmts); i++ { |
| if b.stmts[i].deleted { |
| continue |
| } |
| fn(&b.stmts[i]) |
| } |
| } |
| |
| type Method struct { |
| ID MethodID |
| Arg Expression |
| Body *Block |
| } |
| |
| func newMethod(id MethodID, expr Expression, body *Block) *Method { |
| return &Method{ |
| ID: id, |
| Arg: expr, |
| Body: body, |
| } |
| } |
| |
| // ForAllStatements traverses all statements of a method but makes no guarantees |
| // as to the order in which the traversal is perfomed. |
| func (m *Method) ForAllStatements(fn func(stmt *Statement)) { |
| var ( |
| b *Block |
| blocksToVisit = []*Block{m.Body} |
| ) |
| for len(blocksToVisit) != 0 { |
| b, blocksToVisit = blocksToVisit[0], blocksToVisit[1:] |
| if b == nil { |
| continue |
| } |
| b.ForAllStatements(func(stmt *Statement) { |
| if stmt.body != nil { |
| blocksToVisit = append(blocksToVisit, stmt.body) |
| } |
| for _, localWithBlock := range stmt.variants { |
| blocksToVisit = append(blocksToVisit, localWithBlock.Body) |
| } |
| fn(stmt) |
| }) |
| } |
| } |