Remove the substitution map from zeroInitializer builtin in SIL.

This is a value operation that can work just fine on lowered types,
so there's no need to carry along a formal type. Make the value/address
duality clearer, and enforce it in the verifier.
This commit is contained in:
John McCall
2025-03-26 00:34:15 -04:00
parent a7b8cb79cd
commit 3fe70968cc
13 changed files with 110 additions and 53 deletions

View File

@@ -719,9 +719,6 @@ BUILTIN_MISC_OPERATION(IsNegative, "isNegative", "n", Special)
/// (_ value: Builtin.IntLiteral, _ index: Builtin.Word) -> Builtin.Word
BUILTIN_MISC_OPERATION(WordAtIndex, "wordAtIndex", "n", Special)
/// zeroInitializer has type <T> () -> T
BUILTIN_MISC_OPERATION(ZeroInitializer, "zeroInitializer", "n", Special)
/// once has type (Builtin.RawPointer, (Builtin.RawPointer) -> ())
BUILTIN_MISC_OPERATION(Once, "once", "", Special)
/// onceWithContext has type (Builtin.RawPointer, (Builtin.RawPointer) -> (), Builtin.RawPointer)
@@ -842,6 +839,10 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(Strideof, "strideof", "n", Special)
/// Alignof has type T.Type -> Int
BUILTIN_MISC_OPERATION_WITH_SILGEN(Alignof, "alignof", "n", Special)
/// zeroInitializer has type <T> () -> T, but the SIL builtin has its
/// own rules.
BUILTIN_MISC_OPERATION_WITH_SILGEN(ZeroInitializer, "zeroInitializer", "n", Special)
// getCurrentExecutor: () async -> Builtin.Executor?
//
// Retrieve the SerialExecutorRef on which the current asynchronous

View File

@@ -2422,6 +2422,16 @@ public:
valueType, paramType));
}
/// Create a zero-initialized value of the given (loadable) type.
///
/// This is currently only expected to be used in narrow situations
/// involving bridging and only makes a best effort attempt.
SILValue createZeroInitValue(SILLocation loc, SILType loweredTy);
/// Zero-initialize an object in memory of the given type (which may
/// or may not be loadable).
BuiltinInst *createZeroInitAddr(SILLocation loc, SILValue addr);
//===--------------------------------------------------------------------===//
// Unchecked cast helpers
//===--------------------------------------------------------------------===//

View File

@@ -1327,18 +1327,18 @@ void irgen::emitBuiltinCall(IRGenFunction &IGF, const BuiltinInfo &Builtin,
}
if (Builtin.ID == BuiltinValueKind::ZeroInitializer) {
// Build a zero initializer of the result type.
auto valueTy = getLoweredTypeAndTypeInfo(IGF.IGM,
substitutions.getReplacementTypes()[0]);
if (args.size() > 0) {
auto valueType = argTypes[0];
auto &valueTI = IGF.IGM.getTypeInfo(valueType);
// `memset` the memory addressed by the argument.
auto address = args.claimNext();
IGF.Builder.CreateMemSet(valueTy.second.getAddressForPointer(address),
IGF.Builder.CreateMemSet(valueTI.getAddressForPointer(address),
llvm::ConstantInt::get(IGF.IGM.Int8Ty, 0),
valueTy.second.getSize(IGF, argTypes[0]));
valueTI.getSize(IGF, valueType));
} else {
auto schema = valueTy.second.getSchema();
auto &resultTI = cast<LoadableTypeInfo>(IGF.IGM.getTypeInfo(resultType));
auto schema = resultTI.getSchema();
for (auto &elt : schema) {
out.add(llvm::Constant::getNullValue(elt.getScalarType()));
}

View File

@@ -4165,9 +4165,9 @@ protected:
BuiltinValueKind::ZeroInitializer) {
auto build = assignment.getBuilder(++bi->getIterator());
auto newAddr = assignment.createAllocStack(bi->getType());
build.createZeroInitAddr(bi->getLoc(), newAddr);
assignment.mapValueToAddress(origValue, newAddr);
build.createStore(bi->getLoc(), origValue, newAddr,
StoreOwnershipQualifier::Unqualified);
assignment.markForDeletion(bi);
} else {
singleValueInstructionFallback(bi);
}

View File

@@ -805,6 +805,26 @@ CheckedCastBranchInst *SILBuilder::createCheckedCastBranch(
target2Count, forwardingOwnershipKind));
}
BuiltinInst *SILBuilder::createZeroInitAddr(SILLocation loc, SILValue addr) {
assert(addr->getType().isAddress());
auto &C = getASTContext();
auto zeroInit = getBuiltinValueDecl(C, C.getIdentifier("zeroInitializer"));
return createBuiltin(loc, zeroInit->getBaseIdentifier(),
SILType::getEmptyTupleType(C),
SubstitutionMap(),
addr);
}
SILValue SILBuilder::createZeroInitValue(SILLocation loc, SILType loweredTy) {
assert(loweredTy.isObject());
auto &C = getASTContext();
auto zeroInit = getBuiltinValueDecl(C, C.getIdentifier("zeroInitializer"));
return createBuiltin(loc, zeroInit->getBaseIdentifier(),
loweredTy,
SubstitutionMap(),
{});
}
void SILBuilderWithScope::insertAfter(SILInstruction *inst,
function_ref<void(SILBuilder &)> func) {
if (isa<TermInst>(inst)) {

View File

@@ -1009,6 +1009,11 @@ public:
#define requireAddressType(type, value, valueDescription) \
_requireAddressType<type>(value, valueDescription, #type)
void requireVoidObjectType(SILType type, const Twine &valueDescription) {
_require(type.isObject() && type.isVoid(),
valueDescription + " must be a scalar of type ()");
}
template <class T>
typename CanTypeWrapperTraits<T>::type
_forbidObjectType(SILType type, const Twine &valueDescription,
@@ -2375,6 +2380,23 @@ public:
auto builtinKind = BI->getBuiltinKind();
auto arguments = BI->getArguments();
if (builtinKind == BuiltinValueKind::ZeroInitializer) {
require(!BI->getSubstitutions(),
"zeroInitializer has no generic arguments as a SIL builtin");
if (arguments.size() == 0) {
require(!fnConv.useLoweredAddresses()
|| BI->getType().isLoadable(*BI->getFunction()),
"scalar zeroInitializer must have a loadable result type");
} else {
require(arguments.size() == 1,
"zeroInitializer cannot have multiple arguments");
require(arguments[0]->getType().isAddress(),
"zeroInitializer argument must have address type");
requireVoidObjectType(BI->getType(),
"result of zeroInitializer");
}
}
// Check that 'getCurrentAsyncTask' only occurs within an async function.
if (builtinKind == BuiltinValueKind::GetCurrentAsyncTask) {
require(F.isAsync(),

View File

@@ -2097,6 +2097,26 @@ static ManagedValue emitBuiltinAddressOfRawLayout(SILGenFunction &SGF,
return ManagedValue::forObjectRValueWithoutOwnership(bi);
}
static ManagedValue emitBuiltinZeroInitializer(SILGenFunction &SGF,
SILLocation loc,
SubstitutionMap subs,
ArrayRef<ManagedValue> args,
SGFContext C) {
auto valueType = subs.getReplacementTypes()[0]->getCanonicalType();
auto &valueTL = SGF.getTypeLowering(valueType);
auto loweredValueTy = valueTL.getLoweredType().getObjectType();
if (valueTL.isLoadable() ||
!SGF.F.getConventions().useLoweredAddresses()) {
auto value = SGF.B.createZeroInitValue(loc, loweredValueTy);
return SGF.emitManagedRValueWithCleanup(value, valueTL);
}
SILValue valueAddr = SGF.getBufferForExprResult(loc, loweredValueTy, C);
SGF.B.createZeroInitAddr(loc, valueAddr);
return SGF.manageBufferForExprResult(valueAddr, valueTL, C);
}
static ManagedValue emitBuiltinEmplace(SILGenFunction &SGF,
SILLocation loc,
SubstitutionMap subs,
@@ -2129,15 +2149,7 @@ static ManagedValue emitBuiltinEmplace(SILGenFunction &SGF,
// Aside from providing a modicum of predictability if the memory isn't
// actually initialized, this also serves to communicate to DI that the memory
// is considered initialized from this point.
auto zeroInit = getBuiltinValueDecl(Ctx,
Ctx.getIdentifier("zeroInitializer"));
SGF.B.createBuiltin(loc, zeroInit->getBaseIdentifier(),
SILType::getEmptyTupleType(Ctx),
SubstitutionMap::get(zeroInit->getInnermostDeclContext()
->getGenericSignatureOfContext(),
{resultASTTy},
LookUpConformanceInModule()),
buffer);
SGF.B.createZeroInitAddr(loc, buffer);
SILValue bufferPtr = SGF.B.createAddressToPointer(loc, buffer,
SILType::getPrimitiveObjectType(SGF.getASTContext().TheRawPointerType),

View File

@@ -739,15 +739,7 @@ void SILGenFunction::emitValueConstructor(ConstructorDecl *ctor) {
if (nominal->getAttrs().hasAttribute<RawLayoutAttr>()) {
// Raw memory is not directly decomposable, but we still want to mark
// it as initialized. Use a zero initializer.
auto &C = ctor->getASTContext();
auto zeroInit = getBuiltinValueDecl(C, C.getIdentifier("zeroInitializer"));
B.createBuiltin(ctor, zeroInit->getBaseIdentifier(),
SILType::getEmptyTupleType(C),
SubstitutionMap::get(zeroInit->getInnermostDeclContext()
->getGenericSignatureOfContext(),
{selfDecl->getTypeInContext()},
LookUpConformanceInModule()),
selfLV.getLValueAddress());
B.createZeroInitAddr(ctor, selfLV.getLValueAddress());
} else if (isa<StructDecl>(nominal)
&& lowering.getLoweredType().isMoveOnly()
&& nominal->getStoredProperties().empty()) {

View File

@@ -7,7 +7,7 @@
// Make sure that the SIL ownership verifier passes.
// UnsafeUnretainedBlockClass.init()
// CHECK-LABEL: sil hidden @$s16objc_init_blocks26UnsafeUnretainedBlockClassCACycfc : $@convention(method) (@owned UnsafeUnretainedBlockClass) -> @owned UnsafeUnretainedBlockClass {
// CHECK: [[ZI:%.*]] = builtin "zeroInitializer"<objc_bool_block>() : $objc_bool_block
// CHECK: [[ZI:%.*]] = builtin "zeroInitializer"() : $objc_bool_block
// CHECK: store [[ZI]] to %{{.*}} : $*objc_bool_block
// CHECK-LABEL: } // end sil function '$s16objc_init_blocks26UnsafeUnretainedBlockClassCACycfc'
open class UnsafeUnretainedBlockClass {

View File

@@ -1289,18 +1289,18 @@ bb0(%0 : $*C, %1 : $*C, %2 : @owned $C):
// CHECK-LABEL: @test_builtin_zeroInitializer
// CHECK: PAIR #0.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %0 = alloc_stack
// CHECK-NEXT: r=0,w=0
// CHECK: PAIR #1.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %1 = alloc_stack
// CHECK-NEXT: r=0,w=1
sil @test_builtin_zeroInitializer : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $C
%1 = alloc_stack $C
%2 = builtin "zeroInitializer"<C>(%1 : $*C) : $()
%2 = builtin "zeroInitializer"(%1 : $*C) : $()
%3 = apply undef<C>(%1) : $@convention(thin) <C> () -> @out C
copy_addr [take] %1 to [init] %0 : $*C
dealloc_stack %1 : $*C
@@ -1312,18 +1312,18 @@ bb0:
// CHECK-LABEL: @test_builtin_zeroInitializer_atomicload
// CHECK: PAIR #0.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %0 = alloc_stack
// CHECK-NEXT: r=0,w=0
// CHECK: PAIR #1.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %1 = alloc_stack
// CHECK-NEXT: r=0,w=1
sil @test_builtin_zeroInitializer_atomicload : $@convention(thin) () -> Builtin.Int64 {
bb0:
%0 = alloc_stack $C
%1 = alloc_stack $C
%2 = builtin "zeroInitializer"<C>(%1 : $*C) : $()
%2 = builtin "zeroInitializer"(%1 : $*C) : $()
%3 = apply undef<C>(%1) : $@convention(thin) <C> () -> @out C
copy_addr [take] %1 to [init] %0 : $*C
dealloc_stack %1 : $*C
@@ -1336,18 +1336,18 @@ bb0:
// CHECK-LABEL: @test_builtin_zeroInitializer_atomicstore
// CHECK: PAIR #0.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %0 = alloc_stack
// CHECK-NEXT: r=0,w=0
// CHECK: PAIR #1.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %1 = alloc_stack
// CHECK-NEXT: r=0,w=1
sil @test_builtin_zeroInitializer_atomicstore : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $C
%1 = alloc_stack $C
%2 = builtin "zeroInitializer"<C>(%1 : $*C) : $()
%2 = builtin "zeroInitializer"(%1 : $*C) : $()
%3 = apply undef<C>(%1) : $@convention(thin) <C> () -> @out C
copy_addr [take] %1 to [init] %0 : $*C
dealloc_stack %1 : $*C
@@ -1362,18 +1362,18 @@ bb0:
// CHECK-LABEL: @test_builtin_zeroInitializer_atomicrmw
// CHECK: PAIR #0.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %0 = alloc_stack
// CHECK-NEXT: r=0,w=0
// CHECK: PAIR #1.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %1 = alloc_stack
// CHECK-NEXT: r=0,w=1
sil @test_builtin_zeroInitializer_atomicrmw : $@convention(thin) () -> (Builtin.Int64, Builtin.Int64) {
bb0:
%0 = alloc_stack $C
%1 = alloc_stack $C
%2 = builtin "zeroInitializer"<C>(%1 : $*C) : $()
%2 = builtin "zeroInitializer"(%1 : $*C) : $()
%3 = apply undef<C>(%1) : $@convention(thin) <C> () -> @out C
copy_addr [take] %1 to [init] %0 : $*C
dealloc_stack %1 : $*C
@@ -1389,18 +1389,18 @@ bb0:
// CHECK-LABEL: @test_builtin_zeroInitializer_cmpxchg
// CHECK: PAIR #0.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %0 = alloc_stack
// CHECK-NEXT: r=0,w=0
// CHECK: PAIR #1.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %1 = alloc_stack
// CHECK-NEXT: r=0,w=1
sil @test_builtin_zeroInitializer_cmpxchg : $@convention(thin) () -> (Builtin.Int64, Builtin.Int1) {
bb0:
%0 = alloc_stack $C
%1 = alloc_stack $C
%2 = builtin "zeroInitializer"<C>(%1 : $*C) : $()
%2 = builtin "zeroInitializer"(%1 : $*C) : $()
%3 = apply undef<C>(%1) : $@convention(thin) <C> () -> @out C
copy_addr [take] %1 to [init] %0 : $*C
dealloc_stack %1 : $*C
@@ -1414,18 +1414,18 @@ bb0:
// CHECK-LABEL: @test_builtin_zeroInitializer_fence
// CHECK: PAIR #0.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %0 = alloc_stack
// CHECK-NEXT: r=0,w=0
// CHECK: PAIR #1.
// CHECK-NEXT: %2 = builtin "zeroInitializer"<C>(%1 : $*C)
// CHECK-NEXT: %2 = builtin "zeroInitializer"(%1 : $*C)
// CHECK-NEXT: %1 = alloc_stack
// CHECK-NEXT: r=0,w=1
sil @test_builtin_zeroInitializer_fence : $@convention(thin) () -> () {
bb0:
%0 = alloc_stack $C
%1 = alloc_stack $C
%2 = builtin "zeroInitializer"<C>(%1 : $*C) : $()
%2 = builtin "zeroInitializer"(%1 : $*C) : $()
%3 = apply undef<C>(%1) : $@convention(thin) <C> () -> @out C
copy_addr [take] %1 to [init] %0 : $*C
dealloc_stack %1 : $*C

View File

@@ -20,7 +20,7 @@ struct Lock: ~Copyable {
// CHECK-NEXT: sil{{.*}} @[[INIT:\$.*4LockV.*fC]] :
init() {
// CHECK-NOT: destroy_addr
// CHECK: builtin "zeroInitializer"<Lock>
// CHECK: builtin "zeroInitializer"({{%.*}} : $*Lock)
// CHECK-NOT: destroy_addr
// CHECK: [[F:%.*]] = function_ref @init_lock
// CHECK: apply [[F]](

View File

@@ -20,7 +20,7 @@ public struct Cell<T: ~Copyable>: ~Copyable {
// CHECK-LABEL: sil {{.*}} @$s4CellAAVAARi_zrlEyAByxGxcfC : $@convention(method) <T where T : ~Copyable> (@in T, @thin Cell<T>.Type) -> @out Cell<T> {
// CHECK: bb0({{%.*}} : $*Cell<T>, [[VALUE:%.*]] : $*T, {{%.*}} : $@thin Cell<T>.Type):
// CHECK: {{%.*}} = builtin "zeroInitializer"<Cell<T>>([[SELF:%.*]] : $*Cell<T>) : $()
// CHECK: {{%.*}} = builtin "zeroInitializer"([[SELF:%.*]] : $*Cell<T>) : $()
// CHECK-NEXT: [[RAW_LAYOUT_ADDR:%.*]] = builtin "addressOfRawLayout"<Cell<T>>([[SELF]] : $*Cell<T>) : $Builtin.RawPointer
// CHECK-NEXT: [[POINTER:%.*]] = struct $UnsafeMutablePointer<T> ([[RAW_LAYOUT_ADDR]] : $Builtin.RawPointer)
// Calling 'UnsafeMutablePointer<T>.initialize(to:)'

View File

@@ -254,13 +254,13 @@ bb0(%0 : $*T, %1 : $*T):
// CHECK-LABEL: sil @optimize_builtin_zeroInitialize : {{.*}} {
// CHECK: bb0([[RET_ADDR:%[^,]+]] :
// CHECK: builtin "zeroInitializer"<T>([[RET_ADDR]] : $*T) : $()
// CHECK: builtin "zeroInitializer"([[RET_ADDR]] : $*T) : $()
// CHECK: apply undef<T>([[RET_ADDR]])
// CHECK-LABEL: } // end sil function 'optimize_builtin_zeroInitialize'
sil @optimize_builtin_zeroInitialize : $@convention(thin) <T> () -> @out T {
bb0(%ret_addr : $*T):
%temporary = alloc_stack [lexical] $T
builtin "zeroInitializer"<T>(%temporary : $*T) : $()
builtin "zeroInitializer"(%temporary : $*T) : $()
apply undef<T>(%temporary) : $@convention(thin) <T> () -> @out T
copy_addr [take] %temporary to [init] %ret_addr : $*T
dealloc_stack %temporary : $*T