| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| //===-- CachedConstAccessorsLattice.h ---------------------------*- C++ -*-===// | ||
| // | ||
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | ||
| // See https://llvm.org/LICENSE.txt for license information. | ||
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This file defines the lattice mixin that additionally maintains a cache of | ||
| // stable method call return values to model const accessor member functions. | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #ifndef LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H | ||
| #define LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H | ||
|
|
||
| #include "clang/AST/Expr.h" | ||
| #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" | ||
| #include "clang/Analysis/FlowSensitive/DataflowLattice.h" | ||
| #include "clang/Analysis/FlowSensitive/StorageLocation.h" | ||
| #include "clang/Analysis/FlowSensitive/Value.h" | ||
| #include "llvm/ADT/DenseMap.h" | ||
| #include "llvm/ADT/STLFunctionalExtras.h" | ||
|
|
||
| namespace clang { | ||
| namespace dataflow { | ||
|
|
||
| /// A mixin for a lattice that additionally maintains a cache of stable method | ||
| /// call return values to model const accessors methods. When a non-const method | ||
| /// is called, the cache should be cleared causing the next call to a const | ||
| /// method to be considered a different value. NOTE: The user is responsible for | ||
| /// clearing the cache. | ||
| /// | ||
| /// For example: | ||
| /// | ||
| /// class Bar { | ||
| /// public: | ||
| /// const std::optional<Foo>& getFoo() const; | ||
| /// void clear(); | ||
| /// }; | ||
| // | ||
| /// void func(Bar& s) { | ||
| /// if (s.getFoo().has_value()) { | ||
| /// use(s.getFoo().value()); // safe (checked earlier getFoo()) | ||
| /// s.clear(); | ||
| /// use(s.getFoo().value()); // unsafe (invalidate cache for s) | ||
| /// } | ||
| /// } | ||
| template <typename Base> class CachedConstAccessorsLattice : public Base { | ||
| public: | ||
| using Base::Base; // inherit all constructors | ||
|
|
||
| /// Creates or returns a previously created `Value` associated with a const | ||
| /// method call `obj.getFoo()` where `RecordLoc` is the | ||
| /// `RecordStorageLocation` of `obj`. | ||
| /// Returns nullptr if unable to find or create a value. | ||
| /// | ||
| /// Requirements: | ||
| /// | ||
| /// - `CE` should return a value (not a reference or record type) | ||
| Value * | ||
| getOrCreateConstMethodReturnValue(const RecordStorageLocation &RecordLoc, | ||
| const CallExpr *CE, Environment &Env); | ||
|
|
||
| /// Creates or returns a previously created `StorageLocation` associated with | ||
| /// a const method call `obj.getFoo()` where `RecordLoc` is the | ||
| /// `RecordStorageLocation` of `obj`. | ||
| /// | ||
| /// The callback `Initialize` runs on the storage location if newly created. | ||
| /// Returns nullptr if unable to find or create a value. | ||
| /// | ||
| /// Requirements: | ||
| /// | ||
| /// - `CE` should return a location (GLValue or a record type). | ||
| StorageLocation *getOrCreateConstMethodReturnStorageLocation( | ||
| const RecordStorageLocation &RecordLoc, const CallExpr *CE, | ||
| Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize); | ||
|
|
||
| void clearConstMethodReturnValues(const RecordStorageLocation &RecordLoc) { | ||
| ConstMethodReturnValues.erase(&RecordLoc); | ||
| } | ||
|
|
||
| void clearConstMethodReturnStorageLocations( | ||
| const RecordStorageLocation &RecordLoc) { | ||
| ConstMethodReturnStorageLocations.erase(&RecordLoc); | ||
| } | ||
|
|
||
| bool operator==(const CachedConstAccessorsLattice &Other) const { | ||
| return Base::operator==(Other); | ||
| } | ||
|
|
||
| LatticeJoinEffect join(const CachedConstAccessorsLattice &Other); | ||
|
|
||
| private: | ||
| // Maps a record storage location and const method to the value to return | ||
| // from that const method. | ||
| using ConstMethodReturnValuesType = | ||
| llvm::SmallDenseMap<const RecordStorageLocation *, | ||
| llvm::SmallDenseMap<const FunctionDecl *, Value *>>; | ||
| ConstMethodReturnValuesType ConstMethodReturnValues; | ||
|
|
||
| // Maps a record storage location and const method to the record storage | ||
| // location to return from that const method. | ||
| using ConstMethodReturnStorageLocationsType = llvm::SmallDenseMap< | ||
| const RecordStorageLocation *, | ||
| llvm::SmallDenseMap<const FunctionDecl *, StorageLocation *>>; | ||
| ConstMethodReturnStorageLocationsType ConstMethodReturnStorageLocations; | ||
| }; | ||
|
|
||
| namespace internal { | ||
|
|
||
| template <typename T> | ||
| llvm::SmallDenseMap<const RecordStorageLocation *, | ||
| llvm::SmallDenseMap<const FunctionDecl *, T *>> | ||
| joinConstMethodMap( | ||
| const llvm::SmallDenseMap<const RecordStorageLocation *, | ||
| llvm::SmallDenseMap<const FunctionDecl *, T *>> | ||
| &Map1, | ||
| const llvm::SmallDenseMap<const RecordStorageLocation *, | ||
| llvm::SmallDenseMap<const FunctionDecl *, T *>> | ||
| &Map2, | ||
| LatticeEffect &Effect) { | ||
| llvm::SmallDenseMap<const RecordStorageLocation *, | ||
| llvm::SmallDenseMap<const FunctionDecl *, T *>> | ||
| Result; | ||
| for (auto &[Loc, DeclToT] : Map1) { | ||
| auto It = Map2.find(Loc); | ||
| if (It == Map2.end()) { | ||
| Effect = LatticeJoinEffect::Changed; | ||
| continue; | ||
| } | ||
| const auto &OtherDeclToT = It->second; | ||
| auto &JoinedDeclToT = Result[Loc]; | ||
| for (auto [Func, Var] : DeclToT) { | ||
| T *OtherVar = OtherDeclToT.lookup(Func); | ||
| if (OtherVar == nullptr || OtherVar != Var) { | ||
| Effect = LatticeJoinEffect::Changed; | ||
| continue; | ||
| } | ||
| JoinedDeclToT.insert({Func, Var}); | ||
| } | ||
| } | ||
| return Result; | ||
| } | ||
|
|
||
| } // namespace internal | ||
|
|
||
| template <typename Base> | ||
| LatticeEffect CachedConstAccessorsLattice<Base>::join( | ||
| const CachedConstAccessorsLattice<Base> &Other) { | ||
|
|
||
| LatticeEffect Effect = Base::join(Other); | ||
|
|
||
| // For simplicity, we only retain values that are identical, but not ones that | ||
| // are non-identical but equivalent. This is likely to be sufficient in | ||
| // practice, and it reduces implementation complexity considerably. | ||
|
|
||
| ConstMethodReturnValues = internal::joinConstMethodMap<Value>( | ||
| ConstMethodReturnValues, Other.ConstMethodReturnValues, Effect); | ||
|
|
||
| ConstMethodReturnStorageLocations = | ||
| internal::joinConstMethodMap<StorageLocation>( | ||
| ConstMethodReturnStorageLocations, | ||
| Other.ConstMethodReturnStorageLocations, Effect); | ||
|
|
||
| return Effect; | ||
| } | ||
|
|
||
| template <typename Base> | ||
| Value *CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnValue( | ||
| const RecordStorageLocation &RecordLoc, const CallExpr *CE, | ||
| Environment &Env) { | ||
| QualType Type = CE->getType(); | ||
| assert(!Type.isNull()); | ||
| assert(!Type->isReferenceType()); | ||
| assert(!Type->isRecordType()); | ||
|
|
||
| auto &ObjMap = ConstMethodReturnValues[&RecordLoc]; | ||
| const FunctionDecl *DirectCallee = CE->getDirectCallee(); | ||
| if (DirectCallee == nullptr) | ||
| return nullptr; | ||
| auto it = ObjMap.find(DirectCallee); | ||
| if (it != ObjMap.end()) | ||
| return it->second; | ||
|
|
||
| Value *Val = Env.createValue(Type); | ||
| if (Val != nullptr) | ||
| ObjMap.insert({DirectCallee, Val}); | ||
| return Val; | ||
| } | ||
|
|
||
| template <typename Base> | ||
| StorageLocation * | ||
| CachedConstAccessorsLattice<Base>::getOrCreateConstMethodReturnStorageLocation( | ||
| const RecordStorageLocation &RecordLoc, const CallExpr *CE, | ||
| Environment &Env, llvm::function_ref<void(StorageLocation &)> Initialize) { | ||
| assert(!CE->getType().isNull()); | ||
| assert(CE->isGLValue() || CE->getType()->isRecordType()); | ||
| auto &ObjMap = ConstMethodReturnStorageLocations[&RecordLoc]; | ||
| const FunctionDecl *DirectCallee = CE->getDirectCallee(); | ||
| if (DirectCallee == nullptr) | ||
| return nullptr; | ||
| auto it = ObjMap.find(DirectCallee); | ||
| if (it != ObjMap.end()) | ||
| return it->second; | ||
|
|
||
| StorageLocation &Loc = | ||
| Env.createStorageLocation(CE->getType().getNonReferenceType()); | ||
| Initialize(Loc); | ||
|
|
||
| ObjMap.insert({DirectCallee, &Loc}); | ||
| return &Loc; | ||
| } | ||
|
|
||
| } // namespace dataflow | ||
| } // namespace clang | ||
|
|
||
| #endif // LLVM_CLANG_ANALYSIS_FLOWSENSITIVE_CACHED_CONST_ACCESSORS_LATTICE_H |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,175 +1,219 @@ | ||
| // RUN: %clang_cc1 -std=c++20 -verify %s | ||
| // expected-no-diagnostics | ||
|
|
||
| namespace Primary { | ||
| template<typename T> | ||
| concept D = true; | ||
|
|
||
| template<typename T> | ||
| struct A { | ||
| template<typename U, bool V> | ||
| void f() requires V; | ||
|
|
||
| template<> | ||
| void f<short, true>(); | ||
|
|
||
| template<D U> | ||
| void g(); | ||
|
|
||
| template<typename U, bool V> requires V | ||
| struct B; | ||
|
|
||
| template<typename U, bool V> requires V | ||
| struct B<U*, V>; | ||
|
|
||
| template<> | ||
| struct B<short, true>; | ||
|
|
||
| template<D U> | ||
| struct C; | ||
|
|
||
| template<D U> | ||
| struct C<U*>; | ||
|
|
||
| template<typename U, bool V> requires V | ||
| static int x; | ||
|
|
||
| template<typename U, bool V> requires V | ||
| static int x<U*, V>; | ||
|
|
||
| template<> | ||
| int x<short, true>; | ||
|
|
||
| template<D U> | ||
| static int y; | ||
|
|
||
| template<D U> | ||
| static int y<U*>; | ||
| }; | ||
|
|
||
| template<typename T> | ||
| template<typename U, bool V> | ||
| void A<T>::f() requires V { } | ||
|
|
||
| template<typename T> | ||
| template<D U> | ||
| void A<T>::g() { } | ||
|
|
||
| template<typename T> | ||
| template<typename U, bool V> requires V | ||
| struct A<T>::B { }; | ||
|
|
||
| template<typename T> | ||
| template<typename U, bool V> requires V | ||
| struct A<T>::B<U*, V> { }; | ||
|
|
||
| template<typename T> | ||
| template<typename U, bool V> requires V | ||
| struct A<T>::B<U&, V> { }; | ||
|
|
||
| template<typename T> | ||
| template<D U> | ||
| struct A<T>::C { }; | ||
|
|
||
| template<typename T> | ||
| template<D U> | ||
| struct A<T>::C<U*> { }; | ||
|
|
||
| template<typename T> | ||
| template<typename U, bool V> requires V | ||
| int A<T>::x = 0; | ||
|
|
||
| template<typename T> | ||
| template<typename U, bool V> requires V | ||
| int A<T>::x<U*, V> = 0; | ||
|
|
||
| template<typename T> | ||
| template<typename U, bool V> requires V | ||
| int A<T>::x<U&, V> = 0; | ||
|
|
||
| template<typename T> | ||
| template<D U> | ||
| int A<T>::y = 0; | ||
|
|
||
| template<typename T> | ||
| template<D U> | ||
| int A<T>::y<U*> = 0; | ||
|
|
||
| template<> | ||
| template<typename U, bool V> | ||
| void A<short>::f() requires V; | ||
|
|
||
| template<> | ||
| template<> | ||
| void A<short>::f<int, true>(); | ||
|
|
||
| template<> | ||
| template<> | ||
| void A<void>::f<int, true>(); | ||
|
|
||
| template<> | ||
| template<D U> | ||
| void A<short>::g(); | ||
|
|
||
| template<> | ||
| template<typename U, bool V> requires V | ||
| struct A<int>::B; | ||
|
|
||
| template<> | ||
| template<> | ||
| struct A<int>::B<int, true>; | ||
|
|
||
| template<> | ||
| template<> | ||
| struct A<void>::B<int, true>; | ||
|
|
||
| template<> | ||
| template<typename U, bool V> requires V | ||
| struct A<int>::B<U*, V>; | ||
|
|
||
| template<> | ||
| template<typename U, bool V> requires V | ||
| struct A<int>::B<U&, V>; | ||
|
|
||
| template<> | ||
| template<D U> | ||
| struct A<int>::C; | ||
|
|
||
| template<> | ||
| template<D U> | ||
| struct A<int>::C<U*>; | ||
|
|
||
| template<> | ||
| template<D U> | ||
| struct A<int>::C<U&>; | ||
|
|
||
| template<> | ||
| template<typename U, bool V> requires V | ||
| int A<long>::x; | ||
|
|
||
| template<> | ||
| template<> | ||
| int A<long>::x<int, true>; | ||
|
|
||
| template<> | ||
| template<> | ||
| int A<void>::x<int, true>; | ||
|
|
||
| template<> | ||
| template<typename U, bool V> requires V | ||
| int A<long>::x<U*, V>; | ||
|
|
||
| template<> | ||
| template<typename U, bool V> requires V | ||
| int A<long>::x<U&, V>; | ||
|
|
||
| template<> | ||
| template<D U> | ||
| int A<long>::y; | ||
|
|
||
| template<> | ||
| template<D U> | ||
| int A<long>::y<U*>; | ||
|
|
||
| template<> | ||
| template<D U> | ||
| int A<long>::y<U&>; | ||
| } // namespace Primary | ||
|
|
||
| namespace Partial { | ||
| template<typename T, bool B> | ||
| struct A; | ||
|
|
||
| template<bool U> | ||
| struct A<int, U> | ||
| { | ||
| template<typename V> requires U | ||
| void f(); | ||
|
|
||
| template<typename V> requires U | ||
| static const int x; | ||
|
|
||
| template<typename V> requires U | ||
| struct B; | ||
| }; | ||
|
|
||
| template<bool U> | ||
| template<typename V> requires U | ||
| void A<int, U>::f() { } | ||
|
|
||
| template<bool U> | ||
| template<typename V> requires U | ||
| constexpr int A<int, U>::x = 0; | ||
|
|
||
| template<bool U> | ||
| template<typename V> requires U | ||
| struct A<int, U>::B { }; | ||
|
|
||
| template<> | ||
| template<typename V> requires true | ||
| void A<int, true>::f() { } | ||
|
|
||
| template<> | ||
| template<typename V> requires true | ||
| constexpr int A<int, true>::x = 1; | ||
|
|
||
| template<> | ||
| template<typename V> requires true | ||
| struct A<int, true>::B { }; | ||
| } // namespace Partial |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // RUN: %clang %s -target arm64-apple-darwin -emit-llvm -S -fsanitize-coverage=trace-pc-guard -mllvm -sanitizer-coverage-gated-trace-callbacks=1 -o - | FileCheck %s --check-prefixes=CHECK,GATED | ||
| // RUN: %clang %s -target arm64-apple-darwin -emit-llvm -S -fsanitize-coverage=trace-pc-guard -mllvm -sanitizer-coverage-gated-trace-callbacks=0 -o - | FileCheck %s --check-prefixes=CHECK,PLAIN | ||
| // RUN: not %clang %s -target arm64-apple-darwin -emit-llvm -S -fsanitize-coverage=trace-pc -mllvm -sanitizer-coverage-gated-trace-callbacks=1 -o /dev/null 2>&1 | FileCheck %s --check-prefixes=INCOMPATIBLE | ||
| // RUN: not %clang %s -target arm64-apple-darwin -emit-llvm -S -fsanitize-coverage=inline-8bit-counters -mllvm -sanitizer-coverage-gated-trace-callbacks=1 -o /dev/null 2>&1 | FileCheck %s --check-prefixes=INCOMPATIBLE | ||
| // RUN: not %clang %s -target arm64-apple-darwin -emit-llvm -S -fsanitize-coverage=inline-bool-flag -mllvm -sanitizer-coverage-gated-trace-callbacks=1 -o /dev/null 2>&1 | FileCheck %s --check-prefixes=INCOMPATIBLE | ||
|
|
||
| // Verify that we do not emit the __sancov_gate section for "plain" trace-pc-guard | ||
| // GATED: section "__DATA,__sancov_gate" | ||
| // PLAIN-NOT: section "__DATA,__sancov_gate" | ||
|
|
||
| // Produce an error for all incompatible sanitizer coverage modes. | ||
| // INCOMPATIBLE: error: 'sanitizer-coverage-gated-trace-callbacks' is only supported with trace-pc-guard | ||
|
|
||
| int x[10]; | ||
|
|
||
| // CHECK: define{{.*}} void @foo | ||
| void foo(int n, int m) { | ||
| // COM: Verify that we're emitting the call to __sanitizer_cov_trace_pc_guard upon | ||
| // COM: checking the value of __sancov_should_track. | ||
| // GATED: [[VAL:%.*]] = load i64, {{.*}}@__sancov_should_track | ||
| // GATED-NOT: [[VAL:%.*]] = load i64, i64* @__sancov_should_track | ||
| // GATED-NEXT: [[CMP:%.*]] = icmp ne i64 [[VAL]], 0 | ||
| // GATED-NEXT: br i1 [[CMP]], label %[[L_TRUE:.*]], label %[[L_FALSE:.*]], !prof [[WEIGHTS:!.+]] | ||
| // GATED: [[L_TRUE]]: | ||
| // GATED-NEXT: call void @__sanitizer_cov_trace_pc_guard | ||
| // GATED: br i1 [[CMP]], label %[[L_TRUE_2:.*]], label %[[L_FALSE_2:.*]] | ||
| // GATED: [[L_TRUE_2]]: | ||
| // GATED-NEXT: call void @__sanitizer_cov_trace_pc_guard | ||
| // GATED: [[WEIGHTS]] = !{!"branch_weights", i32 1, i32 100000} | ||
|
|
||
| // COM: With the non-gated instrumentation, we should not emit the | ||
| // COM: __sancov_should_track global. | ||
| // PLAIN-NOT: __sancov_should_track | ||
| // But we should still be emitting the calls to the callback. | ||
| // PLAIN: call void @__sanitizer_cov_trace_pc_guard | ||
| if (n) { | ||
| x[n] = 42; | ||
| if (m) { | ||
| x[m] = 41; | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,67 +1,107 @@ | ||
| // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5 | ||
| // RUN: %clang_cc1 -O0 -cl-std=CL1.2 -triple amdgcn-amd-amdhsa -emit-llvm %s -o - | FileCheck -check-prefixes=CL12 %s | ||
| // RUN: %clang_cc1 -O0 -cl-std=CL2.0 -triple amdgcn-amd-amdhsa -emit-llvm %s -o - | FileCheck -check-prefixes=CL20 %s | ||
|
|
||
| // CL12-LABEL: define dso_local void @func1( | ||
| // CL12-SAME: ptr addrspace(5) noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] { | ||
| // CL12-NEXT: [[ENTRY:.*:]] | ||
| // CL12-NEXT: [[X_ADDR:%.*]] = alloca ptr addrspace(5), align 4, addrspace(5) | ||
| // CL12-NEXT: store ptr addrspace(5) [[X]], ptr addrspace(5) [[X_ADDR]], align 4 | ||
| // CL12-NEXT: [[TMP0:%.*]] = load ptr addrspace(5), ptr addrspace(5) [[X_ADDR]], align 4 | ||
| // CL12-NEXT: store i32 1, ptr addrspace(5) [[TMP0]], align 4 | ||
| // CL12-NEXT: ret void | ||
| // | ||
| // CL20-LABEL: define dso_local void @func1( | ||
| // CL20-SAME: ptr noundef [[X:%.*]]) #[[ATTR0:[0-9]+]] { | ||
| // CL20-NEXT: [[ENTRY:.*:]] | ||
| // CL20-NEXT: [[X_ADDR:%.*]] = alloca ptr, align 8, addrspace(5) | ||
| // CL20-NEXT: store ptr [[X]], ptr addrspace(5) [[X_ADDR]], align 8 | ||
| // CL20-NEXT: [[TMP0:%.*]] = load ptr, ptr addrspace(5) [[X_ADDR]], align 8 | ||
| // CL20-NEXT: store i32 1, ptr [[TMP0]], align 4 | ||
| // CL20-NEXT: ret void | ||
| // | ||
| void func1(int *x) { | ||
| *x = 1; | ||
| } | ||
|
|
||
| // CL12-LABEL: define dso_local void @func2( | ||
| // CL12-SAME: ) #[[ATTR0]] { | ||
| // CL12-NEXT: [[ENTRY:.*:]] | ||
| // CL12-NEXT: [[LV1:%.*]] = alloca i32, align 4, addrspace(5) | ||
| // CL12-NEXT: [[LV2:%.*]] = alloca i32, align 4, addrspace(5) | ||
| // CL12-NEXT: [[LA:%.*]] = alloca [100 x i32], align 4, addrspace(5) | ||
| // CL12-NEXT: [[LP1:%.*]] = alloca ptr addrspace(5), align 4, addrspace(5) | ||
| // CL12-NEXT: [[LP2:%.*]] = alloca ptr addrspace(5), align 4, addrspace(5) | ||
| // CL12-NEXT: [[LVC:%.*]] = alloca i32, align 4, addrspace(5) | ||
| // CL12-NEXT: store i32 1, ptr addrspace(5) [[LV1]], align 4 | ||
| // CL12-NEXT: store i32 2, ptr addrspace(5) [[LV2]], align 4 | ||
| // CL12-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [100 x i32], ptr addrspace(5) [[LA]], i64 0, i64 0 | ||
| // CL12-NEXT: store i32 3, ptr addrspace(5) [[ARRAYIDX]], align 4 | ||
| // CL12-NEXT: store ptr addrspace(5) [[LV1]], ptr addrspace(5) [[LP1]], align 4 | ||
| // CL12-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [100 x i32], ptr addrspace(5) [[LA]], i64 0, i64 0 | ||
| // CL12-NEXT: store ptr addrspace(5) [[ARRAYDECAY]], ptr addrspace(5) [[LP2]], align 4 | ||
| // CL12-NEXT: call void @func1(ptr addrspace(5) noundef [[LV1]]) #[[ATTR2:[0-9]+]] | ||
| // CL12-NEXT: store i32 4, ptr addrspace(5) [[LVC]], align 4 | ||
| // CL12-NEXT: store i32 4, ptr addrspace(5) [[LV1]], align 4 | ||
| // CL12-NEXT: ret void | ||
| // | ||
| // CL20-LABEL: define dso_local void @func2( | ||
| // CL20-SAME: ) #[[ATTR0]] { | ||
| // CL20-NEXT: [[ENTRY:.*:]] | ||
| // CL20-NEXT: [[LV1:%.*]] = alloca i32, align 4, addrspace(5) | ||
| // CL20-NEXT: [[LV2:%.*]] = alloca i32, align 4, addrspace(5) | ||
| // CL20-NEXT: [[LA:%.*]] = alloca [100 x i32], align 4, addrspace(5) | ||
| // CL20-NEXT: [[LP1:%.*]] = alloca ptr, align 8, addrspace(5) | ||
| // CL20-NEXT: [[LP2:%.*]] = alloca ptr, align 8, addrspace(5) | ||
| // CL20-NEXT: [[LVC:%.*]] = alloca i32, align 4, addrspace(5) | ||
| // CL20-NEXT: store i32 1, ptr addrspace(5) [[LV1]], align 4 | ||
| // CL20-NEXT: store i32 2, ptr addrspace(5) [[LV2]], align 4 | ||
| // CL20-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [100 x i32], ptr addrspace(5) [[LA]], i64 0, i64 0 | ||
| // CL20-NEXT: store i32 3, ptr addrspace(5) [[ARRAYIDX]], align 4 | ||
| // CL20-NEXT: [[LV1_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[LV1]] to ptr | ||
| // CL20-NEXT: store ptr [[LV1_ASCAST]], ptr addrspace(5) [[LP1]], align 8 | ||
| // CL20-NEXT: [[ARRAYDECAY:%.*]] = getelementptr inbounds [100 x i32], ptr addrspace(5) [[LA]], i64 0, i64 0 | ||
| // CL20-NEXT: [[ARRAYDECAY_ASCAST:%.*]] = addrspacecast ptr addrspace(5) [[ARRAYDECAY]] to ptr | ||
| // CL20-NEXT: store ptr [[ARRAYDECAY_ASCAST]], ptr addrspace(5) [[LP2]], align 8 | ||
| // CL20-NEXT: [[LV1_ASCAST1:%.*]] = addrspacecast ptr addrspace(5) [[LV1]] to ptr | ||
| // CL20-NEXT: call void @func1(ptr noundef [[LV1_ASCAST1]]) #[[ATTR2:[0-9]+]] | ||
| // CL20-NEXT: store i32 4, ptr addrspace(5) [[LVC]], align 4 | ||
| // CL20-NEXT: store i32 4, ptr addrspace(5) [[LV1]], align 4 | ||
| // CL20-NEXT: ret void | ||
| // | ||
| void func2(void) { | ||
| int lv1; | ||
| lv1 = 1; | ||
|
|
||
| int lv2 = 2; | ||
|
|
||
| int la[100]; | ||
| la[0] = 3; | ||
|
|
||
| int *lp1 = &lv1; | ||
|
|
||
| int *lp2 = la; | ||
|
|
||
| func1(&lv1); | ||
|
|
||
| const int lvc = 4; | ||
| lv1 = lvc; | ||
| } | ||
|
|
||
| // CL12-LABEL: define dso_local void @func3( | ||
| // CL12-SAME: ) #[[ATTR0]] { | ||
| // CL12-NEXT: [[ENTRY:.*:]] | ||
| // CL12-NEXT: [[A:%.*]] = alloca [16 x [1 x float]], align 4, addrspace(5) | ||
| // CL12-NEXT: call void @llvm.memset.p5.i64(ptr addrspace(5) align 4 [[A]], i8 0, i64 64, i1 false) | ||
| // CL12-NEXT: ret void | ||
| // | ||
| // CL20-LABEL: define dso_local void @func3( | ||
| // CL20-SAME: ) #[[ATTR0]] { | ||
| // CL20-NEXT: [[ENTRY:.*:]] | ||
| // CL20-NEXT: [[A:%.*]] = alloca [16 x [1 x float]], align 4, addrspace(5) | ||
| // CL20-NEXT: call void @llvm.memset.p5.i64(ptr addrspace(5) align 4 [[A]], i8 0, i64 64, i1 false) | ||
| // CL20-NEXT: ret void | ||
| // | ||
| void func3(void) { | ||
| float a[16][1] = {{0.}}; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,69 +1,90 @@ | ||
| // RUN: %clang_cc1 -triple spir-unknown-unknown -emit-llvm -cl-ext=+cl_khr_subgroups -O0 -cl-std=clc++ -o - %s | FileCheck --check-prefix=CHECK-SPIR %s | ||
| // RUN: %clang_cc1 -triple %itanium_abi_triple -emit-llvm -cl-ext=+cl_khr_subgroups -O0 -cl-std=clc++ -o - %s | FileCheck %s | ||
| // FIXME: Add MS ABI manglings of OpenCL things and remove %itanium_abi_triple | ||
| // above to support OpenCL in the MS C++ ABI. | ||
|
|
||
| #pragma OPENCL EXTENSION cl_khr_subgroups : enable | ||
|
|
||
| void test1(read_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func i32 @__read_pipe_2(target("spirv.Pipe", 0) %{{.*}}, ptr addrspace(4) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__read_pipe_2(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| read_pipe(p, ptr); | ||
| // CHECK-SPIR: call spir_func target("spirv.ReserveId") @__reserve_read_pipe(target("spirv.Pipe", 0) %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| // CHECK: call ptr @__reserve_read_pipe(ptr %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| reserve_id_t rid = reserve_read_pipe(p, 2); | ||
| // CHECK-SPIR: call spir_func i32 @__read_pipe_4(target("spirv.Pipe", 0) %{{.*}}, ptr addrspace(4) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__read_pipe_4(ptr %{{.*}}, ptr %{{.*}}, i32 {{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| read_pipe(p, rid, 2, ptr); | ||
| // CHECK-SPIR: call spir_func void @__commit_read_pipe(target("spirv.Pipe", 0) %{{.*}}, target("spirv.ReserveId") %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call void @__commit_read_pipe(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| commit_read_pipe(p, rid); | ||
| } | ||
|
|
||
| void test2(write_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func i32 @__write_pipe_2(target("spirv.Pipe", 1) %{{.*}}, ptr addrspace(4) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__write_pipe_2(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| write_pipe(p, ptr); | ||
| // CHECK-SPIR: call spir_func target("spirv.ReserveId") @__reserve_write_pipe(target("spirv.Pipe", 1) %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| // CHECK: call ptr @__reserve_write_pipe(ptr %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| reserve_id_t rid = reserve_write_pipe(p, 2); | ||
| // CHECK-SPIR: call spir_func i32 @__write_pipe_4(target("spirv.Pipe", 1) %{{.*}}, ptr addrspace(4) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__write_pipe_4(ptr %{{.*}}, ptr %{{.*}}, i32 {{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| write_pipe(p, rid, 2, ptr); | ||
| // CHECK-SPIR: call spir_func void @__commit_write_pipe(target("spirv.Pipe", 1) %{{.*}}, target("spirv.ReserveId") %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call void @__commit_write_pipe(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| commit_write_pipe(p, rid); | ||
| } | ||
|
|
||
| void test3(read_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func target("spirv.ReserveId") @__work_group_reserve_read_pipe(target("spirv.Pipe", 0) %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| // CHECK: call ptr @__work_group_reserve_read_pipe(ptr %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| reserve_id_t rid = work_group_reserve_read_pipe(p, 2); | ||
| // CHECK-SPIR: call spir_func void @__work_group_commit_read_pipe(target("spirv.Pipe", 0) %{{.*}}, target("spirv.ReserveId") %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call void @__work_group_commit_read_pipe(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| work_group_commit_read_pipe(p, rid); | ||
| } | ||
|
|
||
| void test4(write_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func target("spirv.ReserveId") @__work_group_reserve_write_pipe(target("spirv.Pipe", 1) %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| // CHECK: call ptr @__work_group_reserve_write_pipe(ptr %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| reserve_id_t rid = work_group_reserve_write_pipe(p, 2); | ||
| // CHECK-SPIR: call spir_func void @__work_group_commit_write_pipe(target("spirv.Pipe", 1) %{{.*}}, target("spirv.ReserveId") %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call void @__work_group_commit_write_pipe(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| work_group_commit_write_pipe(p, rid); | ||
| } | ||
|
|
||
| void test5(read_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func target("spirv.ReserveId") @__sub_group_reserve_read_pipe(target("spirv.Pipe", 0) %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| // CHECK: call ptr @__sub_group_reserve_read_pipe(ptr %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| reserve_id_t rid = sub_group_reserve_read_pipe(p, 2); | ||
| // CHECK-SPIR: call spir_func void @__sub_group_commit_read_pipe(target("spirv.Pipe", 0) %{{.*}}, target("spirv.ReserveId") %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call void @__sub_group_commit_read_pipe(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| sub_group_commit_read_pipe(p, rid); | ||
| } | ||
|
|
||
| void test6(write_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func target("spirv.ReserveId") @__sub_group_reserve_write_pipe(target("spirv.Pipe", 1) %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| // CHECK: call ptr @__sub_group_reserve_write_pipe(ptr %{{.*}}, i32 {{.*}}, i32 4, i32 4) | ||
| reserve_id_t rid = sub_group_reserve_write_pipe(p, 2); | ||
| // CHECK-SPIR: call spir_func void @__sub_group_commit_write_pipe(target("spirv.Pipe", 1) %{{.*}}, target("spirv.ReserveId") %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call void @__sub_group_commit_write_pipe(ptr %{{.*}}, ptr %{{.*}}, i32 4, i32 4) | ||
| sub_group_commit_write_pipe(p, rid); | ||
| } | ||
|
|
||
| void test7(read_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func i32 @__get_pipe_num_packets_ro(target("spirv.Pipe", 0) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__get_pipe_num_packets_ro(ptr %{{.*}}, i32 4, i32 4) | ||
| *ptr = get_pipe_num_packets(p); | ||
| // CHECK-SPIR: call spir_func i32 @__get_pipe_max_packets_ro(target("spirv.Pipe", 0) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__get_pipe_max_packets_ro(ptr %{{.*}}, i32 4, i32 4) | ||
| *ptr = get_pipe_max_packets(p); | ||
| } | ||
|
|
||
| void test8(write_only pipe int p, global int *ptr) { | ||
| // CHECK-SPIR: call spir_func i32 @__get_pipe_num_packets_wo(target("spirv.Pipe", 1) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__get_pipe_num_packets_wo(ptr %{{.*}}, i32 4, i32 4) | ||
| *ptr = get_pipe_num_packets(p); | ||
| // CHECK-SPIR: call spir_func i32 @__get_pipe_max_packets_wo(target("spirv.Pipe", 1) %{{.*}}, i32 4, i32 4) | ||
| // CHECK: call i32 @__get_pipe_max_packets_wo(ptr %{{.*}}, i32 4, i32 4) | ||
| *ptr = get_pipe_max_packets(p); | ||
| } |