Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
591 lines (445 sloc) 23.2 KB
// The MIT License (MIT)
// Copyright (c) 2016, Microsoft
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#pragma once
#include <array> // For arrays in FreeList.
#include <cstdint>
#include <iosfwd> // For debugging output.
#include "NativeJIT/AllocatorVector.h" // Embedded member.
#include "NativeJIT/CodeGen/JumpTable.h" // ExpressionTree embeds Label.
#include "NativeJIT/CodeGen/Register.h"
#include "NativeJIT/TypePredicates.h" // RegisterStorage used in typedef.
#include "Temporary/NonCopyable.h"
namespace Allocators
{
class IAllocator;
}
namespace NativeJIT
{
class ExecutionPreconditionTest;
class FunctionBuffer;
class NodeBase;
class RIPRelativeImmediate;
// A class which increases reference counter on construction and decreases
// it on destruction.
// This class is not thread safe.
class ReferenceCounter final
{
public:
// Default constructor performs no observable reference counting. Used
// in cases where an object of this class needs to be constructed before
// the counter object is known (f. ex. in constructors of classes which
// contain the ReferenceCounter as a member).
ReferenceCounter();
// Increases the reference count in the specified counter.
ReferenceCounter(unsigned& counter);
// Uses the counter in the other object as its counter and increases it.
ReferenceCounter(ReferenceCounter const & other);
// Decreases the current counter.
~ReferenceCounter();
// Decreases the current counter, replaces it with the one in the other
// object and increases that counter.
ReferenceCounter& operator=(ReferenceCounter const & other);
// Decreases the current counter and disassociates itself from it.
void Reset();
private:
// Pointer to the current counter.
unsigned* m_counter;
// Methods to add and remove a reference to the counter.
void AddReference();
void RemoveReference();
};
enum class StorageClass {Direct, Indirect, Immediate};
class ExpressionTree : public NonCopyable
{
private:
class Data;
public:
template <typename T> class Storage;
// Returns the function return register for the specified type.
template <typename T>
static typename Storage<T>::DirectRegister GetResultRegister();
ExpressionTree(Allocators::IAllocator& allocator, FunctionBuffer& code);
Allocators::IAllocator& GetAllocator() const;
FunctionBuffer& GetCodeGenerator() const;
void EnableDiagnostics(std::ostream& out);
void DisableDiagnostics();
// In-place constructs an object using the class allocator. The object's
// lifetime cannot be longer than that of the ExpressionTree.
template <typename T, typename... ConstructorArgs>
T&
PlacementConstruct(ConstructorArgs&&... constructorArgs);
//
// Tree construction
//
unsigned AddNode(NodeBase& node);
// DESIGN NOTE: This might be better if ParameterNode<T> (and get position from it)
// to ensure that other nodes can't be passed to AddParameter. To make
// that possible, a circular include dependency between ExpressionTree.h
// and Node.h (through ParameterNode.h) needs to be broken. Then forward
// declaring ParameterNode<T> and using #include further below would work.
void AddParameter(NodeBase& parameter, unsigned position);
void AddRIPRelative(RIPRelativeImmediate& node);
void ReportFunctionCallNode(unsigned parameterCount);
void Compile();
//
// Storage allocation.
//
template <typename T>
Storage<T> Direct();
template <typename T>
Storage<T> Direct(typename Storage<T>::DirectRegister r);
template <typename T>
Storage<T> RIPRelative(int32_t offset);
// Returns indirect storage relative to the base pointer for a variable
// of type T. It is guaranteed that it's legal to access the whole quadword
// at the target address.
template <typename T>
Storage<T> Temporary();
template <typename T>
Storage<T> Immediate(T value);
template <unsigned SIZE>
void ReleaseRegister(Register<SIZE, false> r);
template <unsigned SIZE>
void ReleaseRegister(Register<SIZE, true> r);
// Given an offset off BaseRegister, checks whether the register
// belongs to a valid offset describing an already allocated slot and,
// if so, releases the corresponding temporary slot. Some valid offsets
// off the base register that don't refer to temporaries include offsets
// referring to compiled function's parameters.
void ReleaseIfTemporary(int32_t offset);
// Returns whether a register is pinned.
template <unsigned SIZE, bool ISFLOAT>
bool IsPinned(Register<SIZE, ISFLOAT> reg);
unsigned GetRXXUsedMask() const;
unsigned GetXMMUsedMask() const;
Label GetStartOfEpilogue() const;
protected:
bool IsDiagnosticsStreamAvailable() const;
// Returns the diagnostic stream. Throws if it is not available.
std::ostream& GetDiagnosticsStream() const;
// Adds a precondition for executing the expression. See the
// m_preconditionTests variable for more information.
void AddExecutionPreconditionTest(ExecutionPreconditionTest& test);
void const * GetUntypedEntryPoint() const;
private:
// Keeps track of used and free registers and the Data* associated with
// each register.
//
// The Data* is kept so that it's easily possible to create another
// Storage around an allocated register when necessary (f. ex. for
// spilling. Thus, there's a required 1-1 mapping between a general
// purpose register and the Data* that refers to it (either as Direct
// or Indirect).
//
// Note that it's neither necessary nor possible to hold information
// about a singular Data* for *Indirect* references to shared base
// registers such as RSP and RIP since multiple Data* refer to them by
// definition and since they don't need to be spilled. The Data* for
// Direct reference to shared base registers is kept, though.
template <unsigned SIZE>
class FreeList
{
public:
// The bit-mask signifying that all valid registers have been allocated.
static const unsigned c_fullUsedMask = (1ul << SIZE) - 1;
FreeList(Allocators::IAllocator& allocator);
// Returns the number of unallocated registers.
unsigned GetFreeCount() const;
bool IsAvailable(unsigned id) const;
unsigned Allocate();
void Allocate(unsigned id);
// Returns a pin for a register. Pinned register cannot be spilled.
// IMPORTANT: Register pinning should be done in a very limited
// scope. Otherwise, in a larger scope (f. ex. before a CodeGen()
// call) there is a risk of pinning some of the registers used for
// parameter passing, which would cause the compilation of a function
// call to fail.
ReferenceCounter GetPin(unsigned id);
// Returns whether a register is pinned.
bool IsPinned(unsigned id) const;
void Release(unsigned id);
// The methods to set and retrieve the Data* owned by the respective
// register. The InitializeData() and UpdateData() methods differ
// only in sanity checks: the former requires that no Data* was
// previously set for the register whereas the latter requires that
// a Data* already existed.
void InitializeData(unsigned id, Data* data);
void UpdateData(unsigned id, Data* data);
Data* GetData(unsigned id) const;
// Returns the bit-mask for used and allocated registers.
unsigned GetUsedMask() const;
unsigned GetFreeMask() const;
// Returns a register mask specifying which registers were touched
// at any point of time, regardless of whether they were later
// released or not.
unsigned GetLifetimeUsedMask() const;
// Returns the ID of an allocated register that is not pinned and
// can be spilled. Throws if there are no such registers available.
unsigned GetAllocatedSpillable() const;
private:
// Helper methods to perform sanity check on arguments and data contents.
void AssertValidID(unsigned id) const;
void AssertValidData(unsigned id) const;
void AssertValidData(unsigned id, Data* data) const;
// The mask tracking registers which are currently allocated.
unsigned m_usedMask;
// The mask tracking registers which were touched at any point of
// time, regardless of whether they were later released or not.
unsigned m_lifetimeUsedMask;
// See the class description for more details.
std::array<Data*, SIZE> m_data;
// An array of currently allocated IDs, oldest at the beginning.
// DESIGN NOTE: Deque and list better satisfy this variable's usage
// pattern in general (elements always added at the back, mostly pulled
// out from the front). However, given the small number of elements
// and the simplicitly of the vector, it is likely to perform better.
AllocatorVector<uint8_t> m_allocatedRegisters;
// Number of active references to a pinned register. The register
// cannot be spilled while it's pinned.
std::array<unsigned, SIZE> m_pinCount;
};
bool IsBasePointer(PointerRegister r) const;
PointerRegister GetBasePointer() const;
// Returns whether the register is one of the reserved/shared base
// registers (instruction, stack or base pointer).
template <unsigned SIZE>
bool IsAnySharedBaseRegister(Register<SIZE, false> r) const;
template <unsigned SIZE>
bool IsAnySharedBaseRegister(Register<SIZE, true> r) const;
// Converts a valid temporary slot index into an offset off base register.
int32_t TemporarySlotToOffset(unsigned temporarySlot);
// If the temporary offset off base register describes a valid allocated
// temporary slot, returns true and fills in the temporary slot out
// parameter. Returns false otherwise.
bool TemporaryOffsetToSlot(int32_t temporaryOffset, unsigned& temporarySlot);
void Pass0();
void Pass1();
void Pass2();
void Pass3();
void Print() const;
// The following template and the alias template provide a way to access
// the free list for a register and a C++ type respectively.
template <bool ISFLOAT>
class FreeListForRegister;
template <typename T>
using FreeListForType = class FreeListForRegister<RegisterStorage<T>::c_isFloat>;
// The allocator and STL-compatible wrapper around it.
Allocators::IAllocator& m_allocator;
Allocators::StlAllocator<void*> m_stlAllocator;
FunctionBuffer & m_code;
// Stream used to print diagnostics or nullptr if disabled.
std::ostream* m_diagnosticsStream;
AllocatorVector<NodeBase*> m_topologicalSort;
AllocatorVector<NodeBase*> m_parameters;
AllocatorVector<RIPRelativeImmediate*> m_ripRelatives;
// Preconditions for evaluating the whole expression. The preconditions
// are evaluated right after the parameters and will cause the function
// to return early if any of them is not met.
AllocatorVector<ExecutionPreconditionTest*> m_preconditionTests;
FreeList<RegisterBase::c_maxIntegerRegisterID + 1> m_rxxFreeList;
FreeList<RegisterBase::c_maxFloatRegisterID + 1> m_xmmFreeList;
AllocatorVector<Storage<void*>> m_reservedRxxRegisterStorages;
AllocatorVector<Storage<double>> m_reservedXmmRegisterStorages;
AllocatorVector<ReferenceCounter> m_reservedRegistersPins;
unsigned m_temporaryCount;
AllocatorVector<int32_t> m_temporaries;
// Maximum number of parameters used in function calls done by the tree.
// Negative value signifies no function calls made.
int m_maxFunctionCallParameters;
PointerRegister m_basePointer;
Label m_startOfEpilogue;
};
class ExpressionTree::Data : public NonCopyable
{
public:
template <unsigned SIZE, bool ISFLOAT>
Data(ExpressionTree& tree, Register<SIZE, ISFLOAT> r);
// DESIGN NOTE: It would be better if indrect memory knew the size of the memory it
// points to, otherwise access violation can happen if larger, non-owned
// memory area is dereferenced.
// Also applies to ConvertDirectToIndirect and Storage::Direct(Register).
Data(ExpressionTree& tree, PointerRegister r, int32_t offset);
// Note: attempt to create an immediate storage will static_assert for
// invalid immediates.
template <typename T>
Data(ExpressionTree& tree, T value);
ExpressionTree& GetTree() const;
StorageClass GetStorageClass() const;
unsigned GetRegisterId() const;
int32_t GetOffset() const;
// Note: attempt to GetImmediate() will static_assert for invalid immediates.
template <typename T>
T GetImmediate() const;
void ConvertDirectToIndirect(int32_t offset);
void ConvertIndirectToDirect();
unsigned GetRefCount() const;
unsigned Decrement();
void Increment();
// Swaps the targets between two Data objects keeping the reference
// count unchanged and notifies the free list of the register change.
// Used when all clients of both data objects need to have the contents
// exchanged.
void SwapContents(Data* other);
private:
// WARNING: This class is designed to be allocated by an arena allocator,
// so its destructor will never be called. Therefore, it should hold no
// resources other than memory from the arena allocator.
~Data();
// The type of the register change that the free list gets notified about.
enum class RegisterChangeType { Initialize, Update };
// Returns true if data's storage class is direct/immediate and if the
// register it refers to is one of the shared registers.
bool IsSharedBaseRegister() const;
// Notifies the free list that register ID of this data object has
// been modified. Calls the templated version of the same method to
// peform the actual work.
void NotifyDataRegisterChange(RegisterChangeType type);
// See above.
template <bool ISFLOAT>
void NotifyDataRegisterChange(RegisterChangeType type);
ExpressionTree& m_tree;
// How the data is stored.
StorageClass m_storageClass;
// Which register.
bool m_isFloat;
unsigned m_registerId;
int32_t m_offset;
// Holds the immediate value for Data whose storage class is Immediate.
size_t m_immediate;
// Who is using it.
unsigned m_refCount;
};
// Storage should be public because it is a return type for Node.
// Storage should be private so that its constructor can take an m_data.
template <typename T>
class ExpressionTree::Storage
{
public:
// All Storages need to be treated as the same class, regardless of
// the template parameter.
template <typename U> friend class Storage;
typedef typename RegisterStorage<T>::RegisterType DirectRegister;
typedef PointerRegister BaseRegister;
typedef typename DirectRegister::FullRegister FullRegister;
// Types of swaps. The Single swap affects only the two storages directly
// involved by swapping their Data*. The AllReferences swap modifies the
// underlying Data objects thus swapping the data for the first storage
// as well as the storages that share the same Data* with the second
// storage as well as the storages that share its Data*.
enum class SwapType { Single, AllReferences };
Storage();
template <typename U>
explicit Storage(const Storage<U>& other);
Storage(Storage const & other);
// Takes ownership of a base storage, adds the offset to it and
// dereferences it to produce a Storage<T>. Equivalent to
// *static_cast<T*>(base + byteOffset).
Storage(Storage<void*>&& base, int32_t byteOffset);
// Creates another storage referencing a register. The register must
// already be allocated. For shared base registers, the Storage will
// reference the direct Data version which cannot be converted to
// indirect nor spilled since it's pinned.
static Storage<T> ForAdditionalReferenceToRegister(
ExpressionTree& tree,
DirectRegister reg);
// Allocates an empty register and creates a direct storage referencing it.
// Can only be called when there are available registers.
static Storage<T> ForAnyFreeRegister(ExpressionTree& tree);
// Allocates a specific empty register and creates a direct storage
// referencing it. Can only be called when the specified register is free.
static Storage<T> ForFreeRegister(ExpressionTree& tree,
DirectRegister reg);
// Creates an indirect storage against a shared base register.
static Storage<T> ForSharedBaseRegister(ExpressionTree& tree,
BaseRegister base,
int32_t offset);
// Creates an immediate storage with the specified value.
static Storage<T> ForImmediate(ExpressionTree& tree, T value);
// Loads the address of an immediate storage into a newly created direct
// storage and resets the original storage. If possible, reuses the
// register from the source storage.
// Note: do not call this method with a last reference to a Temporary as
// the Temporary may be released after the source storage is reset and
// the created storage would point to invalid the memory.
template <typename U>
static Storage<T> AddressOfIndirect(Storage<U>&& indirect);
Storage& operator=(Storage const & other);
~Storage();
void Reset();
bool IsNull() const;
// Returns whether this Storage shares ownership over its Data*.
// Note that for direct storages, IsSoleDataOwner() result of true
// implies that the storage also exclusively owns the register. For
// indirect storages, this implication stands only for non-shared base
// registers.
bool IsSoleDataOwner() const;
StorageClass GetStorageClass() const;
DirectRegister GetDirectRegister() const;
BaseRegister GetBaseRegister() const;
int32_t GetOffset() const;
// Note: The method must be removed for invalid immediates rather than
// static_assert triggered because for some of them (arrays) the method
// has invalid declaration otherwise.
template <typename U = T,
typename ENABLED = typename std::enable_if<ImmediateCategoryOf<U>::value
== ImmediateCategory::InlineImmediate>::type>
U GetImmediate() const;
DirectRegister ConvertToDirect(bool forModification);
// Swaps the contents of the two storages. See SwapType definition for
// more details.
template <typename U>
void Swap(Storage<U>& other, SwapType type);
// If the storage is not the sole data owner, copies the data to stack
// and switches the ownership of other owners to it.
// Does nothing if the storage was already the sole data owner.
// Requires that the storage is direct and that it does not refer to
// one of the shared base registers.
void TakeSoleOwnershipOfDirect();
// Returns a pin for the storage's register. While the pin is held,
// the register cannot be spilled. Can only be called if Storage is
// either direct or if it's indirect and refers to non-shared base
// registers.
ReferenceCounter GetPin();
// Prints the information about the storage's type, value, base register
// and offset to the output stream.
void Print(std::ostream& out) const;
private:
// Types used to select the correct flavor of immediate methods. This is
// necessary because GetStorageClass() is a runtime rather than a compile
// time property. Even though it is not possible to create a
// StorageClass::Immediate storage for an invalid immediate, the code
// that branches depending on various types of storage needs to always
// compile.
struct ValidImmediateStorage {};
struct InvalidImmediateStorage {};
typedef typename std::conditional<CanBeInImmediateStorage<T>::value,
ValidImmediateStorage,
InvalidImmediateStorage>::type
ImmediateFlavor;
Storage(ExpressionTree::Data* data);
void SetData(ExpressionTree::Data* data);
void SetData(Storage& other);
void ConvertImmediateToDirect(bool forModification, ValidImmediateStorage);
void ConvertImmediateToDirect(bool forModification, InvalidImmediateStorage);
void PrintImmediate(std::ostream& out, ValidImmediateStorage) const;
void PrintImmediate(std::ostream& out, InvalidImmediateStorage) const;
ExpressionTree::Data* m_data;
};
template <typename T>
using Storage = typename ExpressionTree::Storage<T>;
}