// 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. #ifndef RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_BASE_H_ #define RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_BASE_H_ #if defined(DART_PRECOMPILED_RUNTIME) #error "AOT runtime should not use compiler sources (including header files)" #endif // defined(DART_PRECOMPILED_RUNTIME) #include "platform/assert.h" #include "platform/unaligned.h" #include "vm/allocation.h" #include "vm/compiler/assembler/object_pool_builder.h" #include "vm/compiler/runtime_api.h" #include "vm/globals.h" #include "vm/growable_array.h" #include "vm/hash_map.h" namespace dart { #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) || \ defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) DECLARE_FLAG(bool, use_far_branches); #endif class MemoryRegion; class Slot; namespace compiler { #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // On ARM and ARM64 branch-link family of instructions puts return address // into a dedicated register (LR), which called code will then preserve // manually if needed. To ensure that LR is not clobbered accidentally we // discourage direct use of the register and instead require users to wrap // their code in one of the macroses below, which would verify that it is // safe to modify LR. // We use RELEASE_ASSERT instead of ASSERT because we use LR state (tracked // by the assembler) to generate different code sequences for write barriers // so we would like to ensure that incorrect code will trigger an assertion // instead of producing incorrect code. // Class representing the state of LR register. In addition to tracking // whether LR currently contain return address or not it also tracks // entered frames - and whether they preserved a return address or not. class LRState { public: LRState(const LRState&) = default; LRState& operator=(const LRState&) = default; bool LRContainsReturnAddress() const { RELEASE_ASSERT(!IsUnknown()); return (state_ & kLRContainsReturnAddressMask) != 0; } LRState SetLRContainsReturnAddress(bool v) const { RELEASE_ASSERT(!IsUnknown()); return LRState(frames_, v ? (state_ | 1) : (state_ & ~1)); } // Returns a |LRState| representing a state after pushing current value // of LR on the stack. LR is assumed clobberable in the new state. LRState EnterFrame() const { RELEASE_ASSERT(!IsUnknown()); // 1 bit is used for LR state the rest for frame states. constexpr auto kMaxFrames = (sizeof(state_) * kBitsPerByte) - 1; RELEASE_ASSERT(frames_ < kMaxFrames); // LSB will be clear after the shift meaning that LR can be clobbered. return LRState(frames_ + 1, state_ << 1); } // Returns a |LRState| representing a state after popping LR from the stack. // Note that for inner frames LR would usually be assumed cloberrable // even after leaving a frame. Only outerframe would restore return address // into LR. LRState LeaveFrame() const { RELEASE_ASSERT(!IsUnknown()); RELEASE_ASSERT(frames_ > 0); return LRState(frames_ - 1, state_ >> 1); } bool IsUnknown() const { return *this == Unknown(); } static LRState Unknown() { return LRState(kUnknownMarker, kUnknownMarker); } static LRState OnEntry() { return LRState(0, 1); } static LRState Clobbered() { return LRState(0, 0); } bool operator==(const LRState& other) const { return frames_ == other.frames_ && state_ == other.state_; } private: LRState(uint8_t frames, uint8_t state) : frames_(frames), state_(state) {} // LR state is encoded in the LSB of state_ bitvector. static constexpr uint8_t kLRContainsReturnAddressMask = 1; static constexpr uint8_t kUnknownMarker = 0xFF; // Number of frames on the stack or kUnknownMarker when representing // Unknown state. uint8_t frames_ = 0; // Bit vector with frames_ + 1 bits: LSB represents LR state, other bits // represent state of LR in each entered frame. Normally this value would // just be (1 << frames_). uint8_t state_ = 1; }; // READS_RETURN_ADDRESS_FROM_LR(...) macro verifies that LR contains return // address before allowing to use it. #define READS_RETURN_ADDRESS_FROM_LR(block) \ do { \ RELEASE_ASSERT(__ lr_state().LRContainsReturnAddress()); \ constexpr Register LR = LR_DO_NOT_USE_DIRECTLY; \ USE(LR); \ block; \ } while (0) // WRITES_RETURN_ADDRESS_TO_LR(...) macro verifies that LR contains return // address before allowing to write into it. LR is considered to still // contain return address after this operation. #define WRITES_RETURN_ADDRESS_TO_LR(block) READS_RETURN_ADDRESS_FROM_LR(block) // CLOBBERS_LR(...) checks that LR does *not* contain return address and it is // safe to clobber it. #define CLOBBERS_LR(block) \ do { \ RELEASE_ASSERT(!(__ lr_state().LRContainsReturnAddress())); \ constexpr Register LR = LR_DO_NOT_USE_DIRECTLY; \ USE(LR); \ block; \ } while (0) // SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(...) checks that LR contains return // address, executes |block| and marks that LR can be safely clobbered // afterwards (assuming that |block| moved LR value onto into another register). #define SPILLS_RETURN_ADDRESS_FROM_LR_TO_REGISTER(block) \ do { \ READS_RETURN_ADDRESS_FROM_LR(block); \ __ set_lr_state(__ lr_state().SetLRContainsReturnAddress(false)); \ } while (0) // RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(...) checks that LR does not // contain return address, executes |block| and marks LR as containing return // address (assuming that |block| restored LR value from another register). #define RESTORES_RETURN_ADDRESS_FROM_REGISTER_TO_LR(block) \ do { \ CLOBBERS_LR(block); \ __ set_lr_state(__ lr_state().SetLRContainsReturnAddress(true)); \ } while (0) // SPILLS_LR_TO_FRAME(...) executes |block| and updates tracked LR state to // record that we entered a frame which preserved LR. LR can be clobbered // afterwards. #define SPILLS_LR_TO_FRAME(block) \ do { \ constexpr Register LR = LR_DO_NOT_USE_DIRECTLY; \ USE(LR); \ block; \ __ set_lr_state(__ lr_state().EnterFrame()); \ } while (0) // RESTORE_LR(...) checks that LR does not contain return address, executes // |block| and updates tracked LR state to record that we exited a frame. // Whether LR contains return address or not after this operation depends on // the frame state (only the outermost frame usually restores LR). #define RESTORES_LR_FROM_FRAME(block) \ do { \ CLOBBERS_LR(block); \ __ set_lr_state(__ lr_state().LeaveFrame()); \ } while (0) #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) enum OperandSize { // Architecture-independent constants. kByte, kUnsignedByte, kTwoBytes, // Halfword (ARM), w(ord) (Intel) kUnsignedTwoBytes, kFourBytes, // Word (ARM), l(ong) (Intel) kUnsignedFourBytes, kEightBytes, // DoubleWord (ARM), q(uadword) (Intel) // ARM-specific constants. kSWord, kDWord, // 32-bit ARM specific constants. kWordPair, kRegList, // 64-bit ARM specific constants. kQWord, #if defined(HAS_SMI_63_BITS) kObjectBytes = kEightBytes, #else kObjectBytes = kFourBytes, #endif }; // For declaring default sizes in AssemblerBase. #if defined(TARGET_ARCH_IS_64_BIT) constexpr OperandSize kWordBytes = kEightBytes; #else constexpr OperandSize kWordBytes = kFourBytes; #endif // Forward declarations. class Assembler; class AssemblerFixup; class AssemblerBuffer; class Address; class FieldAddress; #if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) class Label : public ZoneObject { public: Label() {} ~Label() { // Assert if label is being destroyed with unresolved branches pending. ASSERT(!IsLinked()); } intptr_t Position() const { ASSERT(IsBound()); return position_; } bool IsBound() const { return position_ != -1; } bool IsUnused() const { return !IsBound() && !IsLinked(); } bool IsLinked() const { return unresolved_cb_ != -1 || unresolved_cj_ != -1 || unresolved_b_ != -1 || unresolved_j_ != -1 || unresolved_far_ != -1; } private: int32_t position_ = -1; void BindTo(intptr_t position) { ASSERT(!IsBound()); ASSERT(!IsLinked()); position_ = position; ASSERT(IsBound()); } // Linked lists of unresolved forward branches, threaded through the branch // instructions. The offset encoded in each unresolved branch the delta to the // next instruction in the list, terminated with 0 delta. Each branch class // has a separate list because the offset range of each is different. #define DEFINE_BRANCH_CLASS(name) \ int32_t unresolved_##name##_ = -1; \ int32_t link_##name(int32_t position) { \ ASSERT(position > unresolved_##name##_); \ int32_t offset; \ if (unresolved_##name##_ == -1) { \ offset = 0; \ } else { \ offset = position - unresolved_##name##_; \ ASSERT(offset > 0); \ } \ unresolved_##name##_ = position; \ return offset; \ } DEFINE_BRANCH_CLASS(cb); DEFINE_BRANCH_CLASS(cj); DEFINE_BRANCH_CLASS(b); DEFINE_BRANCH_CLASS(j); DEFINE_BRANCH_CLASS(far); friend class MicroAssembler; DISALLOW_COPY_AND_ASSIGN(Label); }; #else class Label : public ZoneObject { public: Label() : position_(0), unresolved_(0) { #ifdef DEBUG for (int i = 0; i < kMaxUnresolvedBranches; i++) { unresolved_near_positions_[i] = -1; } #endif // DEBUG } ~Label() { // Assert if label is being destroyed with unresolved branches pending. ASSERT(!IsLinked()); ASSERT(!HasNear()); } // Returns the position for bound and linked labels. Cannot be used // for unused labels. intptr_t Position() const { ASSERT(!IsUnused()); return IsBound() ? -position_ - kBias : position_ - kBias; } intptr_t LinkPosition() const { ASSERT(IsLinked()); return position_ - kBias; } intptr_t NearPosition() { ASSERT(HasNear()); return unresolved_near_positions_[--unresolved_]; } bool IsBound() const { return position_ < 0; } bool IsUnused() const { return position_ == 0 && unresolved_ == 0; } bool IsLinked() const { return position_ > 0; } bool HasNear() const { return unresolved_ != 0; } private: #if defined(TARGET_ARCH_X64) || defined(TARGET_ARCH_IA32) static constexpr int kMaxUnresolvedBranches = 20; #else static constexpr int kMaxUnresolvedBranches = 1; // Unused on non-Intel. #endif // Zero position_ means unused (neither bound nor linked to). // Thus we offset actual positions by the given bias to prevent zero // positions from occurring. // Note: we use target::kWordSize as a bias because on ARM // there are assertions that check that distance is aligned. static constexpr int kBias = 4; intptr_t position_; intptr_t unresolved_; intptr_t unresolved_near_positions_[kMaxUnresolvedBranches]; #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) // On ARM/ARM64 we track LR state: whether it contains return address or // whether it can be clobbered. To make sure that our tracking it correct // for non linear code sequences we additionally verify at labels that // incoming states are compatible. LRState lr_state_ = LRState::Unknown(); void UpdateLRState(LRState new_state) { if (lr_state_.IsUnknown()) { lr_state_ = new_state; } else { RELEASE_ASSERT(lr_state_ == new_state); } } #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) void Reinitialize() { position_ = 0; } #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) void BindTo(intptr_t position, LRState lr_state) #else void BindTo(intptr_t position) #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) { ASSERT(!IsBound()); ASSERT(!HasNear()); position_ = -position - kBias; ASSERT(IsBound()); #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) UpdateLRState(lr_state); #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) } #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) void LinkTo(intptr_t position, LRState lr_state) #else void LinkTo(intptr_t position) #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) { ASSERT(!IsBound()); position_ = position + kBias; ASSERT(IsLinked()); #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) UpdateLRState(lr_state); #endif // defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) } void NearLinkTo(intptr_t position) { ASSERT(!IsBound()); ASSERT(unresolved_ < kMaxUnresolvedBranches); unresolved_near_positions_[unresolved_++] = position; } friend class Assembler; DISALLOW_COPY_AND_ASSIGN(Label); }; #endif // 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 ZoneObject { 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 void Emit(T value) { ASSERT(HasEnsuredCapacity()); #if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) || \ defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) // Variable-length instructions in ia32/x64 have unaligned immediates. // Instruction parcels in RISC-V are only 2-byte aligned. StoreUnaligned(reinterpret_cast(cursor_), value); #else // Other architecture have aligned, fixed-length instructions. *reinterpret_cast(cursor_) = value; #endif cursor_ += sizeof(T); } template void Remit() { ASSERT(Size() >= static_cast(sizeof(T))); cursor_ -= sizeof(T); } // Return address to code at |position| bytes. uword Address(intptr_t position) { return contents_ + position; } template T Load(intptr_t position) { ASSERT(position >= 0 && position <= (Size() - static_cast(sizeof(T)))); #if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) || \ defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) // Variable-length instructions in ia32/x64 have unaligned immediates. // Instruction parcels in RISC-V are only 2-byte aligned. return LoadUnaligned(reinterpret_cast(contents_ + position)); #else // Other architecture have aligned, fixed-length instructions. return *reinterpret_cast(contents_ + position); #endif } template void Store(intptr_t position, T value) { ASSERT(position >= 0 && position <= (Size() - static_cast(sizeof(T)))); #if defined(TARGET_ARCH_IA32) || defined(TARGET_ARCH_X64) || \ defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64) // Variable-length instructions in ia32/x64 have unaligned immediates. // Instruction parcels in RISC-V are only 2-byte aligned. StoreUnaligned(reinterpret_cast(contents_ + position), value); #else // Other architecture have aligned, fixed-length instructions. *reinterpret_cast(contents_ + position) = value; #endif } const ZoneGrowableArray& pointer_offsets() const { #if defined(DEBUG) ASSERT(fixups_processed_); #endif return *pointer_offsets_; } #if defined(TARGET_ARCH_IA32) // Emit an object pointer directly in the code. void EmitObject(const Object& object); #endif // 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 constexpr intptr_t kMinimumGap = 32; uword contents_; uword cursor_; uword limit_; AssemblerFixup* fixup_; ZoneGrowableArray* 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; }; class AssemblerBase : public StackResource { public: explicit AssemblerBase(ObjectPoolBuilder* object_pool_builder) : StackResource(ThreadState::Current()), object_pool_builder_(object_pool_builder) {} virtual ~AssemblerBase(); // Used for near/far jumps on IA32/X64, ignored for ARM. enum JumpDistance : bool { kFarJump = false, kNearJump = true, }; intptr_t CodeSize() const { return buffer_.Size(); } uword CodeAddress(intptr_t offset) { return buffer_.Address(offset); } bool HasObjectPoolBuilder() const { return object_pool_builder_ != nullptr; } ObjectPoolBuilder& object_pool_builder() { return *object_pool_builder_; } intptr_t prologue_offset() const { return prologue_offset_; } bool has_monomorphic_entry() const { return has_monomorphic_entry_; } // Tracks if the resulting code should be aligned by kPreferredLoopAlignment // boundary. void mark_should_be_aligned() { should_be_aligned_ = true; } bool should_be_aligned() const { return should_be_aligned_; } void Comment(const char* format, ...) PRINTF_ATTRIBUTE(2, 3); static bool EmittingComments(); virtual void Breakpoint() = 0; virtual void StoreStoreFence() = 0; virtual void SmiTag(Register r) = 0; virtual void Bind(Label* label) = 0; // If Smis are compressed and the Smi value in dst is non-negative, ensures // the upper bits are cleared. If Smis are not compressed, is a no-op. // // Since this operation only affects the unused upper bits when Smis are // compressed, it can be used on registers not allocated as writable. // // The behavior on the upper bits of signed compressed Smis is undefined. #if defined(DART_COMPRESSED_POINTERS) virtual void ExtendNonNegativeSmi(Register dst) { // Default to sign extension and allow architecture-specific assemblers // where an alternative like zero-extension is preferred to override this. ExtendValue(dst, dst, kObjectBytes); } #else void ExtendNonNegativeSmi(Register dst) {} #endif // Extends a value of size sz in src to a value of size kWordBytes in dst. // That is, bits in the source register that are not part of the sz-sized // value are ignored, and if sz is signed, then the value is sign extended. // // Produces no instructions if dst and src are the same and sz is kWordBytes. virtual void ExtendValue(Register dst, Register src, OperandSize sz) = 0; // Extends a value of size sz in src to a tagged Smi value in dst. // That is, bits in the source register that are not part of the sz-sized // value are ignored, and if sz is signed, then the value is sign extended. virtual void ExtendAndSmiTagValue(Register dst, Register src, OperandSize sz) { ExtendValue(dst, src, sz); SmiTag(dst); } // Move the contents of src into dst. // // Produces no instructions if dst and src are the same. virtual void MoveRegister(Register dst, Register src) { ExtendValue(dst, src, kWordBytes); } // Move the contents of src into dst and tag the value in dst as a Smi. virtual void MoveAndSmiTagRegister(Register dst, Register src) { ExtendAndSmiTagValue(dst, src, kWordBytes); } // Inlined allocation in new space of an instance of an object whose instance // size is known at compile time with class ID 'cid'. The generated code has // no runtime calls. Jump to 'failure' if the instance cannot be allocated // here and should be done via runtime call instead. // // ObjectPtr to allocated instance is returned in 'instance_reg'. // // WARNING: The caller is responsible for initializing all GC-visible fields // of the object other than the tags field, which is initialized here. virtual void TryAllocateObject(intptr_t cid, intptr_t instance_size, Label* failure, JumpDistance distance, Register instance_reg, Register temp) = 0; // An alternative version of TryAllocateObject that takes a Class object // and passes the class id and instance size to TryAllocateObject along with // the other arguments. void TryAllocate(const Class& cls, Label* failure, JumpDistance distance, Register instance_reg, Register temp) { TryAllocateObject(target::Class::GetId(cls), target::Class::GetInstanceSize(cls), failure, distance, instance_reg, temp); } virtual void BranchIfSmi(Register reg, Label* label, JumpDistance distance = kFarJump) = 0; // [reg] = [reg] << [shift] virtual void LslImmediate(Register reg, int32_t shift, OperandSize sz = kWordBytes) = 0; // [dst] = [src] << [shift] virtual void LslImmediate(Register dst, Register src, int32_t shift, OperandSize sz = kWordBytes) = 0; // [reg] = [reg] >>> [shift] // // Assumes [sz] is a signed OperandSize. virtual void ArithmeticShiftRightImmediate(Register reg, int32_t shift, OperandSize sz = kWordBytes) = 0; // [dst] = [src] >>> [shift] // // Assumes [sz] is a signed OperandSize. virtual void ArithmeticShiftRightImmediate(Register dst, Register src, int32_t shift, OperandSize sz = kWordBytes) = 0; virtual void CompareWords(Register reg1, Register reg2, intptr_t offset, Register count, Register temp, Label* equals) = 0; void UnrolledMemCopy(Register dst_base, intptr_t dst_offset, Register src_base, intptr_t src_offset, intptr_t size, Register temp); enum CanBeSmi { kValueCanBeSmi, kValueIsNotSmi, }; enum MemoryOrder { // All previous writes to memory in this thread must be visible to other // threads. Currently, only used for lazily populating hash indices in // shared const maps and sets. kRelease, kAcquire, // All other stores. kRelaxedNonAtomic, }; virtual void LoadFieldAddressForOffset(Register reg, Register base, int32_t offset) = 0; virtual void LoadFieldAddressForRegOffset( Register address, Register instance, Register offset_in_words_as_smi) = 0; virtual void LoadAcquire(Register dst, const Address& address, OperandSize size = kWordBytes) = 0; virtual void StoreRelease(Register src, const Address& address, OperandSize size = kWordBytes) = 0; virtual void Load(Register dst, const Address& address, OperandSize sz = kWordBytes) = 0; // Does not use write barriers, use StoreIntoObject instead for boxed fields. virtual void Store(Register src, const Address& address, OperandSize sz = kWordBytes) = 0; // When emitting the write barrier code on IA32, either the caller must // allocate a scratch register or the implementation chooses a register to // save and restore and uses that as a scratch register internally. // Thus, the scratch register is an additional optional argument to // StoreIntoObject, StoreIntoArray, StoreIntoObjectOffset, and StoreBarrier // that defaults to TMP on other architectures. (TMP is kNoRegister on IA32, // so the default value invokes the correct behavior.) // Store into a heap object and applies the appropriate write barriers. // (See StoreBarrier for which are applied on a given architecture.) // // All stores into heap objects must pass through this function or, // if the value can be proven either Smi or old-and-premarked, its NoBarrier // variant. Preserves the [object] and [value] registers. void StoreIntoObject(Register object, // Object being stored into. const Address& address, // Offset into object. Register value, // Value being stored. CanBeSmi can_be_smi = kValueCanBeSmi, MemoryOrder memory_order = kRelaxedNonAtomic, Register scratch = TMP, OperandSize size = kWordBytes); void StoreIntoObjectNoBarrier(Register object, // Object being stored into. const Address& address, // Offset into object. Register value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic, OperandSize size = kWordBytes); virtual void StoreObjectIntoObjectNoBarrier( Register object, // Object being stored into. const Address& address, // Offset into object. const Object& value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic, OperandSize size = kWordBytes) = 0; virtual void LoadIndexedPayload(Register dst, Register base, int32_t offset, Register index, ScaleFactor scale, OperandSize sz = kWordBytes) = 0; // For virtual XOffset methods, the base method implementation creates an // appropriate address from the base register and offset and calls the // corresponding address-taking method. These should be overridden for // architectures where offsets should not be converted to addresses without // additional precautions, for when the ARM-specific Assembler needs // to override with an overloaded version for the Condition argument, // or for when the IA32-specific Assembler needs to override with an // overloaded version for adding a scratch register argument. void LoadAcquireFromOffset(Register dst, Register base, int32_t offset = 0, OperandSize size = kWordBytes); void StoreReleaseToOffset(Register src, Register base, int32_t offset = 0, OperandSize size = kWordBytes); virtual void LoadFromOffset(Register dst, Register base, int32_t offset, OperandSize sz = kWordBytes); // Does not use write barriers, use StoreIntoObject instead for boxed fields. virtual void StoreToOffset(Register src, Register base, int32_t offset, OperandSize sz = kWordBytes); virtual void StoreIntoObjectOffset( Register object, // Object being stored into. int32_t offset, // Offset into object. Register value, // Value being stored. CanBeSmi can_be_smi = kValueCanBeSmi, MemoryOrder memory_order = kRelaxedNonAtomic, Register scratch = TMP, OperandSize size = kWordBytes); virtual void StoreIntoObjectOffsetNoBarrier( Register object, // Object being stored into. int32_t offset, // Offset into object. Register value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic, OperandSize size = kWordBytes); void StoreObjectIntoObjectOffsetNoBarrier( Register object, // Object being stored into. int32_t offset, // Offset into object. const Object& value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic, OperandSize size = kWordBytes); void LoadField(Register dst, const FieldAddress& address, OperandSize sz = kWordBytes); virtual void LoadFieldFromOffset(Register dst, Register base, int32_t offset, OperandSize sz = kWordBytes); // Does not use write barriers, use StoreIntoObjectOffset instead for // boxed fields. virtual void StoreFieldToOffset(Register src, Register base, int32_t offset, OperandSize sz = kWordBytes); // Loads a Smi. In DEBUG mode, also checks that the loaded value is a Smi and // halts if not. void LoadSmi(Register dst, const Address& address) { Load(dst, address); DEBUG_ONLY(VerifySmi(dst)); } // Loads a Smi field from a Dart object. In DEBUG mode, also checks that the // loaded value is a Smi and halts if not. void LoadSmiField(Register dst, const FieldAddress& address); // Loads a Smi. In DEBUG mode, also checks that the loaded value is a Smi and // halts if not. void LoadSmiFromOffset(Register dst, Register base, int32_t offset); // Loads a Smi field from a Dart object. In DEBUG mode, also checks that the // loaded value is a Smi and halts if not. void LoadSmiFieldFromOffset(Register dst, Register base, int32_t offset); #if defined(DART_COMPRESSED_POINTERS) // These are the base methods that all other compressed methods delegate to. // // For the virtual methods, they are only virtual when using compressed // pointers, so the overriding definitions must be guarded with an #ifdef. virtual void LoadCompressedFieldAddressForRegOffset( Register address, Register instance, Register offset_in_words_as_smi) = 0; virtual void LoadAcquireCompressed(Register dst, const Address& address) = 0; virtual void LoadCompressed(Register dst, const Address& address) = 0; virtual void LoadIndexedCompressed(Register dst, Register base, int32_t offset, Register index) = 0; // Loads a compressed Smi. In DEBUG mode, also checks that the loaded value is // a Smi and halts if not. void LoadCompressedSmi(Register dst, const Address& address) { Load(dst, address, kUnsignedFourBytes); // Zero extension. DEBUG_ONLY(VerifySmi(dst);) } #else // These are the base methods that all other compressed methods delegate to. // // The methods are non-virtual and forward to the uncompressed versions. void LoadCompressedFieldAddressForRegOffset(Register address, Register instance, Register offset_in_words_as_smi) { LoadFieldAddressForRegOffset(address, instance, offset_in_words_as_smi); } void LoadAcquireCompressed(Register dst, const Address& address) { LoadAcquire(dst, address); } void LoadCompressed(Register dst, const Address& address) { Load(dst, address); } void LoadIndexedCompressed(Register dst, Register base, int32_t offset, Register index) { LoadIndexedPayload(dst, base, offset, index, TIMES_WORD_SIZE, kWordBytes); } // Loads a compressed Smi. In DEBUG mode, also checks that the loaded value is // a Smi and halts if not. void LoadCompressedSmi(Register dst, const Address& address) { LoadSmi(dst, address); } #endif // defined(DART_COMPRESSED_POINTERS) // Compressed store methods are implemented in AssemblerBase, as the only // difference is whether the entire word is stored or just the low bits. void StoreReleaseCompressed(Register src, const Address& address) { StoreRelease(src, address, kObjectBytes); } void StoreReleaseCompressedToOffset(Register src, Register base, int32_t offset = 0) { StoreReleaseToOffset(src, base, offset, kObjectBytes); } void StoreCompressedIntoObject( Register object, // Object being stored into. const Address& address, // Address to store the value at. Register value, // Value being stored. CanBeSmi can_be_smi = kValueCanBeSmi, MemoryOrder memory_order = kRelaxedNonAtomic, Register scratch = TMP) { StoreIntoObject(object, address, value, can_be_smi, memory_order, TMP, kObjectBytes); } void StoreCompressedIntoObjectNoBarrier( Register object, // Object being stored into. const Address& address, // Address to store the value at. Register value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic) { StoreIntoObjectNoBarrier(object, address, value, memory_order, kObjectBytes); } virtual void StoreCompressedObjectIntoObjectNoBarrier( Register object, // Object being stored into. const Address& address, // Address to store the value at. const Object& value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic) { StoreObjectIntoObjectNoBarrier(object, address, value, memory_order, kObjectBytes); } void StoreCompressedIntoObjectOffset( Register object, // Object being stored into. int32_t offset, // Offset into object. Register value, // Value being stored. CanBeSmi can_be_smi = kValueCanBeSmi, MemoryOrder memory_order = kRelaxedNonAtomic, Register scratch = TMP) { StoreIntoObjectOffset(object, offset, value, can_be_smi, memory_order, TMP, kObjectBytes); } void StoreCompressedIntoObjectOffsetNoBarrier( Register object, // Object being stored into. int32_t offset, // Offset into object. Register value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic) { StoreIntoObjectOffsetNoBarrier(object, offset, value, memory_order, kObjectBytes); } void StoreCompressedObjectIntoObjectOffsetNoBarrier( Register object, // Object being stored into. int32_t offset, // Offset into object. const Object& value, // Value being stored. MemoryOrder memory_order = kRelaxedNonAtomic) { StoreObjectIntoObjectOffsetNoBarrier(object, offset, value, memory_order, kObjectBytes); } void StoreIntoArray(Register object, Register slot, Register value, CanBeSmi can_value_be_smi = kValueCanBeSmi, Register scratch = TMP, OperandSize size = kWordBytes); void StoreCompressedIntoArray(Register object, Register slot, Register value, CanBeSmi can_value_be_smi = kValueCanBeSmi, Register scratch = TMP) { StoreIntoArray(object, slot, value, can_value_be_smi, scratch, kObjectBytes); } // These methods just delegate to the non-Field classes, either passing // along a FieldAddress as the Address or adjusting the offset appropriately. void LoadAcquireCompressedFromOffset(Register dst, Register base, int32_t offset); void LoadAcquireCompressedFieldFromOffset(Register dst, Register base, int32_t offset); void LoadCompressedField(Register dst, const FieldAddress& address); void LoadCompressedFromOffset(Register dst, Register base, int32_t offset); void LoadCompressedFieldFromOffset(Register dst, Register base, int32_t offset); void LoadCompressedSmiField(Register dst, const FieldAddress& address); void LoadCompressedSmiFromOffset(Register dst, Register base, int32_t offset); void LoadCompressedSmiFieldFromOffset(Register dst, Register base, int32_t offset); // There are no StoreCompressedField methods because only Dart objects contain // compressed pointers and compressed pointers may require write barriers, so // StoreCompressedIntoObject should be used instead. void LoadFromSlot(Register dst, Register base, const Slot& slot, MemoryOrder memory_order = kRelaxedNonAtomic); void StoreToSlot(Register src, Register base, const Slot& slot, CanBeSmi can_be_smi, MemoryOrder memory_order = kRelaxedNonAtomic, Register scratch = TMP); void StoreToSlotNoBarrier(Register src, Register base, const Slot& slot, MemoryOrder memory_order = kRelaxedNonAtomic); // Uses the type information of the Slot to determine whether the field // can be a Smi or not. void StoreToSlot(Register src, Register base, const Slot& slot, MemoryOrder memory_order = kRelaxedNonAtomic, Register scratch = TMP); // Truncates upper bits. virtual void LoadInt32FromBoxOrSmi(Register result, Register value) = 0; #if !defined(TARGET_ARCH_IS_32_BIT) virtual void LoadInt64FromBoxOrSmi(Register result, Register value) = 0; #endif // Truncates upper bits on 32 bit archs. void LoadWordFromBoxOrSmi(Register result, Register value) { #if defined(TARGET_ARCH_IS_32_BIT) LoadInt32FromBoxOrSmi(result, value); #else LoadInt64FromBoxOrSmi(result, value); #endif } // Loads nullability from an AbstractType [type] to [dst]. void LoadAbstractTypeNullability(Register dst, Register type); // Loads nullability from an AbstractType [type] and compares it // to [value]. Clobbers [scratch]. void CompareAbstractTypeNullabilityWith(Register type, /*Nullability*/ int8_t value, Register scratch); // [dst] = [base] + ([index] << [scale]) + [disp]. // // Base can be kNoRegister (or ZR if available), in which case // [dst] = [index] << [scale] + [disp] // with a set of emitted instructions optimized for that case. virtual void AddScaled(Register dst, Register base, Register index, ScaleFactor scale, int32_t disp) = 0; virtual void LoadImmediate(Register dst, target::word imm) = 0; virtual void CompareImmediate(Register reg, target::word imm, OperandSize width = kWordBytes) = 0; virtual void CompareWithMemoryValue(Register value, Address address, OperandSize size = kWordBytes) = 0; // [reg] = [reg] & [imm] virtual void AndImmediate(Register reg, target::word imm, OperandSize sz = kWordBytes) = 0; // [dst] = [src] & [imm] virtual void AndImmediate(Register dst, Register src, target::word imm, OperandSize sz = kWordBytes) = 0; virtual void LsrImmediate(Register dst, int32_t shift) = 0; virtual void MulImmediate(Register dst, target::word imm, OperandSize = kWordBytes) = 0; // If src2 == kNoRegister, dst = dst & src1, otherwise dst = src1 & src2. virtual void AndRegisters(Register dst, Register src1, Register src2 = kNoRegister) = 0; // dst = dst << shift. On some architectures, we must use a specific register // for the shift, so either the shift register must be that specific register // or the architecture must define a TMP register, which is clobbered. virtual void LslRegister(Register dst, Register shift) = 0; virtual void ExtractBitField(Register dst, Register src, intptr_t low_bit, intptr_t width) = 0; // Performs CombineHashes from runtime/vm/hash.h on the hashes contained in // dst and other. Puts the result in dst. Clobbers other. // // Note: Only uses the lower 32 bits of the hashes and returns a 32 bit hash. virtual void CombineHashes(Register dst, Register other) = 0; // Performs FinalizeHash from runtime/vm/hash.h on the hash contained in // dst. May clobber scratch if provided, otherwise may clobber TMP. // // Note: Only uses the lower 32 bits of the hash and returns a 32 bit hash. void FinalizeHash(Register hash, Register scratch = TMP) { return FinalizeHashForSize(/*bit_size=*/kBitsPerInt32, hash, scratch); } // Performs FinalizeHash from runtime/vm/hash.h on the hash contained in // dst and returns the result, masked to a maximum of [bit_size] bits. // May clobber scratch if provided, otherwise may clobber TMP. // // Note: Only uses the lower 32 bits of the hash. Since the underlying // algorithm produces 32-bit values, assumes 0 < [bit_size] <= 32. virtual void FinalizeHashForSize(intptr_t bit_size, Register hash, Register scratch = TMP) = 0; void LoadTypeClassId(Register dst, Register src); virtual void EnsureHasClassIdInDEBUG(intptr_t cid, Register src, Register scratch, bool can_be_null = false) = 0; intptr_t InsertAlignedRelocation(BSS::Relocation reloc); void MsanUnpoison(Register base, intptr_t length_in_bytes); void MsanUnpoison(Register base, Register length_in_bytes); void Unimplemented(const char* message); void Untested(const char* message); void Unreachable(const char* message); void Stop(const char* message); void FinalizeInstructions(const MemoryRegion& region) { buffer_.FinalizeInstructions(region); } // Count the fixups that produce a pointer offset, without processing // the fixups. intptr_t CountPointerOffsets() const { return buffer_.CountPointerOffsets(); } const ZoneGrowableArray& GetPointerOffsets() const { return buffer_.pointer_offsets(); } class CodeComment : public ZoneObject { public: CodeComment(intptr_t pc_offset, const String& comment) : pc_offset_(pc_offset), comment_(comment) {} intptr_t pc_offset() const { return pc_offset_; } const String& comment() const { return comment_; } private: intptr_t pc_offset_; const String& comment_; DISALLOW_COPY_AND_ASSIGN(CodeComment); }; const GrowableArray& comments() const { return comments_; } void BindUncheckedEntryPoint() { ASSERT(unchecked_entry_offset_ == 0); unchecked_entry_offset_ = CodeSize(); } // Returns the offset (from the very beginning of the instructions) to the // unchecked entry point (incl. prologue/frame setup, etc.). intptr_t UncheckedEntryOffset() const { return unchecked_entry_offset_; } enum RangeCheckCondition { kIfNotInRange = 0, kIfInRange = 1, }; // Jumps to [target] if [condition] is satisfied. // // [low] and [high] are inclusive. // If [temp] is kNoRegister, then [value] is overwritten. // Note: Using a valid [temp] register generates an additional // instruction on x64/ia32. virtual void RangeCheck(Register value, Register temp, intptr_t low, intptr_t high, RangeCheckCondition condition, Label* target) = 0; // Checks [dst] for a Smi, halting if it does not contain one. void VerifySmi(Register dst) { Label done; BranchIfSmi(dst, &done, kNearJump); Stop("Expected Smi"); Bind(&done); } static inline intptr_t OperandSizeInBits(OperandSize os) { switch (os) { case kByte: case kUnsignedByte: return kBitsPerInt8; case kTwoBytes: case kUnsignedTwoBytes: return kBitsPerInt16; case kFourBytes: case kUnsignedFourBytes: return kBitsPerInt32; case kEightBytes: return kBitsPerInt64; default: UNREACHABLE(); return kBitsPerInt64; } } static inline bool IsSignedOperand(OperandSize os) { switch (os) { case kByte: case kTwoBytes: case kFourBytes: case kEightBytes: return true; case kUnsignedByte: case kUnsignedTwoBytes: case kUnsignedFourBytes: case kSWord: case kDWord: case kQWord: return false; default: UNREACHABLE(); break; } return false; } static inline bool NeedsSignExtension(OperandSize os) { if (!IsSignedOperand(os)) return false; switch (os) { case kByte: case kTwoBytes: return true; case kFourBytes: #if defined(TARGET_ARCH_IS_32_BIT) return false; #else return true; #endif #if defined(TARGET_ARCH_IS_64_BIT) case kEightBytes: return false; #endif default: UNREACHABLE(); return false; } } protected: AssemblerBuffer buffer_; // Contains position independent code. int32_t prologue_offset_ = -1; bool has_monomorphic_entry_ = false; bool should_be_aligned_ = false; intptr_t unchecked_entry_offset_ = 0; private: // Apply the generational write barrier on all architectures and incremental // write barrier on non-IA32 architectures. // // On IA32, since the incremental write barrier is not applied, // concurrent marking cannot be enabled. virtual void StoreBarrier(Register object, // Object being stored into. Register value, // Value being stored. CanBeSmi can_be_smi, Register scratch) = 0; // Apply the generational write barrier on all architectures and incremental // write barrier on non-IA32 architectures when storing into an array. // // On IA32, since the incremental write barrier is not applied, // concurrent marking cannot be enabled. virtual void ArrayStoreBarrier(Register object, // Object being stored into. Register slot, // Slot being stored into. Register value, // Value being stored. CanBeSmi can_be_smi, Register scratch) = 0; // Checks that storing [value] into [object] does not require a write barrier. virtual void VerifyStoreNeedsNoWriteBarrier(Register object, Register value) = 0; GrowableArray comments_; ObjectPoolBuilder* object_pool_builder_; }; // For leaf runtime calls. For non-leaf runtime calls, use // Assembler::CallRuntime. class LeafRuntimeScope : public ValueObject { public: // Enters a frame, saves registers, and aligns the stack according to the C // ABI. // // If [preserve_registers] is false, only registers normally preserved at a // Dart call will be preserved (SP, FP, THR, PP, CODE_REG, RA). Suitable for // use in IL instructions marked with LocationSummary::kCall. // If [preserve registers] is true, all registers allocatable by Dart (roughly // everything but TMP, TMP2) will be preserved. Suitable for non-call IL // instructions like the write barrier. LeafRuntimeScope(Assembler* assembler, intptr_t frame_size, bool preserve_registers); // Restores registers and leaves the frame. ~LeafRuntimeScope(); // Sets the current tag, calls the runtime function, and restores the current // tag. void Call(const RuntimeEntry& entry, intptr_t argument_count); private: Assembler* const assembler_; const bool preserve_registers_; }; } // namespace compiler } // namespace dart #endif // RUNTIME_VM_COMPILER_ASSEMBLER_ASSEMBLER_BASE_H_