5cf1c0bcf6
Previous to this patch, both the lithium and TurboFan register allocators tracked allocated registers by "indices", rather than the register codes used elsewhere in the runtime. This patch ensures that codes are used everywhere, and in the process cleans up a bunch of redundant code and adds more structure to how the set of allocatable registers is defined. Some highlights of changes: * TurboFan's RegisterConfiguration class moved to V8's top level so that it can be shared with Crankshaft. * Various "ToAllocationIndex" and related methods removed. * Code that can be easily shared between Register classes on different platforms is now shared. * The list of allocatable registers on each platform is declared as a list rather than implicitly via the register index <-> code mapping. Committed: https://crrev.com/80bc6f6e11f79524e3f1ad05579583adfd5f18b2 Cr-Commit-Position: refs/heads/master@{#30913} Committed: https://crrev.com/7b7a8205d9a00c678fb7a6e032a55fecbc1509cf Cr-Commit-Position: refs/heads/master@{#31075} Review URL: https://codereview.chromium.org/1287383003 Cr-Commit-Position: refs/heads/master@{#31087}
912 lines
28 KiB
C++
912 lines
28 KiB
C++
// Copyright 2013 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#ifndef V8_ARM64_SIMULATOR_ARM64_H_
|
|
#define V8_ARM64_SIMULATOR_ARM64_H_
|
|
|
|
#include <stdarg.h>
|
|
#include <vector>
|
|
|
|
#include "src/allocation.h"
|
|
#include "src/arm64/assembler-arm64.h"
|
|
#include "src/arm64/decoder-arm64.h"
|
|
#include "src/arm64/disasm-arm64.h"
|
|
#include "src/arm64/instrument-arm64.h"
|
|
#include "src/assembler.h"
|
|
#include "src/globals.h"
|
|
#include "src/utils.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
|
|
#if !defined(USE_SIMULATOR)
|
|
|
|
// Running without a simulator on a native ARM64 platform.
|
|
// When running without a simulator we call the entry directly.
|
|
#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
|
|
(entry(p0, p1, p2, p3, p4))
|
|
|
|
typedef int (*arm64_regexp_matcher)(String* input,
|
|
int64_t start_offset,
|
|
const byte* input_start,
|
|
const byte* input_end,
|
|
int* output,
|
|
int64_t output_size,
|
|
Address stack_base,
|
|
int64_t direct_call,
|
|
void* return_address,
|
|
Isolate* isolate);
|
|
|
|
// Call the generated regexp code directly. The code at the entry address
|
|
// should act as a function matching the type arm64_regexp_matcher.
|
|
// The ninth argument is a dummy that reserves the space used for
|
|
// the return address added by the ExitFrame in native calls.
|
|
#define CALL_GENERATED_REGEXP_CODE(entry, p0, p1, p2, p3, p4, p5, p6, p7, p8) \
|
|
(FUNCTION_CAST<arm64_regexp_matcher>(entry)( \
|
|
p0, p1, p2, p3, p4, p5, p6, p7, NULL, p8))
|
|
|
|
// Running without a simulator there is nothing to do.
|
|
class SimulatorStack : public v8::internal::AllStatic {
|
|
public:
|
|
static uintptr_t JsLimitFromCLimit(v8::internal::Isolate* isolate,
|
|
uintptr_t c_limit) {
|
|
USE(isolate);
|
|
return c_limit;
|
|
}
|
|
|
|
static uintptr_t RegisterCTryCatch(uintptr_t try_catch_address) {
|
|
return try_catch_address;
|
|
}
|
|
|
|
static void UnregisterCTryCatch() { }
|
|
};
|
|
|
|
#else // !defined(USE_SIMULATOR)
|
|
|
|
|
|
// The proper way to initialize a simulated system register (such as NZCV) is as
|
|
// follows:
|
|
// SimSystemRegister nzcv = SimSystemRegister::DefaultValueFor(NZCV);
|
|
class SimSystemRegister {
|
|
public:
|
|
// The default constructor represents a register which has no writable bits.
|
|
// It is not possible to set its value to anything other than 0.
|
|
SimSystemRegister() : value_(0), write_ignore_mask_(0xffffffff) { }
|
|
|
|
uint32_t RawValue() const {
|
|
return value_;
|
|
}
|
|
|
|
void SetRawValue(uint32_t new_value) {
|
|
value_ = (value_ & write_ignore_mask_) | (new_value & ~write_ignore_mask_);
|
|
}
|
|
|
|
uint32_t Bits(int msb, int lsb) const {
|
|
return unsigned_bitextract_32(msb, lsb, value_);
|
|
}
|
|
|
|
int32_t SignedBits(int msb, int lsb) const {
|
|
return signed_bitextract_32(msb, lsb, value_);
|
|
}
|
|
|
|
void SetBits(int msb, int lsb, uint32_t bits);
|
|
|
|
// Default system register values.
|
|
static SimSystemRegister DefaultValueFor(SystemRegister id);
|
|
|
|
#define DEFINE_GETTER(Name, HighBit, LowBit, Func, Type) \
|
|
Type Name() const { return static_cast<Type>(Func(HighBit, LowBit)); } \
|
|
void Set##Name(Type bits) { \
|
|
SetBits(HighBit, LowBit, static_cast<Type>(bits)); \
|
|
}
|
|
#define DEFINE_WRITE_IGNORE_MASK(Name, Mask) \
|
|
static const uint32_t Name##WriteIgnoreMask = ~static_cast<uint32_t>(Mask);
|
|
SYSTEM_REGISTER_FIELDS_LIST(DEFINE_GETTER, DEFINE_WRITE_IGNORE_MASK)
|
|
#undef DEFINE_ZERO_BITS
|
|
#undef DEFINE_GETTER
|
|
|
|
protected:
|
|
// Most system registers only implement a few of the bits in the word. Other
|
|
// bits are "read-as-zero, write-ignored". The write_ignore_mask argument
|
|
// describes the bits which are not modifiable.
|
|
SimSystemRegister(uint32_t value, uint32_t write_ignore_mask)
|
|
: value_(value), write_ignore_mask_(write_ignore_mask) { }
|
|
|
|
uint32_t value_;
|
|
uint32_t write_ignore_mask_;
|
|
};
|
|
|
|
|
|
// Represent a register (r0-r31, v0-v31).
|
|
class SimRegisterBase {
|
|
public:
|
|
template<typename T>
|
|
void Set(T new_value) {
|
|
value_ = 0;
|
|
memcpy(&value_, &new_value, sizeof(T));
|
|
}
|
|
|
|
template<typename T>
|
|
T Get() const {
|
|
T result;
|
|
memcpy(&result, &value_, sizeof(T));
|
|
return result;
|
|
}
|
|
|
|
protected:
|
|
int64_t value_;
|
|
};
|
|
|
|
|
|
typedef SimRegisterBase SimRegister; // r0-r31
|
|
typedef SimRegisterBase SimFPRegister; // v0-v31
|
|
|
|
|
|
class Simulator : public DecoderVisitor {
|
|
public:
|
|
static void FlushICache(v8::internal::HashMap* i_cache, void* start,
|
|
size_t size) {
|
|
USE(i_cache);
|
|
USE(start);
|
|
USE(size);
|
|
}
|
|
|
|
explicit Simulator(Decoder<DispatchingDecoderVisitor>* decoder,
|
|
Isolate* isolate = NULL,
|
|
FILE* stream = stderr);
|
|
Simulator();
|
|
~Simulator();
|
|
|
|
// System functions.
|
|
|
|
static void Initialize(Isolate* isolate);
|
|
|
|
static void TearDown(HashMap* i_cache, Redirection* first);
|
|
|
|
static Simulator* current(v8::internal::Isolate* isolate);
|
|
|
|
class CallArgument;
|
|
|
|
// Call an arbitrary function taking an arbitrary number of arguments. The
|
|
// varargs list must be a set of arguments with type CallArgument, and
|
|
// terminated by CallArgument::End().
|
|
void CallVoid(byte* entry, CallArgument* args);
|
|
|
|
// Like CallVoid, but expect a return value.
|
|
int64_t CallInt64(byte* entry, CallArgument* args);
|
|
double CallDouble(byte* entry, CallArgument* args);
|
|
|
|
// V8 calls into generated JS code with 5 parameters and into
|
|
// generated RegExp code with 10 parameters. These are convenience functions,
|
|
// which set up the simulator state and grab the result on return.
|
|
int64_t CallJS(byte* entry,
|
|
Object* new_target,
|
|
Object* target,
|
|
Object* revc,
|
|
int64_t argc,
|
|
Object*** argv);
|
|
int64_t CallRegExp(byte* entry,
|
|
String* input,
|
|
int64_t start_offset,
|
|
const byte* input_start,
|
|
const byte* input_end,
|
|
int* output,
|
|
int64_t output_size,
|
|
Address stack_base,
|
|
int64_t direct_call,
|
|
void* return_address,
|
|
Isolate* isolate);
|
|
|
|
// A wrapper class that stores an argument for one of the above Call
|
|
// functions.
|
|
//
|
|
// Only arguments up to 64 bits in size are supported.
|
|
class CallArgument {
|
|
public:
|
|
template<typename T>
|
|
explicit CallArgument(T argument) {
|
|
bits_ = 0;
|
|
DCHECK(sizeof(argument) <= sizeof(bits_));
|
|
memcpy(&bits_, &argument, sizeof(argument));
|
|
type_ = X_ARG;
|
|
}
|
|
|
|
explicit CallArgument(double argument) {
|
|
DCHECK(sizeof(argument) == sizeof(bits_));
|
|
memcpy(&bits_, &argument, sizeof(argument));
|
|
type_ = D_ARG;
|
|
}
|
|
|
|
explicit CallArgument(float argument) {
|
|
// TODO(all): CallArgument(float) is untested, remove this check once
|
|
// tested.
|
|
UNIMPLEMENTED();
|
|
// Make the D register a NaN to try to trap errors if the callee expects a
|
|
// double. If it expects a float, the callee should ignore the top word.
|
|
DCHECK(sizeof(kFP64SignallingNaN) == sizeof(bits_));
|
|
memcpy(&bits_, &kFP64SignallingNaN, sizeof(kFP64SignallingNaN));
|
|
// Write the float payload to the S register.
|
|
DCHECK(sizeof(argument) <= sizeof(bits_));
|
|
memcpy(&bits_, &argument, sizeof(argument));
|
|
type_ = D_ARG;
|
|
}
|
|
|
|
// This indicates the end of the arguments list, so that CallArgument
|
|
// objects can be passed into varargs functions.
|
|
static CallArgument End() { return CallArgument(); }
|
|
|
|
int64_t bits() const { return bits_; }
|
|
bool IsEnd() const { return type_ == NO_ARG; }
|
|
bool IsX() const { return type_ == X_ARG; }
|
|
bool IsD() const { return type_ == D_ARG; }
|
|
|
|
private:
|
|
enum CallArgumentType { X_ARG, D_ARG, NO_ARG };
|
|
|
|
// All arguments are aligned to at least 64 bits and we don't support
|
|
// passing bigger arguments, so the payload size can be fixed at 64 bits.
|
|
int64_t bits_;
|
|
CallArgumentType type_;
|
|
|
|
CallArgument() { type_ = NO_ARG; }
|
|
};
|
|
|
|
|
|
// Start the debugging command line.
|
|
void Debug();
|
|
|
|
bool GetValue(const char* desc, int64_t* value);
|
|
|
|
bool PrintValue(const char* desc);
|
|
|
|
// Push an address onto the JS stack.
|
|
uintptr_t PushAddress(uintptr_t address);
|
|
|
|
// Pop an address from the JS stack.
|
|
uintptr_t PopAddress();
|
|
|
|
// Accessor to the internal simulator stack area.
|
|
uintptr_t StackLimit(uintptr_t c_limit) const;
|
|
|
|
void ResetState();
|
|
|
|
// Runtime call support.
|
|
static void* RedirectExternalReference(void* external_function,
|
|
ExternalReference::Type type);
|
|
void DoRuntimeCall(Instruction* instr);
|
|
|
|
// Run the simulator.
|
|
static const Instruction* kEndOfSimAddress;
|
|
void DecodeInstruction();
|
|
void Run();
|
|
void RunFrom(Instruction* start);
|
|
|
|
// Simulation helpers.
|
|
template <typename T>
|
|
void set_pc(T new_pc) {
|
|
DCHECK(sizeof(T) == sizeof(pc_));
|
|
memcpy(&pc_, &new_pc, sizeof(T));
|
|
pc_modified_ = true;
|
|
}
|
|
Instruction* pc() { return pc_; }
|
|
|
|
void increment_pc() {
|
|
if (!pc_modified_) {
|
|
pc_ = pc_->following();
|
|
}
|
|
|
|
pc_modified_ = false;
|
|
}
|
|
|
|
virtual void Decode(Instruction* instr) {
|
|
decoder_->Decode(instr);
|
|
}
|
|
|
|
void ExecuteInstruction() {
|
|
DCHECK(IsAligned(reinterpret_cast<uintptr_t>(pc_), kInstructionSize));
|
|
CheckBreakNext();
|
|
Decode(pc_);
|
|
increment_pc();
|
|
CheckBreakpoints();
|
|
}
|
|
|
|
// Declare all Visitor functions.
|
|
#define DECLARE(A) void Visit##A(Instruction* instr);
|
|
VISITOR_LIST(DECLARE)
|
|
#undef DECLARE
|
|
|
|
bool IsZeroRegister(unsigned code, Reg31Mode r31mode) const {
|
|
return ((code == 31) && (r31mode == Reg31IsZeroRegister));
|
|
}
|
|
|
|
// Register accessors.
|
|
// Return 'size' bits of the value of an integer register, as the specified
|
|
// type. The value is zero-extended to fill the result.
|
|
//
|
|
template<typename T>
|
|
T reg(unsigned code, Reg31Mode r31mode = Reg31IsZeroRegister) const {
|
|
DCHECK(code < kNumberOfRegisters);
|
|
if (IsZeroRegister(code, r31mode)) {
|
|
return 0;
|
|
}
|
|
return registers_[code].Get<T>();
|
|
}
|
|
|
|
// Common specialized accessors for the reg() template.
|
|
int32_t wreg(unsigned code, Reg31Mode r31mode = Reg31IsZeroRegister) const {
|
|
return reg<int32_t>(code, r31mode);
|
|
}
|
|
|
|
int64_t xreg(unsigned code, Reg31Mode r31mode = Reg31IsZeroRegister) const {
|
|
return reg<int64_t>(code, r31mode);
|
|
}
|
|
|
|
// Write 'value' into an integer register. The value is zero-extended. This
|
|
// behaviour matches AArch64 register writes.
|
|
template<typename T>
|
|
void set_reg(unsigned code, T value,
|
|
Reg31Mode r31mode = Reg31IsZeroRegister) {
|
|
set_reg_no_log(code, value, r31mode);
|
|
LogRegister(code, r31mode);
|
|
}
|
|
|
|
// Common specialized accessors for the set_reg() template.
|
|
void set_wreg(unsigned code, int32_t value,
|
|
Reg31Mode r31mode = Reg31IsZeroRegister) {
|
|
set_reg(code, value, r31mode);
|
|
}
|
|
|
|
void set_xreg(unsigned code, int64_t value,
|
|
Reg31Mode r31mode = Reg31IsZeroRegister) {
|
|
set_reg(code, value, r31mode);
|
|
}
|
|
|
|
// As above, but don't automatically log the register update.
|
|
template <typename T>
|
|
void set_reg_no_log(unsigned code, T value,
|
|
Reg31Mode r31mode = Reg31IsZeroRegister) {
|
|
DCHECK(code < kNumberOfRegisters);
|
|
if (!IsZeroRegister(code, r31mode)) {
|
|
registers_[code].Set(value);
|
|
}
|
|
}
|
|
|
|
void set_wreg_no_log(unsigned code, int32_t value,
|
|
Reg31Mode r31mode = Reg31IsZeroRegister) {
|
|
set_reg_no_log(code, value, r31mode);
|
|
}
|
|
|
|
void set_xreg_no_log(unsigned code, int64_t value,
|
|
Reg31Mode r31mode = Reg31IsZeroRegister) {
|
|
set_reg_no_log(code, value, r31mode);
|
|
}
|
|
|
|
// Commonly-used special cases.
|
|
template<typename T>
|
|
void set_lr(T value) {
|
|
DCHECK(sizeof(T) == kPointerSize);
|
|
set_reg(kLinkRegCode, value);
|
|
}
|
|
|
|
template<typename T>
|
|
void set_sp(T value) {
|
|
DCHECK(sizeof(T) == kPointerSize);
|
|
set_reg(31, value, Reg31IsStackPointer);
|
|
}
|
|
|
|
int64_t sp() { return xreg(31, Reg31IsStackPointer); }
|
|
int64_t jssp() { return xreg(kJSSPCode, Reg31IsStackPointer); }
|
|
int64_t fp() {
|
|
return xreg(kFramePointerRegCode, Reg31IsStackPointer);
|
|
}
|
|
Instruction* lr() { return reg<Instruction*>(kLinkRegCode); }
|
|
|
|
Address get_sp() const { return reg<Address>(31, Reg31IsStackPointer); }
|
|
|
|
template<typename T>
|
|
T fpreg(unsigned code) const {
|
|
DCHECK(code < kNumberOfRegisters);
|
|
return fpregisters_[code].Get<T>();
|
|
}
|
|
|
|
// Common specialized accessors for the fpreg() template.
|
|
float sreg(unsigned code) const {
|
|
return fpreg<float>(code);
|
|
}
|
|
|
|
uint32_t sreg_bits(unsigned code) const {
|
|
return fpreg<uint32_t>(code);
|
|
}
|
|
|
|
double dreg(unsigned code) const {
|
|
return fpreg<double>(code);
|
|
}
|
|
|
|
uint64_t dreg_bits(unsigned code) const {
|
|
return fpreg<uint64_t>(code);
|
|
}
|
|
|
|
double fpreg(unsigned size, unsigned code) const {
|
|
switch (size) {
|
|
case kSRegSizeInBits: return sreg(code);
|
|
case kDRegSizeInBits: return dreg(code);
|
|
default:
|
|
UNREACHABLE();
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
// Write 'value' into a floating-point register. The value is zero-extended.
|
|
// This behaviour matches AArch64 register writes.
|
|
template<typename T>
|
|
void set_fpreg(unsigned code, T value) {
|
|
set_fpreg_no_log(code, value);
|
|
|
|
if (sizeof(value) <= kSRegSize) {
|
|
LogFPRegister(code, kPrintSRegValue);
|
|
} else {
|
|
LogFPRegister(code, kPrintDRegValue);
|
|
}
|
|
}
|
|
|
|
// Common specialized accessors for the set_fpreg() template.
|
|
void set_sreg(unsigned code, float value) {
|
|
set_fpreg(code, value);
|
|
}
|
|
|
|
void set_sreg_bits(unsigned code, uint32_t value) {
|
|
set_fpreg(code, value);
|
|
}
|
|
|
|
void set_dreg(unsigned code, double value) {
|
|
set_fpreg(code, value);
|
|
}
|
|
|
|
void set_dreg_bits(unsigned code, uint64_t value) {
|
|
set_fpreg(code, value);
|
|
}
|
|
|
|
// As above, but don't automatically log the register update.
|
|
template <typename T>
|
|
void set_fpreg_no_log(unsigned code, T value) {
|
|
DCHECK((sizeof(value) == kDRegSize) || (sizeof(value) == kSRegSize));
|
|
DCHECK(code < kNumberOfFPRegisters);
|
|
fpregisters_[code].Set(value);
|
|
}
|
|
|
|
void set_sreg_no_log(unsigned code, float value) {
|
|
set_fpreg_no_log(code, value);
|
|
}
|
|
|
|
void set_dreg_no_log(unsigned code, double value) {
|
|
set_fpreg_no_log(code, value);
|
|
}
|
|
|
|
SimSystemRegister& nzcv() { return nzcv_; }
|
|
SimSystemRegister& fpcr() { return fpcr_; }
|
|
|
|
// Debug helpers
|
|
|
|
// Simulator breakpoints.
|
|
struct Breakpoint {
|
|
Instruction* location;
|
|
bool enabled;
|
|
};
|
|
std::vector<Breakpoint> breakpoints_;
|
|
void SetBreakpoint(Instruction* breakpoint);
|
|
void ListBreakpoints();
|
|
void CheckBreakpoints();
|
|
|
|
// Helpers for the 'next' command.
|
|
// When this is set, the Simulator will insert a breakpoint after the next BL
|
|
// instruction it meets.
|
|
bool break_on_next_;
|
|
// Check if the Simulator should insert a break after the current instruction
|
|
// for the 'next' command.
|
|
void CheckBreakNext();
|
|
|
|
// Disassemble instruction at the given address.
|
|
void PrintInstructionsAt(Instruction* pc, uint64_t count);
|
|
|
|
// Print all registers of the specified types.
|
|
void PrintRegisters();
|
|
void PrintFPRegisters();
|
|
void PrintSystemRegisters();
|
|
|
|
// Like Print* (above), but respect log_parameters().
|
|
void LogSystemRegisters() {
|
|
if (log_parameters() & LOG_SYS_REGS) PrintSystemRegisters();
|
|
}
|
|
void LogRegisters() {
|
|
if (log_parameters() & LOG_REGS) PrintRegisters();
|
|
}
|
|
void LogFPRegisters() {
|
|
if (log_parameters() & LOG_FP_REGS) PrintFPRegisters();
|
|
}
|
|
|
|
// Specify relevant register sizes, for PrintFPRegister.
|
|
//
|
|
// These values are bit masks; they can be combined in case multiple views of
|
|
// a machine register are interesting.
|
|
enum PrintFPRegisterSizes {
|
|
kPrintDRegValue = 1 << kDRegSize,
|
|
kPrintSRegValue = 1 << kSRegSize,
|
|
kPrintAllFPRegValues = kPrintDRegValue | kPrintSRegValue
|
|
};
|
|
|
|
// Print individual register values (after update).
|
|
void PrintRegister(unsigned code, Reg31Mode r31mode = Reg31IsStackPointer);
|
|
void PrintFPRegister(unsigned code,
|
|
PrintFPRegisterSizes sizes = kPrintAllFPRegValues);
|
|
void PrintSystemRegister(SystemRegister id);
|
|
|
|
// Like Print* (above), but respect log_parameters().
|
|
void LogRegister(unsigned code, Reg31Mode r31mode = Reg31IsStackPointer) {
|
|
if (log_parameters() & LOG_REGS) PrintRegister(code, r31mode);
|
|
}
|
|
void LogFPRegister(unsigned code,
|
|
PrintFPRegisterSizes sizes = kPrintAllFPRegValues) {
|
|
if (log_parameters() & LOG_FP_REGS) PrintFPRegister(code, sizes);
|
|
}
|
|
void LogSystemRegister(SystemRegister id) {
|
|
if (log_parameters() & LOG_SYS_REGS) PrintSystemRegister(id);
|
|
}
|
|
|
|
// Print memory accesses.
|
|
void PrintRead(uintptr_t address, size_t size, unsigned reg_code);
|
|
void PrintReadFP(uintptr_t address, size_t size, unsigned reg_code);
|
|
void PrintWrite(uintptr_t address, size_t size, unsigned reg_code);
|
|
void PrintWriteFP(uintptr_t address, size_t size, unsigned reg_code);
|
|
|
|
// Like Print* (above), but respect log_parameters().
|
|
void LogRead(uintptr_t address, size_t size, unsigned reg_code) {
|
|
if (log_parameters() & LOG_REGS) PrintRead(address, size, reg_code);
|
|
}
|
|
void LogReadFP(uintptr_t address, size_t size, unsigned reg_code) {
|
|
if (log_parameters() & LOG_FP_REGS) PrintReadFP(address, size, reg_code);
|
|
}
|
|
void LogWrite(uintptr_t address, size_t size, unsigned reg_code) {
|
|
if (log_parameters() & LOG_WRITE) PrintWrite(address, size, reg_code);
|
|
}
|
|
void LogWriteFP(uintptr_t address, size_t size, unsigned reg_code) {
|
|
if (log_parameters() & LOG_WRITE) PrintWriteFP(address, size, reg_code);
|
|
}
|
|
|
|
int log_parameters() { return log_parameters_; }
|
|
void set_log_parameters(int new_parameters) {
|
|
log_parameters_ = new_parameters;
|
|
if (!decoder_) {
|
|
if (new_parameters & LOG_DISASM) {
|
|
PrintF("Run --debug-sim to dynamically turn on disassembler\n");
|
|
}
|
|
return;
|
|
}
|
|
if (new_parameters & LOG_DISASM) {
|
|
decoder_->InsertVisitorBefore(print_disasm_, this);
|
|
} else {
|
|
decoder_->RemoveVisitor(print_disasm_);
|
|
}
|
|
}
|
|
|
|
static inline const char* WRegNameForCode(unsigned code,
|
|
Reg31Mode mode = Reg31IsZeroRegister);
|
|
static inline const char* XRegNameForCode(unsigned code,
|
|
Reg31Mode mode = Reg31IsZeroRegister);
|
|
static inline const char* SRegNameForCode(unsigned code);
|
|
static inline const char* DRegNameForCode(unsigned code);
|
|
static inline const char* VRegNameForCode(unsigned code);
|
|
static inline int CodeFromName(const char* name);
|
|
|
|
protected:
|
|
// Simulation helpers ------------------------------------
|
|
bool ConditionPassed(Condition cond) {
|
|
SimSystemRegister& flags = nzcv();
|
|
switch (cond) {
|
|
case eq:
|
|
return flags.Z();
|
|
case ne:
|
|
return !flags.Z();
|
|
case hs:
|
|
return flags.C();
|
|
case lo:
|
|
return !flags.C();
|
|
case mi:
|
|
return flags.N();
|
|
case pl:
|
|
return !flags.N();
|
|
case vs:
|
|
return flags.V();
|
|
case vc:
|
|
return !flags.V();
|
|
case hi:
|
|
return flags.C() && !flags.Z();
|
|
case ls:
|
|
return !(flags.C() && !flags.Z());
|
|
case ge:
|
|
return flags.N() == flags.V();
|
|
case lt:
|
|
return flags.N() != flags.V();
|
|
case gt:
|
|
return !flags.Z() && (flags.N() == flags.V());
|
|
case le:
|
|
return !(!flags.Z() && (flags.N() == flags.V()));
|
|
case nv: // Fall through.
|
|
case al:
|
|
return true;
|
|
default:
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ConditionFailed(Condition cond) {
|
|
return !ConditionPassed(cond);
|
|
}
|
|
|
|
template<typename T>
|
|
void AddSubHelper(Instruction* instr, T op2);
|
|
template<typename T>
|
|
T AddWithCarry(bool set_flags,
|
|
T src1,
|
|
T src2,
|
|
T carry_in = 0);
|
|
template<typename T>
|
|
void AddSubWithCarry(Instruction* instr);
|
|
template<typename T>
|
|
void LogicalHelper(Instruction* instr, T op2);
|
|
template<typename T>
|
|
void ConditionalCompareHelper(Instruction* instr, T op2);
|
|
void LoadStoreHelper(Instruction* instr,
|
|
int64_t offset,
|
|
AddrMode addrmode);
|
|
void LoadStorePairHelper(Instruction* instr, AddrMode addrmode);
|
|
uintptr_t LoadStoreAddress(unsigned addr_reg, int64_t offset,
|
|
AddrMode addrmode);
|
|
void LoadStoreWriteBack(unsigned addr_reg,
|
|
int64_t offset,
|
|
AddrMode addrmode);
|
|
void CheckMemoryAccess(uintptr_t address, uintptr_t stack);
|
|
|
|
// Memory read helpers.
|
|
template <typename T, typename A>
|
|
T MemoryRead(A address) {
|
|
T value;
|
|
STATIC_ASSERT((sizeof(value) == 1) || (sizeof(value) == 2) ||
|
|
(sizeof(value) == 4) || (sizeof(value) == 8));
|
|
memcpy(&value, reinterpret_cast<const void*>(address), sizeof(value));
|
|
return value;
|
|
}
|
|
|
|
// Memory write helpers.
|
|
template <typename T, typename A>
|
|
void MemoryWrite(A address, T value) {
|
|
STATIC_ASSERT((sizeof(value) == 1) || (sizeof(value) == 2) ||
|
|
(sizeof(value) == 4) || (sizeof(value) == 8));
|
|
memcpy(reinterpret_cast<void*>(address), &value, sizeof(value));
|
|
}
|
|
|
|
template <typename T>
|
|
T ShiftOperand(T value,
|
|
Shift shift_type,
|
|
unsigned amount);
|
|
template <typename T>
|
|
T ExtendValue(T value,
|
|
Extend extend_type,
|
|
unsigned left_shift = 0);
|
|
template <typename T>
|
|
void Extract(Instruction* instr);
|
|
template <typename T>
|
|
void DataProcessing2Source(Instruction* instr);
|
|
template <typename T>
|
|
void BitfieldHelper(Instruction* instr);
|
|
|
|
template <typename T>
|
|
T FPDefaultNaN() const;
|
|
|
|
void FPCompare(double val0, double val1);
|
|
double FPRoundInt(double value, FPRounding round_mode);
|
|
double FPToDouble(float value);
|
|
float FPToFloat(double value, FPRounding round_mode);
|
|
double FixedToDouble(int64_t src, int fbits, FPRounding round_mode);
|
|
double UFixedToDouble(uint64_t src, int fbits, FPRounding round_mode);
|
|
float FixedToFloat(int64_t src, int fbits, FPRounding round_mode);
|
|
float UFixedToFloat(uint64_t src, int fbits, FPRounding round_mode);
|
|
int32_t FPToInt32(double value, FPRounding rmode);
|
|
int64_t FPToInt64(double value, FPRounding rmode);
|
|
uint32_t FPToUInt32(double value, FPRounding rmode);
|
|
uint64_t FPToUInt64(double value, FPRounding rmode);
|
|
|
|
template <typename T>
|
|
T FPAdd(T op1, T op2);
|
|
|
|
template <typename T>
|
|
T FPDiv(T op1, T op2);
|
|
|
|
template <typename T>
|
|
T FPMax(T a, T b);
|
|
|
|
template <typename T>
|
|
T FPMaxNM(T a, T b);
|
|
|
|
template <typename T>
|
|
T FPMin(T a, T b);
|
|
|
|
template <typename T>
|
|
T FPMinNM(T a, T b);
|
|
|
|
template <typename T>
|
|
T FPMul(T op1, T op2);
|
|
|
|
template <typename T>
|
|
T FPMulAdd(T a, T op1, T op2);
|
|
|
|
template <typename T>
|
|
T FPSqrt(T op);
|
|
|
|
template <typename T>
|
|
T FPSub(T op1, T op2);
|
|
|
|
// Standard NaN processing.
|
|
template <typename T>
|
|
T FPProcessNaN(T op);
|
|
|
|
bool FPProcessNaNs(Instruction* instr);
|
|
|
|
template <typename T>
|
|
T FPProcessNaNs(T op1, T op2);
|
|
|
|
template <typename T>
|
|
T FPProcessNaNs3(T op1, T op2, T op3);
|
|
|
|
void CheckStackAlignment();
|
|
|
|
inline void CheckPCSComplianceAndRun();
|
|
|
|
#ifdef DEBUG
|
|
// Corruption values should have their least significant byte cleared to
|
|
// allow the code of the register being corrupted to be inserted.
|
|
static const uint64_t kCallerSavedRegisterCorruptionValue =
|
|
0xca11edc0de000000UL;
|
|
// This value is a NaN in both 32-bit and 64-bit FP.
|
|
static const uint64_t kCallerSavedFPRegisterCorruptionValue =
|
|
0x7ff000007f801000UL;
|
|
// This value is a mix of 32/64-bits NaN and "verbose" immediate.
|
|
static const uint64_t kDefaultCPURegisterCorruptionValue =
|
|
0x7ffbad007f8bad00UL;
|
|
|
|
void CorruptRegisters(CPURegList* list,
|
|
uint64_t value = kDefaultCPURegisterCorruptionValue);
|
|
void CorruptAllCallerSavedCPURegisters();
|
|
#endif
|
|
|
|
// Pseudo Printf instruction
|
|
void DoPrintf(Instruction* instr);
|
|
|
|
// Processor state ---------------------------------------
|
|
|
|
// Output stream.
|
|
FILE* stream_;
|
|
PrintDisassembler* print_disasm_;
|
|
void PRINTF_METHOD_CHECKING TraceSim(const char* format, ...);
|
|
|
|
// Instrumentation.
|
|
Instrument* instrument_;
|
|
|
|
// General purpose registers. Register 31 is the stack pointer.
|
|
SimRegister registers_[kNumberOfRegisters];
|
|
|
|
// Floating point registers
|
|
SimFPRegister fpregisters_[kNumberOfFPRegisters];
|
|
|
|
// Processor state
|
|
// bits[31, 27]: Condition flags N, Z, C, and V.
|
|
// (Negative, Zero, Carry, Overflow)
|
|
SimSystemRegister nzcv_;
|
|
|
|
// Floating-Point Control Register
|
|
SimSystemRegister fpcr_;
|
|
|
|
// Only a subset of FPCR features are supported by the simulator. This helper
|
|
// checks that the FPCR settings are supported.
|
|
//
|
|
// This is checked when floating-point instructions are executed, not when
|
|
// FPCR is set. This allows generated code to modify FPCR for external
|
|
// functions, or to save and restore it when entering and leaving generated
|
|
// code.
|
|
void AssertSupportedFPCR() {
|
|
DCHECK(fpcr().FZ() == 0); // No flush-to-zero support.
|
|
DCHECK(fpcr().RMode() == FPTieEven); // Ties-to-even rounding only.
|
|
|
|
// The simulator does not support half-precision operations so fpcr().AHP()
|
|
// is irrelevant, and is not checked here.
|
|
}
|
|
|
|
template <typename T>
|
|
static int CalcNFlag(T result) {
|
|
return (result >> (sizeof(T) * 8 - 1)) & 1;
|
|
}
|
|
|
|
static int CalcZFlag(uint64_t result) {
|
|
return result == 0;
|
|
}
|
|
|
|
static const uint32_t kConditionFlagsMask = 0xf0000000;
|
|
|
|
// Stack
|
|
uintptr_t stack_;
|
|
static const size_t stack_protection_size_ = KB;
|
|
size_t stack_size_;
|
|
uintptr_t stack_limit_;
|
|
|
|
Decoder<DispatchingDecoderVisitor>* decoder_;
|
|
Decoder<DispatchingDecoderVisitor>* disassembler_decoder_;
|
|
|
|
// Indicates if the pc has been modified by the instruction and should not be
|
|
// automatically incremented.
|
|
bool pc_modified_;
|
|
Instruction* pc_;
|
|
|
|
static const char* xreg_names[];
|
|
static const char* wreg_names[];
|
|
static const char* sreg_names[];
|
|
static const char* dreg_names[];
|
|
static const char* vreg_names[];
|
|
|
|
// Debugger input.
|
|
void set_last_debugger_input(char* input) {
|
|
DeleteArray(last_debugger_input_);
|
|
last_debugger_input_ = input;
|
|
}
|
|
char* last_debugger_input() { return last_debugger_input_; }
|
|
char* last_debugger_input_;
|
|
|
|
private:
|
|
void Init(FILE* stream);
|
|
|
|
int log_parameters_;
|
|
Isolate* isolate_;
|
|
};
|
|
|
|
|
|
// When running with the simulator transition into simulated execution at this
|
|
// point.
|
|
#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
|
|
reinterpret_cast<Object*>(Simulator::current(Isolate::Current())->CallJS( \
|
|
FUNCTION_ADDR(entry), \
|
|
p0, p1, p2, p3, p4))
|
|
|
|
#define CALL_GENERATED_REGEXP_CODE(entry, p0, p1, p2, p3, p4, p5, p6, p7, p8) \
|
|
static_cast<int>( \
|
|
Simulator::current(Isolate::Current()) \
|
|
->CallRegExp(entry, p0, p1, p2, p3, p4, p5, p6, p7, NULL, p8))
|
|
|
|
|
|
// The simulator has its own stack. Thus it has a different stack limit from
|
|
// the C-based native code. The JS-based limit normally points near the end of
|
|
// the simulator stack. When the C-based limit is exhausted we reflect that by
|
|
// lowering the JS-based limit as well, to make stack checks trigger.
|
|
class SimulatorStack : public v8::internal::AllStatic {
|
|
public:
|
|
static uintptr_t JsLimitFromCLimit(v8::internal::Isolate* isolate,
|
|
uintptr_t c_limit) {
|
|
return Simulator::current(isolate)->StackLimit(c_limit);
|
|
}
|
|
|
|
static uintptr_t RegisterCTryCatch(uintptr_t try_catch_address) {
|
|
Simulator* sim = Simulator::current(Isolate::Current());
|
|
return sim->PushAddress(try_catch_address);
|
|
}
|
|
|
|
static void UnregisterCTryCatch() {
|
|
Simulator::current(Isolate::Current())->PopAddress();
|
|
}
|
|
};
|
|
|
|
#endif // !defined(USE_SIMULATOR)
|
|
|
|
} // namespace internal
|
|
} // namespace v8
|
|
|
|
#endif // V8_ARM64_SIMULATOR_ARM64_H_
|