Skip to content

Commit d037183

Browse files
Merge pull request #27640 from ravikandhadai/constexpr-closure-support
[Constant Evaluator] Add support for interpreting SIL code with partial applies (i.e., closure creations).
2 parents 3815eff + 940119c commit d037183

File tree

5 files changed

+286
-1
lines changed

5 files changed

+286
-1
lines changed

include/swift/SIL/SILConstants.h

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct DerivedAddressValue;
3333
struct EnumWithPayloadSymbolicValue;
3434
struct SymbolicValueMemoryObject;
3535
struct UnknownSymbolicValue;
36+
struct SymbolicClosure;
3637

3738
extern llvm::cl::opt<unsigned> ConstExprLimit;
3839

@@ -261,6 +262,9 @@ class SymbolicValue {
261262

262263
/// This represents an array.
263264
RK_Array,
265+
266+
/// This represents a closure.
267+
RK_Closure,
264268
};
265269

266270
union {
@@ -305,7 +309,7 @@ class SymbolicValue {
305309
/// information about the memory object and access path of the access.
306310
DerivedAddressValue *derivedAddress;
307311

308-
// The following fields are for representing an Array.
312+
// The following two fields are for representing an Array.
309313
//
310314
// In Swift, an array is a non-trivial struct that stores a reference to an
311315
// internal storage: _ContiguousArrayStorage. Though arrays have value
@@ -329,6 +333,10 @@ class SymbolicValue {
329333
/// When this symbolic value is of an "Array" kind, this stores a memory
330334
/// object that contains a SymbolicArrayStorage value.
331335
SymbolicValueMemoryObject *array;
336+
337+
/// When this symbolic value is of "Closure" kind, store a pointer to the
338+
/// symbolic representation of the closure.
339+
SymbolicClosure *closure;
332340
} value;
333341

334342
RepresentationKind representationKind : 8;
@@ -384,6 +392,9 @@ class SymbolicValue {
384392
/// This represents an array value.
385393
Array,
386394

395+
/// This represents a closure.
396+
Closure,
397+
387398
/// These values are generally only seen internally to the system, external
388399
/// clients shouldn't have to deal with them.
389400
UninitMemory
@@ -533,6 +544,22 @@ class SymbolicValue {
533544
/// Return the type of this array symbolic value.
534545
Type getArrayType() const;
535546

547+
/// Create and return a symbolic value that represents a closure.
548+
/// \param target SILFunction corresponding the target of the closure.
549+
/// \param capturedArguments an array consisting of SILValues of captured
550+
/// arguments along with their symbolic values when available.
551+
/// \param allocator the allocator to use for storing the contents of this
552+
/// symbolic value.
553+
static SymbolicValue makeClosure(
554+
SILFunction *target,
555+
ArrayRef<std::pair<SILValue, Optional<SymbolicValue>>> capturedArguments,
556+
SymbolicValueAllocator &allocator);
557+
558+
SymbolicClosure *getClosure() const {
559+
assert(getKind() == Closure);
560+
return value.closure;
561+
}
562+
536563
//===--------------------------------------------------------------------===//
537564
// Helpers
538565

@@ -607,6 +634,57 @@ struct SymbolicValueMemoryObject {
607634
SymbolicValueMemoryObject(const SymbolicValueMemoryObject &) = delete;
608635
void operator=(const SymbolicValueMemoryObject &) = delete;
609636
};
637+
638+
using SymbolicClosureArgument = std::pair<SILValue, Optional<SymbolicValue>>;
639+
640+
/// Representation of a symbolic closure. A symbolic closure consists of a
641+
/// SILFunction and an array of SIL values, corresponding to the captured
642+
/// arguments, and (optional) symbolic values representing the constant values
643+
/// of the captured arguments. The symbolic values are optional as it is not
644+
/// necessary for every captured argument to be a constant, which enables
645+
/// representing closures whose captured arguments are not compile-time
646+
/// constants.
647+
struct SymbolicClosure final
648+
: private llvm::TrailingObjects<SymbolicClosure, SymbolicClosureArgument> {
649+
650+
friend class llvm::TrailingObjects<SymbolicClosure, SymbolicClosureArgument>;
651+
652+
private:
653+
654+
SILFunction *target;
655+
656+
// The number of SIL values captured by the closure.
657+
unsigned numCaptures;
658+
659+
// True iff there exists captured arguments whose constant value is not known.
660+
bool hasNonConstantCaptures = true;
661+
662+
SymbolicClosure() = delete;
663+
SymbolicClosure(const SymbolicClosure &) = delete;
664+
SymbolicClosure(SILFunction *callee, unsigned numArguments,
665+
bool nonConstantCaptures)
666+
: target(callee), numCaptures(numArguments),
667+
hasNonConstantCaptures(nonConstantCaptures) {}
668+
669+
public:
670+
static SymbolicClosure *create(SILFunction *callee,
671+
ArrayRef<SymbolicClosureArgument> args,
672+
SymbolicValueAllocator &allocator);
673+
674+
ArrayRef<SymbolicClosureArgument> getCaptures() const {
675+
return {getTrailingObjects<SymbolicClosureArgument>(), numCaptures};
676+
}
677+
678+
// This is used by the llvm::TrailingObjects base class.
679+
size_t numTrailingObjects(OverloadToken<SymbolicClosureArgument>) const {
680+
return numCaptures;
681+
}
682+
683+
SILFunction *getTarget() {
684+
return target;
685+
}
686+
};
687+
610688
} // end namespace swift
611689

612690
#endif

lib/SIL/SILConstants.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,29 @@ void SymbolicValue::print(llvm::raw_ostream &os, unsigned indent) const {
126126
case RK_Array: {
127127
os << getArrayType() << ": \n";
128128
getStorageOfArray().print(os, indent);
129+
return;
130+
}
131+
case RK_Closure: {
132+
SymbolicClosure *clo = getClosure();
133+
SILFunction *target = clo->getTarget();
134+
std::string targetName = target->getName();
135+
os << "closure: target: " << targetName;
136+
ArrayRef<SymbolicClosureArgument> args = clo->getCaptures();
137+
os << " captures [\n";
138+
for (SymbolicClosureArgument closureArg : args) {
139+
os.indent(indent + 2) << closureArg.first << "\n";
140+
}
141+
os.indent(indent) << "] values: [\n";
142+
for (SymbolicClosureArgument closureArg : args) {
143+
Optional<SymbolicValue> value = closureArg.second;
144+
if (!value.hasValue()) {
145+
os.indent(indent + 2) << "nil\n";
146+
continue;
147+
}
148+
value->print(os, indent + 2);
149+
}
150+
os.indent(indent) << "]\n";
151+
return;
129152
}
130153
}
131154
}
@@ -162,6 +185,8 @@ SymbolicValue::Kind SymbolicValue::getKind() const {
162185
return ArrayStorage;
163186
case RK_Array:
164187
return Array;
188+
case RK_Closure:
189+
return Closure;
165190
}
166191
llvm_unreachable("covered switch");
167192
}
@@ -219,6 +244,11 @@ SymbolicValue::cloneInto(SymbolicValueAllocator &allocator) const {
219244
SymbolicValue clonedStorage = getStorageOfArray().cloneInto(allocator);
220245
return getArray(getArrayType(), clonedStorage, allocator);
221246
}
247+
case RK_Closure: {
248+
SymbolicClosure *clo = getClosure();
249+
ArrayRef<SymbolicClosureArgument> closureArgs = clo->getCaptures();
250+
return SymbolicValue::makeClosure(clo->getTarget(), closureArgs, allocator);
251+
}
222252
}
223253
llvm_unreachable("covered switch");
224254
}
@@ -661,6 +691,44 @@ Type SymbolicValue::getArrayType() const {
661691
return value.array->getType();
662692
}
663693

694+
//===----------------------------------------------------------------------===//
695+
// Symbolic Closure
696+
//===----------------------------------------------------------------------===//
697+
698+
SymbolicValue SymbolicValue::makeClosure(SILFunction *target,
699+
ArrayRef<SymbolicClosureArgument> args,
700+
SymbolicValueAllocator &allocator) {
701+
auto clo = SymbolicClosure::create(target, args, allocator);
702+
SymbolicValue result;
703+
result.representationKind = RK_Closure;
704+
result.value.closure = clo;
705+
return result;
706+
}
707+
708+
SymbolicClosure *SymbolicClosure::create(SILFunction *target,
709+
ArrayRef<SymbolicClosureArgument> args,
710+
SymbolicValueAllocator &allocator) {
711+
// Determine whether there are captured arguments without a symbolic value.
712+
bool hasNonConstantCapture = false;
713+
for (SymbolicClosureArgument closureArg : args) {
714+
if (!closureArg.second) {
715+
hasNonConstantCapture = true;
716+
break;
717+
}
718+
}
719+
720+
auto byteSizeOfArgs =
721+
SymbolicClosure::totalSizeToAlloc<SymbolicClosureArgument>(args.size());
722+
auto rawMem = allocator.allocate(byteSizeOfArgs, alignof(SymbolicClosure));
723+
// Placement initialize the object.
724+
auto closure = ::new (rawMem)
725+
SymbolicClosure(target, args.size(), hasNonConstantCapture);
726+
std::uninitialized_copy(
727+
args.begin(), args.end(),
728+
closure->getTrailingObjects<SymbolicClosureArgument>());
729+
return closure;
730+
}
731+
664732
//===----------------------------------------------------------------------===//
665733
// Higher level code
666734
//===----------------------------------------------------------------------===//

lib/SILOptimizer/Utils/ConstExpr.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,36 @@ ConstExprFunctionState::evaluateFlowSensitive(SILInstruction *inst) {
16301630
injectEnumInst->getOperand());
16311631
}
16321632

1633+
if (auto *papply = dyn_cast<PartialApplyInst>(inst)) {
1634+
SILValue calleeOperand = papply->getOperand(0);
1635+
SymbolicValue calleeValue = getConstantValue(calleeOperand);
1636+
if (!calleeValue.isConstant())
1637+
return calleeValue;
1638+
if (calleeValue.getKind() != SymbolicValue::Function) {
1639+
return getUnknown(evaluator, (SILInstruction *)papply,
1640+
UnknownReason::InvalidOperandValue);
1641+
}
1642+
1643+
SILFunction *target = calleeValue.getFunctionValue();
1644+
assert(target != nullptr);
1645+
1646+
// Arguments to this partial-apply instruction are the captures of the
1647+
// closure.
1648+
SmallVector<SymbolicClosureArgument, 4> captures;
1649+
for (SILValue argument : papply->getArguments()) {
1650+
SymbolicValue argumentValue = getConstantValue(argument);
1651+
if (!argumentValue.isConstant()) {
1652+
captures.push_back({ argument, None });
1653+
continue;
1654+
}
1655+
captures.push_back({ argument, argumentValue });
1656+
}
1657+
auto closureVal = SymbolicValue::makeClosure(target, captures,
1658+
evaluator.getAllocator());
1659+
setValue(papply, closureVal);
1660+
return None;
1661+
}
1662+
16331663
// If the instruction produces a result, try computing it, and fail if the
16341664
// computation fails.
16351665
if (auto *singleValueInst = dyn_cast<SingleValueInstruction>(inst)) {
@@ -1934,6 +1964,8 @@ ConstExprStepEvaluator::skipByMakingEffectsNonConstant(
19341964
constKind == SymbolicValue::Aggregate ||
19351965
constKind == SymbolicValue::Enum ||
19361966
constKind == SymbolicValue::EnumWithPayload ||
1967+
constKind == SymbolicValue::Array ||
1968+
constKind == SymbolicValue::Closure ||
19371969
constKind == SymbolicValue::UninitMemory);
19381970

19391971
if (constKind != SymbolicValue::Address) {

test/SILOptimizer/constant_evaluable_subset_test.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,3 +784,52 @@ func testArrayAppendNonEmpty(_ x: String) -> [String] {
784784
func interpretArrayAppendNonEmpty() -> [String] {
785785
return testArrayAppendNonEmpty("mkdir")
786786
}
787+
788+
// CHECK-LABEL: @testClosureInit
789+
// CHECK-NOT: error:
790+
@_semantics("constant_evaluable")
791+
func testClosureInit(_ x: Int) -> () -> Int {
792+
return { x }
793+
}
794+
795+
@_semantics("test_driver")
796+
func interpretClosureCreation() -> () -> Int {
797+
return testClosureInit(19)
798+
}
799+
800+
// CHECK-LABEL: @testClosureChaining
801+
// CHECK-NOT: error:
802+
@_semantics("constant_evaluable")
803+
func testClosureChaining(_ x: Int, _ y: Int) -> () -> Int {
804+
let clo: (Int) -> Int = { $0 + x }
805+
return { clo(y) }
806+
}
807+
808+
@_semantics("test_driver")
809+
func interpretClosureChains() -> () -> Int {
810+
return testClosureChaining(191, 201)
811+
}
812+
813+
// CHECK-LABEL: @testClosureWithNonConstantCaptures
814+
// CHECK-NOT: error:
815+
@_semantics("constant_evaluable")
816+
func testClosureWithNonConstantCaptures(_ x: @escaping () -> Int) -> () -> Int {
817+
return x
818+
}
819+
820+
@_semantics("test_driver")
821+
func interpretClosureWithNonConstantCaptures(_ x: Int) -> () -> Int {
822+
return testClosureWithNonConstantCaptures({ x })
823+
}
824+
825+
// CHECK-LABEL: @testAutoClosure
826+
// CHECK-NOT: error:
827+
@_semantics("constant_evaluable")
828+
func testAutoClosure(_ x: @escaping @autoclosure () -> Int) -> () -> Int {
829+
return x
830+
}
831+
832+
@_semantics("test_driver")
833+
func interpretAutoClosure(_ x: Int) -> () -> Int {
834+
return testAutoClosure(x)
835+
}

0 commit comments

Comments
 (0)