From c1838c89fc4cfdd76bfd018fec6ad5c83b4b38a8 Mon Sep 17 00:00:00 2001 From: baldurk Date: Wed, 10 Jun 2020 13:14:21 +0100 Subject: [PATCH] Handle atomic instructions --- .../driver/shaders/dxil/dxil_bytecode.cpp | 298 +++++++++++++++++- renderdoc/driver/shaders/dxil/dxil_bytecode.h | 42 ++- .../driver/shaders/dxil/dxil_disassemble.cpp | 158 ++++++++++ 3 files changed, 495 insertions(+), 3 deletions(-) diff --git a/renderdoc/driver/shaders/dxil/dxil_bytecode.cpp b/renderdoc/driver/shaders/dxil/dxil_bytecode.cpp index 3f289ab75..fd82ace90 100644 --- a/renderdoc/driver/shaders/dxil/dxil_bytecode.cpp +++ b/renderdoc/driver/shaders/dxil/dxil_bytecode.cpp @@ -1263,7 +1263,10 @@ Program::Program(const byte *bytes, size_t length) case 10: inst.op = Instruction::IToPtr; break; case 11: inst.op = Instruction::Bitcast; break; case 12: inst.op = Instruction::AddrSpaceCast; break; - default: RDCERR("Unhandled cast type %d", op.ops[2]); + default: + inst.op = Instruction::Bitcast; + RDCERR("Unhandled cast type %d", op.ops[2]); + break; } m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()}); @@ -1338,7 +1341,10 @@ Program::Program(const byte *bytes, size_t length) case 10: inst.op = Instruction::And; break; case 11: inst.op = Instruction::Or; break; case 12: inst.op = Instruction::Xor; break; - default: RDCERR("Unhandled binop type %d", op.ops[2]); + default: + inst.op = Instruction::And; + RDCERR("Unhandled binop type %d", op.ops[2]); + break; } if(op.ops.size() > 3) @@ -1569,6 +1575,11 @@ Program::Program(const byte *bytes, size_t length) case 39: inst.op = Instruction::SGreaterEqual; break; case 40: inst.op = Instruction::SLess; break; case 41: inst.op = Instruction::SLessEqual; break; + + default: + inst.op = Instruction::FOrdFalse; + RDCERR("Unexpected comparison %llu", op.ops[2]); + break; } // fast math flags @@ -1737,6 +1748,289 @@ Program::Program(const byte *bytes, size_t length) f.instructions.push_back(inst); } + else if(IS_KNOWN(op.id, FunctionRecord::INST_LOADATOMIC)) + { + Instruction inst; + + inst.op = Instruction::LoadAtomic; + + inst.args.push_back(getSymbol(op.ops[0])); + + size_t idx = 1; + + if(op.ops.size() == idx + 5) + { + inst.type = &m_Types[op.ops[idx++]]; + } + else + { + inst.type = GetSymbolType(f, inst.args[0]); + RDCASSERT(inst.type->type == Type::Pointer); + inst.type = inst.type->inner; + } + + inst.align = (1U << op.ops[idx]) >> 1; + inst.opFlags |= + (op.ops[idx + 1] != 0) ? InstructionFlags::Volatile : InstructionFlags::NoFlags; + + // success ordering + switch(op.ops[idx + 2]) + { + case 0: break; + case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break; + case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break; + case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break; + case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break; + case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break; + case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break; + default: + RDCERR("Unexpected success ordering %llu", op.ops[idx + 2]); + inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; + break; + } + + // synchronisation scope + switch(op.ops[idx + 3]) + { + case 0: inst.opFlags |= InstructionFlags::SingleThread; break; + case 1: break; + default: RDCERR("Unexpected synchronisation scope %llu", op.ops[idx + 3]); break; + } + + m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()}); + + f.instructions.push_back(inst); + } + else if(IS_KNOWN(op.id, FunctionRecord::INST_STOREATOMIC_OLD) || + IS_KNOWN(op.id, FunctionRecord::INST_STOREATOMIC)) + { + Instruction inst; + + inst.op = Instruction::StoreAtomic; + + inst.type = GetVoidType(); + + inst.args.push_back(getSymbol(op.ops[0])); + inst.args.push_back(getSymbol(op.ops[1])); + + inst.align = (1U << op.ops[2]) >> 1; + inst.opFlags |= + (op.ops[3] != 0) ? InstructionFlags::Volatile : InstructionFlags::NoFlags; + + // success ordering + switch(op.ops[4]) + { + case 0: break; + case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break; + case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break; + case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break; + case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break; + case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break; + case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break; + default: + RDCERR("Unexpected success ordering %llu", op.ops[4]); + inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; + break; + } + + // synchronisation scope + switch(op.ops[5]) + { + case 0: inst.opFlags |= InstructionFlags::SingleThread; break; + case 1: break; + default: RDCERR("Unexpected synchronisation scope %llu", op.ops[5]); break; + } + + f.instructions.push_back(inst); + } + else if(IS_KNOWN(op.id, FunctionRecord::INST_ATOMICRMW)) + { + Instruction inst; + + // pointer to atomically modify + inst.args.push_back(getSymbol(op.ops[0])); + + // type is the pointee of the first argument + inst.type = GetSymbolType(f, inst.args.back()); + RDCASSERT(inst.type->type == Type::Pointer); + inst.type = inst.type->inner; + + // parameter value + inst.args.push_back(getSymbol(op.ops[1])); + + switch(op.ops[2]) + { + case 0: inst.op = Instruction::AtomicExchange; break; + case 1: inst.op = Instruction::AtomicAdd; break; + case 2: inst.op = Instruction::AtomicSub; break; + case 3: inst.op = Instruction::AtomicAnd; break; + case 4: inst.op = Instruction::AtomicNand; break; + case 5: inst.op = Instruction::AtomicOr; break; + case 6: inst.op = Instruction::AtomicXor; break; + case 7: inst.op = Instruction::AtomicMax; break; + case 8: inst.op = Instruction::AtomicMin; break; + case 9: inst.op = Instruction::AtomicUMax; break; + case 10: inst.op = Instruction::AtomicUMin; break; + default: + RDCERR("Unhandled atomicrmw op %llu", op.ops[2]); + inst.op = Instruction::AtomicExchange; + break; + } + + if(op.ops[3]) + inst.opFlags |= InstructionFlags::Volatile; + + // success ordering + switch(op.ops[4]) + { + case 0: break; + case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break; + case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break; + case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break; + case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break; + case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break; + case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break; + default: + RDCERR("Unexpected success ordering %llu", op.ops[4]); + inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; + break; + } + + // synchronisation scope + switch(op.ops[5]) + { + case 0: inst.opFlags |= InstructionFlags::SingleThread; break; + case 1: break; + default: RDCERR("Unexpected synchronisation scope %llu", op.ops[5]); break; + } + + m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()}); + + f.instructions.push_back(inst); + } + else if(IS_KNOWN(op.id, FunctionRecord::INST_CMPXCHG) || + IS_KNOWN(op.id, FunctionRecord::INST_CMPXCHG_OLD)) + { + Instruction inst; + + inst.op = Instruction::CompareExchange; + + // pointer to atomically modify + inst.args.push_back(getSymbol(op.ops[0])); + + // type is the pointee of the first argument + inst.type = GetSymbolType(f, inst.args.back()); + RDCASSERT(inst.type->type == Type::Pointer); + inst.type = inst.type->inner; + + // combined with a bool, search for a struct like that + const Type *boolType = GetBoolType(); + + for(const Type &t : m_Types) + { + if(t.type == Type::Struct && t.members.size() == 2 && t.members[0] == inst.type && + t.members[1] == boolType) + { + inst.type = &t; + break; + } + } + + RDCASSERT(inst.type->type == Type::Struct); + + // expect modern encoding with weak parameters. + RDCASSERT(op.ops.size() >= 8); + + // compare value + inst.args.push_back(getSymbol(op.ops[1])); + + // new replacement value + inst.args.push_back(getSymbol(op.ops[2])); + + if(op.ops[3]) + inst.opFlags |= InstructionFlags::Volatile; + + // success ordering + switch(op.ops[4]) + { + case 0: break; + case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break; + case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break; + case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break; + case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break; + case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break; + case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break; + default: + RDCERR("Unexpected success ordering %llu", op.ops[4]); + inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; + break; + } + + // synchronisation scope + switch(op.ops[5]) + { + case 0: inst.opFlags |= InstructionFlags::SingleThread; break; + case 1: break; + default: RDCERR("Unexpected synchronisation scope %llu", op.ops[5]); break; + } + + // failure ordering + switch(op.ops[6]) + { + case 0: break; + case 1: inst.opFlags |= InstructionFlags::FailureUnordered; break; + case 2: inst.opFlags |= InstructionFlags::FailureMonotonic; break; + case 3: inst.opFlags |= InstructionFlags::FailureAcquire; break; + case 4: inst.opFlags |= InstructionFlags::FailureRelease; break; + case 5: inst.opFlags |= InstructionFlags::FailureAcquireRelease; break; + case 6: inst.opFlags |= InstructionFlags::FailureSequentiallyConsistent; break; + default: + RDCERR("Unexpected failure ordering %llu", op.ops[6]); + inst.opFlags |= InstructionFlags::FailureSequentiallyConsistent; + break; + } + + if(op.ops[7]) + inst.opFlags |= InstructionFlags::Weak; + + m_Symbols.push_back({SymbolType::Instruction, f.instructions.size()}); + + f.instructions.push_back(inst); + } + else if(IS_KNOWN(op.id, FunctionRecord::INST_FENCE)) + { + Instruction inst; + + inst.op = Instruction::Fence; + + inst.type = GetVoidType(); + + // success ordering + switch(op.ops[0]) + { + case 0: break; + case 1: inst.opFlags |= InstructionFlags::SuccessUnordered; break; + case 2: inst.opFlags |= InstructionFlags::SuccessMonotonic; break; + case 3: inst.opFlags |= InstructionFlags::SuccessAcquire; break; + case 4: inst.opFlags |= InstructionFlags::SuccessRelease; break; + case 5: inst.opFlags |= InstructionFlags::SuccessAcquireRelease; break; + case 6: inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; break; + default: + RDCERR("Unexpected success ordering %llu", op.ops[0]); + inst.opFlags |= InstructionFlags::SuccessSequentiallyConsistent; + break; + } + + // synchronisation scope + switch(op.ops[1]) + { + case 0: inst.opFlags |= InstructionFlags::SingleThread; break; + case 1: break; + default: RDCERR("Unexpected synchronisation scope %llu", op.ops[1]); break; + } + + f.instructions.push_back(inst); + } else if(IS_KNOWN(op.id, FunctionRecord::INST_EXTRACTELT)) { // DXIL claims to be scalarised so should this appear? diff --git a/renderdoc/driver/shaders/dxil/dxil_bytecode.h b/renderdoc/driver/shaders/dxil/dxil_bytecode.h index 8ae7f3b20..15fa3e8f6 100644 --- a/renderdoc/driver/shaders/dxil/dxil_bytecode.h +++ b/renderdoc/driver/shaders/dxil/dxil_bytecode.h @@ -315,9 +315,34 @@ enum class InstructionFlags : uint32_t // shifts/divs Exact = (1 << 7), - // load/store + // load/store/atomic InBounds = (1 << 8), Volatile = (1 << 9), + + // atomics + Weak = (1 << 10), + SingleThread = (1 << 11), + // CrossThread = (0 << 11), + + SuccessOrderMask = (0x7 << 12), + + // SuccessNotAtomic = (0x0 << 12), + SuccessUnordered = (0x1 << 12), + SuccessMonotonic = (0x2 << 12), + SuccessAcquire = (0x3 << 12), + SuccessRelease = (0x4 << 12), + SuccessAcquireRelease = (0x5 << 12), + SuccessSequentiallyConsistent = (0x6 << 12), + + FailureOrderMask = (0x7 << 15), + + // FailureNotAtomic = (0x0 << 15), + FailureUnordered = (0x1 << 15), + FailureMonotonic = (0x2 << 15), + FailureAcquire = (0x3 << 15), + FailureRelease = (0x4 << 15), + FailureAcquireRelease = (0x5 << 15), + FailureSequentiallyConsistent = (0x6 << 15), }; BITMASK_OPERATORS(InstructionFlags); @@ -404,6 +429,21 @@ struct Instruction Branch, Phi, Switch, + Fence, + CompareExchange, + LoadAtomic, + StoreAtomic, + AtomicExchange, + AtomicAdd, + AtomicSub, + AtomicAnd, + AtomicNand, + AtomicOr, + AtomicXor, + AtomicMax, + AtomicMin, + AtomicUMax, + AtomicUMin, } op = Unknown; InstructionFlags opFlags = InstructionFlags::NoFlags; diff --git a/renderdoc/driver/shaders/dxil/dxil_disassemble.cpp b/renderdoc/driver/shaders/dxil/dxil_disassemble.cpp index d16730093..4f2e13fdf 100644 --- a/renderdoc/driver/shaders/dxil/dxil_disassemble.cpp +++ b/renderdoc/driver/shaders/dxil/dxil_disassemble.cpp @@ -653,6 +653,8 @@ void Program::MakeDisassemblyString() case Instruction::Load: { m_Disassembly += "load "; + if(inst.opFlags & InstructionFlags::Volatile) + m_Disassembly += "volatile "; m_Disassembly += inst.type->toString(); m_Disassembly += ", "; bool first = true; @@ -670,6 +672,8 @@ void Program::MakeDisassemblyString() case Instruction::Store: { m_Disassembly += "store "; + if(inst.opFlags & InstructionFlags::Volatile) + m_Disassembly += "volatile "; m_Disassembly += argToString(inst.args[1], true); m_Disassembly += ", "; m_Disassembly += argToString(inst.args[0], true); @@ -862,6 +866,160 @@ void Program::MakeDisassemblyString() m_Disassembly += " ]"; break; } + case Instruction::Fence: + { + m_Disassembly += "fence "; + if(inst.opFlags & InstructionFlags::SingleThread) + m_Disassembly += "singlethread "; + switch((inst.opFlags & InstructionFlags::SuccessOrderMask)) + { + case InstructionFlags::SuccessUnordered: m_Disassembly += "unordered"; break; + case InstructionFlags::SuccessMonotonic: m_Disassembly += "monotonic"; break; + case InstructionFlags::SuccessAcquire: m_Disassembly += "acquire"; break; + case InstructionFlags::SuccessRelease: m_Disassembly += "release"; break; + case InstructionFlags::SuccessAcquireRelease: m_Disassembly += "acq_rel"; break; + case InstructionFlags::SuccessSequentiallyConsistent: + m_Disassembly += "seq_cst"; + break; + default: break; + } + } + case Instruction::LoadAtomic: + { + m_Disassembly += "load atomic "; + if(inst.opFlags & InstructionFlags::Volatile) + m_Disassembly += "volatile "; + m_Disassembly += inst.type->toString(); + m_Disassembly += ", "; + bool first = true; + for(Symbol &s : inst.args) + { + if(!first) + m_Disassembly += ", "; + + m_Disassembly += argToString(s, true); + first = false; + } + m_Disassembly += StringFormat::Fmt(", align %u", inst.align); + break; + } + case Instruction::StoreAtomic: + { + m_Disassembly += "store atomic "; + if(inst.opFlags & InstructionFlags::Volatile) + m_Disassembly += "volatile "; + m_Disassembly += argToString(inst.args[1], true); + m_Disassembly += ", "; + m_Disassembly += argToString(inst.args[0], true); + m_Disassembly += StringFormat::Fmt(", align %u", inst.align); + break; + } + case Instruction::CompareExchange: + { + m_Disassembly += "cmpxchg "; + if(inst.opFlags & InstructionFlags::Weak) + m_Disassembly += "weak "; + if(inst.opFlags & InstructionFlags::Volatile) + m_Disassembly += "volatile "; + + bool first = true; + for(Symbol &s : inst.args) + { + if(!first) + m_Disassembly += ", "; + + m_Disassembly += argToString(s, true); + first = false; + } + + m_Disassembly += " "; + if(inst.opFlags & InstructionFlags::SingleThread) + m_Disassembly += "singlethread "; + switch((inst.opFlags & InstructionFlags::SuccessOrderMask)) + { + case InstructionFlags::SuccessUnordered: m_Disassembly += "unordered"; break; + case InstructionFlags::SuccessMonotonic: m_Disassembly += "monotonic"; break; + case InstructionFlags::SuccessAcquire: m_Disassembly += "acquire"; break; + case InstructionFlags::SuccessRelease: m_Disassembly += "release"; break; + case InstructionFlags::SuccessAcquireRelease: m_Disassembly += "acq_rel"; break; + case InstructionFlags::SuccessSequentiallyConsistent: + m_Disassembly += "seq_cst"; + break; + default: break; + } + m_Disassembly += " "; + switch((inst.opFlags & InstructionFlags::FailureOrderMask)) + { + case InstructionFlags::FailureUnordered: m_Disassembly += "unordered"; break; + case InstructionFlags::FailureMonotonic: m_Disassembly += "monotonic"; break; + case InstructionFlags::FailureAcquire: m_Disassembly += "acquire"; break; + case InstructionFlags::FailureRelease: m_Disassembly += "release"; break; + case InstructionFlags::FailureAcquireRelease: m_Disassembly += "acq_rel"; break; + case InstructionFlags::FailureSequentiallyConsistent: + m_Disassembly += "seq_cst"; + break; + default: break; + } + break; + } + case Instruction::AtomicExchange: + case Instruction::AtomicAdd: + case Instruction::AtomicSub: + case Instruction::AtomicAnd: + case Instruction::AtomicNand: + case Instruction::AtomicOr: + case Instruction::AtomicXor: + case Instruction::AtomicMax: + case Instruction::AtomicMin: + case Instruction::AtomicUMax: + case Instruction::AtomicUMin: + { + m_Disassembly += "atomicrmw "; + if(inst.opFlags & InstructionFlags::Volatile) + m_Disassembly += "volatile "; + switch(inst.op) + { + case Instruction::AtomicExchange: m_Disassembly += "xchg "; break; + case Instruction::AtomicAdd: m_Disassembly += "add "; break; + case Instruction::AtomicSub: m_Disassembly += "sub "; break; + case Instruction::AtomicAnd: m_Disassembly += "and "; break; + case Instruction::AtomicNand: m_Disassembly += "nand "; break; + case Instruction::AtomicOr: m_Disassembly += "or "; break; + case Instruction::AtomicXor: m_Disassembly += "xor "; break; + case Instruction::AtomicMax: m_Disassembly += "max "; break; + case Instruction::AtomicMin: m_Disassembly += "min "; break; + case Instruction::AtomicUMax: m_Disassembly += "umax "; break; + case Instruction::AtomicUMin: m_Disassembly += "umin "; break; + default: break; + } + + bool first = true; + for(Symbol &s : inst.args) + { + if(!first) + m_Disassembly += ", "; + + m_Disassembly += argToString(s, true); + first = false; + } + + m_Disassembly += " "; + if(inst.opFlags & InstructionFlags::SingleThread) + m_Disassembly += "singlethread "; + switch((inst.opFlags & InstructionFlags::SuccessOrderMask)) + { + case InstructionFlags::SuccessUnordered: m_Disassembly += "unordered"; break; + case InstructionFlags::SuccessMonotonic: m_Disassembly += "monotonic"; break; + case InstructionFlags::SuccessAcquire: m_Disassembly += "acquire"; break; + case InstructionFlags::SuccessRelease: m_Disassembly += "release"; break; + case InstructionFlags::SuccessAcquireRelease: m_Disassembly += "acq_rel"; break; + case InstructionFlags::SuccessSequentiallyConsistent: + m_Disassembly += "seq_cst"; + break; + default: break; + } + break; + } } if(inst.debugLoc != ~0U)