mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
[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:
parent
022b362e84
commit
1c2d378068
|
@ -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());
|
||||
|
|
69
pkg/vm/lib/metadata/table_selector.dart
Normal file
69
pkg/vm/lib/metadata/table_selector.dart
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in a new issue