7ff2dd4117
The call sequence is very similar to a classic IC call, except the guarded class and the target are loaded indirectly from the constant pool instead of as immediates. In the monomorphic case, we call directly to the expected target with a class check in the callee. In the unlinked, polymorphic and megamorphic cases, we call a stub; these case are now call-through instead of call-and-return. Every code, except stubs involved in switchable calls, includes the class check sequence at the beginning. So we now distinguish between a checked and an unchecked entry point. Generated code except the switchable call continues to use the unchecked entry point. PC offsets are calculated relative to the beginning of the instruction stream, rather than either entry point. BUG= R=fschneider@google.com Review URL: https://codereview.chromium.org/2226893002 .
352 lines
10 KiB
C++
352 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 VM_ASSEMBLER_H_
|
|
#define VM_ASSEMBLER_H_
|
|
|
|
#include "platform/assert.h"
|
|
#include "vm/allocation.h"
|
|
#include "vm/globals.h"
|
|
#include "vm/growable_array.h"
|
|
#include "vm/hash_map.h"
|
|
#include "vm/object.h"
|
|
|
|
namespace dart {
|
|
|
|
#if defined(TARGET_ARCH_ARM) || \
|
|
defined(TARGET_ARCH_ARM64) || \
|
|
defined(TARGET_ARCH_MIPS)
|
|
DECLARE_FLAG(bool, use_far_branches);
|
|
#endif
|
|
|
|
// Forward declarations.
|
|
class Assembler;
|
|
class AssemblerFixup;
|
|
class AssemblerBuffer;
|
|
class MemoryRegion;
|
|
|
|
|
|
// External labels keep a function pointer to allow them
|
|
// to be called from code generated by the assembler.
|
|
class ExternalLabel : public ValueObject {
|
|
public:
|
|
explicit ExternalLabel(uword address) : address_(address) {}
|
|
|
|
bool is_resolved() const { return address_ != 0; }
|
|
uword address() const {
|
|
ASSERT(is_resolved());
|
|
return address_;
|
|
}
|
|
|
|
private:
|
|
const uword address_;
|
|
};
|
|
|
|
|
|
// Assembler fixups are positions in generated code that hold relocation
|
|
// information that needs to be processed before finalizing the code
|
|
// into executable memory.
|
|
class AssemblerFixup : public ZoneAllocated {
|
|
public:
|
|
virtual void Process(const MemoryRegion& region, intptr_t position) = 0;
|
|
|
|
virtual bool IsPointerOffset() const = 0;
|
|
|
|
// It would be ideal if the destructor method could be made private,
|
|
// but the g++ compiler complains when this is subclassed.
|
|
virtual ~AssemblerFixup() { UNREACHABLE(); }
|
|
|
|
private:
|
|
AssemblerFixup* previous_;
|
|
intptr_t position_;
|
|
|
|
AssemblerFixup* previous() const { return previous_; }
|
|
void set_previous(AssemblerFixup* previous) { previous_ = previous; }
|
|
|
|
intptr_t position() const { return position_; }
|
|
void set_position(intptr_t position) { position_ = position; }
|
|
|
|
friend class AssemblerBuffer;
|
|
};
|
|
|
|
|
|
// Assembler buffers are used to emit binary code. They grow on demand.
|
|
class AssemblerBuffer : public ValueObject {
|
|
public:
|
|
AssemblerBuffer();
|
|
~AssemblerBuffer();
|
|
|
|
// Basic support for emitting, loading, and storing.
|
|
template<typename T> void Emit(T value) {
|
|
ASSERT(HasEnsuredCapacity());
|
|
*reinterpret_cast<T*>(cursor_) = value;
|
|
cursor_ += sizeof(T);
|
|
}
|
|
|
|
template<typename T> void Remit() {
|
|
ASSERT(Size() >= static_cast<intptr_t>(sizeof(T)));
|
|
cursor_ -= sizeof(T);
|
|
}
|
|
|
|
// Return address to code at |position| bytes.
|
|
uword Address(intptr_t position) {
|
|
return contents_ + position;
|
|
}
|
|
|
|
template<typename T> T Load(intptr_t position) {
|
|
ASSERT(position >= 0 &&
|
|
position <= (Size() - static_cast<intptr_t>(sizeof(T))));
|
|
return *reinterpret_cast<T*>(contents_ + position);
|
|
}
|
|
|
|
template<typename T> void Store(intptr_t position, T value) {
|
|
ASSERT(position >= 0 &&
|
|
position <= (Size() - static_cast<intptr_t>(sizeof(T))));
|
|
*reinterpret_cast<T*>(contents_ + position) = value;
|
|
}
|
|
|
|
const ZoneGrowableArray<intptr_t>& pointer_offsets() const {
|
|
#if defined(DEBUG)
|
|
ASSERT(fixups_processed_);
|
|
#endif
|
|
return *pointer_offsets_;
|
|
}
|
|
|
|
// Emit an object pointer directly in the code.
|
|
void EmitObject(const Object& object);
|
|
|
|
// Emit a fixup at the current location.
|
|
void EmitFixup(AssemblerFixup* fixup) {
|
|
fixup->set_previous(fixup_);
|
|
fixup->set_position(Size());
|
|
fixup_ = fixup;
|
|
}
|
|
|
|
// Count the fixups that produce a pointer offset, without processing
|
|
// the fixups.
|
|
intptr_t CountPointerOffsets() const;
|
|
|
|
// Get the size of the emitted code.
|
|
intptr_t Size() const { return cursor_ - contents_; }
|
|
uword contents() const { return contents_; }
|
|
|
|
// Copy the assembled instructions into the specified memory block
|
|
// and apply all fixups.
|
|
void FinalizeInstructions(const MemoryRegion& region);
|
|
|
|
// To emit an instruction to the assembler buffer, the EnsureCapacity helper
|
|
// must be used to guarantee that the underlying data area is big enough to
|
|
// hold the emitted instruction. Usage:
|
|
//
|
|
// AssemblerBuffer buffer;
|
|
// AssemblerBuffer::EnsureCapacity ensured(&buffer);
|
|
// ... emit bytes for single instruction ...
|
|
|
|
#if defined(DEBUG)
|
|
class EnsureCapacity : public ValueObject {
|
|
public:
|
|
explicit EnsureCapacity(AssemblerBuffer* buffer);
|
|
~EnsureCapacity();
|
|
|
|
private:
|
|
AssemblerBuffer* buffer_;
|
|
intptr_t gap_;
|
|
|
|
intptr_t ComputeGap() { return buffer_->Capacity() - buffer_->Size(); }
|
|
};
|
|
|
|
bool has_ensured_capacity_;
|
|
bool HasEnsuredCapacity() const { return has_ensured_capacity_; }
|
|
#else
|
|
class EnsureCapacity : public ValueObject {
|
|
public:
|
|
explicit EnsureCapacity(AssemblerBuffer* buffer) {
|
|
if (buffer->cursor() >= buffer->limit()) buffer->ExtendCapacity();
|
|
}
|
|
};
|
|
|
|
// When building the C++ tests, assertion code is enabled. To allow
|
|
// asserting that the user of the assembler buffer has ensured the
|
|
// capacity needed for emitting, we add a dummy method in non-debug mode.
|
|
bool HasEnsuredCapacity() const { return true; }
|
|
#endif
|
|
|
|
// Returns the position in the instruction stream.
|
|
intptr_t GetPosition() const { return cursor_ - contents_; }
|
|
|
|
void Reset() { cursor_ = contents_; }
|
|
|
|
private:
|
|
// The limit is set to kMinimumGap bytes before the end of the data area.
|
|
// This leaves enough space for the longest possible instruction and allows
|
|
// for a single, fast space check per instruction.
|
|
static const intptr_t kMinimumGap = 32;
|
|
|
|
uword contents_;
|
|
uword cursor_;
|
|
uword limit_;
|
|
AssemblerFixup* fixup_;
|
|
ZoneGrowableArray<intptr_t>* pointer_offsets_;
|
|
#if defined(DEBUG)
|
|
bool fixups_processed_;
|
|
#endif
|
|
|
|
uword cursor() const { return cursor_; }
|
|
uword limit() const { return limit_; }
|
|
intptr_t Capacity() const {
|
|
ASSERT(limit_ >= contents_);
|
|
return (limit_ - contents_) + kMinimumGap;
|
|
}
|
|
|
|
// Process the fixup chain.
|
|
void ProcessFixups(const MemoryRegion& region);
|
|
|
|
// Compute the limit based on the data area and the capacity. See
|
|
// description of kMinimumGap for the reasoning behind the value.
|
|
static uword ComputeLimit(uword data, intptr_t capacity) {
|
|
return data + capacity - kMinimumGap;
|
|
}
|
|
|
|
void ExtendCapacity();
|
|
|
|
friend class AssemblerFixup;
|
|
};
|
|
|
|
|
|
struct ObjectPoolWrapperEntry {
|
|
ObjectPoolWrapperEntry()
|
|
: raw_value_(), type_(), equivalence_() { }
|
|
explicit ObjectPoolWrapperEntry(const Object* obj)
|
|
: obj_(obj), type_(ObjectPool::kTaggedObject), equivalence_(obj) { }
|
|
explicit ObjectPoolWrapperEntry(const Object* obj, const Object* eqv)
|
|
: obj_(obj), type_(ObjectPool::kTaggedObject), equivalence_(eqv) { }
|
|
ObjectPoolWrapperEntry(uword value, ObjectPool::EntryType info)
|
|
: raw_value_(value), type_(info), equivalence_() { }
|
|
|
|
union {
|
|
const Object* obj_;
|
|
uword raw_value_;
|
|
};
|
|
ObjectPool::EntryType type_;
|
|
const Object* equivalence_;
|
|
};
|
|
|
|
|
|
// Pair type parameter for DirectChainedHashMap used for the constant pool.
|
|
class ObjIndexPair {
|
|
public:
|
|
// Typedefs needed for the DirectChainedHashMap template.
|
|
typedef ObjectPoolWrapperEntry Key;
|
|
typedef intptr_t Value;
|
|
typedef ObjIndexPair Pair;
|
|
|
|
static const intptr_t kNoIndex = -1;
|
|
|
|
ObjIndexPair() : key_(static_cast<uword>(NULL), ObjectPool::kTaggedObject),
|
|
value_(kNoIndex) { }
|
|
|
|
ObjIndexPair(Key key, Value value) : value_(value) {
|
|
key_.type_ = key.type_;
|
|
if (key.type_ == ObjectPool::kTaggedObject) {
|
|
key_.obj_ = key.obj_;
|
|
key_.equivalence_ = key.equivalence_;
|
|
} else {
|
|
key_.raw_value_ = key.raw_value_;
|
|
}
|
|
}
|
|
|
|
static Key KeyOf(Pair kv) { return kv.key_; }
|
|
|
|
static Value ValueOf(Pair kv) { return kv.value_; }
|
|
|
|
static intptr_t Hashcode(Key key) {
|
|
if (key.type_ != ObjectPool::kTaggedObject) {
|
|
return key.raw_value_;
|
|
}
|
|
if (key.obj_->IsSmi()) {
|
|
return Smi::Cast(*key.obj_).Value();
|
|
}
|
|
// TODO(asiva) For now we assert that the object is from Old space
|
|
// and use the address of the raw object, once the weak_entry_table code
|
|
// in heap allows for multiple thread access we should switch this code
|
|
// to create a temporary raw obj => id mapping and use that.
|
|
ASSERT(key.obj_->IsOld());
|
|
return reinterpret_cast<intptr_t>(key.obj_->raw());
|
|
}
|
|
|
|
static inline bool IsKeyEqual(Pair kv, Key key) {
|
|
if (kv.key_.type_ != key.type_) return false;
|
|
if (kv.key_.type_ == ObjectPool::kTaggedObject) {
|
|
return (kv.key_.obj_->raw() == key.obj_->raw()) &&
|
|
(kv.key_.equivalence_->raw() == key.equivalence_->raw());
|
|
}
|
|
return kv.key_.raw_value_ == key.raw_value_;
|
|
}
|
|
|
|
private:
|
|
Key key_;
|
|
Value value_;
|
|
};
|
|
|
|
|
|
enum Patchability {
|
|
kPatchable,
|
|
kNotPatchable,
|
|
};
|
|
|
|
|
|
class ObjectPoolWrapper : public ValueObject {
|
|
public:
|
|
intptr_t AddObject(const Object& obj,
|
|
Patchability patchable = kNotPatchable);
|
|
intptr_t AddImmediate(uword imm);
|
|
|
|
intptr_t FindObject(const Object& obj,
|
|
Patchability patchable = kNotPatchable);
|
|
intptr_t FindObject(const Object& obj,
|
|
const Object& equivalence);
|
|
intptr_t FindImmediate(uword imm);
|
|
intptr_t FindNativeEntry(const ExternalLabel* label,
|
|
Patchability patchable);
|
|
|
|
RawObjectPool* MakeObjectPool();
|
|
|
|
private:
|
|
intptr_t AddObject(ObjectPoolWrapperEntry entry, Patchability patchable);
|
|
intptr_t FindObject(ObjectPoolWrapperEntry entry, Patchability patchable);
|
|
|
|
// Objects and jump targets.
|
|
GrowableArray<ObjectPoolWrapperEntry> object_pool_;
|
|
|
|
// Hashmap for fast lookup in object pool.
|
|
DirectChainedHashMap<ObjIndexPair> object_pool_index_table_;
|
|
};
|
|
|
|
|
|
enum RestorePP {
|
|
kRestoreCallerPP,
|
|
kKeepCalleePP
|
|
};
|
|
|
|
} // namespace dart
|
|
|
|
|
|
#if defined(TARGET_ARCH_IA32)
|
|
#include "vm/assembler_ia32.h"
|
|
#elif defined(TARGET_ARCH_X64)
|
|
#include "vm/assembler_x64.h"
|
|
#elif defined(TARGET_ARCH_ARM)
|
|
#include "vm/assembler_arm.h"
|
|
#elif defined(TARGET_ARCH_ARM64)
|
|
#include "vm/assembler_arm64.h"
|
|
#elif defined(TARGET_ARCH_MIPS)
|
|
#include "vm/assembler_mips.h"
|
|
#elif defined(TARGET_ARCH_DBC)
|
|
#include "vm/assembler_dbc.h"
|
|
#else
|
|
#error Unknown architecture.
|
|
#endif
|
|
|
|
#endif // VM_ASSEMBLER_H_
|