OpaqueValues: add support for property wrappers

resolves rdar://163071245
This commit is contained in:
Kavon Farvardin
2025-10-21 16:02:25 -07:00
parent 12a915ff6d
commit 19bd65c89b
15 changed files with 212 additions and 97 deletions

View File

@@ -515,6 +515,9 @@ public:
- getNumIndirectSILErrorResults()];
}
/// WARNING: Do not use this from SILGen!
/// Use methods such as `isSILIndirect` or query the ParameterInfo instead.
///
/// Return the SIL argument convention of apply/entry argument at
/// the given argument index.
SILArgumentConvention getSILArgumentConvention(unsigned index) const;

View File

@@ -87,6 +87,8 @@ SILParameterInfo SILFunctionArgument::getKnownParameterInfo() const {
return getFunction()->getConventions().getParamInfoForSILArg(getIndex());
}
/// WARNING: Do not use this from SILGen!
/// Use methods such as `isSILIndirect` or query the ParameterInfo instead.
SILArgumentConvention
SILFunctionConventions::getSILArgumentConvention(unsigned index) const {
assert(index < getNumSILArguments());

View File

@@ -3189,9 +3189,9 @@ public:
CanSILFunctionType initTy = initFn->getType().castTo<SILFunctionType>();
SILFunctionConventions initConv(initTy, AI->getModule());
require(initConv.getNumIndirectSILResults() ==
require(initConv.getResults().size() ==
AI->getNumInitializedProperties(),
"init function has invalid number of indirect results");
"init function has invalid number of results");
checkAssigOrInitInstAccessorArgs(Src->getType(), initConv);
}

View File

@@ -1822,35 +1822,46 @@ void SILGenFunction::emitPropertyWrappedFieldInitAccessor(
RegularLocation Loc(value);
Loc.markAutoGenerated();
auto loweredFuncDeclTy = F.getLoweredFunctionType();
bool isLocalContext = vd->getDeclContext()->isLocalContext();
auto createArgument = [&](VarDecl *property, SILType type,
bool markUninitialized = false) {
auto *arg = ParamDecl::createImplicit(
getASTContext(), property->getBaseIdentifier(),
property->getBaseIdentifier(), property->getInterfaceType(),
declContext, ParamSpecifier::InOut);
RegularLocation loc(property);
loc.markAutoGenerated();
SILValue argValue = F.begin()->createFunctionArgument(type, arg);
if (markUninitialized) {
argValue = B.createMarkUninitializedOut(loc, argValue);
}
VarLocs[arg] = VarLoc(argValue, SILAccessEnforcement::Static);
InitAccessorArgumentMappings[property] = arg;
};
// Emit @out backing storage argument
auto backingStorage = vd->getPropertyWrapperBackingProperty();
auto backingStorageTy = getSILTypeInContext(
loweredFuncDeclTy->getResults()[0], loweredFuncDeclTy);
createArgument(backingStorage, backingStorageTy, /*markUninitialized=*/true);
Type returnTy = vd->getPropertyWrapperBackingPropertyType();
// Emit `newValue` argument
/// This thunk uses its own return convention, unlike a usual function,
/// by always returning its result indirect, even if the type is trivial.
///
/// Because of that, much of the work that would normally be handled in SILGen
/// using standard function conventions is done manually here, when
/// -enable-sil-opaque-values is disabled. Part of the reason for this is
/// that calls to this accessor are lowered after DefiniteInitialization.
if (useLoweredAddresses()) {
returnTy = TupleType::getEmpty(F.getASTContext());
auto loweredFuncDeclTy = F.getLoweredFunctionType();
auto createArgument = [&](VarDecl *property, SILType type,
bool markUninitialized = false) {
auto *arg = ParamDecl::createImplicit(
getASTContext(), property->getBaseIdentifier(),
property->getBaseIdentifier(), property->getInterfaceType(),
declContext, ParamSpecifier::InOut);
RegularLocation loc(property);
loc.markAutoGenerated();
SILValue argValue = F.begin()->createFunctionArgument(type, arg);
if (markUninitialized) {
argValue = B.createMarkUninitializedOut(loc, argValue);
}
VarLocs[arg] = VarLoc(argValue, SILAccessEnforcement::Static);
InitAccessorArgumentMappings[property] = arg;
};
// Emit @out backing storage argument
auto backingStorageTy = getSILTypeInContext(
loweredFuncDeclTy->getResults()[0], loweredFuncDeclTy);
createArgument(backingStorage, backingStorageTy, /*markUninitialized=*/true);
}
// Create the `newValue` argument
ParameterList *params = nullptr;
auto newValueParam = new (ctx)
ParamDecl(SourceLoc(), SourceLoc(), ctx.getIdentifier("$input_value"),
@@ -1864,9 +1875,10 @@ void SILGenFunction::emitPropertyWrappedFieldInitAccessor(
// Nominal contexts have an extra trailing metatype argument,
// local contexts do not
const bool isLocalContext = vd->getDeclContext()->isLocalContext();
auto numIgnoredParams = isLocalContext ? 0 : 1;
emitBasicProlog(declContext, params,
/*selfParam=*/nullptr, TupleType::getEmpty(F.getASTContext()),
/*selfParam=*/nullptr, returnTy,
/*errorType=*/std::nullopt,
/*throwsLoc=*/SourceLoc(),
/*ignored parameters*/ numIgnoredParams);
@@ -1875,9 +1887,14 @@ void SILGenFunction::emitPropertyWrappedFieldInitAccessor(
if (!isLocalContext)
emitConstructorMetatypeArg(*this, vd);
prepareEpilog(declContext, TupleType::getEmpty(F.getASTContext()),
prepareEpilog(declContext, returnTy,
std::nullopt, CleanupLocation(Loc));
if (EmitProfilerIncrement)
emitProfilerIncrement(value);
FullExpr scope(Cleanups, CleanupLocation(value));
// Create an opaque value binding that maps 'newValue' to the wrapper's
// wrappedValue AST placeholder. This makes the argument available when
// init(wrappedValue:) is emitted
@@ -1889,16 +1906,19 @@ void SILGenFunction::emitPropertyWrappedFieldInitAccessor(
maybeEmitValueOfLocalVarDecl(newValueParam, AccessKind::Read));
assert(value == initInfo.getInitFromWrappedValue());
// Prepare InitializationPtr for the @out return buffer
FullExpr scope(Cleanups, CleanupLocation(value));
auto backingStorageArg = InitAccessorArgumentMappings[backingStorage];
auto backingStorageAddr = VarLocs[backingStorageArg].value;
InitializationPtr init(new KnownAddressInitialization(backingStorageAddr));
if (useLoweredAddresses()) {
// Prepare InitializationPtr for the @out return buffer
auto backingStorageArg = InitAccessorArgumentMappings[backingStorage];
auto backingStorageAddr = VarLocs[backingStorageArg].value;
InitializationPtr init(new KnownAddressInitialization(backingStorageAddr));
// Intialize the @out buffer with the given expression
emitExprInto(value, init.get());
// Intialize the @out buffer with the given expression
emitExprInto(value, init.get());
} else {
emitReturnExpr(Loc, value);
}
// Emit epilog/cleanups
emitEpilog(Loc);
mergeCleanupBlocks();
}
}

View File

@@ -1883,9 +1883,10 @@ SILGenFunction::emitApplyOfSetterToBase(SILLocation loc, SILDeclRef setter,
assert(base);
SILValue capturedBase;
unsigned argIdx = setterConv.getNumSILArguments() - 1;
unsigned argIdx = setterConv.getSILArgIndexOfSelf();
auto paramInfo = setterConv.getParamInfoForSILArg(argIdx);
if (setterConv.getSILArgumentConvention(argIdx).isInoutConvention()) {
if (paramInfo.isIndirectMutating()) {
capturedBase = base.getValue();
} else if (base.getType().isAddress() &&
base.getType().getObjectType() ==
@@ -1975,9 +1976,9 @@ void SILGenFunction::emitAssignOrInit(SILLocation loc, ManagedValue selfValue,
SILFunctionConventions initConv(initTy, SGM.M);
auto newValueArgIdx = initConv.getSILArgIndexOfFirstParam();
auto newValueParamInfo = initConv.getParamInfoForSILArg(newValueArgIdx);
// If we need the argument in memory, materialize an address.
if (initConv.getSILArgumentConvention(newValueArgIdx)
.isIndirectConvention() &&
if (initConv.isSILIndirect(newValueParamInfo) &&
!newValue.getType().isAddress()) {
newValue = newValue.materialize(*this, loc);
}

View File

@@ -1528,8 +1528,10 @@ namespace {
return cast<AccessorDecl>(Accessor.getFuncDecl());
}
ManagedValue emitValue(SILGenFunction &SGF, SILLocation loc, VarDecl *field,
ArgumentSource &&value, AccessorKind accessorKind) {
ManagedValue emitValueForAssignOrInit(SILGenFunction &SGF, SILLocation loc,
VarDecl *field,
ArgumentSource &&value,
AccessorKind accessorKind) {
auto accessorInfo = SGF.getConstantInfo(
SGF.getTypeExpansionContext(),
SILDeclRef(field->getOpaqueAccessor(accessorKind)));
@@ -1553,7 +1555,8 @@ namespace {
assert(value.isRValue());
ManagedValue Mval =
std::move(value).asKnownRValue(SGF).getAsSingleValue(SGF, loc);
auto param = accessorTy->getParameters()[0];
auto paramIdx = accessorConv.getSILArgIndexOfFirstParam();
auto param = accessorConv.getParamInfoForSILArg(paramIdx);
SILType loweredSubstArgType = Mval.getType();
if (param.isIndirectInOut()) {
loweredSubstArgType =
@@ -1572,11 +1575,8 @@ namespace {
fieldTy->getCanonicalType());
}
auto newValueArgIdx = accessorConv.getNumIndirectSILResults();
// If we need the argument in memory, materialize an address.
if (accessorConv.getSILArgumentConvention(newValueArgIdx)
.isIndirectConvention() &&
!Mval.getType().isAddress()) {
if (accessorConv.isSILIndirect(param) && !Mval.getType().isAddress()) {
Mval = Mval.materialize(SGF, loc);
}
@@ -1610,7 +1610,7 @@ namespace {
override {
VarDecl *field = cast<VarDecl>(Storage);
auto Mval =
emitValue(SGF, loc, field, std::move(value), AccessorKind::Init);
emitValueForAssignOrInit(SGF, loc, field, std::move(value), AccessorKind::Init);
SGF.emitAssignOrInit(loc, base, field, Mval, Substitutions);
}
@@ -1875,7 +1875,7 @@ namespace {
// Create the assign_or_init SIL instruction
auto Mval =
emitValue(SGF, loc, field, std::move(value), AccessorKind::Set);
emitValueForAssignOrInit(SGF, loc, field, std::move(value), AccessorKind::Set);
auto selfOrLocal = selfMetatype ? base.getValue() : proj.forward(SGF);
SGF.B.createAssignOrInit(loc, field, selfOrLocal, Mval.forward(SGF),
initFn.getValue(), setterFn,

View File

@@ -1331,11 +1331,9 @@ static void emitCaptureArguments(SILGenFunction &SGF,
case CaptureKind::Immutable: {
auto argIndex = SGF.F.begin()->getNumArguments();
// Non-escaping stored decls are captured as the address of the value.
auto argConv = SGF.F.getConventions().getSILArgumentConvention(argIndex);
bool isInOut = (argConv == SILArgumentConvention::Indirect_Inout ||
argConv == SILArgumentConvention::Indirect_InoutAliasable);
auto param = SGF.F.getConventions().getParamInfoForSILArg(argIndex);
if (SGF.F.getConventions().isSILIndirect(param)) {
auto fnConv = SGF.F.getConventions();
auto paramInfo = fnConv.getParamInfoForSILArg(argIndex);
if (fnConv.isSILIndirect(paramInfo)) {
ty = ty.getAddressType();
}
auto *fArg = SGF.F.begin()->createFunctionArgument(ty, VD);
@@ -1343,7 +1341,8 @@ static void emitCaptureArguments(SILGenFunction &SGF,
arg = SILValue(fArg);
if (isNoImplicitCopy && !arg->getType().isMoveOnly()) {
switch (argConv) {
// FIXME: this incompatible with -enable-sil-opaque-values
switch (fnConv.getSILArgumentConvention(argIndex)) {
case SILArgumentConvention::Indirect_Inout:
case SILArgumentConvention::Indirect_InoutAliasable:
case SILArgumentConvention::Indirect_In:
@@ -1377,6 +1376,7 @@ static void emitCaptureArguments(SILGenFunction &SGF,
// in SIL since it is illegal to capture an inout value in an escaping
// closure. The later code knows how to handle that we have the
// mark_unresolved_non_copyable_value here.
bool isInOut = paramInfo.isIndirectMutating();
if (isInOut && arg->getType().isMoveOnly()) {
arg = SGF.B.createMarkUnresolvedNonCopyableValueInst(
Loc, arg,

View File

@@ -206,8 +206,8 @@ lowerAssignOrInitInstruction(SILBuilderWithScope &b,
auto inLocalContext =
inst->getProperty()->getDeclContext()->isLocalContext();
auto selfOrLocalValue = inst->getSelfOrLocalOperand();
bool isRefSelf =
const auto selfOrLocalValue = inst->getSelfOrLocalOperand();
const bool isRefSelf =
selfOrLocalValue->getType().getASTType()->mayHaveSuperclass();
auto emitFieldReference = [&](SILValue selfRef, VarDecl *field,
@@ -225,40 +225,58 @@ lowerAssignOrInitInstruction(SILBuilderWithScope &b,
return fieldRef;
};
SmallVector<SILValue> arguments;
/// Returns the reference of self ready for modification, and a flag
/// indicating whether a begin_access was emitted that must be ended.
auto emitBeginModifyOfSelf = [&]() -> std::pair<SILValue, bool> {
if (isRefSelf)
return {b.emitBeginBorrowOperation(loc, selfOrLocalValue), false};
// First, emit all of the properties listed in `initializes`. They
// are passed as indirect results.
SILValue selfRef = nullptr;
bool needInsertEndAccess = false;
if (inLocalContext) {
// add the local projection which is for the _x backing local storage
arguments.push_back(selfOrLocalValue);
} else {
if (isRefSelf) {
selfRef = b.emitBeginBorrowOperation(loc, selfOrLocalValue);
} else if (isa<BeginAccessInst>(selfOrLocalValue)) {
// Don't insert an access scope if there is already one. This avoids
// inserting a dynamic access check when the parent is static (and therefore
// can be statically enforced).
selfRef = selfOrLocalValue;
}
else {
selfRef =
b.createBeginAccess(loc, selfOrLocalValue, SILAccessKind::Modify,
SILAccessEnforcement::Dynamic,
/*noNestedConflict=*/false,
/*fromBuiltin=*/false);
needInsertEndAccess = true;
// Don't insert an access scope if there is already one. This avoids
// inserting a dynamic access check when the parent is static (and
// therefore can be statically enforced).
if (auto *bai = dyn_cast<BeginAccessInst>(selfOrLocalValue)) {
bai->setAccessKind(SILAccessKind::Modify);
return {selfOrLocalValue, false};
}
unsigned index = 0;
inst->forEachInitializedProperty([&](VarDecl *property) {
arguments.push_back(emitFieldReference(
selfRef, property,
/*emitDestroy=*/inst->isPropertyAlreadyInitialized(index)));
index++;
});
return {b.createBeginAccess(loc, selfOrLocalValue,
SILAccessKind::Modify,
SILAccessEnforcement::Dynamic,
/*noNestedConflict=*/false,
/*fromBuiltin=*/false),
/*needsEndAccess=*/true};
};
auto initializeAddress = [&b](SILLocation loc, SILValue src, SILValue dest) {
ASSERT(dest->getType().isAddress());
auto qualifier = src->getType().isTrivial(b.getFunction())
? StoreOwnershipQualifier::Trivial
: StoreOwnershipQualifier::Init;
b.createStore(loc, src, dest, qualifier);
};
// Set-up the arguments for the apply.
SmallVector<SILValue> arguments;
SILValue selfRef = nullptr;
bool needInsertEndAccess = false;
if (convention.useLoweredAddresses()) {
// First, emit all of the properties listed in `initializes`. They
// are passed as indirect results.
if (inLocalContext) {
// add the local projection which is for the _x backing local storage
arguments.push_back(selfOrLocalValue);
} else {
std::tie(selfRef, needInsertEndAccess) = emitBeginModifyOfSelf();
unsigned index = 0;
inst->forEachInitializedProperty([&](VarDecl *property) {
arguments.push_back(emitFieldReference(
selfRef, property,
/*emitDestroy=*/inst->isPropertyAlreadyInitialized(index)));
index++;
});
}
}
// Now emit `initialValue` which is the only argument specified
@@ -270,7 +288,40 @@ lowerAssignOrInitInstruction(SILBuilderWithScope &b,
for (auto *property : inst->getAccessedProperties())
arguments.push_back(emitFieldReference(selfRef, property));
b.createApply(loc, initFn, SubstitutionMap(), arguments);
// Actually emit the apply.
auto apply = b.createApply(loc, initFn, SubstitutionMap(), arguments);
ASSERT(apply->getNumResults() == 1 && "multi-result not handled yet");
// Handle direct results in the case of -enable-sil-opaque-values
if (!convention.useLoweredAddresses()) {
// Write the result(s) to the field(s) to be initialized.
if (inLocalContext) {
initializeAddress(loc, apply->getResult(0), selfOrLocalValue);
} else {
std::tie(selfRef, needInsertEndAccess) = emitBeginModifyOfSelf();
SILValue result = apply->getResult(0);
ASSERT(!result->getType().isTuple() && "insert destructure_tuple?");
SmallVector<SILValue> results;
results.push_back(result);
ASSERT(results.size() == inst->getNumInitializedProperties());
unsigned index = 0;
inst->forEachInitializedProperty([&](VarDecl *property) {
auto fieldRef = emitFieldReference(
selfRef, property,
/*emitDestroy=*/inst->isPropertyAlreadyInitialized(index));
initializeAddress(loc, results[index], fieldRef);
index++;
});
}
}
if (selfRef) {
if (isRefSelf) {

View File

@@ -1,4 +1,5 @@
// RUN: %target-run-simple-swift | %FileCheck %s
// RUN: %target-run-simple-swift(-Xfrontend -enable-sil-opaque-values) | %FileCheck %s
// REQUIRES: executable_test
protocol Observed: AnyObject {

View File

@@ -1,6 +1,10 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-module -o %t -enable-library-evolution %S/Inputs/property_wrapper_defs.swift
// RUN: %target-swift-emit-silgen -Xllvm -sil-print-types -primary-file %s -I %t | %FileCheck %s
// RUN: %target-swift-emit-silgen -sil-verify-all -Xllvm -sil-print-types -primary-file %s -I %t | %FileCheck %s --check-prefixes CHECK,ADR
// RUN: %target-swift-frontend -emit-module -o %t -enable-library-evolution %S/Inputs/property_wrapper_defs.swift -enable-sil-opaque-values
// RUN: %target-swift-emit-silgen -sil-verify-all -Xllvm -sil-print-types -primary-file %s -I %t -enable-sil-opaque-values | %FileCheck %s --check-prefixes CHECK,OV
import property_wrapper_defs
@propertyWrapper
@@ -58,18 +62,22 @@ func forceHasMemberwiseInit() {
// variable initialization expression of HasMemberwiseInit._x
// CHECK-LABEL: sil hidden [transparent] [ossa] @$s17property_wrappers17HasMemberwiseInitV2_x33_{{.*}}AA7WrapperVySbGvpfi : $@convention(thin) <T where T : DefaultInit> () -> Wrapper<Bool> {
// CHECK: bb0:
// CHECK: integer_literal $Builtin.Int1, 0
// CHECK-NOT: return
// CHECK: function_ref @$sSb22_builtinBooleanLiteralSbBi1__tcfC : $@convention(method) (Builtin.Int1, @thin Bool.Type) -> Bool
// CHECK-NOT: return
// CHECK: function_ref @$s17property_wrappers7WrapperV5valueACyxGx_tcfC : $@convention(method) <τ_0_0> (@in τ_0_0, @thin Wrapper<τ_0_0>.Type) -> @out Wrapper<τ_0_0> // user: %9
// CHECK: function_ref @$s17property_wrappers7WrapperV5valueACyxGx_tcfC : $@convention(method) <τ_0_0> (@in τ_0_0, @thin Wrapper<τ_0_0>.Type) -> @out Wrapper<τ_0_0>
// CHECK: return {{%.*}} : $Wrapper<Bool>
// variable initialization expression of HasMemberwiseInit.$y
// CHECK-LABEL: sil hidden [transparent] [ossa] @$s17property_wrappers17HasMemberwiseInitV2_y33_{{.*}}23WrapperWithInitialValueVyxGvpfi : $@convention(thin) <T where T : DefaultInit> () -> @out
// CHECK: bb0(%0 : $*T):
// ADR: bb0(%0 : $*T):
// OV: bb0:
// CHECK-NOT: return
// CHECK: witness_method $T, #DefaultInit.init!allocator : <Self where Self : DefaultInit> (Self.Type) -> () -> Self : $@convention(witness_method: DefaultInit) <τ_0_0 where τ_0_0 : DefaultInit> (@thick τ_0_0.Type) -> @out τ_0_0
// ADR: return {{%.*}} : $()
// OV: return {{%.*}} : $T
// variable initialization expression of HasMemberwiseInit._z
// CHECK-LABEL: sil hidden [transparent] [ossa] @$s17property_wrappers17HasMemberwiseInitV2_z33_{{.*}}23WrapperWithInitialValueVySiGvpfi : $@convention(thin) <T where T : DefaultInit> () -> WrapperWithInitialValue<Int> {
@@ -78,6 +86,7 @@ func forceHasMemberwiseInit() {
// CHECK: integer_literal $Builtin.IntLiteral, 17
// CHECK-NOT: return
// CHECK: function_ref @$s17property_wrappers23WrapperWithInitialValueV07wrappedF0ACyxGx_tcfC : $@convention(method) <τ_0_0> (@in τ_0_0, @thin WrapperWithInitialValue<τ_0_0>.Type) -> @out WrapperWithInitialValue<τ_0_0>
// CHECK: return {{%.*}} : $WrapperWithInitialValue<Int>
// variable initialization expression of HasMemberwiseInit._p
// CHECK-LABEL: sil hidden [transparent] [ossa] @$s17property_wrappers17HasMemberwiseInitV2_p33_{{.*}}23WrapperWithInitialValueVySbGvpfi : $@convention(thin) <T where T : DefaultInit> () -> Bool {

View File

@@ -3,6 +3,10 @@
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s
// RUN: %target-build-swift %s -Xfrontend -enable-sil-opaque-values -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s
// REQUIRES: executable_test
@propertyWrapper

View File

@@ -1,4 +1,5 @@
// RUN: %target-swift-frontend -emit-sil -verify %s
// RUN: %target-swift-frontend -enable-sil-opaque-values -emit-sil -verify %s
@propertyWrapper
final class ClassWrapper<T> {

View File

@@ -3,6 +3,10 @@
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out
// RUN: %target-build-swift %s -Xfrontend -enable-sil-opaque-values -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out
// REQUIRES: executable_test
import StdlibUnittest

View File

@@ -1,4 +1,4 @@
// RUN: %target-swift-frontend -enable-sil-opaque-values -parse-as-library -emit-sil -O %s | %FileCheck %s
// RUN: %target-swift-frontend -sil-verify-all -enable-sil-opaque-values -parse-as-library -emit-sil -O %s | %FileCheck %s
// Verify the arguments. When AccessPathVerification runs, it will be checked
// that the ParamDecl that AddressLowering synthesizes has a specifier
@@ -9,3 +9,17 @@
public func min<T: Comparable>(_ x: T, _ y: T) -> T {
return y < x ? y : x
}
// This example use to produce invalid SIL after RawSILInstLowering of assign_or_init
@propertyWrapper
struct WrapperWithInitialValue<T> {
var wrappedValue: T
}
public protocol TestProtocol {}
public class TestClass<T> {
@WrapperWithInitialValue var value: T
init<U: TestProtocol>(value: T, protocol: U) {
self.value = value
}
}

View File

@@ -1,8 +1,13 @@
// RUN: %empty-directory(%t)
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %s -module-name=a -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s
// RUN: %target-build-swift %s -Xfrontend -enable-sil-opaque-values -module-name=a -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s
// REQUIRES: executable_test
@propertyWrapper struct D<Value> {