Skip to content

Commit

Permalink
Add inline-assembly support
Browse files Browse the repository at this point in the history
So far, it is complicated and time-consuming to test the behavior of
certain instructions or their interactions. The easiest method to test
a sequence of instructions to date, is to make a simple graphics test,
disassemble the pipelines, edit the assembly, recompile them and use
pipeline replacement to load the edited code.

This patch adds a simpler and more robust way by letting one write
inline assembly in glsl.

glsl allows strings only in a single place, which is the debug print
instruction. Using the special '%ra' format specifier [0] triggers llpc
to interpret the second argument as an inline assembly string.

The arguments to printf are:
- "%ra" for radeon-assembly
- assembly instructions
- variable constraints
- "no output" or a variable where the output is stored (optional if
  there are no further arguments)
- zero or more arguments

See the llvm reference for more details on how to write the constraint
string: https://llvm.org/docs/LangRef.html#inline-assembler-expressions

The added tests contain an example of how inline assembly can be used.

[0]: The list of existing format specifiers does not include a %r:
https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/master/docs/debug_printf.md#debug-printf-format-string
  • Loading branch information
Flakebi committed Apr 6, 2022
1 parent 890379c commit 4ad82ab
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 5 deletions.
26 changes: 26 additions & 0 deletions llpc/test/shaderdb/extensions/OpExtInst_Print_InlineAsm.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
; RUN: amdllpc -spvgen-dir=%spvgendir% -v %gfxip %s | FileCheck -check-prefix=SHADERTEST %s
; SHADERTEST-LABEL: {{^// LLPC}} SPIRV-to-LLVM translation results
; SHADERTEST: = call float asm sideeffect "v_mul_f32 $0, $1, 2.0", "=v,r"(float
; SHADERTEST: AMDLLPC SUCCESS
*/

#version 450

#extension GL_EXT_debug_printf : enable

layout(binding = 0) uniform Uniforms
{
float x;
};

layout(location = 0) out vec4 fragColor;

void main()
{
float x = x;

debugPrintfEXT("%ra", "v_mul_f32 $0, $1, 2.0", "=v,r", x, x);

fragColor = vec4(x);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
; RUN: not --crash amdllpc -spvgen-dir=%spvgendir% -v %gfxip %s 2>&1 | FileCheck -check-prefix=SHADERTEST %s
; SHADERTEST: Not enough arguments for inline assembly {{.*}} 'my %ra'
*/

#version 450

#extension GL_EXT_debug_printf : enable

layout(location = 0) out vec4 fragColor;

void main()
{
debugPrintfEXT("my %ra");
fragColor = vec4(0.0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
; RUN: not --crash amdllpc -spvgen-dir=%spvgendir% -v %gfxip %s 2>&1 | FileCheck -check-prefix=SHADERTEST %s
; SHADERTEST: Unexpected string 'v_another_inst' {{.*}} Expected a variable or 'no output'
*/

#version 450

#extension GL_EXT_debug_printf : enable

layout(location = 0) out vec4 fragColor;

void main()
{
debugPrintfEXT("%ra", "v_inst", "", "v_another_inst");
fragColor = vec4(0.0);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
; RUN: not --crash amdllpc -spvgen-dir=%spvgendir% -v %gfxip %s 2>&1 | FileCheck -check-prefix=SHADERTEST %s
; SHADERTEST: Unexpected value '' {{.*}} Expected a variable
*/

#version 450

#extension GL_EXT_debug_printf : enable

layout(location = 0) out vec4 fragColor;

void main()
{
debugPrintfEXT("%ra", "v_mov $0, 1.0", "", 1);
fragColor = vec4(0.0);
}
82 changes: 79 additions & 3 deletions llpc/translator/lib/SPIRV/SPIRVReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/IntrinsicsAMDGPU.h"
Expand Down Expand Up @@ -3778,9 +3779,84 @@ template <> Value *SPIRVToLLVM::transValueWithOpcode<OpGroupFMaxNonUniformAMD>(S
template <> Value *SPIRVToLLVM::transValueWithOpcode<OpExtInst>(SPIRVValue *const spvValue) {
SPIRVExtInst *const spvExtInst = static_cast<SPIRVExtInst *>(spvValue);

// Just ignore this set of extended instructions
if (m_bm->getBuiltinSet(spvExtInst->getExtSetId()) == SPIRVEIS_NonSemanticInfo)
return nullptr;
if (m_bm->getBuiltinSet(spvExtInst->getExtSetId()) == SPIRVEIS_NonSemanticInfo) {
switch (spvExtInst->getExtOp()) {
case 1: { // DebugPrintf
auto &spvArgs = spvExtInst->getArguments();
assert(spvArgs.size() >= 1 && "printf must have at least one argument");
auto &formatString = m_bm->get<SPIRVString>(spvArgs[0])->getStr();
// Ignore printfs that don't have a matching format specifiers
if (formatString.find("%r") == std::string::npos)
return nullptr;

if (formatString.find("%ra") != std::string::npos) {
if (spvArgs.size() < 2)
cantFail(make_error<StringError>(Twine("Not enough arguments for inline assembly in printf with format string '") + formatString + "'",
inconvertibleErrorCode()));

// Handle %ra (radeon-assembly) format specifier
// The first non-format string argument is a string for the inline-assembly.
// The second argument is a string for the constraints.
// The third argument (optional if there are no more arguments) is either a variable
// to write the output to or the string "no output"
// The rest of the arguments are passed as arguments to the inline assembly

auto *builder = getBuilder();
BasicBlock *const block = builder->GetInsertBlock();
Function *const func = block->getParent();
SmallVector<Type *> argumentTypes;
SmallVector<Value *> arguments;
Type *returnType = builder->getVoidTy();
Value *returnValue = nullptr;

if (spvArgs.size() > 3) {
auto *returnEntry = m_bm->getEntry(spvArgs[3]);
if (returnEntry->getOpCode() == OpString) {
auto &returnEntryStr = static_cast<SPIRVString *>(returnEntry)->getStr();
if (returnEntryStr != "no output")
cantFail(make_error<StringError>(Twine("Unexpected string '") + returnEntryStr + "' for return argument in inline assembly. Expected a variable or 'no output'",
inconvertibleErrorCode()));
} else {
auto *returnSpvValue = static_cast<SPIRVValue *>(returnEntry);
// Look through OpLoad and store the return value there
if(returnSpvValue->getOpCode() != OpLoad)
cantFail(make_error<StringError>(Twine("Unexpected value '") + returnSpvValue->getName() + "' for return argument in inline assembly. Expected a variable (OpLoad) or 'no output'",
inconvertibleErrorCode()));
auto *loadInst = static_cast<SPIRVLoad *>(returnSpvValue);
returnValue = transValue(loadInst->getSrc(), func, block);
returnType = transType(returnSpvValue->getType());
}
}

for (unsigned i = 4; i < spvArgs.size(); i++) {
auto *argVal = transValue(m_bm->getValue(spvArgs[i]), func, block);
argumentTypes.push_back(argVal->getType());
arguments.push_back(argVal);
}

auto &asmString = m_bm->get<SPIRVString>(spvArgs[1])->getStr();
auto &constraints = m_bm->get<SPIRVString>(spvArgs[2])->getStr();
auto *functionTy = FunctionType::get(returnType, argumentTypes, false);
auto *inlineAsm = InlineAsm::get(functionTy, asmString, constraints, true);

Value *lastInst= builder->CreateCall(inlineAsm, arguments);
// Store return value to given variable
if (returnValue)
lastInst = builder->CreateStore(lastInst, returnValue);

return lastInst;
}

llvm_unreachable("Unknown printf %r format specifier");
cantFail(make_error<StringError>(Twine("Unknown printf %r format specifier in string '") + formatString + "'. Possible values are %ra for assembly.",
inconvertibleErrorCode()));
return nullptr;
}
default:
// Just ignore this set of extended instructions
return nullptr;
}
}

std::vector<SPIRVValue *> spvArgValues = spvExtInst->getArgumentValues();

Expand Down
1 change: 0 additions & 1 deletion llpc/util/llpcShaderModuleHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,6 @@ unsigned ShaderModuleHelper::trimSpirvDebugInfo(const BinaryData *spvBin, llvm::
unsigned opCode = (codePos[0] & OpCodeMask);
unsigned wordCount = (codePos[0] >> WordCountShift);
switch (opCode) {
case OpString:
case OpSource:
case OpSourceContinued:
case OpSourceExtension:
Expand Down
2 changes: 1 addition & 1 deletion test/amber/a16.amber
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
# Modifications Copyright (c) 2022 Advanced Micro Devices, Inc. All rights reserved.

# TODO Remove the not once this is fixed in LLVM
# RUN: not run_amber_test.py --icd %icd %s | FileCheck %s
# RUN: run_amber_test.py --icd %icd %s | FileCheck %s
# REQUIRES: gfx10+

# Test a16 image samples with bias. The bias needs to be converted to 16 bit.
Expand Down
51 changes: 51 additions & 0 deletions test/amber/inline-asm.amber
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!amber

# RUN: run_amber_test.py --icd %icd %s | FileCheck %s
# REQUIRES: gfx10.1.0

# Test that inline assembly from printf strings is executed.

SHADER vertex vert_shader GLSL
#version 430

layout(location = 0) in vec3 position_in;
layout(location = 0) out vec2 position_out;

void main() {
gl_Position = vec4(position_in, 1.0);
position_out = gl_Position.xy;
}
END

SHADER fragment frag_shader GLSL
#version 430
#extension GL_EXT_debug_printf : enable

layout(location = 0) in vec2 position_in;
layout(location = 0) out vec4 color_out;

void main() {
// FIXME Inline assembly doesn't appear in pipeline dumps, so we can't check for it here
// CHECK-NOT: v_mul_f32 {{.*}}, 2.0
vec2 pos = (position_in + vec2(1.0)) / 2.0;
color_out = vec4(pos, 0.0, 1.0);
float x = color_out.x;
debugPrintfEXT("%ra", "v_mul_f32 $0, $1, 2.0", "=v,r", x, x);
color_out.x = x;
}
END

BUFFER framebuffer FORMAT B8G8R8A8_UNORM

PIPELINE graphics pipeline
ATTACH vert_shader
ATTACH frag_shader
BIND BUFFER framebuffer AS color LOCATION 0
FRAMEBUFFER_SIZE 32 32
END

CLEAR_COLOR pipeline 0 0 0 255
CLEAR pipeline
RUN pipeline DRAW_RECT POS 0 0 SIZE 32 32

EXPECT framebuffer IDX 16 16 SIZE 1 1 EQ_RGB 255 131 0

0 comments on commit 4ad82ab

Please sign in to comment.