已开启
fix: keep llvm.cj.blackhole opaque through the optimizer to block LICM #604
yanjs创建于 25 天前
fix: keep llvm.cj.blackhole opaque through the optimizer to block LICM #604
已开启
共 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 | + | ||
| 2892 | static bool deleteUnusedBlackHole(Module &M) { | 2918 | static 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)* %14 | 72 | 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 | +} | ||