Skip to content

Commit 981dca5

Browse files
committed
[Constant Evaluator] Add support for thin_to_thick_function SIL instruction,
and fix bugs in the evaluator in the implementation of array.append, and in loading/linking external function.
1 parent ecf76c8 commit 981dca5

File tree

4 files changed

+203
-61
lines changed

4 files changed

+203
-61
lines changed

include/swift/SILOptimizer/Utils/ConstExpr.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ class ConstExprStepEvaluator {
207207
const SmallPtrSetImpl<SILFunction *> &getFuncsCalledDuringEvaluation() {
208208
return evaluator.getFuncsCalledDuringEvaluation();
209209
}
210+
211+
/// Dump the internal state to standard error for debugging.
212+
void dumpState();
210213
};
211214

212215
bool isKnownConstantEvaluableFunction(SILFunction *fun);

lib/SILOptimizer/Utils/ConstExpr.cpp

Lines changed: 89 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ enum class WellKnownFunction {
5555
// String.percentEscapedString.getter
5656
StringEscapePercent,
5757
// _assertionFailure(_: StaticString, _: StaticString, file: StaticString,...)
58-
AssertionFailure
58+
AssertionFailure,
59+
// A function taking one argument that prints the symbolic value of the
60+
// argument during constant evaluation. This must only be used for debugging.
61+
DebugPrint
5962
};
6063

6164
static llvm::Optional<WellKnownFunction> classifyFunction(SILFunction *fn) {
@@ -80,6 +83,12 @@ static llvm::Optional<WellKnownFunction> classifyFunction(SILFunction *fn) {
8083
return WellKnownFunction::StringEscapePercent;
8184
if (fn->hasSemanticsAttrThatStartsWith("programtermination_point"))
8285
return WellKnownFunction::AssertionFailure;
86+
// A call to a function with the following semantics annotation will be
87+
// considered as a DebugPrint operation. The evaluator will print the value
88+
// of the single argument passed to this function call to the standard error.
89+
// This functionality must be used only for debugging the evaluator.
90+
if (fn->hasSemanticsAttrThatStartsWith("constant_evaluator_debug_print"))
91+
return WellKnownFunction::DebugPrint;
8392
return None;
8493
}
8594

@@ -222,6 +231,13 @@ class ConstExprFunctionState {
222231
llvm::Optional<SymbolicValue>
223232
computeWellKnownCallResult(ApplyInst *apply, WellKnownFunction callee);
224233

234+
/// Evaluate a closure creation instruction which is either a partial_apply
235+
/// instruction or a thin_to_think_function instruction. On success, this
236+
/// function will bind the \c closureInst parameter to its symbolic value.
237+
/// On failure, it returns the unknown symbolic value that captures the error.
238+
llvm::Optional<SymbolicValue>
239+
evaluateClosureCreation(SingleValueInstruction *closureInst);
240+
225241
SymbolicValue getSingleWriterAddressValue(SILValue addr);
226242
SymbolicValue getConstAddrAndLoadResult(SILValue addr);
227243
SymbolicValue loadAddrValue(SILValue addr, SymbolicValue addrVal);
@@ -883,36 +899,19 @@ ConstExprFunctionState::computeWellKnownCallResult(ApplyInst *apply,
883899
conventions.getNumIndirectSILResults() == 0 &&
884900
"unexpected Array.append(_:) signature");
885901
// Get the element to be appended which is passed indirectly (@in).
886-
SymbolicValue elementAddress = getConstantValue(apply->getOperand(1));
887-
if (!elementAddress.isConstant())
888-
return elementAddress;
889-
890-
auto invalidOperand = [&]() {
902+
SymbolicValue element = getConstAddrAndLoadResult(apply->getOperand(1));
903+
if (!element.isConstant())
904+
return element;
905+
906+
// Get the array value. The array is passed @inout and could be a property
907+
// of a struct.
908+
SILValue arrayAddress = apply->getOperand(2);
909+
SymbolicValue arrayValue = getConstAddrAndLoadResult(arrayAddress);
910+
if (!arrayValue.isConstant())
911+
return arrayValue;
912+
if (arrayValue.getKind() != SymbolicValue::Array) {
891913
return getUnknown(evaluator, (SILInstruction *)apply,
892914
UnknownReason::InvalidOperandValue);
893-
};
894-
if (elementAddress.getKind() != SymbolicValue::Address) {
895-
// TODO: store the operand number in the error message here.
896-
return invalidOperand();
897-
}
898-
899-
SmallVector<unsigned, 4> elementAP;
900-
SymbolicValue element =
901-
elementAddress.getAddressValue(elementAP)->getValue();
902-
903-
// Get the array value. The array is passed @inout.
904-
SymbolicValue arrayAddress = getConstantValue(apply->getOperand(2));
905-
if (!arrayAddress.isConstant())
906-
return arrayAddress;
907-
if (arrayAddress.getKind() != SymbolicValue::Address)
908-
return invalidOperand();
909-
910-
SmallVector<unsigned, 4> arrayAP;
911-
SymbolicValueMemoryObject *arrayMemoryObject =
912-
arrayAddress.getAddressValue(arrayAP);
913-
SymbolicValue arrayValue = arrayMemoryObject->getValue();
914-
if (arrayValue.getKind() != SymbolicValue::Array) {
915-
return invalidOperand();
916915
}
917916

918917
// Create a new array storage by appending the \c element to the existing
@@ -930,7 +929,7 @@ ConstExprFunctionState::computeWellKnownCallResult(ApplyInst *apply,
930929
newElements, elementType, allocator);
931930
SymbolicValue newArray = SymbolicValue::getArray(arrayValue.getArrayType(),
932931
newStorage, allocator);
933-
arrayMemoryObject->setIndexedElement(arrayAP, newArray, allocator);
932+
computeFSStore(newArray, arrayAddress);
934933
return None;
935934
}
936935
case WellKnownFunction::StringInitEmpty: { // String.init()
@@ -1056,6 +1055,21 @@ ConstExprFunctionState::computeWellKnownCallResult(ApplyInst *apply,
10561055
setValue(apply, resultVal);
10571056
return None;
10581057
}
1058+
case WellKnownFunction::DebugPrint: {
1059+
assert(apply->getNumArguments() == 1 &&
1060+
"debug_print function must take exactly one argument");
1061+
SILValue argument = apply->getArgument(0);
1062+
SymbolicValue argValue = getConstantValue(argument);
1063+
llvm::errs() << "Debug print output ";
1064+
argValue.print(llvm::errs());
1065+
if (argValue.getKind() != SymbolicValue::Address)
1066+
return None;
1067+
1068+
llvm::errs() << "\n Addressed Memory Object: ";
1069+
SymbolicValueMemoryObject *memObj = argValue.getAddressValueMemoryObject();
1070+
memObj->getValue().print(llvm::errs());
1071+
return None;
1072+
}
10591073
}
10601074
llvm_unreachable("unhandled WellKnownFunction");
10611075
}
@@ -1092,10 +1106,11 @@ ConstExprFunctionState::computeCallResult(ApplyInst *apply) {
10921106
}
10931107

10941108
// If we reached an external function that hasn't been deserialized yet, make
1095-
// sure to pull it in so we can see its body. If that fails, then we can't
1096-
// analyze the function.
1109+
// sure to pull it in so we can see its body. If that fails, then we can't
1110+
// analyze the function. Note: pull in everything referenced from another
1111+
// module in case some referenced functions have non-public linkage.
10971112
if (callee->isExternalDeclaration()) {
1098-
callee->getModule().loadFunction(callee);
1113+
apply->getModule().linkFunction(callee, SILModule::LinkingMode::LinkAll);
10991114
if (callee->isExternalDeclaration())
11001115
return computeOpaqueCallResult(apply, callee);
11011116
}
@@ -1547,6 +1562,43 @@ ConstExprFunctionState::computeFSStore(SymbolicValue storedCst, SILValue dest) {
15471562
return None;
15481563
}
15491564

1565+
llvm::Optional<SymbolicValue> ConstExprFunctionState::evaluateClosureCreation(
1566+
SingleValueInstruction *closureInst) {
1567+
assert(isa<PartialApplyInst>(closureInst) ||
1568+
isa<ThinToThickFunctionInst>(closureInst));
1569+
SILValue calleeOperand = closureInst->getOperand(0);
1570+
SymbolicValue calleeValue = getConstantValue(calleeOperand);
1571+
if (!calleeValue.isConstant())
1572+
return calleeValue;
1573+
if (calleeValue.getKind() != SymbolicValue::Function) {
1574+
return getUnknown(evaluator, (SILInstruction *)closureInst,
1575+
UnknownReason::InvalidOperandValue);
1576+
}
1577+
1578+
SILFunction *target = calleeValue.getFunctionValue();
1579+
assert(target != nullptr);
1580+
1581+
SmallVector<SymbolicClosureArgument, 4> captures;
1582+
1583+
// If this is a partial-apply instruction, arguments to this partial-apply
1584+
// instruction are the captures of the closure.
1585+
if (PartialApplyInst *papply = dyn_cast<PartialApplyInst>(closureInst)) {
1586+
for (SILValue capturedSILValue : papply->getArguments()) {
1587+
SymbolicValue capturedSymbolicValue = getConstantValue(capturedSILValue);
1588+
if (!capturedSymbolicValue.isConstant()) {
1589+
captures.push_back({capturedSILValue, None});
1590+
continue;
1591+
}
1592+
captures.push_back({capturedSILValue, capturedSymbolicValue});
1593+
}
1594+
}
1595+
1596+
auto closureVal =
1597+
SymbolicValue::makeClosure(target, captures, evaluator.getAllocator());
1598+
setValue(closureInst, closureVal);
1599+
return None;
1600+
}
1601+
15501602
/// Evaluate the specified instruction in a flow sensitive way, for use by
15511603
/// the constexpr function evaluator. This does not handle control flow
15521604
/// statements. This returns None on success, and an Unknown SymbolicValue with
@@ -1630,34 +1682,8 @@ ConstExprFunctionState::evaluateFlowSensitive(SILInstruction *inst) {
16301682
injectEnumInst->getOperand());
16311683
}
16321684

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;
1685+
if (isa<PartialApplyInst>(inst) || isa<ThinToThickFunctionInst>(inst)) {
1686+
return evaluateClosureCreation(cast<SingleValueInstruction>(inst));
16611687
}
16621688

16631689
// If the instruction produces a result, try computing it, and fail if the
@@ -2073,6 +2099,8 @@ ConstExprStepEvaluator::lookupConstValue(SILValue value) {
20732099
return res;
20742100
}
20752101

2102+
void ConstExprStepEvaluator::dumpState() { internalState->dump(); }
2103+
20762104
bool swift::isKnownConstantEvaluableFunction(SILFunction *fun) {
20772105
return classifyFunction(fun).hasValue();
20782106
}

test/SILOptimizer/constant_evaluable_subset_test.swift

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,24 @@ func interpretArrayAppendNonEmpty() -> [String] {
785785
return testArrayAppendNonEmpty("mkdir")
786786
}
787787

788+
struct StructContaningArray {
789+
var array: [Int]
790+
}
791+
792+
// CHECK-LABEL: @testArrayFieldAppend
793+
// CHECK-NOT: error:
794+
@_semantics("constant_evaluable")
795+
func testArrayFieldAppend(_ x: Int) -> StructContaningArray {
796+
var s = StructContaningArray(array: [])
797+
s.array.append(x)
798+
return s
799+
}
800+
801+
@_semantics("test_driver")
802+
func interpretArrayFieldAppend() -> StructContaningArray {
803+
return testArrayFieldAppend(0)
804+
}
805+
788806
// CHECK-LABEL: @testClosureInit
789807
// CHECK-NOT: error:
790808
@_semantics("constant_evaluable")
@@ -833,3 +851,44 @@ func testAutoClosure(_ x: @escaping @autoclosure () -> Int) -> () -> Int {
833851
func interpretAutoClosure(_ x: Int) -> () -> Int {
834852
return testAutoClosure(x)
835853
}
854+
855+
// Test thin-to-thick function conversion.
856+
857+
func someFunction(_ x: Int) -> Int {
858+
return x + 1
859+
}
860+
861+
// CHECK-LABEL: @testThinToThick
862+
// CHECK-NOT: error:
863+
@_semantics("constant_evaluable")
864+
func testThinToThick() -> (Int) -> Int {
865+
return someFunction
866+
}
867+
868+
@_semantics("test_driver")
869+
func interpretThinToThick() -> (Int) -> Int {
870+
return testThinToThick()
871+
}
872+
873+
// Test closures and arrays combination.
874+
875+
// CHECK-LABEL: @testArrayOfClosures
876+
// CHECK-NOT: error:
877+
@_semantics("constant_evaluable")
878+
func testArrayOfClosures(_ byte: @escaping () -> Int) -> [(Int) -> Int] {
879+
var closureArray: [(Int) -> Int] = []
880+
// Append a simple closure.
881+
closureArray.append({ arg in
882+
return 0
883+
})
884+
// Append a closure that does computation.
885+
closureArray.append({ arg in
886+
return byte() + arg
887+
})
888+
return closureArray
889+
}
890+
891+
@_semantics("test_driver")
892+
func interpretArrayOfClosures() -> [(Int) -> Int] {
893+
return testArrayOfClosures({ 10 })
894+
}

test/SILOptimizer/constant_evaluator_test.sil

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1336,6 +1336,41 @@ bb0:
13361336
// CHECK: agg: 1 elt: int: 14
13371337
// CHECK: agg: 1 elt: int: 100
13381338

1339+
struct StructContainingArray {
1340+
var array: [Int64]
1341+
}
1342+
1343+
// CHECK-LABEL: @interpretArrayAppendViaStructElementAddr
1344+
sil [ossa] @interpretArrayAppendViaStructElementAddr : $@convention(thin) () -> @owned StructContainingArray {
1345+
bb0:
1346+
%3 = metatype $@thin Array<Int64>.Type
1347+
// function_ref Array.init()
1348+
%4 = function_ref @$sS2ayxGycfC : $@convention(method) <τ_0_0> (@thin Array<τ_0_0>.Type) -> @owned Array<τ_0_0>
1349+
%5 = apply %4<Int64>(%3) : $@convention(method) <τ_0_0> (@thin Array<τ_0_0>.Type) -> @owned Array<τ_0_0>
1350+
%6 = struct $StructContainingArray (%5 : $Array<Int64>)
1351+
1352+
%7 = alloc_stack $StructContainingArray, var, name "s"
1353+
store %6 to [init] %7 : $*StructContainingArray
1354+
%8 = struct_element_addr %7 : $*StructContainingArray, #StructContainingArray.array
1355+
1356+
%9 = integer_literal $Builtin.Int64, 105
1357+
%10 = struct $Int64 (%9 : $Builtin.Int64)
1358+
%11 = alloc_stack $Int64
1359+
store %10 to [trivial] %11 : $*Int64
1360+
1361+
// function_ref Array.append(_:)
1362+
%13 = function_ref @$sSa6appendyyxnF : $@convention(method) <τ_0_0> (@in τ_0_0, @inout Array<τ_0_0>) -> ()
1363+
%14 = apply %13<Int64>(%11, %8) : $@convention(method) <τ_0_0> (@in τ_0_0, @inout Array<τ_0_0>) -> ()
1364+
dealloc_stack %11 : $*Int64
1365+
1366+
%18 = load [copy] %7 : $*StructContainingArray
1367+
destroy_addr %7 : $*StructContainingArray
1368+
dealloc_stack %7 : $*StructContainingArray
1369+
return %18 : $StructContainingArray
1370+
} // CHECK: agg: 1 elt: Array<Int64>:
1371+
// CHECK: size: 1
1372+
// CHECK: agg: 1 elt: int: 105
1373+
13391374
/// Test appending of a static string to an array. The construction of a static
13401375
/// string is a bit complicated due to the use of instructions like "ptrtoint".
13411376
/// This tests that array append works with such complex constant values as well.
@@ -1423,3 +1458,20 @@ bb0:
14231458
// CHECK: %1
14241459
// CHECK: values:
14251460
// CHECK: int: 991
1461+
1462+
1463+
sil private @closure4 : $@convention(thin) () -> Int64 {
1464+
bb0:
1465+
%0 = integer_literal $Builtin.Int64, 71
1466+
%1 = struct $Int64 (%0 : $Builtin.Int64)
1467+
return %1 : $Int64
1468+
}
1469+
1470+
// CHECK-LABEL: @interpretThinToThickFunction
1471+
sil @interpretThinToThickFunction: $@convention(thin) () -> @owned @callee_guaranteed () -> Int64 {
1472+
bb0:
1473+
%0 = function_ref @closure4 : $@convention(thin) () -> Int64
1474+
%3 = thin_to_thick_function %0 : $@convention(thin) () -> Int64 to $@callee_guaranteed () -> Int64
1475+
return %3 : $@callee_guaranteed () -> Int64
1476+
} // CHECK: Returns closure: target: closure4 captures
1477+
// CHECK-NEXT: values:

0 commit comments

Comments
 (0)