已开启
fix: keep llvm.cj.blackhole opaque through the optimizer to block LICM #604
fix: keep llvm.cj.blackhole opaque through the optimizer to block LICM #604
已开启
yanjs创建于 25 天前
4 个文件变更+103-5
@@ -216,6 +216,8 @@ public:
216 216 
217 M.addModuleFlag(Module::Warning, "HasRewrittenStatepoint", true);217 M.addModuleFlag(Module::Warning, "HasRewrittenStatepoint", true);
218 218 
219+ Changed |= deleteUnusedBlackHole(M);
220+ 
219 if (!Changed)221 if (!Changed)
220 return false;222 return false;
221 223 
@@ -2889,10 +2891,35 @@ void prepareBlackHoleBody(Function *F) {
2889 ReturnInst::Create(F->getContext(), F->getArg(0), BB);2891 ReturnInst::Create(F->getContext(), F->getArg(0), BB);
2890}2892}
2891 2893 
2894+// Eliminate the intrinsic form of blackhole. llvm.cj.blackhole is kept
2895+// unlowered through the whole optimization pipeline (its default memory
2896+// effects block LICM from hoisting loads across it) and is removed here,
2897+// after the last optimization pass, at zero runtime cost.
2898+static bool deleteBlackHoleIntrinsic(Module &M) {
2899+ Function *Intr = M.getFunction(Intrinsic::getName(Intrinsic::cj_blackhole));
2900+ if (!Intr)
2901+ return false;
2902+ SmallVector<CallBase *, 32> Users;
2903+ for (auto *U : Intr->users()) {
2904+ auto *CB = dyn_cast<CallBase>(U);
2905+ assert(CB != nullptr && "User of llvm.cj.blackhole must be call instruction");
2906+ Users.push_back(CB);
2907+ }
2908+ for (auto *CB : Users) {
2909+ if (!CB->use_empty())
2910+ CB->replaceAllUsesWith(CB->getArgOperand(0));
2911+ CB->eraseFromParent();
2912+ }
2913+ if (Intr->use_empty())
2914+ Intr->eraseFromParent();
2915+ return !Users.empty();
2916+}
2917+ 
2892static bool deleteUnusedBlackHole(Module &M) {2918static bool deleteUnusedBlackHole(Module &M) {
2919+ bool Changed = deleteBlackHoleIntrinsic(M);
2893 Function *F = M.getFunction("CJ_LLVM_BlackHole");2920 Function *F = M.getFunction("CJ_LLVM_BlackHole");
2894 if (!F || F->use_empty())2921 if (!F || F->use_empty())
2895- return false;2922+ return Changed;
2896 prepareBlackHoleBody(F);2923 prepareBlackHoleBody(F);
2897 SmallVector<CallBase *, 32> Users;2924 SmallVector<CallBase *, 32> Users;
2898 for (auto *U : F->users()) {2925 for (auto *U : F->users()) {
@@ -105,6 +105,10 @@ const static StdMap<unsigned, StringRef> RuntimeMap {
105 {Intrinsic::cj_get_exported_ref, "CJ_MCC_GetExportedRef"},105 {Intrinsic::cj_get_exported_ref, "CJ_MCC_GetExportedRef"},
106 {Intrinsic::cj_remove_exported_ref, "CJ_MCC_RemoveExportedRef"},106 {Intrinsic::cj_remove_exported_ref, "CJ_MCC_RemoveExportedRef"},
107 {Intrinsic::cj_create_export_handle, "CJ_MCC_CreateExportHandle"},107 {Intrinsic::cj_create_export_handle, "CJ_MCC_CreateExportHandle"},
108+ // cj_blackhole is deliberately NOT lowered here: the intrinsic form has
109+ // default (may-read/may-write) memory effects and therefore blocks LICM
110+ // from hoisting loop-invariant loads across it. It survives the whole
111+ // optimization pipeline and is eliminated in CJRewriteStatepoint.
108 {Intrinsic::cj_blackhole, "CJ_LLVM_BlackHole"},112 {Intrinsic::cj_blackhole, "CJ_LLVM_BlackHole"},
109 {Intrinsic::cj_get_lambda_addr, "CJ_MCC_GetJSLambdaAddr"}};113 {Intrinsic::cj_get_lambda_addr, "CJ_MCC_GetJSLambdaAddr"}};
110 114 
@@ -827,9 +831,6 @@ private:
827 if (Func->getName().isSetDebugLocation())831 if (Func->getName().isSetDebugLocation())
828 Func->setUnnamedAddr(GlobalValue::UnnamedAddr::Local);832 Func->setUnnamedAddr(GlobalValue::UnnamedAddr::Local);
829 RTFuncMap[Callee] = Func;833 RTFuncMap[Callee] = Func;
830- if (CI->getIntrinsicID() == Intrinsic::cj_blackhole) {
831- Func->addFnAttr(Attribute::ReadOnly);
832- }
833 return Func;834 return Func;
834 }835 }
835 836 
@@ -1023,7 +1024,6 @@ static bool runtimeLoweringFunc(Function &F, CJIntrinsicLowering &Lowering) {
1023 case Intrinsic::cj_set_gc_threshold:1024 case Intrinsic::cj_set_gc_threshold:
1024 case Intrinsic::cj_post_throw_exception:1025 case Intrinsic::cj_post_throw_exception:
1025 case Intrinsic::cj_register_implicit_exception_raisers:1026 case Intrinsic::cj_register_implicit_exception_raisers:
1026- case Intrinsic::cj_blackhole:
1027 case Intrinsic::cj_get_lambda_addr:1027 case Intrinsic::cj_get_lambda_addr:
1028 Lowering.replaceWithRuntimeFunc(CI, true, false);1028 Lowering.replaceWithRuntimeFunc(CI, true, false);
1029 Changed = true;1029 Changed = true;
@@ -71,3 +71,22 @@ allocas:
71 %14 = addrspacecast i8* %13 to i8 addrspace(1)*71 %14 = addrspacecast i8* %13 to i8 addrspace(1)*
72 ret i8 addrspace(1)* %1472 ret i8 addrspace(1)* %14
73}73}
74+ 
75+declare i8* @llvm.cj.blackhole(i8*)
76+ 
77+; The intrinsic form must be eliminated as well: used results are replaced
78+; with the argument, unused calls are erased.
79+; CHECK-LABEL: _CN7default4foo3Hv(
80+define i64 @_CN7default4foo3Hv() {
81+allocas:
82+ %b = alloca i64, align 8
83+ store i64 5, i64* %b, align 8
84+ %0 = bitcast i64* %b to i8*
85+; CHECK-NOT: call i8* @llvm.cj.blackhole
86+ %1 = call i8* @llvm.cj.blackhole(i8* nonnull %0)
87+ %2 = bitcast i8* %1 to i64*
88+ %3 = load i64, i64* %2, align 8
89+; CHECK-NOT: call i8* @llvm.cj.blackhole
90+ call i8* @llvm.cj.blackhole(i8* nonnull %0)
91+ ret i64 %3
92+}
@@ -0,0 +1,52 @@
1+; RUN: opt < %s -passes=licm -S | FileCheck %s
2+ 
3+; llvm.cj.blackhole has default (may-read/may-write) memory effects, so a
4+; loop-invariant load must NOT be hoisted above it. This is what makes
5+; std.runtime.blackBox effective against LICM in @Bench measurement loops.
6+ 
7+declare i8* @llvm.cj.blackhole(i8*)
8+ 
9+define i64 @blackhole_blocks_licm(i64* %p, i64 %n) {
10+entry:
11+ br label %loop
12+ 
13+; CHECK-LABEL: @blackhole_blocks_licm(
14+; CHECK: loop:
15+; CHECK-NEXT: %i = phi
16+; CHECK-NEXT: %v = load i64, i64* %p
17+loop:
18+ %i = phi i64 [ 0, %entry ], [ %inc, %loop ]
19+ %v = load i64, i64* %p, align 8
20+ %p8 = bitcast i64* %p to i8*
21+; CHECK-NEXT: call i8* @llvm.cj.blackhole
22+ %bh = call i8* @llvm.cj.blackhole(i8* %p8)
23+ %inc = add i64 %i, 1
24+ %cmp = icmp ult i64 %inc, %n
25+ br i1 %cmp, label %loop, label %exit
26+ 
27+exit:
28+ %r = add i64 %v, %i
29+ ret i64 %r
30+}
31+ 
32+; Contrast case: without the blackhole call the same load leaves the loop
33+; (sunk to the exit block, executed only once).
34+define i64 @no_blackhole_load_leaves_loop(i64* dereferenceable(8) %p, i64 %n) {
35+entry:
36+ br label %loop
37+ 
38+loop:
39+ %i = phi i64 [ 0, %entry ], [ %inc, %loop ]
40+ %v = load i64, i64* %p, align 8
41+ %inc = add i64 %i, 1
42+ %cmp = icmp ult i64 %inc, %n
43+ br i1 %cmp, label %loop, label %exit
44+ 
45+; CHECK-LABEL: @no_blackhole_load_leaves_loop(
46+; CHECK: exit:
47+; CHECK-NEXT: %i.lcssa = phi
48+; CHECK-NEXT: load i64, i64* %p
49+exit:
50+ %r = add i64 %v, %i
51+ ret i64 %r
52+}