| 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 |
|---|---|---|
| @@ -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); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,305 @@ | ||
| //===- unittests/Analysis/FlowSensitive/CachedConstAccessorsLatticeTest.cpp ==// | ||
| // | ||
| // 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 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| #include "clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h" | ||
|
|
||
| #include <cassert> | ||
| #include <memory> | ||
|
|
||
| #include "clang/AST/Decl.h" | ||
| #include "clang/AST/DeclBase.h" | ||
| #include "clang/AST/DeclCXX.h" | ||
| #include "clang/AST/Expr.h" | ||
| #include "clang/AST/Type.h" | ||
| #include "clang/ASTMatchers/ASTMatchFinder.h" | ||
| #include "clang/ASTMatchers/ASTMatchers.h" | ||
| #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" | ||
| #include "clang/Analysis/FlowSensitive/DataflowLattice.h" | ||
| #include "clang/Analysis/FlowSensitive/NoopLattice.h" | ||
| #include "clang/Analysis/FlowSensitive/StorageLocation.h" | ||
| #include "clang/Analysis/FlowSensitive/Value.h" | ||
| #include "clang/Analysis/FlowSensitive/WatchedLiteralsSolver.h" | ||
| #include "clang/Basic/LLVM.h" | ||
| #include "clang/Testing/TestAST.h" | ||
| #include "gmock/gmock.h" | ||
| #include "gtest/gtest.h" | ||
|
|
||
| namespace clang::dataflow { | ||
| namespace { | ||
|
|
||
| using ast_matchers::BoundNodes; | ||
| using ast_matchers::callee; | ||
| using ast_matchers::cxxMemberCallExpr; | ||
| using ast_matchers::functionDecl; | ||
| using ast_matchers::hasName; | ||
| using ast_matchers::match; | ||
| using ast_matchers::selectFirst; | ||
|
|
||
| using dataflow::DataflowAnalysisContext; | ||
| using dataflow::Environment; | ||
| using dataflow::LatticeJoinEffect; | ||
| using dataflow::RecordStorageLocation; | ||
| using dataflow::Value; | ||
| using dataflow::WatchedLiteralsSolver; | ||
|
|
||
| using testing::SizeIs; | ||
|
|
||
| NamedDecl *lookup(StringRef Name, const DeclContext &DC) { | ||
| auto Result = DC.lookup(&DC.getParentASTContext().Idents.get(Name)); | ||
| EXPECT_TRUE(Result.isSingleResult()) << Name; | ||
| return Result.front(); | ||
| } | ||
|
|
||
| class CachedConstAccessorsLatticeTest : public ::testing::Test { | ||
| protected: | ||
| using LatticeT = CachedConstAccessorsLattice<NoopLattice>; | ||
|
|
||
| DataflowAnalysisContext DACtx{std::make_unique<WatchedLiteralsSolver>()}; | ||
| Environment Env{DACtx}; | ||
| }; | ||
|
|
||
| // Basic test AST with two const methods (return a value, and return a ref). | ||
| struct CommonTestInputs { | ||
| CommonTestInputs() | ||
| : AST(R"cpp( | ||
| struct S { | ||
| int *valProperty() const; | ||
| int &refProperty() const; | ||
| }; | ||
| void target() { | ||
| S s; | ||
| s.valProperty(); | ||
| S s2; | ||
| s2.refProperty(); | ||
| } | ||
| )cpp") { | ||
| auto *SDecl = cast<CXXRecordDecl>( | ||
| lookup("S", *AST.context().getTranslationUnitDecl())); | ||
| SType = AST.context().getRecordType(SDecl); | ||
| CallVal = selectFirst<CallExpr>( | ||
| "call", | ||
| match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty")))) | ||
| .bind("call"), | ||
| AST.context())); | ||
| assert(CallVal != nullptr); | ||
|
|
||
| CallRef = selectFirst<CallExpr>( | ||
| "call", | ||
| match(cxxMemberCallExpr(callee(functionDecl(hasName("refProperty")))) | ||
| .bind("call"), | ||
| AST.context())); | ||
| assert(CallRef != nullptr); | ||
| } | ||
|
|
||
| TestAST AST; | ||
| QualType SType; | ||
| const CallExpr *CallVal; | ||
| const CallExpr *CallRef; | ||
| }; | ||
|
|
||
| TEST_F(CachedConstAccessorsLatticeTest, | ||
| SamePrimitiveValBeforeClearOrDiffAfterClear) { | ||
| CommonTestInputs Inputs; | ||
| auto *CE = Inputs.CallVal; | ||
| RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), | ||
| {}); | ||
|
|
||
| LatticeT Lattice; | ||
| Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
| Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
|
|
||
| EXPECT_EQ(Val1, Val2); | ||
|
|
||
| Lattice.clearConstMethodReturnValues(Loc); | ||
| Value *Val3 = Lattice.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
|
|
||
| EXPECT_NE(Val3, Val1); | ||
| EXPECT_NE(Val3, Val2); | ||
| } | ||
|
|
||
| TEST_F(CachedConstAccessorsLatticeTest, SameLocBeforeClearOrDiffAfterClear) { | ||
| CommonTestInputs Inputs; | ||
| auto *CE = Inputs.CallRef; | ||
| RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), | ||
| {}); | ||
|
|
||
| LatticeT Lattice; | ||
| auto NopInit = [](StorageLocation &) {}; | ||
| StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( | ||
| Loc, CE, Env, NopInit); | ||
| auto NotCalled = [](StorageLocation &) { | ||
| ASSERT_TRUE(false) << "Not reached"; | ||
| }; | ||
| StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( | ||
| Loc, CE, Env, NotCalled); | ||
|
|
||
| EXPECT_EQ(Loc1, Loc2); | ||
|
|
||
| Lattice.clearConstMethodReturnStorageLocations(Loc); | ||
| StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( | ||
| Loc, CE, Env, NopInit); | ||
|
|
||
| EXPECT_NE(Loc3, Loc1); | ||
| EXPECT_NE(Loc3, Loc2); | ||
| } | ||
|
|
||
| TEST_F(CachedConstAccessorsLatticeTest, | ||
| SameStructValBeforeClearOrDiffAfterClear) { | ||
| TestAST AST(R"cpp( | ||
| struct S { | ||
| S structValProperty() const; | ||
| }; | ||
| void target() { | ||
| S s; | ||
| s.structValProperty(); | ||
| } | ||
| )cpp"); | ||
| auto *SDecl = | ||
| cast<CXXRecordDecl>(lookup("S", *AST.context().getTranslationUnitDecl())); | ||
| QualType SType = AST.context().getRecordType(SDecl); | ||
| const CallExpr *CE = selectFirst<CallExpr>( | ||
| "call", match(cxxMemberCallExpr( | ||
| callee(functionDecl(hasName("structValProperty")))) | ||
| .bind("call"), | ||
| AST.context())); | ||
| ASSERT_NE(CE, nullptr); | ||
|
|
||
| RecordStorageLocation Loc(SType, RecordStorageLocation::FieldToLoc(), {}); | ||
|
|
||
| LatticeT Lattice; | ||
| // Accessors that return a record by value are modeled by a record storage | ||
| // location (instead of a Value). | ||
| auto NopInit = [](StorageLocation &) {}; | ||
| StorageLocation *Loc1 = Lattice.getOrCreateConstMethodReturnStorageLocation( | ||
| Loc, CE, Env, NopInit); | ||
| auto NotCalled = [](StorageLocation &) { | ||
| ASSERT_TRUE(false) << "Not reached"; | ||
| }; | ||
| StorageLocation *Loc2 = Lattice.getOrCreateConstMethodReturnStorageLocation( | ||
| Loc, CE, Env, NotCalled); | ||
|
|
||
| EXPECT_EQ(Loc1, Loc2); | ||
|
|
||
| Lattice.clearConstMethodReturnStorageLocations(Loc); | ||
| StorageLocation *Loc3 = Lattice.getOrCreateConstMethodReturnStorageLocation( | ||
| Loc, CE, Env, NopInit); | ||
|
|
||
| EXPECT_NE(Loc3, Loc1); | ||
| EXPECT_NE(Loc3, Loc1); | ||
| } | ||
|
|
||
| TEST_F(CachedConstAccessorsLatticeTest, ClearDifferentLocs) { | ||
| CommonTestInputs Inputs; | ||
| auto *CE = Inputs.CallRef; | ||
| RecordStorageLocation LocS1(Inputs.SType, RecordStorageLocation::FieldToLoc(), | ||
| {}); | ||
| RecordStorageLocation LocS2(Inputs.SType, RecordStorageLocation::FieldToLoc(), | ||
| {}); | ||
|
|
||
| LatticeT Lattice; | ||
| auto NopInit = [](StorageLocation &) {}; | ||
| StorageLocation *RetLoc1 = | ||
| Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env, | ||
| NopInit); | ||
| Lattice.clearConstMethodReturnStorageLocations(LocS2); | ||
| auto NotCalled = [](StorageLocation &) { | ||
| ASSERT_TRUE(false) << "Not reached"; | ||
| }; | ||
| StorageLocation *RetLoc2 = | ||
| Lattice.getOrCreateConstMethodReturnStorageLocation(LocS1, CE, Env, | ||
| NotCalled); | ||
|
|
||
| EXPECT_EQ(RetLoc1, RetLoc2); | ||
| } | ||
|
|
||
| TEST_F(CachedConstAccessorsLatticeTest, DifferentValsFromDifferentLocs) { | ||
| TestAST AST(R"cpp( | ||
| struct S { | ||
| int *valProperty() const; | ||
| }; | ||
| void target() { | ||
| S s1; | ||
| s1.valProperty(); | ||
| S s2; | ||
| s2.valProperty(); | ||
| } | ||
| )cpp"); | ||
| auto *SDecl = | ||
| cast<CXXRecordDecl>(lookup("S", *AST.context().getTranslationUnitDecl())); | ||
| QualType SType = AST.context().getRecordType(SDecl); | ||
| SmallVector<BoundNodes, 1> valPropertyCalls = | ||
| match(cxxMemberCallExpr(callee(functionDecl(hasName("valProperty")))) | ||
| .bind("call"), | ||
| AST.context()); | ||
| ASSERT_THAT(valPropertyCalls, SizeIs(2)); | ||
|
|
||
| const CallExpr *CE1 = selectFirst<CallExpr>("call", valPropertyCalls); | ||
| ASSERT_NE(CE1, nullptr); | ||
|
|
||
| valPropertyCalls.erase(valPropertyCalls.begin()); | ||
| const CallExpr *CE2 = selectFirst<CallExpr>("call", valPropertyCalls); | ||
| ASSERT_NE(CE2, nullptr); | ||
| ASSERT_NE(CE1, CE2); | ||
|
|
||
| RecordStorageLocation LocS1(SType, RecordStorageLocation::FieldToLoc(), {}); | ||
| RecordStorageLocation LocS2(SType, RecordStorageLocation::FieldToLoc(), {}); | ||
|
|
||
| LatticeT Lattice; | ||
| Value *Val1 = Lattice.getOrCreateConstMethodReturnValue(LocS1, CE1, Env); | ||
| Value *Val2 = Lattice.getOrCreateConstMethodReturnValue(LocS2, CE2, Env); | ||
|
|
||
| EXPECT_NE(Val1, Val2); | ||
| } | ||
|
|
||
| TEST_F(CachedConstAccessorsLatticeTest, JoinSameNoop) { | ||
| CommonTestInputs Inputs; | ||
| auto *CE = Inputs.CallVal; | ||
| RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), | ||
| {}); | ||
|
|
||
| LatticeT EmptyLattice; | ||
| LatticeT EmptyLattice2; | ||
| EXPECT_EQ(EmptyLattice.join(EmptyLattice2), LatticeJoinEffect::Unchanged); | ||
|
|
||
| LatticeT Lattice1; | ||
| Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
| EXPECT_EQ(Lattice1.join(Lattice1), LatticeJoinEffect::Unchanged); | ||
| } | ||
|
|
||
| TEST_F(CachedConstAccessorsLatticeTest, ProducesNewValueAfterJoinDistinct) { | ||
| CommonTestInputs Inputs; | ||
| auto *CE = Inputs.CallVal; | ||
| RecordStorageLocation Loc(Inputs.SType, RecordStorageLocation::FieldToLoc(), | ||
| {}); | ||
|
|
||
| // L1 w/ v vs L2 empty | ||
| LatticeT Lattice1; | ||
| Value *Val1 = Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
|
|
||
| LatticeT EmptyLattice; | ||
|
|
||
| EXPECT_EQ(Lattice1.join(EmptyLattice), LatticeJoinEffect::Changed); | ||
| Value *ValAfterJoin = | ||
| Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
|
|
||
| EXPECT_NE(ValAfterJoin, Val1); | ||
|
|
||
| // L1 w/ v1 vs L3 w/ v2 | ||
| LatticeT Lattice3; | ||
| Value *Val3 = Lattice3.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
|
|
||
| EXPECT_EQ(Lattice1.join(Lattice3), LatticeJoinEffect::Changed); | ||
| Value *ValAfterJoin2 = | ||
| Lattice1.getOrCreateConstMethodReturnValue(Loc, CE, Env); | ||
|
|
||
| EXPECT_NE(ValAfterJoin2, ValAfterJoin); | ||
| EXPECT_NE(ValAfterJoin2, Val3); | ||
| } | ||
|
|
||
| } // namespace | ||
| } // namespace clang::dataflow |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| #include <Foundation/Foundation.h> | ||
|
|
||
| @interface Foo : NSObject | ||
| @end | ||
|
|
||
| @implementation Foo | ||
| @end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // RUN: rm -rf %t && mkdir -p %t | ||
| // RUN: %clang -c -o %t/EmptyClassFoo.o %S/Inputs/EmptyClassFoo.m | ||
| // RUN: ar r %t/libFooClass.a %t/EmptyClassFoo.o | ||
| // RUN: %clang -c -o %t/force-objc.o %s | ||
| // RUN: %llvm_jitlink -ObjC %t/force-objc.o -L%t -lFooClass | ||
| // | ||
| // REQUIRES: system-darwin && host-arch-compatible | ||
|
|
||
| id objc_getClass(const char *name); | ||
|
|
||
| int main(int argc, char *argv[]) { | ||
| // Return succeess if we find Foo, error otherwise. | ||
| return objc_getClass("Foo") ? 0 : 1; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| extern "C" int x; | ||
|
|
||
| namespace { | ||
|
|
||
| struct Init { | ||
| public: | ||
| Init() { x = 1; } | ||
| }; | ||
|
|
||
| Init SetX; | ||
|
|
||
| } // namespace |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| // Check that the -all_load flag to llvm-jitlink causes all objects from | ||
| // archives to be loaded, regardless of whether or not they're referenced. | ||
| // | ||
| // RUN: rm -rf %t && mkdir -p %t | ||
| // RUN: %clangxx -c -o %t/SetX.o %S/Inputs/SetGlobalIntXInConstructor.cpp | ||
| // RUN: ar r %t/libSetX.a %t/SetX.o | ||
| // RUN: %clang -c -o %t/all_load.o %s | ||
| // RUN: %llvm_jitlink -all_load %t/all_load.o -L%t -lSetX | ||
| // | ||
| // REQUIRES: system-darwin && host-arch-compatible | ||
|
|
||
| int x = 0; | ||
|
|
||
| int main(int argc, char *argv[]) { return x == 1 ? 0 : 1; } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| !RUN: %flang --target=aarch64-unknown-linux-gnu -fuse-ld=ld -fopenmp -rtlib=libgcc -### %s 2>&1 | FileCheck --check-prefixes=GCC %s | ||
| !RUN: %flang --target=aarch64-unknown-linux-gnu -fuse-ld=ld -fopenmp -rtlib=compiler-rt -### %s 2>&1 | FileCheck --check-prefixes=CRT %s | ||
|
|
||
| !GCC: -latomic | ||
| !CRT-NOT: -latomic |