Skip to content

Commit 940119c

Browse files
committed
[Constant Evaluator] Add support for interpreting SIL code with
partial applies (i.e., closure creations). A new SymbolicValue: SymbolicClosure represents a closure. It tracks the SILFunction corresponding to the target of the closure and the SIL and Symbolic Values of the captured arguments. The representation does not impose that all captured values must have an associated symbolic value. This allows the evaluator to track creations of closures whose captured arguments or bodies are not constant evaluable. This commit does not add support for closure applications. It only adds suppport for closure creations, which correspond to partial_apply instructions in SIL.
1 parent 5e8c4ad commit 940119c

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)