Skip to content

Commit

Permalink
Improvements to the type scavenger. (#1775)
Browse files Browse the repository at this point in the history
The main improvement is to enable intrinsics with more complex typing rules to
be described. For example, memcpy's requirement that its two pointer arguments
have the same type.

There is still a larger rewrite of the type scavenger to use TypedPointerType
coming down the line. However, this rewrite also needs opaque types to represent
the deferred type concept, which is why it hasn't been done yet. This is an
intermediate step in the rewrite which better supports some of the other
intrinsics with current issues.

Original commit:
KhronosGroup/SPIRV-LLVM-Translator@df51179
  • Loading branch information
jcranmer-intel authored and KornevNikita committed Jan 19, 2023
1 parent 3549f63 commit df39701
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 69 deletions.
138 changes: 80 additions & 58 deletions llvm-spirv/lib/SPIRV/SPIRVTypeScavenger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -118,52 +118,74 @@ static Type *getPointerUseType(Function *F, Op Opcode, unsigned ArgNo) {
}
}

void SPIRVTypeScavenger::deduceIntrinsicTypes(Function &F, Intrinsic::ID Id) {
static constexpr unsigned Return = ~0U;
auto AddParameter = [&](unsigned ArgNo, DeducedType Ty) {
if (ArgNo == Return) {
// TODO: Handle return types properly.
} else {
Argument *Arg = F.getArg(ArgNo);
LLVM_DEBUG(dbgs() << "Parameter " << *Arg << " of " << F.getName()
<< " has type " << Ty << "\n");
DeducedTypes[Arg] = Ty;
bool SPIRVTypeScavenger::typeIntrinsicCall(
CallBase &CB, SmallVectorImpl<std::pair<unsigned, DeducedType>> &ArgTys) {
Function *TargetFn = CB.getCalledFunction();
assert(TargetFn && TargetFn->isDeclaration() &&
"Call is not an intrinsic function call");
LLVMContext &Ctx = TargetFn->getContext();

if (auto IntrinID = TargetFn->getIntrinsicID()) {
switch (IntrinID) {
case Intrinsic::memcpy: {
// First two parameters are pointers, but it may be any pointer type.
DeducedType MemcpyTy = new DeferredType;
ArgTys.emplace_back(0, MemcpyTy);
ArgTys.emplace_back(1, MemcpyTy);
break;
}
};
LLVMContext &Ctx = F.getContext();
case Intrinsic::memset:
ArgTys.emplace_back(0, Type::getInt8Ty(Ctx));
break;
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::invariant_start:
// These intrinsics were stored as i8* as typed pointers, and the SPIR-V
// writer will expect these to be i8*, even if they can be any pointer
// type.
ArgTys.emplace_back(1, Type::getInt8Ty(Ctx));
break;
case Intrinsic::invariant_end:
// This is like invariant_start with an extra string parameter in the
// beginning (so the pointer object moves to argument two).
ArgTys.emplace_back(0, Type::getInt8Ty(Ctx));
ArgTys.emplace_back(2, Type::getInt8Ty(Ctx));
break;
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
// The first parameter of these is an i8*.
ArgTys.emplace_back(0, Type::getInt8Ty(Ctx));
[[fallthrough]];
case Intrinsic::annotation:
// Second and third parameters are strings, which should be constants
// for global variables. Nominally, this is i8*, but we specifically
// *do not* want to insert bitcast instructions (they need to remain
// global constants).
break;
case Intrinsic::stacksave:
// TODO: support return type.
break;
case Intrinsic::stackrestore:
ArgTys.emplace_back(0, Type::getInt8Ty(Ctx));
break;
case Intrinsic::instrprof_cover:
case Intrinsic::instrprof_increment:
case Intrinsic::instrprof_increment_step:
case Intrinsic::instrprof_value_profile:
// llvm.instrprof.* intrinsics are not supported
ArgTys.emplace_back(0, Type::getInt8Ty(Ctx));
break;
// TODO: handle masked gather/scatter intrinsics. This requires support
// for vector-of-pointers in the type scavenger.
default:
return false;
}
} else if (TargetFn->getName().startswith("_Z18__spirv_ocl_printf")) {
ArgTys.emplace_back(0, Type::getInt8Ty(Ctx));
} else
return false;

switch (Id) {
case Intrinsic::memcpy:
// First parameter is a pointer, but it may be any pointer type.
return;
case Intrinsic::lifetime_start:
case Intrinsic::lifetime_end:
case Intrinsic::invariant_start:
case Intrinsic::invariant_end:
AddParameter(1, Type::getInt8Ty(Ctx));
return;
// Second and third parameters are strings, which mean nothing.
case Intrinsic::annotation:
return;
case Intrinsic::var_annotation:
case Intrinsic::ptr_annotation:
AddParameter(0, Type::getInt8Ty(Ctx));
// Second and third parameters are strings, so they can be any type.
return;
case Intrinsic::stacksave:
AddParameter(Return, Type::getInt8Ty(Ctx));
return;
case Intrinsic::stackrestore:
AddParameter(0, Type::getInt8Ty(Ctx));
return;
// llvm.instrprof.* intrinsics are not supported
case Intrinsic::instrprof_cover:
case Intrinsic::instrprof_increment:
case Intrinsic::instrprof_increment_step:
case Intrinsic::instrprof_value_profile:
AddParameter(0, Type::getInt8Ty(Ctx));
return;
}
return true;
}

static Type *getParamType(const AttributeList &AL, unsigned ArgNo) {
Expand Down Expand Up @@ -222,15 +244,13 @@ void SPIRVTypeScavenger::deduceFunctionType(Function &F) {
}
}

if (auto IntrinID = F.getIntrinsicID()) {
deduceIntrinsicTypes(F, IntrinID);
}

// If the function is a mangled name, try to recover types from the Itanium
// name mangling.
if (F.getName().startswith("_Z")) {
SmallVector<Type *, 8> ParamTypes;
getParameterTypes(&F, ParamTypes);
if (!getParameterTypes(&F, ParamTypes)) {
return;
}
for (Argument *Arg : PointerArgs) {
if (auto *Ty = dyn_cast<TypedPointerType>(ParamTypes[Arg->getArgNo()])) {
DeducedTypes[Arg] = Ty->getElementType();
Expand Down Expand Up @@ -447,15 +467,17 @@ void SPIRVTypeScavenger::correctUseTypes(Instruction &I) {
// If we have an identified function for the call instruction, map the
// arguments we pass in to the argument requirements of the function.
if (Function *F = CB->getCalledFunction()) {
for (Use &U : CB->args()) {
// If we're calling a var-arg method, we have more operands than the
// function has parameters. Bail out if we hit that point.
unsigned ArgNo = CB->getArgOperandNo(&U);
if (ArgNo >= F->arg_size())
break;
if (U->getType()->isPointerTy())
PointerOperands.emplace_back(
U.getOperandNo(), computePointerElementType(F->getArg(ArgNo)));
if (!F->isDeclaration() || !typeIntrinsicCall(*CB, PointerOperands)) {
for (Use &U : CB->args()) {
// If we're calling a var-arg method, we have more operands than the
// function has parameters. Bail out if we hit that point.
unsigned ArgNo = CB->getArgOperandNo(&U);
if (ArgNo >= F->arg_size())
break;
if (U->getType()->isPointerTy())
PointerOperands.emplace_back(
U.getOperandNo(), computePointerElementType(F->getArg(ArgNo)));
}
}
}
}
Expand Down
15 changes: 12 additions & 3 deletions llvm-spirv/lib/SPIRV/SPIRVTypeScavenger.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#define SPIRVTYPESCAVENGER_H

#include "llvm/ADT/PointerUnion.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ValueMap.h"

Expand Down Expand Up @@ -98,9 +99,17 @@ class SPIRVTypeScavenger {
/// analysis on the module.
void deduceFunctionType(Function &F);

/// This assigns known pointer element types for parameters of LLVM
/// intrinsics.
void deduceIntrinsicTypes(Function &F, Intrinsic::ID Id);
/// This computes the known types of a call to an LLVM intrinsic or specific
/// well-known function name. Returns true if the call filled in type
/// information.
///
/// The ArgTys parameter contains a list of known type uses for the parameters
/// of the function call. Each element is a pair, with the first being the
/// operand number, and the second indicating either a known type or an
/// unknown type variable (DeferredType).
bool
typeIntrinsicCall(CallBase &CB,
SmallVectorImpl<std::pair<unsigned, DeducedType>> &ArgTys);

/// Compute pointer element types for all pertinent values in the module.
void typeModule(Module &M);
Expand Down
16 changes: 8 additions & 8 deletions llvm-spirv/test/transcoding/multiple_user_semantic.ll
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@

; CHECK-SPIRV-DAG: Name [[#ClassMember:]] "class.Sample"
; CHECK-SPIRV-DAG: Decorate [[#Var:]] UserSemantic "var_annotation_a"
; CHECK-SPIRV-DAG: Decorate [[#Var]] UserSemantic "var_annotation_b"
; CHECK-SPIRV-DAG: Decorate [[#Var]] UserSemantic "var_annotation_b2"
; CHECK-SPIRV-DAG: MemberDecorate [[#ClassMember]] 0 UserSemantic "class_annotation_a"
; CHECK-SPIRV-DAG: MemberDecorate [[#ClassMember]] 0 UserSemantic "class_annotation_b"
; CHECK-SPIRV-DAG: MemberDecorate [[#ClassMember]] 0 UserSemantic "class_annotation_b2"
; CHECK-SPIRV: Variable [[#]] [[#Var]] [[#]]

; CHECK-LLVM: @[[StrStructA:[0-9_.]+]] = {{.*}}"class_annotation_a\00"
; CHECK-LLVM: @[[StrStructB:[0-9_.]+]] = {{.*}}"class_annotation_b\00"
; CHECK-LLVM: @[[StrStructB:[0-9_.]+]] = {{.*}}"class_annotation_b2\00"
; CHECK-LLVM: @[[StrA:[0-9_.]+]] = {{.*}}"var_annotation_a\00"
; CHECK-LLVM: @[[StrB:[0-9_.]+]] = {{.*}}"var_annotation_b\00"
; CHECK-LLVM: @[[StrB:[0-9_.]+]] = {{.*}}"var_annotation_b2\00"
; CHECK-LLVM: %[[#StructMember:]] = alloca %class.Sample, align 4
; CHECK-LLVM: %[[#GEP1:]] = getelementptr inbounds %class.Sample, %class.Sample* %[[#StructMember]], i32 0, i32 0
; CHECK-LLVM: call i32* @llvm.ptr.annotation.p0i32.p0i8(i32* %[[#GEP1:]], i8* getelementptr inbounds ([19 x i8], [19 x i8]* @[[StrStructA]], i32 0, i32 0), i8* undef, i32 undef, i8* undef)
; CHECK-LLVM: %[[#GEP2:]] = getelementptr inbounds %class.Sample, %class.Sample* %[[#StructMember]], i32 0, i32 0
; CHECK-LLVM: call i32* @llvm.ptr.annotation.p0i32.p0i8(i32* %[[#GEP2]], i8* getelementptr inbounds ([19 x i8], [19 x i8]* @[[StrStructB]], i32 0, i32 0), i8* undef, i32 undef, i8* undef)
; CHECK-LLVM: call i32* @llvm.ptr.annotation.p0i32.p0i8(i32* %[[#GEP2]], i8* getelementptr inbounds ([20 x i8], [20 x i8]* @[[StrStructB]], i32 0, i32 0), i8* undef, i32 undef, i8* undef)
; CHECK-LLVM: [[#Var:]] = alloca i32, align 4
; CHECK-LLVM: [[#Bitcast1:]] = bitcast i32* %[[#Var]] to i8*
; CHECK-LLVM: call void @llvm.var.annotation.p0i8.p0i8(i8* %[[#Bitcast1]], i8* getelementptr inbounds ([17 x i8], [17 x i8]* @[[StrA]], i32 0, i32 0), i8* undef, i32 undef, i8* undef)
; CHECK-LLVM: [[#Bitcast2:]] = bitcast i32* %[[#Var]] to i8*
; CHECK-LLVM: call void @llvm.var.annotation.p0i8.p0i8(i8* %[[#Bitcast2]], i8* getelementptr inbounds ([17 x i8], [17 x i8]* @[[StrB]], i32 0, i32 0), i8* undef, i32 undef, i8* undef)
; CHECK-LLVM: call void @llvm.var.annotation.p0i8.p0i8(i8* %[[#Bitcast2]], i8* getelementptr inbounds ([18 x i8], [18 x i8]* @[[StrB]], i32 0, i32 0), i8* undef, i32 undef, i8* undef)


source_filename = "llvm-link"
Expand All @@ -41,9 +41,9 @@ target triple = "spir64"

@.str = private unnamed_addr constant [19 x i8] c"class_annotation_a\00", section "llvm.metadata"
@.str.1 = private unnamed_addr constant [17 x i8] c"/app/example.cpp\00", section "llvm.metadata"
@.str.2 = private unnamed_addr constant [19 x i8] c"class_annotation_b\00", section "llvm.metadata"
@.str.2 = private unnamed_addr constant [20 x i8] c"class_annotation_b2\00", section "llvm.metadata"
@.str.3 = private unnamed_addr constant [17 x i8] c"var_annotation_a\00", section "llvm.metadata"
@.str.4 = private unnamed_addr constant [17 x i8] c"var_annotation_b\00", section "llvm.metadata"
@.str.4 = private unnamed_addr constant [18 x i8] c"var_annotation_b2\00", section "llvm.metadata"

define spir_func void @test() {
%1 = alloca %class.Sample, align 4
Expand Down

0 comments on commit df39701

Please sign in to comment.