76c6282de6
This is a Dart-tailored implementation of the "row-displacement dispatch table" technique for closed-world instance calls: All interface targets in the program are grouped into selectors such that all targets that could potentially be called from the same call site have the same selector (currently just grouped by name). Each selector is assigned a selector offset such that offset + classid is unique for all selector/classid combinations where the class implements the selector. At every instance call site that has an interface target (i.e. where the static type of the receiver is not dynamic), the selector offset + receiver classid is computed and used as index into a global table of entry points. If the receiver can be null (as determined by the front-end TFA and the VM type propagation), a null check is inserted before the call. An arguments descriptor is provided (only) for selectors that need it (those which have type parameters or optional/named parameters). The dispatch table calls don't need the monomorphic entry code, so for functions that are only called via dispatch table calls (i.e. never called dynamically), the monomorphic entry code is left out. Some future improvements to the table dispatch implementation are mentioned in https://github.com/dart-lang/sdk/issues/40188 The table dispatch flag is disabled by default in this commit. A separate commit enables the flag. Change-Id: Ic2911742b4a2c9a8d3bc7df60605454cbe4c0714 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/126648 Commit-Queue: Aske Simon Christensen <askesc@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Alexander Markov <alexmarkov@google.com>
158 lines
5.1 KiB
C++
158 lines
5.1 KiB
C++
// 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.
|
|
|
|
#include "vm/dispatch_table.h"
|
|
|
|
#include "vm/clustered_snapshot.h"
|
|
#include "vm/hash_map.h"
|
|
#include "vm/object.h"
|
|
|
|
namespace dart {
|
|
|
|
// The serialized format of the dispatch table is a sequence of variable-length
|
|
// integers (using the built-in variable-length integer encoding/decoding of
|
|
// the stream). Each encoded integer e is interpreted thus:
|
|
// -kRecentCount .. -1 Pick value from the recent values buffer at index -1-e.
|
|
// 0 Empty (unused) entry.
|
|
// 1 .. kMaxRepeat Repeat previous entry e times.
|
|
// kIndexBase or higher Pick entry point from the code object at index
|
|
// e-kIndexBase in the code array and also put it into
|
|
// the recent values buffer at the next index round-robin.
|
|
|
|
// Constants for serialization format. Chosen such that repeats and recent
|
|
// values are encoded as single bytes.
|
|
static const intptr_t kMaxRepeat = 63;
|
|
static const intptr_t kRecentCount = 64; // Must be a power of two.
|
|
static const intptr_t kRecentMask = kRecentCount - 1;
|
|
static const intptr_t kIndexBase = kMaxRepeat + 1;
|
|
|
|
uword DispatchTable::EntryPointFor(const Code& code) {
|
|
return code.EntryPoint();
|
|
}
|
|
|
|
void DispatchTable::SetCodeAt(intptr_t index, const Code& code) {
|
|
ASSERT(index >= 0 && index < length());
|
|
// The table is built with the same representation as it has at runtime, that
|
|
// is, table entries are function entry points. This representation assumes
|
|
// that the code will not move between table building and serialization.
|
|
// This property is upheld by the fact that the GC does not move code around.
|
|
array_[index] = EntryPointFor(code);
|
|
}
|
|
|
|
intptr_t DispatchTable::Serialize(Serializer* serializer,
|
|
const DispatchTable* table,
|
|
const GrowableArray<RawCode*>& code_objects) {
|
|
const intptr_t bytes_before = serializer->bytes_written();
|
|
if (table != nullptr) {
|
|
table->Serialize(serializer, code_objects);
|
|
} else {
|
|
serializer->Write<uint32_t>(0);
|
|
}
|
|
return serializer->bytes_written() - bytes_before;
|
|
}
|
|
|
|
void DispatchTable::Serialize(
|
|
Serializer* serializer,
|
|
const GrowableArray<RawCode*>& code_objects) const {
|
|
Code& code = Code::Handle();
|
|
IntMap<intptr_t> entry_to_index;
|
|
for (intptr_t i = 0; i < code_objects.length(); i++) {
|
|
code = code_objects[i];
|
|
const uword entry = EntryPointFor(code);
|
|
if (!entry_to_index.HasKey(entry)) {
|
|
entry_to_index.Insert(entry, i + 1);
|
|
}
|
|
}
|
|
|
|
uword prev_entry = 0;
|
|
uword recent[kRecentCount] = {0};
|
|
intptr_t recent_index = 0;
|
|
intptr_t repeat_count = 0;
|
|
|
|
serializer->Write<uint32_t>(length());
|
|
for (intptr_t i = 0; i < length(); i++) {
|
|
const uword entry = array_[i];
|
|
if (entry == prev_entry) {
|
|
if (++repeat_count == kMaxRepeat) {
|
|
serializer->Write<uint32_t>(kMaxRepeat);
|
|
repeat_count = 0;
|
|
}
|
|
} else {
|
|
if (repeat_count > 0) {
|
|
serializer->Write<uint32_t>(repeat_count);
|
|
repeat_count = 0;
|
|
}
|
|
if (entry == 0) {
|
|
serializer->Write<uint32_t>(0);
|
|
} else {
|
|
bool found_recent = false;
|
|
for (intptr_t r = 0; r < kRecentCount; r++) {
|
|
if (recent[r] == entry) {
|
|
serializer->Write<uint32_t>(~r);
|
|
found_recent = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found_recent) {
|
|
intptr_t index = entry_to_index.Lookup(entry) - 1;
|
|
ASSERT(index != -1);
|
|
ASSERT(EntryPointFor(Code::Handle(code_objects[index])) == entry);
|
|
serializer->Write<uint32_t>(kIndexBase + index);
|
|
recent[recent_index] = entry;
|
|
recent_index = (recent_index + 1) & kRecentMask;
|
|
}
|
|
}
|
|
}
|
|
prev_entry = entry;
|
|
}
|
|
if (repeat_count > 0) {
|
|
serializer->Write<uint32_t>(repeat_count);
|
|
}
|
|
}
|
|
|
|
DispatchTable* DispatchTable::Deserialize(Deserializer* deserializer,
|
|
const Array& code_array) {
|
|
const intptr_t length = deserializer->Read<uint32_t>();
|
|
if (length == 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
DispatchTable* table = new DispatchTable(length);
|
|
|
|
Code& code = Code::Handle();
|
|
|
|
uword value = 0;
|
|
uword recent[kRecentCount] = {0};
|
|
intptr_t recent_index = 0;
|
|
intptr_t repeat_count = 0;
|
|
for (intptr_t i = 0; i < length; i++) {
|
|
if (repeat_count > 0) {
|
|
repeat_count--;
|
|
} else {
|
|
int32_t encoded = deserializer->Read<uint32_t>();
|
|
if (encoded == 0) {
|
|
value = 0;
|
|
} else if (encoded < 0) {
|
|
intptr_t r = ~encoded;
|
|
ASSERT(r < kRecentCount);
|
|
value = recent[r];
|
|
} else if (encoded <= kMaxRepeat) {
|
|
repeat_count = encoded - 1;
|
|
} else {
|
|
intptr_t index = encoded - kIndexBase;
|
|
code ^= code_array.At(index);
|
|
value = EntryPointFor(code);
|
|
recent[recent_index] = value;
|
|
recent_index = (recent_index + 1) & kRecentMask;
|
|
}
|
|
}
|
|
table->array_[i] = value;
|
|
}
|
|
ASSERT(repeat_count == 0);
|
|
|
|
return table;
|
|
}
|
|
|
|
} // namespace dart
|