Add alternate path to DOT_PRODUCT_3/4 for use_fast_dot_product that skips all the status register stuff and just remaps inf to qnan Add OPCODE_TO_SINGLE to replace the CONVERT_F32_F64 - CONVERT_F64_F32 sequence we used to emit with the idea that a backend could implement a more correct rounding behavior if possible on its arch Remove some impossible sequences like MUL_HI_I8/I16, MUL_ADD_F32, DIV_V128. These instructions have no equivalent in PPC. Many other instructions are unused/dead code and should be removed to make the x64 backend a better reference for future ones Add backend_flags to Instr. Basically, flags field that a backend can use for whatever it wants when generating code. Add backend instr flag to x64 that tells it to not generate code for an instruction. this allows sequences to consume subsequent instructions Generate actual x64 code for VSL instruction instead of using callnativesafe Detect repeated COMPARE instructions w/ identical operands and reuse the results in FLAGS if so. this eliminates a ton of garbage compare/set instructions. If a COMPARE instructions destination is stored to context with no intervening instruction and no additional uses besides the store, do setx [ctx address] Detect prefetchw and use it in CACHE_CONTROL if prefetch for write is requested instead of doing prefetch to all cache levels Fixed an accident in an earlier commit by me, VECTOR_DENORMFLUSH was not being emitted at all, so denormal inputs to MUL_ADD_V128 were not becoming zero and outputs from DOT_PRODUCT_X were not either. I believe this introduced a bug into RDR where a wagon wouldnt spawn? (https://discord.com/channels/308194948048486401/308207592482668545/1000443975817252874) Compute fresx in double precision using RECIP_F64 and then round to single instead of doing (double)(1.0f / (float)value), matching original behavior better Refactor some of ppc_emit_fpu, much of the InstrEmit function are identical except for whether they round to single or not Added "tail emitters" to X64Emitter. These are callbacks that get invoked with their label and the X64Emitter after the epilog code. This allows us to move cold code out of the critical path and in the future place constant pools near functions guest_to_host_thunk/host_to_guest_thunk now gets directly rel32 called, instead of doing a mov Add X64BackendContext structure, represents data before the start of the PPCContext Instead of doing branchless sequence, do a compare and jump to tail emitted code for address translation. This makes converting addresses a 3 uop affair in most cases. Do qnan move for dot product in a tail emitter Detect whether EFLAGS bits are independent variables for the current cpu (not really detecting it ehe, just checking if zen) and if so generate inc/dec for add/sub 1 Detect whether low 32 bits of membase are 0. If they are then we can use membasereg.cvt32() in place of immediate 0 in many places, particularly in stores Detect LOAD MODIFY STORE pattern for context variables (currently only done for 64 bit ones) and turn them into modify [context ptr]. This is done for add, sub, and, or, xor, not, neg Tail emit error handling for TRAP opcodes Stub out unused trap opcodes like TRAP_TRUE_I32, TRAP_TRUE_I64, TRAP_TRUE_I16 (the call_true/return_true opcodes for these types are also probably unused) Remove BackpropTruncations. It was poorly written and causes crashes on the game Viva pinata (https://discord.com/channels/308194948048486401/701111856600711208/1000249460451983420)
302 lines
12 KiB
C++
302 lines
12 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2021 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#ifndef XENIA_CPU_HIR_HIR_BUILDER_H_
|
|
#define XENIA_CPU_HIR_HIR_BUILDER_H_
|
|
|
|
#include <vector>
|
|
|
|
#include "third_party/fmt/include/fmt/format.h"
|
|
#include "xenia/base/arena.h"
|
|
#include "xenia/base/string_buffer.h"
|
|
#include "xenia/cpu/hir/block.h"
|
|
#include "xenia/cpu/hir/instr.h"
|
|
#include "xenia/cpu/hir/label.h"
|
|
#include "xenia/cpu/hir/opcodes.h"
|
|
#include "xenia/cpu/hir/value.h"
|
|
#include "xenia/cpu/mmio_handler.h"
|
|
|
|
namespace xe {
|
|
namespace cpu {
|
|
namespace hir {
|
|
|
|
enum FunctionAttributes {
|
|
FUNCTION_ATTRIB_INLINE = (1 << 1),
|
|
};
|
|
|
|
class HIRBuilder {
|
|
public:
|
|
HIRBuilder();
|
|
virtual ~HIRBuilder();
|
|
|
|
virtual void Reset();
|
|
virtual bool Finalize();
|
|
|
|
void Dump(StringBuffer* str);
|
|
void AssertNoCycles();
|
|
|
|
Arena* arena() const { return arena_; }
|
|
|
|
uint32_t attributes() const { return attributes_; }
|
|
void set_attributes(uint32_t value) { attributes_ = value; }
|
|
|
|
std::vector<Value*>& locals() { return locals_; }
|
|
|
|
uint32_t max_value_ordinal() const { return next_value_ordinal_; }
|
|
|
|
Block* first_block() const { return block_head_; }
|
|
Block* last_block() const { return block_tail_; }
|
|
Block* current_block() const;
|
|
Instr* last_instr() const;
|
|
|
|
Label* NewLabel();
|
|
void MarkLabel(Label* label, Block* block = 0);
|
|
void InsertLabel(Label* label, Instr* prev_instr);
|
|
void ResetLabelTags();
|
|
|
|
void AddEdge(Block* src, Block* dest, uint32_t flags);
|
|
void RemoveEdge(Block* src, Block* dest);
|
|
void RemoveEdge(Edge* edge);
|
|
void RemoveBlock(Block* block);
|
|
void MergeAdjacentBlocks(Block* left, Block* right);
|
|
|
|
// static allocations:
|
|
// Value* AllocStatic(size_t length);
|
|
|
|
void Comment(const std::string_view value);
|
|
void Comment(const StringBuffer& value);
|
|
|
|
template <typename... Args>
|
|
void CommentFormat(const std::string_view format, const Args&... args) {
|
|
static const uint32_t kMaxCommentSize = 1024;
|
|
char* p = reinterpret_cast<char*>(arena_->Alloc(kMaxCommentSize, 1));
|
|
auto result = fmt::format_to_n(p, kMaxCommentSize - 1, format, args...);
|
|
p[result.size] = '\0';
|
|
size_t rewind = kMaxCommentSize - 1 - result.size;
|
|
arena_->Rewind(rewind);
|
|
CommentBuffer(p);
|
|
}
|
|
|
|
void Nop();
|
|
|
|
void SourceOffset(uint32_t offset);
|
|
|
|
// trace info/etc
|
|
void DebugBreak();
|
|
void DebugBreakTrue(Value* cond);
|
|
|
|
void Trap(uint16_t trap_code = 0);
|
|
void TrapTrue(Value* cond, uint16_t trap_code = 0);
|
|
|
|
void Call(Function* symbol, uint16_t call_flags = 0);
|
|
void CallTrue(Value* cond, Function* symbol, uint16_t call_flags = 0);
|
|
void CallIndirect(Value* value, uint16_t call_flags = 0);
|
|
void CallIndirectTrue(Value* cond, Value* value, uint16_t call_flags = 0);
|
|
void CallExtern(Function* symbol);
|
|
void Return();
|
|
void ReturnTrue(Value* cond);
|
|
void SetReturnAddress(Value* value);
|
|
|
|
void Branch(Label* label, uint16_t branch_flags = 0);
|
|
void Branch(Block* block, uint16_t branch_flags = 0);
|
|
void BranchTrue(Value* cond, Label* label, uint16_t branch_flags = 0);
|
|
void BranchFalse(Value* cond, Label* label, uint16_t branch_flags = 0);
|
|
|
|
Value* AllocValue(TypeName type = INT64_TYPE);
|
|
Value* CloneValue(Value* source);
|
|
|
|
// phi type_name, Block* b1, Value* v1, Block* b2, Value* v2, etc
|
|
Value* Assign(Value* value);
|
|
Value* Cast(Value* value, TypeName target_type);
|
|
Value* ZeroExtend(Value* value, TypeName target_type);
|
|
Value* SignExtend(Value* value, TypeName target_type);
|
|
Value* Truncate(Value* value, TypeName target_type);
|
|
Value* Convert(Value* value, TypeName target_type,
|
|
RoundMode round_mode = ROUND_TO_ZERO);
|
|
Value* Round(Value* value, RoundMode round_mode);
|
|
Value* VectorConvertI2F(Value* value, uint32_t arithmetic_flags = 0);
|
|
Value* VectorConvertF2I(Value* value, uint32_t arithmetic_flags = 0);
|
|
|
|
Value* LoadZero(TypeName type);
|
|
Value* LoadZeroInt8() { return LoadZero(INT8_TYPE); }
|
|
Value* LoadZeroInt16() { return LoadZero(INT16_TYPE); }
|
|
Value* LoadZeroInt32() { return LoadZero(INT32_TYPE); }
|
|
Value* LoadZeroInt64() { return LoadZero(INT64_TYPE); }
|
|
Value* LoadZeroFloat32() { return LoadZero(FLOAT32_TYPE); }
|
|
Value* LoadZeroFloat64() { return LoadZero(FLOAT64_TYPE); }
|
|
Value* LoadZeroVec128() { return LoadZero(VEC128_TYPE); }
|
|
|
|
Value* LoadConstantInt8(int8_t value);
|
|
Value* LoadConstantUint8(uint8_t value);
|
|
Value* LoadConstantInt16(int16_t value);
|
|
Value* LoadConstantUint16(uint16_t value);
|
|
Value* LoadConstantInt32(int32_t value);
|
|
Value* LoadConstantUint32(uint32_t value);
|
|
Value* LoadConstantInt64(int64_t value);
|
|
Value* LoadConstantUint64(uint64_t value);
|
|
Value* LoadConstantFloat32(float value);
|
|
Value* LoadConstantFloat64(double value);
|
|
Value* LoadConstantVec128(const vec128_t& value);
|
|
|
|
Value* LoadVectorShl(Value* sh);
|
|
Value* LoadVectorShr(Value* sh);
|
|
|
|
Value* LoadClock();
|
|
|
|
Value* AllocLocal(TypeName type);
|
|
Value* LoadLocal(Value* slot);
|
|
void StoreLocal(Value* slot, Value* value);
|
|
|
|
Value* LoadContext(size_t offset, TypeName type);
|
|
void StoreContext(size_t offset, Value* value);
|
|
void ContextBarrier();
|
|
|
|
Value* LoadMmio(cpu::MMIORange* mmio_range, uint32_t address, TypeName type);
|
|
void StoreMmio(cpu::MMIORange* mmio_range, uint32_t address, Value* value);
|
|
|
|
Value* LoadOffset(Value* address, Value* offset, TypeName type,
|
|
uint32_t load_flags = 0);
|
|
void StoreOffset(Value* address, Value* offset, Value* value,
|
|
uint32_t store_flags = 0);
|
|
|
|
Value* Load(Value* address, TypeName type, uint32_t load_flags = 0);
|
|
void Store(Value* address, Value* value, uint32_t store_flags = 0);
|
|
void Memset(Value* address, Value* value, Value* length);
|
|
void CacheControl(Value* address, size_t cache_line_size,
|
|
CacheControlType type);
|
|
void MemoryBarrier();
|
|
|
|
void SetRoundingMode(Value* value);
|
|
Value* Max(Value* value1, Value* value2);
|
|
Value* VectorMax(Value* value1, Value* value2, TypeName part_type,
|
|
uint32_t arithmetic_flags = 0);
|
|
Value* Min(Value* value1, Value* value2);
|
|
Value* VectorMin(Value* value1, Value* value2, TypeName part_type,
|
|
uint32_t arithmetic_flags = 0);
|
|
Value* Select(Value* cond, Value* value1, Value* value2);
|
|
Value* IsTrue(Value* value);
|
|
Value* IsFalse(Value* value);
|
|
Value* IsNan(Value* value);
|
|
Value* CompareEQ(Value* value1, Value* value2);
|
|
Value* CompareNE(Value* value1, Value* value2);
|
|
Value* CompareSLT(Value* value1, Value* value2);
|
|
Value* CompareSLE(Value* value1, Value* value2);
|
|
Value* CompareSGT(Value* value1, Value* value2);
|
|
Value* CompareSGE(Value* value1, Value* value2);
|
|
Value* CompareULT(Value* value1, Value* value2);
|
|
Value* CompareULE(Value* value1, Value* value2);
|
|
Value* CompareUGT(Value* value1, Value* value2);
|
|
Value* CompareUGE(Value* value1, Value* value2);
|
|
Value* DidSaturate(Value* value);
|
|
Value* VectorCompareEQ(Value* value1, Value* value2, TypeName part_type);
|
|
Value* VectorCompareSGT(Value* value1, Value* value2, TypeName part_type);
|
|
Value* VectorCompareSGE(Value* value1, Value* value2, TypeName part_type);
|
|
Value* VectorCompareUGT(Value* value1, Value* value2, TypeName part_type);
|
|
Value* VectorCompareUGE(Value* value1, Value* value2, TypeName part_type);
|
|
Value* VectorDenormFlush(Value* value1);
|
|
Value* ToSingle(Value* value);
|
|
Value* Add(Value* value1, Value* value2, uint32_t arithmetic_flags = 0);
|
|
Value* AddWithCarry(Value* value1, Value* value2, Value* value3,
|
|
uint32_t arithmetic_flags = 0);
|
|
Value* VectorAdd(Value* value1, Value* value2, TypeName part_type,
|
|
uint32_t arithmetic_flags = 0);
|
|
Value* Sub(Value* value1, Value* value2, uint32_t arithmetic_flags = 0);
|
|
Value* VectorSub(Value* value1, Value* value2, TypeName part_type,
|
|
uint32_t arithmetic_flags = 0);
|
|
Value* Mul(Value* value1, Value* value2, uint32_t arithmetic_flags = 0);
|
|
Value* MulHi(Value* value1, Value* value2, uint32_t arithmetic_flags = 0);
|
|
Value* Div(Value* value1, Value* value2, uint32_t arithmetic_flags = 0);
|
|
Value* MulAdd(Value* value1, Value* value2, Value* value3); // (1 * 2) + 3
|
|
Value* MulSub(Value* value1, Value* value2, Value* value3); // (1 * 2) - 3
|
|
Value* Neg(Value* value);
|
|
Value* Abs(Value* value);
|
|
Value* Sqrt(Value* value);
|
|
Value* RSqrt(Value* value);
|
|
Value* Recip(Value* value);
|
|
Value* Pow2(Value* value);
|
|
Value* Log2(Value* value);
|
|
Value* DotProduct3(Value* value1, Value* value2);
|
|
Value* DotProduct4(Value* value1, Value* value2);
|
|
|
|
Value* And(Value* value1, Value* value2);
|
|
Value* AndNot(Value* value1, Value* value2);
|
|
Value* Or(Value* value1, Value* value2);
|
|
Value* Xor(Value* value1, Value* value2);
|
|
Value* Not(Value* value);
|
|
Value* Shl(Value* value1, Value* value2);
|
|
Value* Shl(Value* value1, int8_t value2);
|
|
Value* VectorShl(Value* value1, Value* value2, TypeName part_type);
|
|
Value* Shr(Value* value1, Value* value2);
|
|
Value* Shr(Value* value1, int8_t value2);
|
|
Value* VectorShr(Value* value1, Value* value2, TypeName part_type);
|
|
Value* Sha(Value* value1, Value* value2);
|
|
Value* Sha(Value* value1, int8_t value2);
|
|
Value* VectorSha(Value* value1, Value* value2, TypeName part_type);
|
|
Value* RotateLeft(Value* value1, Value* value2);
|
|
Value* VectorRotateLeft(Value* value1, Value* value2, TypeName part_type);
|
|
Value* VectorAverage(Value* value1, Value* value2, TypeName part_type,
|
|
uint32_t arithmetic_flags);
|
|
Value* ByteSwap(Value* value);
|
|
Value* CountLeadingZeros(Value* value);
|
|
Value* Insert(Value* value, Value* index, Value* part);
|
|
Value* Insert(Value* value, uint64_t index, Value* part);
|
|
Value* Extract(Value* value, Value* index, TypeName target_type);
|
|
Value* Extract(Value* value, uint8_t index, TypeName target_type);
|
|
// i8->i16/i32/... (i8|i8 / i8|i8|i8|i8 / ...)
|
|
// i8/i16/i32 -> vec128
|
|
Value* Splat(Value* value, TypeName target_type);
|
|
Value* Permute(Value* control, Value* value1, Value* value2,
|
|
TypeName part_type);
|
|
Value* Swizzle(Value* value, TypeName part_type, uint32_t swizzle_mask);
|
|
// SelectBits(cond, value1, value2)
|
|
Value* Pack(Value* value, uint32_t pack_flags = 0);
|
|
Value* Pack(Value* value1, Value* value2, uint32_t pack_flags = 0);
|
|
Value* Unpack(Value* value, uint32_t pack_flags = 0);
|
|
|
|
Value* AtomicExchange(Value* address, Value* new_value);
|
|
Value* AtomicCompareExchange(Value* address, Value* old_value,
|
|
Value* new_value);
|
|
Value* AtomicAdd(Value* address, Value* value);
|
|
Value* AtomicSub(Value* address, Value* value);
|
|
|
|
protected:
|
|
void DumpValue(StringBuffer* str, Value* value);
|
|
void DumpOp(StringBuffer* str, OpcodeSignatureType sig_type, Instr::Op* op);
|
|
|
|
private:
|
|
Block* AppendBlock();
|
|
void EndBlock();
|
|
bool IsUnconditionalJump(Instr* instr);
|
|
Instr* AppendInstr(const OpcodeInfo& opcode, uint16_t flags, Value* dest = 0);
|
|
void CommentBuffer(const char* p);
|
|
Value* CompareXX(const OpcodeInfo& opcode, Value* value1, Value* value2);
|
|
Value* VectorCompareXX(const OpcodeInfo& opcode, Value* value1, Value* value2,
|
|
TypeName part_type);
|
|
|
|
protected:
|
|
Arena* arena_;
|
|
|
|
uint32_t attributes_;
|
|
|
|
uint32_t next_label_id_;
|
|
uint32_t next_value_ordinal_;
|
|
|
|
std::vector<Value*> locals_;
|
|
|
|
Block* block_head_;
|
|
Block* block_tail_;
|
|
Block* current_block_;
|
|
};
|
|
|
|
} // namespace hir
|
|
} // namespace cpu
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_CPU_HIR_HIR_BUILDER_H_
|