Files
sdk/runtime/vm/compiler/assembler/object_pool_builder.h
T
Daco Harkes 0cd55a18c1 [vm] Introduce cachable idempotent calls
Adds a `CachableIdempotentCallInstr` that can be invoked via
`@pragma('vm:cachable-idempotent')` if the call-sites is force
optimized.

The object pool is not visited by the scavenger. So, we store the
results as unboxed integers. Consequently, only dart functions that
return integers can be cached.

Cachable idempotent calls should never be inlined. After the first
call the function not be called again.

The call itself is on a slow path to avoid register spilling on the
fast path.

TEST=vm/cc/IRTest_CachableIdempotentCall
TEST=runtime/tests/vm/dart/cachable_idempotent_test.dart

Bug: https://github.com/dart-lang/sdk/issues/51618
Change-Id: I612e896f27add76f57796c060157e14cc687a0fd
Cq-Include-Trybots: luci.dart.try:vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-asan-linux-release-x64-try,vm-aot-linux-debug-simarm_x64-try,vm-aot-linux-debug-simriscv64-try,vm-aot-mac-release-arm64-try,vm-aot-mac-release-x64-try,vm-aot-msan-linux-release-x64-try,vm-aot-obfuscate-linux-release-x64-try,vm-aot-tsan-linux-release-x64-try,vm-aot-ubsan-linux-release-x64-try,vm-aot-win-debug-arm64-try,vm-aot-win-debug-x64c-try,vm-aot-win-release-x64-try,vm-appjit-linux-debug-x64-try,vm-asan-linux-release-x64-try,vm-checked-mac-release-arm64-try,vm-eager-optimization-linux-release-ia32-try,vm-eager-optimization-linux-release-x64-try,vm-kernel-linux-debug-x64-try,vm-kernel-precomp-linux-release-x64-try,vm-linux-debug-ia32-try,vm-linux-debug-simriscv64-try,vm-linux-debug-x64-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-msan-linux-release-x64-try,vm-reload-linux-debug-x64-try,vm-reload-rollback-linux-debug-x64-try,vm-tsan-linux-release-x64-try,vm-ubsan-linux-release-x64-try,vm-win-debug-arm64-try,vm-win-debug-x64-try,vm-win-debug-x64c-try,vm-win-release-ia32-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/301601
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
2023-10-27 07:55:47 +00:00

312 lines
10 KiB
C++

// Copyright (c) 2012, 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.
#ifndef RUNTIME_VM_COMPILER_ASSEMBLER_OBJECT_POOL_BUILDER_H_
#define RUNTIME_VM_COMPILER_ASSEMBLER_OBJECT_POOL_BUILDER_H_
#include "platform/assert.h"
#include "platform/globals.h"
#include "vm/bitfield.h"
#include "vm/hash_map.h"
namespace dart {
class Object;
namespace compiler {
class ExternalLabel;
bool IsSameObject(const Object& a, const Object& b);
struct ObjectPoolBuilderEntry {
enum Patchability {
kPatchable = 0,
kNotPatchable,
};
enum SnapshotBehavior {
kSnapshotable,
// This should never be snapshot. Typically an memory address in the current
// process.
kNotSnapshotable,
// Set the value to StubCode::CallBootstrapNative() on snapshot reading.
kResetToBootstrapNative,
// Only used in AOT. Every switchable call site will put (`ic_data`,
// [kTaggedObject] `code`) into the object pool. The `code` is initialized
// (at AOT compile-time) to be [StubCode::SwitchableCallMiss].
//
// The extra indirection via the `code` object is removed by storing
// (`ic_data`, [kImmediate] `entrypoint`) in the object pool instead on
// deserialization.
kResetToSwitchableCallMissEntryPoint,
// Set the value to 0 on snapshot writing.
kSetToZero,
};
enum EntryType {
kImmediate = 0,
kTaggedObject,
kNativeFunction,
// Used only during object pool building to find duplicates. Become multiple
// kImmediate in the final pool.
#if defined(TARGET_ARCH_IS_32_BIT)
kImmediate64,
#endif
kImmediate128,
};
using TypeBits = BitField<uint8_t, EntryType, 0, 4>;
using PatchableBit = BitField<uint8_t, Patchability, TypeBits::kNextBit, 1>;
using SnapshotBehaviorBits =
BitField<uint8_t, SnapshotBehavior, PatchableBit::kNextBit, 3>;
static inline uint8_t EncodeTraits(
EntryType type,
Patchability patchable,
SnapshotBehavior snapshot_behavior = SnapshotBehavior::kSnapshotable) {
return TypeBits::encode(type) | PatchableBit::encode(patchable) |
PatchableBit::encode(patchable) |
SnapshotBehaviorBits::encode(snapshot_behavior);
}
ObjectPoolBuilderEntry() : imm128_(), entry_bits_(0), equivalence_() {}
ObjectPoolBuilderEntry(const Object* obj,
Patchability patchable,
SnapshotBehavior snapshot_behavior = kSnapshotable)
: ObjectPoolBuilderEntry(obj, obj, patchable, snapshot_behavior) {}
ObjectPoolBuilderEntry(const Object* obj,
const Object* eqv,
Patchability patchable,
SnapshotBehavior snapshot_behavior = kSnapshotable)
: obj_(obj),
entry_bits_(EncodeTraits(kTaggedObject, patchable, snapshot_behavior)),
equivalence_(eqv) {}
ObjectPoolBuilderEntry(
uword value,
EntryType info,
Patchability patchable,
SnapshotBehavior snapshot_behavior = SnapshotBehavior::kSnapshotable)
: imm_(value),
entry_bits_(EncodeTraits(info, patchable, snapshot_behavior)),
equivalence_() {}
#if defined(ARCH_IS_32_BIT)
ObjectPoolBuilderEntry(uint64_t value, EntryType info, Patchability patchable)
: imm64_(value),
entry_bits_(EncodeTraits(info, patchable)),
equivalence_() {}
#endif
ObjectPoolBuilderEntry(simd128_value_t value,
EntryType info,
Patchability patchable)
: imm128_(value),
entry_bits_(EncodeTraits(info, patchable)),
equivalence_() {}
EntryType type() const { return TypeBits::decode(entry_bits_); }
Patchability patchable() const { return PatchableBit::decode(entry_bits_); }
SnapshotBehavior snapshot_behavior() const {
return SnapshotBehaviorBits::decode(entry_bits_);
}
union {
const Object* obj_;
uword imm_;
uint64_t imm64_;
simd128_value_t imm128_;
};
uint8_t entry_bits_;
const Object* equivalence_;
};
// Pair type parameter for DirectChainedHashMap used for the constant pool.
class ObjIndexPair {
public:
// Typedefs needed for the DirectChainedHashMap template.
typedef ObjectPoolBuilderEntry Key;
typedef intptr_t Value;
typedef ObjIndexPair Pair;
static constexpr intptr_t kNoIndex = -1;
ObjIndexPair()
: key_(reinterpret_cast<uword>(nullptr),
ObjectPoolBuilderEntry::kTaggedObject,
ObjectPoolBuilderEntry::kPatchable),
value_(kNoIndex) {}
ObjIndexPair(Key key, Value value) : value_(value) {
key_.entry_bits_ = key.entry_bits_;
if (key.type() == ObjectPoolBuilderEntry::kTaggedObject) {
key_.obj_ = key.obj_;
key_.equivalence_ = key.equivalence_;
} else if (key.type() == ObjectPoolBuilderEntry::kImmediate128) {
key_.imm128_ = key.imm128_;
#if defined(TARGET_ARCH_IS_32_BIT)
} else if (key.type() == ObjectPoolBuilderEntry::kImmediate64) {
key_.imm64_ = key.imm64_;
#endif
} else {
key_.imm_ = key.imm_;
}
}
static Key KeyOf(Pair kv) { return kv.key_; }
static Value ValueOf(Pair kv) { return kv.value_; }
static uword Hash(Key key);
static inline bool IsKeyEqual(Pair kv, Key key) {
if (kv.key_.entry_bits_ != key.entry_bits_) return false;
if (kv.key_.type() == ObjectPoolBuilderEntry::kTaggedObject) {
return IsSameObject(*kv.key_.obj_, *key.obj_) &&
IsSameObject(*kv.key_.equivalence_, *key.equivalence_);
}
if (kv.key_.type() == ObjectPoolBuilderEntry::kImmediate128) {
return (kv.key_.imm128_.int_storage[0] == key.imm128_.int_storage[0]) &&
(kv.key_.imm128_.int_storage[1] == key.imm128_.int_storage[1]) &&
(kv.key_.imm128_.int_storage[2] == key.imm128_.int_storage[2]) &&
(kv.key_.imm128_.int_storage[3] == key.imm128_.int_storage[3]);
}
#if defined(TARGET_ARCH_IS_32_BIT)
if (kv.key_.type() == ObjectPoolBuilderEntry::kImmediate64) {
return kv.key_.imm64_ == key.imm64_;
}
#endif
return kv.key_.imm_ == key.imm_;
}
private:
Key key_;
Value value_;
};
class ObjectPoolBuilder : public ValueObject {
public:
// When generating AOT code in the bare instructions mode we might use a two
// stage process of forming the pool - first accumulate objects in the
// intermediary pool and then commit them into the global pool at the
// end of a successful compilation. Here [parent] is the pool into which
// we are going to commit objects.
// See PrecompileParsedFunctionHelper::Compile for more information.
explicit ObjectPoolBuilder(ObjectPoolBuilder* parent = nullptr)
: parent_(parent),
base_index_(parent != nullptr ? parent->CurrentLength() : 0),
zone_(nullptr) {}
~ObjectPoolBuilder() {
if (zone_ != nullptr) {
Reset();
zone_ = nullptr;
}
}
// Clears all existing entries in this object pool builder.
//
// Note: Any code which has been compiled via this builder might use offsets
// into the pool which are not correct anymore.
void Reset();
// Initialize this object pool builder with a [zone].
//
// Any objects added later on will be referenced using handles from [zone].
void InitializeWithZone(Zone* zone) {
ASSERT(object_pool_.length() == 0);
ASSERT(zone_ == nullptr && zone != nullptr);
zone_ = zone;
}
intptr_t AddObject(
const Object& obj,
ObjectPoolBuilderEntry::Patchability patchable =
ObjectPoolBuilderEntry::kNotPatchable,
ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
ObjectPoolBuilderEntry::kSnapshotable);
intptr_t AddImmediate(
uword imm,
ObjectPoolBuilderEntry::Patchability patchable =
ObjectPoolBuilderEntry::kNotPatchable,
ObjectPoolBuilderEntry::SnapshotBehavior snapshotability =
ObjectPoolBuilderEntry::kSnapshotable);
intptr_t AddImmediate64(uint64_t imm);
intptr_t AddImmediate128(simd128_value_t imm);
intptr_t FindObject(
const Object& obj,
ObjectPoolBuilderEntry::Patchability patchable =
ObjectPoolBuilderEntry::kNotPatchable,
ObjectPoolBuilderEntry::SnapshotBehavior snapshot_behavior =
ObjectPoolBuilderEntry::kSnapshotable);
intptr_t FindObject(const Object& obj, const Object& equivalence);
intptr_t FindImmediate(uword imm);
intptr_t FindImmediate64(uint64_t imm);
intptr_t FindImmediate128(simd128_value_t imm);
intptr_t FindNativeFunction(const ExternalLabel* label,
ObjectPoolBuilderEntry::Patchability patchable);
intptr_t CurrentLength() const {
return object_pool_.length() + used_from_parent_.length();
}
ObjectPoolBuilderEntry& EntryAt(intptr_t i) {
if (i < used_from_parent_.length()) {
return parent_->EntryAt(used_from_parent_[i]);
}
return object_pool_[i - used_from_parent_.length()];
}
const ObjectPoolBuilderEntry& EntryAt(intptr_t i) const {
if (i < used_from_parent_.length()) {
return parent_->EntryAt(used_from_parent_[i]);
}
return object_pool_[i - used_from_parent_.length()];
}
intptr_t AddObject(ObjectPoolBuilderEntry entry);
// Try appending all entries from this pool into the parent pool.
// This might fail if parent pool was modified invalidating indices which
// we produced. In this case this function will return false.
bool TryCommitToParent();
bool HasParent() const { return parent_ != nullptr; }
private:
intptr_t FindObject(ObjectPoolBuilderEntry entry);
// Parent pool into which all entries from this pool will be added at
// the end of the successful compilation.
ObjectPoolBuilder* const parent_;
// Base index at which entries will be inserted into the parent pool.
// Should be equal to parent_->CurrentLength() - but is cached here
// to detect cases when parent pool grows due to nested code generations.
const intptr_t base_index_;
GrowableArray<intptr_t> used_from_parent_;
// Objects and jump targets.
GrowableArray<ObjectPoolBuilderEntry> object_pool_;
// Hashmap for fast lookup in object pool.
DirectChainedHashMap<ObjIndexPair> object_pool_index_table_;
// The zone used for allocating the handles we keep in the map and array (or
// nullptr, in which case allocations happen using the zone active at the
// point of insertion).
Zone* zone_;
};
} // namespace compiler
} // namespace dart
#endif // RUNTIME_VM_COMPILER_ASSEMBLER_OBJECT_POOL_BUILDER_H_