// Copyright 2015, VIXL authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of ARM Limited nor the names of its contributors may be
// used to endorse or promote products derived from this software without
// specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef VIXL_AARCH32_LABEL_AARCH32_H_
#define VIXL_AARCH32_LABEL_AARCH32_H_
extern "C" {
#include <stdint.h>
}
#include <algorithm>
#include <cstddef>
#include <iomanip>
#include <list>
#include "utils-vixl.h"
#include "constants-aarch32.h"
namespace vixl {
namespace aarch32 {
class VeneerPoolManager;
class MacroAssembler;
class Label {
public:
typedef int32_t Offset;
static const Offset kMaxOffset = 0x7fffffff;
class LabelEmitOperator {
Label::Offset max_backward_;
Label::Offset max_forward_;
public:
LabelEmitOperator(Label::Offset max_backward, Label::Offset max_forward)
: max_backward_(max_backward), max_forward_(max_forward) {}
virtual ~LabelEmitOperator() {}
virtual uint32_t Encode(uint32_t /*instr*/,
Label::Offset /*pc*/,
const Label* /*label*/) const {
return 0;
}
Label::Offset GetMaxForwardDistance() const { return max_forward_; }
Label::Offset GetMaxBackwardDistance() const { return max_backward_; }
};
class ForwardReference {
public:
ForwardReference(int32_t location,
const LabelEmitOperator& op,
InstructionSet isa)
: location_(location), op_(op), isa_(isa), is_branch_(false) {
#if defined(VIXL_INCLUDE_TARGET_A32_ONLY)
USE(isa_);
VIXL_ASSERT(isa_ == A32);
#elif defined(VIXL_INCLUDE_TARGET_T32_ONLY)
USE(isa_);
VIXL_ASSERT(isa == T32);
#endif
}
Offset GetMaxForwardDistance() const { return op_.GetMaxForwardDistance(); }
int32_t GetLocation() const { return location_; }
uint32_t GetStatePCOffset() const {
return IsUsingT32() ? kT32PcDelta : kA32PcDelta;
}
#if defined(VIXL_INCLUDE_TARGET_A32_ONLY)
bool IsUsingT32() const { return false; }
#elif defined(VIXL_INCLUDE_TARGET_T32_ONLY)
bool IsUsingT32() const { return true; }
#else
bool IsUsingT32() const { return isa_ == T32; }
#endif
bool IsBranch() const { return is_branch_; }
void SetIsBranch() { is_branch_ = true; }
const LabelEmitOperator& GetEmitOperator() const { return op_; }
Offset GetCheckpoint() const {
// The load instructions align down PC before adding the offset.
// The alignment is only needed for T32 as A32 instructions are always
// 4 byte aligned.
int32_t pc = GetLocation() + GetStatePCOffset();
return GetMaxForwardDistance() +
((IsUsingT32() && !IsBranch()) ? AlignDown(pc, 4) : pc);
}
private:
int32_t location_;
const LabelEmitOperator& op_;
InstructionSet isa_;
bool is_branch_;
};
typedef std::list<ForwardReference> ForwardRefList;
enum UpdateCheckpointOption { kNoUpdateNecessary, kRecomputeCheckpoint };
static bool CompareCheckpoints(const ForwardReference& a,
const ForwardReference& b) {
return a.GetCheckpoint() < b.GetCheckpoint();
}
Offset GetNextCheckpoint() {
if (HasForwardReference()) {
ForwardRefList::iterator min_checkpoint =
std::min_element(forward_.begin(),
forward_.end(),
CompareCheckpoints);
return (*min_checkpoint).GetCheckpoint();
}
return kMaxOffset;
}
public:
Label()
: imm_offset_(kMaxOffset),
pc_offset_(0),
is_bound_(false),
minus_zero_(false),
isa_(kDefaultISA),
referenced_(false),
veneer_pool_manager_(NULL),
is_near_(false),
checkpoint_(kMaxOffset) {}
explicit Label(Offset offset, uint32_t pc_offset, bool minus_zero = false)
: imm_offset_(offset),
pc_offset_(pc_offset),
is_bound_(true),
minus_zero_(minus_zero),
isa_(kDefaultISA),
referenced_(false),
veneer_pool_manager_(NULL),
is_near_(false),
checkpoint_(kMaxOffset) {}
~Label() VIXL_THROW_IN_NEGATIVE_TESTING_MODE(std::runtime_error) {
#ifdef VIXL_DEBUG
if (referenced_ && !is_bound_) {
VIXL_ABORT_WITH_MSG("Label used but not bound.\n");
}
#endif
}
#undef DEFAULT_IS_T32
bool IsBound() const { return is_bound_; }
bool HasForwardReference() const { return !forward_.empty(); }
void Bind(Offset offset, InstructionSet isa) {
VIXL_ASSERT(!IsBound());
USE(isa);
USE(isa_);
imm_offset_ = offset;
is_bound_ = true;
#if defined(VIXL_INCLUDE_TARGET_A32_ONLY)
VIXL_ASSERT(isa == A32);
#elif defined(VIXL_INCLUDE_TARGET_T32_ONLY)
VIXL_ASSERT(isa == T32);
#else
isa_ = isa;
#endif
}
uint32_t GetPcOffset() const { return pc_offset_; }
Offset GetLocation() const {
VIXL_ASSERT(IsBound());
return imm_offset_ + static_cast<Offset>(pc_offset_);
}
bool IsUsingT32() const {
VIXL_ASSERT(IsBound()); // Must be bound to know its ISA.
#if defined(VIXL_INCLUDE_TARGET_A32_ONLY)
return false;
#elif defined(VIXL_INCLUDE_TARGET_T32_ONLY)
return true;
#else
return isa_ == T32;
#endif
}
bool IsMinusZero() const {
VIXL_ASSERT(IsBound());
return minus_zero_;
}
void SetReferenced() { referenced_ = true; }
bool IsReferenced() const { return referenced_; }
bool IsInVeneerPool() const { return veneer_pool_manager_ != NULL; }
VeneerPoolManager* GetVeneerPoolManager() const {
return veneer_pool_manager_;
}
void SetVeneerPoolManager(VeneerPoolManager* veneer_pool_manager,
bool is_near) {
veneer_pool_manager_ = veneer_pool_manager;
is_near_ = is_near;
}
void ClearVeneerPoolManager() { veneer_pool_manager_ = NULL; }
bool IsNear() const { return is_near_; }
void SetCheckpoint(Offset checkpoint) { checkpoint_ = checkpoint; }
Offset GetCheckpoint() const { return checkpoint_; }
Offset GetAlignedCheckpoint(int byte_align) const {
return AlignDown(GetCheckpoint(), byte_align);
}
void AddForwardRef(int32_t instr_location,
InstructionSet isa,
const LabelEmitOperator& op) {
VIXL_ASSERT(referenced_);
forward_.push_back(ForwardReference(instr_location, op, isa));
}
ForwardRefList::iterator GetFirstForwardRef() { return forward_.begin(); }
ForwardRefList::iterator GetEndForwardRef() { return forward_.end(); }
const ForwardReference* GetForwardRefBack() const {
if (forward_.empty()) return NULL;
return &forward_.back();
}
// Erase an item in the list. We don't have to recompute the checkpoint as
// the caller does it.
ForwardRefList::iterator Erase(ForwardRefList::iterator ref) {
return forward_.erase(ref);
}
ForwardReference& GetBackForwardRef() { return forward_.back(); }
void ClearForwardRef() { forward_.clear(); }
// Only used by the literal pool.
// Removes the last forward reference, in particular because of a rewind.
// TODO(all): This is hard to test as the checkpoint could be affected only
// if the literal has multiple forward references. So, the literal has to be
// shared between multiple instructions and part of the literal pool which
// is not yet supperted.
void InvalidateLastForwardReference(
UpdateCheckpointOption update_checkpoint = kRecomputeCheckpoint) {
if (!IsBound()) {
VIXL_ASSERT(HasForwardReference());
forward_.pop_back();
}
VIXL_ASSERT((update_checkpoint == kNoUpdateNecessary) &&
((checkpoint_ == GetNextCheckpoint()) ||
((checkpoint_ == Label::kMaxOffset) && forward_.empty())));
if (update_checkpoint == kRecomputeCheckpoint) {
checkpoint_ = GetNextCheckpoint();
}
}
// Only used by the literal pool.
// Update the checkpoint as the shorter distance from the last
// literal in the pool's reference location to the point
// where the forward reference will fail.
// The last forward reference is assumed to be the one freshly
// added regarding this literal.
void UpdateCheckpoint() {
if (HasForwardReference()) {
const ForwardReference& ref = forward_.back();
checkpoint_ = std::min(checkpoint_, ref.GetCheckpoint());
}
VIXL_ASSERT(GetNextCheckpoint() == checkpoint_);
}
static bool CompareLabels(Label* a, Label* b) {
return a->GetCheckpoint() < b->GetCheckpoint();
}
private:
// Once bound, location of this label in the code buffer.
Offset imm_offset_;
uint32_t pc_offset_;
// Is the label bound.
bool is_bound_;
// Special flag for 'pc - 0'.
bool minus_zero_;
// Which ISA is the label in.
InstructionSet isa_;
// True if the label has been used at least once.
bool referenced_;
// Not null if the label is currently inserted in the veneer pool.
VeneerPoolManager* veneer_pool_manager_;
// True if the label is inserted in the near_labels_ list.
bool is_near_;
// Contains the references to the unbound label
ForwardRefList forward_;
// Max offset in the code buffer. Must be emitted before this checkpoint.
Offset checkpoint_;
};
class VeneerPoolManager {
public:
explicit VeneerPoolManager(MacroAssembler* masm)
: masm_(masm),
near_checkpoint_(Label::kMaxOffset),
far_checkpoint_(Label::kMaxOffset),
max_near_checkpoint_(0),
near_checkpoint_margin_(0),
last_label_reference_offset_(0),
monitor_(0) {}
bool IsEmpty() const {
return (near_labels_.size() + far_labels_.size()) == 0;
}
Label::Offset GetCheckpoint() const {
// For the far labels, we subtract the veneer size. This way avoids problems
// when two label have the same checkpoint. In the usual case, we lose some
// range but, as the minimum range for far labels is 1 mega byte, it's not
// very important.
size_t veneer_max_size = GetMaxSize();
VIXL_ASSERT(IsInt32(veneer_max_size));
Label::Offset tmp =
far_checkpoint_ - static_cast<Label::Offset>(veneer_max_size);
// Make room for a branch over the pools.
return std::min(near_checkpoint_, tmp) - kMaxInstructionSizeInBytes -
near_checkpoint_margin_;
}
size_t GetMaxSize() const {
return (near_labels_.size() + far_labels_.size()) *
kMaxInstructionSizeInBytes;
}
void AddLabel(Label* label);
void RemoveLabel(Label* label);
void EmitLabel(Label* label, Label::Offset emitted_target);
void Emit(Label::Offset target);
void Block() { monitor_++; }
void Release();
bool IsBlocked() const { return monitor_ != 0; }
private:
MacroAssembler* masm_;
// Lists of all unbound labels which are used by a branch instruction.
std::list<Label*> near_labels_;
std::list<Label*> far_labels_;
// Offset in the code buffer after which the veneer needs to be emitted.
// It's the lowest checkpoint value in the associated list.
// A default value of Label::kMaxOffset means that the checkpoint is
// invalid (no entry in the list).
Label::Offset near_checkpoint_;
Label::Offset far_checkpoint_;
// Highest checkpoint value for the near list.
Label::Offset max_near_checkpoint_;
// Margin we have to take to ensure that 16 bit branch instructions will be
// able to generate 32 bit veneers.
uint32_t near_checkpoint_margin_;
// Offset where the last reference to a label has been added to the pool.
Label::Offset last_label_reference_offset_;
// Indicates whether the emission of this pool is blocked.
int monitor_;
};
} // namespace aarch32
} // namespace vixl
#endif // VIXL_AARCH32_LABEL_AARCH32_H_