Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion llvm/lib/SYCLLowerIR/SYCLPropagateAspectsUsage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,38 @@ void setSyclFixedTargetsMD(const std::vector<Function *> &EntryPoints,
F->setMetadata("sycl_fixed_targets", MDN);
}

void collectVirtualFunctionSetInfo(
Function &F, StringMap<std::vector<Function *>> &VirtualFunctionSets) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it won't require refactoring of existing code (due to shared data structures, or functions), I would add const to F argument (and everywhere else where it is applicable, i.e. to processDeclaredVirtualFunctionSets arguments as well)

if (!F.hasFnAttribute("indirectly-callable"))
return;
Attribute IndirectlyCallableAttr = F.getFnAttribute("indirectly-callable");
StringRef SetName = IndirectlyCallableAttr.getValueAsString();
VirtualFunctionSets[SetName].push_back(&F);
}

// For each set S of virtual functions that F declares,
// propagate S through the CG and then add the aspects
// used by S to F.
void processDeclaredVirtualFunctionSets(
Function *F, CallGraphTy &CG, FunctionToAspectsMapTy &AspectsMap,
SmallPtrSet<const Function *, 16> &Visited,
StringMap<std::vector<Function *>> &VirtualFunctionSets) {
if (!F->hasFnAttribute("calls-indirectly"))
return;
Attribute CallsIndirectlyAttr = F->getFnAttribute("calls-indirectly");
SmallVector<StringRef, 4> DeclaredVirtualFunctionSetNames;
CallsIndirectlyAttr.getValueAsString().split(DeclaredVirtualFunctionSetNames,
",");
auto &AspectsF = AspectsMap[F];
for (auto Name : DeclaredVirtualFunctionSetNames) {
for (auto VFn : VirtualFunctionSets[Name]) {
propagateAspectsThroughCG(VFn, CG, AspectsMap, Visited);
for (auto Aspect : AspectsMap[VFn])
AspectsF.insert(Aspect);
}
}
}

/// Returns a map of functions with corresponding used aspects.
std::pair<FunctionToAspectsMapTy, FunctionToAspectsMapTy>
buildFunctionsToAspectsMap(Module &M, TypeToAspectsMapTy &TypesWithAspects,
Expand All @@ -655,16 +687,21 @@ buildFunctionsToAspectsMap(Module &M, TypeToAspectsMapTy &TypesWithAspects,
bool ValidateAspects, bool FP64ConvEmu) {
FunctionToAspectsMapTy FunctionToUsedAspects;
FunctionToAspectsMapTy FunctionToDeclaredAspects;
StringMap<std::vector<Function *>> VirtualFunctionSets;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we are using std::vector over SmallVector or some other LLVM data structure?
Also we might want to make a type alias so the type is cleaner.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should likely be SmallVector. At least for sequential containers LLVM Programmer's Manual is clear that you should pick the first one which suites your needs and std::vector is at the very end of the list

CallGraphTy CG;

for (Function &F : M.functions()) {
processFunction(F, FunctionToUsedAspects, FunctionToDeclaredAspects,
TypesWithAspects, CG, AspectValues, FP64ConvEmu);
collectVirtualFunctionSetInfo(F, VirtualFunctionSets);
}

SmallPtrSet<const Function *, 16> Visited;
for (Function *F : EntryPoints)
for (Function *F : EntryPoints) {
propagateAspectsThroughCG(F, CG, FunctionToUsedAspects, Visited);
processDeclaredVirtualFunctionSets(F, CG, FunctionToUsedAspects, Visited,
VirtualFunctionSets);
}

if (ValidateAspects)
validateUsedAspectsForFunctions(FunctionToUsedAspects, AspectValues,
Expand Down
20 changes: 20 additions & 0 deletions llvm/test/SYCLLowerIR/PropagateAspectsUsage/virtual-functions-1.ll
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we put those into a subfolder to make codebase cleaner (in case we add more tests and in general)?

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
; RUN: opt -passes=sycl-propagate-aspects-usage < %s -S | FileCheck %s

; CHECK: @vfn() #0 !sycl_used_aspects ![[#aspects:]]
define spir_func void @vfn() #0 {
%tmp = alloca double
ret void
}

; CHECK: @foo() #1 !sycl_used_aspects ![[#aspects]]
define spir_kernel void @foo() #1 {
ret void
}

; CHECK: ![[#aspects]] = !{i32 6}

attributes #0 = { "indirectly-callable"="_ZTSv" }
attributes #1 = { "calls-indirectly"="_ZTSv" }

!sycl_aspects = !{!0}
!0 = !{!"fp64", i32 6}
36 changes: 36 additions & 0 deletions llvm/test/SYCLLowerIR/PropagateAspectsUsage/virtual-functions-2.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
; RUN: opt -passes=sycl-propagate-aspects-usage < %s -S | FileCheck %s

%Foo = type { i32 }
%Bar = type { i32 }

; CHECK: @vfnFoo() #0 !sycl_used_aspects ![[#aspectsFoo:]]
define spir_func void @vfnFoo() #0 {
%tmp = alloca %Foo
ret void
}

; CHECK: @vfnBar() #1 !sycl_used_aspects ![[#aspectsBar:]]
define spir_func void @vfnBar() #1 {
%tmp = alloca %Bar
ret void
}

; CHECK: @kernel() #2 !sycl_used_aspects ![[#aspectsKernel:]]
define spir_kernel void @kernel() #2 {
ret void
}

; CHECK: ![[#aspectsFoo]] = !{i32 1}
; CHECK: ![[#aspectsBar]] = !{i32 2}
; CHECK: ![[#aspectsKernel]] = !{i32 1, i32 2}

attributes #0 = { "indirectly-callable"="setFoo" }
attributes #1 = { "indirectly-callable"="setBar" }
attributes #2 = { "calls-indirectly"="setFoo,setBar" }

!sycl_aspects = !{!0}
!0 = !{!"fp64", i32 6}

!sycl_types_that_use_aspects = !{!1, !2}
!1 = !{!"Foo", i32 1}
!2 = !{!"Bar", i32 2}
51 changes: 51 additions & 0 deletions llvm/test/SYCLLowerIR/PropagateAspectsUsage/virtual-functions-3.ll
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design doc also states the following:

NOTE: if the aspects propagation pass is ever extended to track function
pointers, then aspects attached to virtual functions should not be attached
to kernels using this mechanism. For example, if a kernel uses a variable,
which is initialized with a function pointer to a virtual function which uses
an aspect, then such kernel should not be considered as using that aspect.
Properties-based mechanism which is described above should be used for aspects
propagation for virtual functions.

The idea was to support the case like this one:

struct set_fp64;
struct set_fp32;

struct Foo {
  virtual SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(indirectly_callable_in<set_fp64>)
  void bar() {
    double d;
  }

  virtual SYCL_EXT_ONEAPI_FUNCTION_PROPERTY(indirectly_callable_in<set_fp32>)
  void baz() {
    float f;
  }
};

int main() {
  queue q;
  auto *ptr = malloc_device<Foo>(1, q);
  q.single_task<class Construct>([=]() {
    new (ptr) Foo;
  });
}

Construct kernel could be launched on a device which doesn't support fp64 and it shouldn't result in any errors, i.e. even though Construct kernel under the hood operates with a vtable which references Foo::bar, the kernel shouldn't be considered to use fp64 aspect.

That should be true already, because propagating aspects like that would require extra code, but I think that we should add a test to indicate to us that such addition should not be performed. Or, even if it is performed, then it should ignore virtual functions, because they use their own special mechanism.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
; RUN: opt -passes=sycl-propagate-aspects-usage < %s -S | FileCheck %s

%Foo = type { i32 }
%Bar = type { i32 }

; CHECK: @vfnFoo() #0 !sycl_used_aspects ![[#aspectsFoo:]]
define spir_func void @vfnFoo() #0 {
call void @subFoo()
ret void
}

define spir_func void @subFoo() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we check the aspects for subFoo and subBar too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, but that part should be already covered by tests for "core" aspects propagation mechanism. Those tests are specially focused on scenarios where a kernel does not use a function directly, but still inherits its aspects, because it uses a "set" where this function belongs to.

%tmp = alloca %Foo
ret void
}

; CHECK: @vfnBar() #1 !sycl_used_aspects ![[#aspectsBar:]]
define spir_func void @vfnBar() #1 {
call void @subBar()
ret void
}

define spir_func void @subBar() {
%tmp = alloca %Bar
ret void
}

; CHECK: @kernelA() #2 !sycl_used_aspects ![[#aspectsFoo]]
define spir_kernel void @kernelA() #2 {
ret void
}

; CHECK: @kernelB() #3 !sycl_used_aspects ![[#aspectsBar]]
define spir_kernel void @kernelB() #3 {
ret void
}

; CHECK: ![[#aspectsFoo]] = !{i32 1}
; CHECK: ![[#aspectsBar]] = !{i32 2}

attributes #0 = { "indirectly-callable"="setFoo" }
attributes #1 = { "indirectly-callable"="setBar" }
attributes #2 = { "calls-indirectly"="setFoo" }
attributes #3 = { "calls-indirectly"="setBar" }

!sycl_aspects = !{!0}
!0 = !{!"fp64", i32 6}

!sycl_types_that_use_aspects = !{!1, !2}
!1 = !{!"Foo", i32 1}
!2 = !{!"Bar", i32 2}
Loading