diff --git a/include/llvm/IR/Intrinsics.td b/include/llvm/IR/Intrinsics.td index 98d48de5103..5457e9577c4 100644 --- a/include/llvm/IR/Intrinsics.td +++ b/include/llvm/IR/Intrinsics.td @@ -493,6 +493,22 @@ def int_experimental_patchpoint_i64 : Intrinsic<[llvm_i64_ty], llvm_vararg_ty], [Throws]>; + +//===------------------------ Garbage Collection Intrinsics ---------------===// +// These are documented in docs/Statepoint.rst + +def int_experimental_gc_statepoint : Intrinsic<[llvm_i32_ty], + [llvm_anyptr_ty, llvm_i32_ty, + llvm_i32_ty, llvm_vararg_ty]>; + +def int_experimental_gc_result_int : Intrinsic<[llvm_anyint_ty], [llvm_i32_ty]>; +def int_experimental_gc_result_float : Intrinsic<[llvm_anyfloat_ty], + [llvm_i32_ty]>; +def int_experimental_gc_result_ptr : Intrinsic<[llvm_anyptr_ty], [llvm_i32_ty]>; + +def int_experimental_gc_relocate : Intrinsic<[llvm_anyptr_ty], + [llvm_i32_ty, llvm_i32_ty, llvm_i32_ty]>; + //===-------------------------- Other Intrinsics --------------------------===// // def int_flt_rounds : Intrinsic<[llvm_i32_ty]>, diff --git a/lib/Analysis/TargetTransformInfo.cpp b/lib/Analysis/TargetTransformInfo.cpp index c1ffb9daefa..3d8a37811ba 100644 --- a/lib/Analysis/TargetTransformInfo.cpp +++ b/lib/Analysis/TargetTransformInfo.cpp @@ -403,6 +403,10 @@ struct NoTTI final : ImmutablePass, TargetTransformInfo { case Intrinsic::objectsize: case Intrinsic::ptr_annotation: case Intrinsic::var_annotation: + case Intrinsic::experimental_gc_result_int: + case Intrinsic::experimental_gc_result_float: + case Intrinsic::experimental_gc_result_ptr: + case Intrinsic::experimental_gc_relocate: // These intrinsics don't actually represent code after lowering. return TCC_Free; } diff --git a/lib/IR/Verifier.cpp b/lib/IR/Verifier.cpp index 9698dbd77fd..887631b0171 100644 --- a/lib/IR/Verifier.cpp +++ b/lib/IR/Verifier.cpp @@ -2559,7 +2559,88 @@ void Verifier::visitIntrinsicFunctionCall(Intrinsic::ID ID, CallInst &CI) { Assert1(isa(CI.getArgOperand(1)), "llvm.invariant.end parameter #2 must be a constant integer", &CI); break; + + case Intrinsic::experimental_gc_statepoint: { + // target, # call args = 0, # deopt args = 0, #gc args = 0 -> 4 args + assert(CI.getNumArgOperands() >= 4 && + "not enough arguments to statepoint"); + for (User* U : CI.users()) { + const CallInst* GCRelocCall = cast(U); + const Function *GCRelocFn = GCRelocCall->getCalledFunction(); + Assert1(GCRelocFn && GCRelocFn->isDeclaration() && + (GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_result_int || + GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_result_float || + GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_result_ptr || + GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_relocate), + "gc.result or gc.relocate are the only value uses of statepoint", &CI); + if (GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_result_int || + GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_result_float || + GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_result_ptr ) { + Assert1(GCRelocCall->getNumArgOperands() == 1, "wrong number of arguments", &CI); + Assert2(GCRelocCall->getArgOperand(0) == &CI, "connected to wrong statepoint", &CI, GCRelocCall); + } else if (GCRelocFn->getIntrinsicID() == Intrinsic::experimental_gc_relocate) { + Assert1(GCRelocCall->getNumArgOperands() == 3, "wrong number of arguments", &CI); + Assert2(GCRelocCall->getArgOperand(0) == &CI, "connected to wrong statepoint", &CI, GCRelocCall); + } else { + llvm_unreachable("unsupported use type - how'd we get past the assert?"); + } + } + + // Note: It is legal for a single derived pointer to be listed multiple + // times. It's non-optimal, but it is legal. It can also happen after + // insertion if we strip a bitcast away. + // Note: It is really tempting to check that each base is relocated and + // that a derived pointer is never reused as a base pointer. This turns + // out to be problematic since optimizations run after safepoint insertion + // can recognize equality properties that the insertion logic doesn't know + // about. See example statepoint.ll in the verifier subdirectory + break; } + case Intrinsic::experimental_gc_result_int: + case Intrinsic::experimental_gc_result_float: + case Intrinsic::experimental_gc_result_ptr: { + Assert1(CI.getNumArgOperands() == 1, "wrong number of arguments", &CI); + + // Are we tied to a statepoint properly? + CallSite StatepointCS(CI.getArgOperand(0)); + const Function *StatepointFn = StatepointCS.getCalledFunction(); + Assert2(StatepointFn && StatepointFn->isDeclaration() && + StatepointFn->getIntrinsicID() == Intrinsic::experimental_gc_statepoint, + "token must be from a statepoint", &CI, CI.getArgOperand(0)); + break; + } + case Intrinsic::experimental_gc_relocate: { + // Some checks to ensure gc.relocate has the correct set of + // parameters. TODO: we can make these tests much stricter. + Assert1(CI.getNumArgOperands() == 3, "wrong number of arguments", &CI); + + // Are we tied to a statepoint properly? + CallSite StatepointCS(CI.getArgOperand(0)); + const Function *StatepointFn = + StatepointCS.getInstruction() ? StatepointCS.getCalledFunction() : NULL; + Assert2(StatepointFn && StatepointFn->isDeclaration() && + StatepointFn->getIntrinsicID() == Intrinsic::experimental_gc_statepoint, + "token must be from a statepoint", &CI, CI.getArgOperand(0)); + + // Both the base and derived must be piped through the safepoint + Value* Base = CI.getArgOperand(1); + Assert1( isa(Base), "must be integer offset", &CI); + + Value* Derived = CI.getArgOperand(2); + Assert1( isa(Derived), "must be integer offset", &CI); + + const int BaseIndex = cast(Base)->getZExtValue(); + const int DerivedIndex = cast(Derived)->getZExtValue(); + // Check the bounds + Assert1(0 <= BaseIndex && + BaseIndex < (int)StatepointCS.arg_size(), + "index out of bounds", &CI); + Assert1(0 <= DerivedIndex && + DerivedIndex < (int)StatepointCS.arg_size(), + "index out of bounds", &CI); + break; + } + }; } void DebugInfoVerifier::verifyDebugInfo() { diff --git a/test/Verifier/statepoint.ll b/test/Verifier/statepoint.ll new file mode 100644 index 00000000000..3fbaeb53f91 --- /dev/null +++ b/test/Verifier/statepoint.ll @@ -0,0 +1,50 @@ +; RUN: opt -S %s -verify | FileCheck %s + +declare void @use(...) +declare i8 addrspace(1)* @llvm.gc.relocate.p1i8(i32, i32, i32) +declare i32 @llvm.statepoint.p0f_isVoidf(void ()*, i32, i32, ...) + +;; Basic usage +define i8 addrspace(1)* @test1(i8 addrspace(1)* %arg) { +entry: + %cast = bitcast i8 addrspace(1)* %arg to i64 addrspace(1)* + %safepoint_token = call i32 (void ()*, i32, i32, ...)* @llvm.statepoint.p0f_isVoidf(void ()* undef, i32 0, i32 0, i32 5, i32 0, i32 0, i32 0, i32 10, i32 0, i8 addrspace(1)* %arg, i64 addrspace(1)* %cast, i8 addrspace(1)* %arg, i8 addrspace(1)* %arg) + %reloc = call i8 addrspace(1)* @llvm.gc.relocate.p1i8(i32 %safepoint_token, i32 9, i32 10) + ;; It is perfectly legal to relocate the same value multiple times... + %reloc2 = call i8 addrspace(1)* @llvm.gc.relocate.p1i8(i32 %safepoint_token, i32 9, i32 10) + %reloc3 = call i8 addrspace(1)* @llvm.gc.relocate.p1i8(i32 %safepoint_token, i32 10, i32 9) + ret i8 addrspace(1)* %reloc +; CHECK-LABEL: test1 +; CHECK: statepoint +; CHECK: gc.relocate +; CHECK: gc.relocate +; CHECK: gc.relocate +; CHECK: ret i8 addrspace(1)* %reloc +} + +; This test catches two cases where the verifier was too strict: +; 1) A base doesn't need to be relocated if it's never used again +; 2) A value can be replaced by one which is known equal. This +; means a potentially derived pointer can be known base and that +; we can't check that derived pointer are never bases. +define void @test2(i8 addrspace(1)* %arg, i64 addrspace(1)* %arg2) { +entry: + %cast = bitcast i8 addrspace(1)* %arg to i64 addrspace(1)* + %c = icmp eq i64 addrspace(1)* %cast, %arg2 + br i1 %c, label %equal, label %notequal + +notequal: + ret void + +equal: +%safepoint_token = call i32 (void ()*, i32, i32, ...)* @llvm.statepoint.p0f_isVoidf(void ()* undef, i32 0, i32 0, i32 5, i32 0, i32 0, i32 0, i32 10, i32 0, i8 addrspace(1)* %arg, i64 addrspace(1)* %cast, i8 addrspace(1)* %arg, i8 addrspace(1)* %arg) + %reloc = call i8 addrspace(1)* @llvm.gc.relocate.p1i8(i32 %safepoint_token, i32 9, i32 10) + call void undef(i8 addrspace(1)* %reloc) + ret void +; CHECK-LABEL: test2 +; CHECK-LABEL: equal +; CHECK: statepoint +; CHECK-NEXT: %reloc = call +; CHECK-NEXT: call +; CHECK-NEXT: ret voi +}