mirror of
https://github.com/c64scene-ar/llvm-6502.git
synced 2024-12-13 04:30:23 +00:00
[Statepoints 1/4] Statepoint infrastructure for garbage collection: IR Intrinsics
The statepoint intrinsics are intended to enable precise root tracking through the compiler as to support garbage collectors of all types. The addition of the statepoint intrinsics to LLVM should have no impact on the compilation of any program which does not contain them. There are no side tables created, no extra metadata, and no inhibited optimizations. A statepoint works by transforming a call site (or safepoint poll site) into an explicit relocation operation. It is the frontend's responsibility (or eventually the safepoint insertion pass we've developed, but that's not part of this patch series) to ensure that any live pointer to a GC object is correctly added to the statepoint and explicitly relocated. The relocated value is just a normal SSA value (as seen by the optimizer), so merges of relocated and unrelocated values are just normal phis. The explicit relocation operation, the fact the statepoint is assumed to clobber all memory, and the optimizers standard semantics ensure that the relocations flow through IR optimizations correctly. This is the first patch in a small series. This patch contains only the IR parts; the documentation and backend support will be following separately. The entire series can be seen as one combined whole in http://reviews.llvm.org/D5683. Reviewed by: atrick, ributzka git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@223078 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
parent
b043278834
commit
204e21b51a
@ -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]>,
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -2559,7 +2559,88 @@ void Verifier::visitIntrinsicFunctionCall(Intrinsic::ID ID, CallInst &CI) {
|
||||
Assert1(isa<ConstantInt>(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<const CallInst>(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<ConstantInt>(Base), "must be integer offset", &CI);
|
||||
|
||||
Value* Derived = CI.getArgOperand(2);
|
||||
Assert1( isa<ConstantInt>(Derived), "must be integer offset", &CI);
|
||||
|
||||
const int BaseIndex = cast<ConstantInt>(Base)->getZExtValue();
|
||||
const int DerivedIndex = cast<ConstantInt>(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() {
|
||||
|
50
test/Verifier/statepoint.ll
Normal file
50
test/Verifier/statepoint.ll
Normal file
@ -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
|
||||
}
|
Loading…
Reference in New Issue
Block a user