[vm/aot] Use TFA to eliminate unused selectors in dispatch table.

For each selector, count the number of calls to the selector that are
both reachable and polymorphic according to the TFA. Only include
selectors in the table with non-zero counts.

This reduces the dispatch table size by 30% in dart2js and by 49% in
Flutter Gallery (312k memory use reduction on ARM64).

Call counts are transferred in a dedicated metadata block with
per-selector information. This mechanism also prepares for transferring
other per-selector information in the future.

Change-Id: Iba15aa4d6c50e67e53c3fd8e542123d3fc98bd07
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/132603
Reviewed-by: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Aske Simon Christensen 2020-02-13 15:32:48 +00:00 committed by commit-bot@chromium.org
parent 022b362e84
commit 1c2d378068
10 changed files with 232 additions and 10 deletions

View file

@ -14,6 +14,8 @@ import 'package:vm/metadata/inferred_type.dart'
show InferredTypeMetadataRepository;
import 'package:vm/metadata/procedure_attributes.dart'
show ProcedureAttributesMetadataRepository;
import 'package:vm/metadata/table_selector.dart'
show TableSelectorMetadataRepository;
import 'package:vm/metadata/unreachable.dart'
show UnreachableNodeMetadataRepository;
import 'package:vm/metadata/call_site_attributes.dart'
@ -39,6 +41,7 @@ main(List<String> arguments) async {
component.addMetadataRepository(new DirectCallMetadataRepository());
component.addMetadataRepository(new InferredTypeMetadataRepository());
component.addMetadataRepository(new ProcedureAttributesMetadataRepository());
component.addMetadataRepository(new TableSelectorMetadataRepository());
component.addMetadataRepository(new UnreachableNodeMetadataRepository());
component.addMetadataRepository(new BytecodeMetadataRepository());
component.addMetadataRepository(new CallSiteAttributesMetadataRepository());

View file

@ -0,0 +1,69 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:kernel/ast.dart';
import 'procedure_attributes.dart';
// Information associated with a selector, used by the dispatch table generator.
class TableSelectorInfo {
int callCount;
TableSelectorInfo() : callCount = 0;
TableSelectorInfo.readFromBinary(BinarySource source)
: callCount = source.readUInt();
void writeToBinary(BinarySink sink) {
sink.writeUInt30(callCount);
}
}
class TableSelectorMetadata {
final List<TableSelectorInfo> selectors;
TableSelectorMetadata()
: selectors = <TableSelectorInfo>[TableSelectorInfo()] {
assert(
selectors.length == ProcedureAttributesMetadata.kInvalidSelectorId + 1);
}
TableSelectorMetadata.fromSelectors(this.selectors);
int addSelector() {
final int selectorId = selectors.length;
selectors.add(TableSelectorInfo());
return selectorId;
}
}
class TableSelectorMetadataRepository
extends MetadataRepository<TableSelectorMetadata> {
static const repositoryTag = 'vm.table-selector.metadata';
@override
final String tag = repositoryTag;
@override
final Map<TreeNode, TableSelectorMetadata> mapping =
<TreeNode, TableSelectorMetadata>{};
@override
void writeToBinary(
TableSelectorMetadata metadata, Node node, BinarySink sink) {
final List<TableSelectorInfo> selectors = metadata.selectors;
sink.writeUInt30(selectors.length);
for (TableSelectorInfo selector in selectors) {
selector.writeToBinary(sink);
}
}
@override
TableSelectorMetadata readFromBinary(Node node, BinarySource source) {
final int length = source.readUInt();
final List<TableSelectorInfo> selectors = List<TableSelectorInfo>.generate(
length, (_) => TableSelectorInfo.readFromBinary(source));
return TableSelectorMetadata.fromSelectors(selectors);
}
}

View file

@ -5,18 +5,20 @@
import 'package:kernel/ast.dart';
import '../../metadata/procedure_attributes.dart';
import '../../metadata/table_selector.dart';
// Assigns dispatch table selector IDs to interface targets.
// TODO(dartbug.com/40188): Implement a more fine-grained assignment based on
// hierarchy connectedness.
class TableSelectorAssigner {
final TableSelectorMetadata metadata = TableSelectorMetadata();
final Map<Name, int> _methodSelectorId = {};
final Map<Name, int> _getterSelectorId = {};
final Map<Name, int> _setterSelectorId = {};
int _nextSelectorId = ProcedureAttributesMetadata.kInvalidSelectorId + 1;
int _selectorIdForMap(Map<Name, int> map, Member member) {
return map.putIfAbsent(member.name, () => _nextSelectorId++);
return map.putIfAbsent(member.name, () => metadata.addSelector());
}
int methodOrSetterSelectorId(Member member) {
@ -57,4 +59,16 @@ class TableSelectorAssigner {
}
throw "Unexpected member kind '${member.runtimeType}'";
}
void registerCall(int selectorId) {
metadata.selectors[selectorId].callCount++;
}
void registerMethodOrSetterCall(Member member) {
registerCall(methodOrSetterSelectorId(member));
}
void registerGetterCall(Member member) {
registerCall(getterSelectorId(member));
}
}

View file

@ -25,6 +25,7 @@ import '../devirtualization.dart' show Devirtualization;
import '../../metadata/direct_call.dart';
import '../../metadata/inferred_type.dart';
import '../../metadata/procedure_attributes.dart';
import '../../metadata/table_selector.dart';
import '../../metadata/unreachable.dart';
const bool kDumpClassHierarchy =
@ -116,23 +117,34 @@ class TFADevirtualization extends Devirtualization {
class AnnotateKernel extends RecursiveVisitor<Null> {
final TypeFlowAnalysis _typeFlowAnalysis;
final FieldMorpher fieldMorpher;
final DirectCallMetadataRepository _directCallMetadataRepository;
final InferredTypeMetadataRepository _inferredTypeMetadata;
final UnreachableNodeMetadataRepository _unreachableNodeMetadata;
final ProcedureAttributesMetadataRepository _procedureAttributesMetadata;
final TableSelectorMetadataRepository _tableSelectorMetadata;
final TableSelectorAssigner _tableSelectorAssigner;
final Class _intClass;
Constant _nullConstant;
AnnotateKernel(Component component, this._typeFlowAnalysis, this.fieldMorpher)
: _inferredTypeMetadata = new InferredTypeMetadataRepository(),
: _directCallMetadataRepository =
component.metadata[DirectCallMetadataRepository.repositoryTag],
_inferredTypeMetadata = new InferredTypeMetadataRepository(),
_unreachableNodeMetadata = new UnreachableNodeMetadataRepository(),
_procedureAttributesMetadata =
new ProcedureAttributesMetadataRepository(),
_tableSelectorMetadata = new TableSelectorMetadataRepository(),
_tableSelectorAssigner = new TableSelectorAssigner(),
_intClass = _typeFlowAnalysis.environment.coreTypes.intClass {
component.addMetadataRepository(_inferredTypeMetadata);
component.addMetadataRepository(_unreachableNodeMetadata);
component.addMetadataRepository(_procedureAttributesMetadata);
component.addMetadataRepository(_tableSelectorMetadata);
}
// Query whether a call site was marked as a direct call by the analysis.
bool _callSiteUsesDirectCall(TreeNode node) {
return _directCallMetadataRepository.mapping.containsKey(node);
}
InferredType _convertType(Type type,
@ -246,6 +258,17 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
_setInferredType(node, resultType,
skipCheck: markSkipCheck, receiverNotInt: markReceiverNotInt);
}
// Tell the table selector assigner about the callsite.
final Selector selector = callSite.selector;
if (selector is InterfaceSelector && !_callSiteUsesDirectCall(node)) {
if (node is PropertyGet) {
_tableSelectorAssigner.registerGetterCall(selector.member);
} else {
assertx(node is MethodInvocation || node is PropertySet);
_tableSelectorAssigner.registerMethodOrSetterCall(selector.member);
}
}
}
void _annotateMember(Member member) {
@ -399,6 +422,12 @@ class AnnotateKernel extends RecursiveVisitor<Null> {
_annotateCallSite(node, node.target);
super.visitStaticSet(node);
}
@override
visitComponent(Component node) {
super.visitComponent(node);
_tableSelectorMetadata.mapping[node] = _tableSelectorAssigner.metadata;
}
}
/// Tree shaking based on results of type flow analysis (TFA).

View file

@ -400,17 +400,20 @@ const TableSelector* SelectorMap::GetSelector(
const int32_t sid = SelectorId(interface_target);
if (sid == kInvalidSelectorId) return nullptr;
const TableSelector* selector = &selectors_[sid];
if (!selector->IsUsed()) return nullptr;
if (selector->offset == kInvalidSelectorOffset) return nullptr;
return selector;
}
void SelectorMap::AddSelector(int32_t call_count) {
const int32_t added_sid = selectors_.length();
selectors_.Add(TableSelector(added_sid, call_count, kInvalidSelectorOffset));
}
void SelectorMap::SetSelectorProperties(int32_t sid,
bool on_null_interface,
bool requires_args_descriptor) {
while (selectors_.length() <= sid) {
const int32_t added_sid = selectors_.length();
selectors_.Add(TableSelector{added_sid, kInvalidSelectorId, false, false});
}
ASSERT(sid < selectors_.length());
selectors_[sid].on_null_interface |= on_null_interface;
selectors_[sid].requires_args_descriptor |= requires_args_descriptor;
}
@ -429,11 +432,26 @@ DispatchTableGenerator::DispatchTableGenerator(Zone* zone)
void DispatchTableGenerator::Initialize(ClassTable* table) {
classes_ = table;
ReadTableSelectorInfo();
NumberSelectors();
SetupSelectorRows();
ComputeSelectorOffsets();
}
void DispatchTableGenerator::ReadTableSelectorInfo() {
const auto& object_class = Class::Handle(Z, classes_->At(kInstanceCid));
const auto& script = Script::Handle(Z, object_class.script());
const auto& info = KernelProgramInfo::Handle(Z, script.kernel_program_info());
kernel::TableSelectorMetadata* metadata =
kernel::TableSelectorMetadataForProgram(info, Z);
// This assert will fail if gen_kernel was run in non-AOT mode or without TFA.
RELEASE_ASSERT(metadata != nullptr);
for (intptr_t i = 0; i < metadata->selectors.length(); i++) {
const kernel::TableSelectorInfo* info = &metadata->selectors[i];
selector_map_.AddSelector(info->call_count);
}
}
void DispatchTableGenerator::NumberSelectors() {
num_classes_ = classes_->NumCids();
@ -583,7 +601,8 @@ void DispatchTableGenerator::SetupSelectorRows() {
// Retain all selectors that contain implementation intervals.
for (intptr_t i = 0; i < num_selectors_; i++) {
if (selector_rows[i].Finalize()) {
const TableSelector& selector = selector_map_.selectors_[i];
if (selector.IsUsed() && selector_rows[i].Finalize()) {
table_rows_.Add(&selector_rows[i]);
}
}

View file

@ -19,10 +19,16 @@ namespace compiler {
class SelectorRow;
struct TableSelector {
TableSelector(int32_t id, int32_t call_count, int32_t offset)
: id(id), call_count(call_count), offset(offset) {}
bool IsUsed() const { return call_count > 0; }
int32_t id;
int32_t call_count;
int32_t offset;
bool on_null_interface;
bool requires_args_descriptor;
bool on_null_interface = false;
bool requires_args_descriptor = false;
};
class SelectorMap {
@ -40,6 +46,7 @@ class SelectorMap {
int32_t SelectorId(const Function& interface_target) const;
void AddSelector(int32_t call_count);
void SetSelectorProperties(int32_t sid,
bool on_null_interface,
bool requires_args_descriptor);
@ -68,6 +75,7 @@ class DispatchTableGenerator {
DispatchTable* BuildTable();
private:
void ReadTableSelectorInfo();
void NumberSelectors();
void SetupSelectorRows();
void ComputeSelectorOffsets();

View file

@ -1824,6 +1824,35 @@ CallSiteAttributesMetadataHelper::GetCallSiteAttributes(intptr_t node_offset) {
return metadata;
}
TableSelectorMetadataHelper::TableSelectorMetadataHelper(
KernelReaderHelper* helper)
: MetadataHelper(helper, tag(), /* precompiler_only = */ true) {}
TableSelectorMetadata* TableSelectorMetadataHelper::GetTableSelectorMetadata(
Zone* zone) {
const intptr_t node_offset = GetComponentMetadataPayloadOffset();
const intptr_t md_offset = GetNextMetadataPayloadOffset(node_offset);
if (md_offset < 0) {
return nullptr;
}
AlternativeReadingScopeWithNewData alt(&helper_->reader_,
&H.metadata_payloads(), md_offset);
const intptr_t num_selectors = helper_->ReadUInt();
TableSelectorMetadata* metadata =
new (zone) TableSelectorMetadata(num_selectors);
for (intptr_t i = 0; i < num_selectors; i++) {
ReadTableSelectorInfo(&metadata->selectors[i]);
}
return metadata;
}
void TableSelectorMetadataHelper::ReadTableSelectorInfo(
TableSelectorInfo* info) {
info->call_count = helper_->ReadUInt();
}
intptr_t KernelReaderHelper::ReaderOffset() const {
return reader_.offset();
}

View file

@ -1027,6 +1027,39 @@ class CallSiteAttributesMetadataHelper : public MetadataHelper {
DISALLOW_COPY_AND_ASSIGN(CallSiteAttributesMetadataHelper);
};
// Information about a table selector computed by the TFA.
struct TableSelectorInfo {
int call_count = 0;
};
// Collection of table selector information for all selectors in the program.
class TableSelectorMetadata : public ZoneAllocated {
public:
explicit TableSelectorMetadata(intptr_t num_selectors)
: selectors(num_selectors) {
selectors.FillWith(TableSelectorInfo(), 0, num_selectors);
}
GrowableArray<TableSelectorInfo> selectors;
DISALLOW_COPY_AND_ASSIGN(TableSelectorMetadata);
};
// Helper class which provides access to table selector metadata.
class TableSelectorMetadataHelper : public MetadataHelper {
public:
static const char* tag() { return "vm.table-selector.metadata"; }
explicit TableSelectorMetadataHelper(KernelReaderHelper* helper);
TableSelectorMetadata* GetTableSelectorMetadata(Zone* zone);
private:
void ReadTableSelectorInfo(TableSelectorInfo* info);
DISALLOW_COPY_AND_ASSIGN(TableSelectorMetadataHelper);
};
class KernelReaderHelper {
public:
KernelReaderHelper(Zone* zone,
@ -1162,6 +1195,7 @@ class KernelReaderHelper {
friend class ProcedureHelper;
friend class SimpleExpressionConverter;
friend class ScopeBuilder;
friend class TableSelectorMetadataHelper;
friend class TypeParameterHelper;
friend class TypeTranslator;
friend class VariableDeclarationHelper;

View file

@ -864,6 +864,18 @@ ProcedureAttributesMetadata ProcedureAttributesOf(const Field& field,
field.KernelDataProgramOffset(), field.kernel_offset());
}
TableSelectorMetadata* TableSelectorMetadataForProgram(
const KernelProgramInfo& info,
Zone* zone) {
TranslationHelper translation_helper(Thread::Current());
translation_helper.InitFromKernelProgramInfo(info);
const auto& data = ExternalTypedData::Handle(zone, info.metadata_payloads());
KernelReaderHelper reader_helper(zone, &translation_helper,
Script::Handle(zone), data, 0);
TableSelectorMetadataHelper table_selector_metadata_helper(&reader_helper);
return table_selector_metadata_helper.GetTableSelectorMetadata(zone);
}
} // namespace kernel
} // namespace dart

View file

@ -43,6 +43,7 @@ namespace kernel {
class Reader;
struct ProcedureAttributesMetadata;
class TableSelectorMetadata;
class StringIndex {
public:
@ -224,6 +225,10 @@ ProcedureAttributesMetadata ProcedureAttributesOf(const Function& function,
ProcedureAttributesMetadata ProcedureAttributesOf(const Field& field,
Zone* zone);
TableSelectorMetadata* TableSelectorMetadataForProgram(
const KernelProgramInfo& info,
Zone* zone);
} // namespace kernel
} // namespace dart