Skip to content

Commit b0e56f7

Browse files
committed
[Const evaluator] Enable stepwise constant evaluator to skip
instructions without evaluating them while conservatively accounting for the effects of the skipped instructions on the interpreter state.
1 parent ec4931a commit b0e56f7

File tree

7 files changed

+582
-29
lines changed

7 files changed

+582
-29
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,13 @@ NOTE(constexpr_witness_call_with_no_target_found, none,
395395
NOTE(constexpr_witness_call_found_here, none,
396396
"witness method call found here", ())
397397

398+
NOTE(constexpr_unknown_control_flow_due_to_skip,none, "branch depends on "
399+
"non-constant value produced by an unevaluated instructions", ())
400+
NOTE(constexpr_returned_by_unevaluated_instruction,none,
401+
"return value of an unevaluated instruction is not a constant", ())
402+
NOTE(constexpr_mutated_by_unevaluated_instruction,none, "value mutable by an "
403+
"unevaluated instruction is not a constant", ())
404+
398405
ERROR(non_physical_addressof,none,
399406
"addressof only works with purely physical lvalues; "
400407
"use `withUnsafePointer` or `withUnsafeBytes` unless you're implementing "

include/swift/SIL/SILConstants.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ enum class UnknownReason {
9090
/// A top-level value has multiple writers. This is only relevant in the
9191
/// non-flow-sensitive evaluation mode, which is used by #assert.
9292
MutipleTopLevelWriters,
93+
94+
/// Indicates the return value of an instruction that was not evaluated during
95+
/// interpretation.
96+
ReturnedByUnevaluatedInstruction,
97+
98+
/// Indicates that the value was possibly modified by an instruction
99+
/// that was not evaluated during the interpretation.
100+
MutatedByUnevaluatedInstruction,
93101
};
94102

95103
/// An abstract class that exposes functions for allocating symbolic values.
@@ -416,6 +424,8 @@ class SymbolicValue {
416424
/// reason, we fall back to using the specified location.
417425
void emitUnknownDiagnosticNotes(SILLocation fallbackLoc);
418426

427+
bool isUnknownDueToUnevaluatedInstructions();
428+
419429
/// Clone this SymbolicValue into the specified Allocator and return the new
420430
/// version. This only works for valid constants.
421431
SymbolicValue cloneInto(SymbolicValueAllocator &allocator) const;

include/swift/SILOptimizer/Utils/ConstExpr.h

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ class ConstExprEvaluator {
8585

8686
/// A constant-expression evaluator that can be used to step through a control
8787
/// flow graph (SILFunction body) by evaluating one instruction at a time.
88+
/// This evaluator can also "skip" instructions without evaluating them and
89+
/// only track constant values of variables whose values could be computed.
8890
class ConstExprStepEvaluator {
8991
private:
9092
ConstExprEvaluator evaluator;
@@ -97,10 +99,6 @@ class ConstExprStepEvaluator {
9799
/// evaluation.
98100
SmallPtrSet<SILBasicBlock *, 8> visitedBlocks;
99101

100-
Optional<SymbolicValue>
101-
incrementStepsAndCheckLimit(SILInstruction *inst,
102-
bool includeInInstructionLimit);
103-
104102
ConstExprStepEvaluator(const ConstExprEvaluator &) = delete;
105103
void operator=(const ConstExprEvaluator &) = delete;
106104

@@ -122,12 +120,61 @@ class ConstExprStepEvaluator {
122120
/// Second element is None, if the evaluation is successful.
123121
/// Otherwise, is an unknown symbolic value that contains the error.
124122
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
125-
evaluate(SILBasicBlock::iterator instI,
126-
bool includeInInstructionLimit = true);
123+
evaluate(SILBasicBlock::iterator instI);
124+
125+
/// Skip the instruction without evaluating it and conservatively account for
126+
/// the effects of the instruction on the internal state. This operation
127+
/// resets to an unknown symbolic value any portion of a
128+
/// SymbolicValueMemoryObject that could possibly be mutated by the given
129+
/// instruction. This function preserves the soundness of the interpretation.
130+
/// \param instI instruction to be skipped.
131+
/// \returns a pair where the first and second elements are defined as
132+
/// follows:
133+
/// The first element, if is not None, is the iterator to the next
134+
/// instruction from the where the evaluation must continue.
135+
/// The first element is None if the next instruction from where the
136+
/// evaluation must continue cannot be determined.
137+
/// This would be the case if `instI` is a branch like a `condbr`.
138+
///
139+
/// Second element is None if skipping the instruction is successful.
140+
/// Otherwise, it is an unknown symbolic value containing the error.
141+
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
142+
skipByMakingEffectsNonConstant(SILBasicBlock::iterator instI);
143+
144+
/// Try evaluating an instruction and if the evaluation fails, skip the
145+
/// instruction and make it effects non constant. Note that it may not always
146+
/// be possible to skip an instruction whose evaluation failed and
147+
/// continue evalution (e.g. a conditional branch).
148+
/// See `evaluate` and `skipByMakingEffectsNonConstant` functions for their
149+
/// semantics.
150+
/// \param instI instruction to be evaluated in the current interpreter state.
151+
/// \returns a pair where the first and second elements are defined as
152+
/// follows:
153+
/// The first element, if is not None, is the iterator to the next
154+
/// instruction from the where the evaluation must continue.
155+
/// The first element is None iff both `evaluate` and `skip` functions
156+
/// failed to determine the next instruction to continue evaluation from.
157+
///
158+
/// Second element is None if the evaluation is successful.
159+
/// Otherwise, it is an unknown symbolic value containing the error.
160+
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
161+
tryEvaluateOrElseMakeEffectsNonConstant(SILBasicBlock::iterator instI);
127162

128163
Optional<SymbolicValue> lookupConstValue(SILValue value);
129164

130165
bool isKnownFunction(SILFunction *fun);
166+
167+
/// Returns true if and only if `errorVal` denotes an error that requires
168+
/// aborting interpretation and returning the error. Skipping an instruction
169+
/// that produces such errors is not a valid behavior.
170+
bool isFailStopError(SymbolicValue errorVal);
171+
172+
/// Return the number of instructions evaluated for the last `evaluate`
173+
/// operation. This could be used by the clients to limit the number of
174+
/// instructions that should be evaluated by the step-wise evaluator.
175+
/// Note that 'skipByMakingEffectsNonConstant' operation is not considered
176+
/// as an evaluation.
177+
unsigned instructionsEvaluatedByLastEvaluation() { return stepsEvaluated; }
131178
};
132179

133180
} // end namespace swift

lib/SIL/SILConstants.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,12 @@ SymbolicValue SymbolicValue::lookThroughSingleElementAggregates() const {
568568
}
569569
}
570570

571+
bool SymbolicValue::isUnknownDueToUnevaluatedInstructions() {
572+
auto unknownReason = getUnknownReason();
573+
return (unknownReason == UnknownReason::ReturnedByUnevaluatedInstruction ||
574+
unknownReason == UnknownReason::MutatedByUnevaluatedInstruction);
575+
}
576+
571577
/// Given that this is an 'Unknown' value, emit diagnostic notes providing
572578
/// context about what the problem is. Specifically, point to interesting
573579
/// source locations and function calls in the call stack.
@@ -676,6 +682,12 @@ void SymbolicValue::emitUnknownDiagnosticNotes(SILLocation fallbackLoc) {
676682
if (emitTriggerLocInDiag)
677683
diagnose(ctx, *triggerLoc, diag::constexpr_witness_call_found_here);
678684
return;
685+
case UnknownReason::ReturnedByUnevaluatedInstruction:
686+
diagnose(ctx, diagLoc, diag::constexpr_returned_by_unevaluated_instruction);
687+
break;
688+
case UnknownReason::MutatedByUnevaluatedInstruction:
689+
diagnose(ctx, diagLoc, diag::constexpr_mutated_by_unevaluated_instruction);
690+
break;
679691
}
680692
// TODO: print the call-stack in a controlled way if needed.
681693
}

lib/SILOptimizer/UtilityPasses/ConstantEvaluatorTester.cpp

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#define DEBUG_TYPE "sil-constant-evaluation-tester"
14+
#include "swift/AST/DiagnosticsSIL.h"
1415
#include "swift/SIL/SILConstants.h"
1516
#include "swift/SILOptimizer/PassManager/Passes.h"
1617
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -20,6 +21,12 @@ using namespace swift;
2021

2122
namespace {
2223

24+
template <typename... T, typename... U>
25+
static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc,
26+
Diag<T...> diag, U &&... args) {
27+
return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
28+
}
29+
2330
/// A compiler pass for testing constant evaluator in the step-wise evaluation
2431
/// mode. The pass evaluates SIL functions whose names start with "interpret"
2532
/// and outputs the constant value returned by the function or diagnostics if
@@ -31,6 +38,18 @@ class ConstantEvaluatorTester : public SILFunctionTransform {
3138
return fun->getName().startswith("interpret");
3239
}
3340

41+
bool shouldSkipInstruction(SILInstruction *inst) {
42+
auto *applyInst = dyn_cast<ApplyInst>(inst);
43+
if (!applyInst)
44+
return false;
45+
46+
auto *callee = applyInst->getReferencedFunction();
47+
if (!callee)
48+
return false;
49+
50+
return callee->getName().startswith("skip");
51+
}
52+
3453
void run() override {
3554
SILFunction *fun = getFunction();
3655

@@ -52,6 +71,7 @@ class ConstantEvaluatorTester : public SILFunctionTransform {
5271
if (!returnVal) {
5372
llvm::errs() << "Returns unknown"
5473
<< "\n";
74+
break;
5575
}
5676
llvm::errs() << "Returns " << returnVal.getValue() << "\n";
5777
break;
@@ -60,15 +80,33 @@ class ConstantEvaluatorTester : public SILFunctionTransform {
6080
Optional<SILBasicBlock::iterator> nextInstOpt;
6181
Optional<SymbolicValue> errorVal;
6282

63-
std::tie(nextInstOpt, errorVal) = stepEvaluator.evaluate(currI);
64-
if (errorVal.hasValue()) {
65-
// Diagnose the error.
66-
assert(errorVal->getKind() == SymbolicValue::Unknown);
83+
// If the instruction is marked as skip, skip it and make its effects
84+
// non-constant. Otherwise, try evaluating the instruction and if the
85+
// evaluation fails due to a previously skipped instruction,
86+
// skip the current instruction.
87+
if (shouldSkipInstruction(inst)) {
88+
std::tie(nextInstOpt, errorVal) =
89+
stepEvaluator.skipByMakingEffectsNonConstant(currI);
90+
} else {
91+
std::tie(nextInstOpt, errorVal) =
92+
stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
93+
}
94+
95+
// Diagnose errors in the evaluation. Unknown symbolic values produced
96+
// by skipping instructions are not considered errors.
97+
if (errorVal.hasValue() &&
98+
!errorVal->isUnknownDueToUnevaluatedInstructions()) {
99+
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
100+
break;
101+
}
102+
103+
if (!nextInstOpt) {
104+
diagnose(fun->getASTContext(), inst->getLoc().getSourceLoc(),
105+
diag::constexpr_unknown_control_flow_due_to_skip);
67106
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
68107
break;
69108
}
70109

71-
assert(nextInstOpt);
72110
currI = nextInstOpt.getValue();
73111
}
74112
}

lib/SILOptimizer/Utils/ConstExpr.cpp

Lines changed: 125 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,11 @@ SymbolicValue ConstExprFunctionState::computeConstantValue(SILValue value) {
278278

279279
for (unsigned i = 0, e = inst->getNumOperands(); i != e; ++i) {
280280
auto val = getConstantValue(inst->getOperand(i));
281-
if (!val.isConstant())
281+
if (!val.isConstant() && !val.isUnknownDueToUnevaluatedInstructions())
282282
return val;
283+
// Unknown values due to unevaluated instructions can be assigned to
284+
// struct properties as they are not indicative of a fatal error or
285+
// trap.
283286
elts.push_back(val);
284287
}
285288

@@ -1528,28 +1531,132 @@ ConstExprStepEvaluator::ConstExprStepEvaluator(SymbolicValueAllocator &alloc,
15281531

15291532
ConstExprStepEvaluator::~ConstExprStepEvaluator() { delete internalState; }
15301533

1531-
Optional<SymbolicValue> ConstExprStepEvaluator::incrementStepsAndCheckLimit(
1532-
SILInstruction *inst, bool includeInInstructionLimit) {
1533-
if (includeInInstructionLimit && ++stepsEvaluated > ConstExprLimit) {
1534-
// Note that there is no call stack to associate with the unknown value
1535-
// as the interpreter is at the top-level here.
1536-
return SymbolicValue::getUnknown(inst, UnknownReason::TooManyInstructions,
1537-
{}, evaluator.getAllocator());
1534+
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
1535+
ConstExprStepEvaluator::evaluate(SILBasicBlock::iterator instI) {
1536+
// Reset `stepsEvaluated` to zero.
1537+
stepsEvaluated = 0;
1538+
return internalState->evaluateInstructionAndGetNext(instI, visitedBlocks);
1539+
}
1540+
1541+
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
1542+
ConstExprStepEvaluator::skipByMakingEffectsNonConstant(
1543+
SILBasicBlock::iterator instI) {
1544+
SILInstruction *inst = &(*instI);
1545+
1546+
// Set all constant state that could be mutated by the instruction
1547+
// to an unknown symbolic value.
1548+
for (auto &operand : inst->getAllOperands()) {
1549+
auto constValOpt = lookupConstValue(operand.get());
1550+
if (!constValOpt) {
1551+
continue;
1552+
}
1553+
auto constVal = constValOpt.getValue();
1554+
auto constKind = constVal.getKind();
1555+
1556+
// Skip can only be invoked on value types or addresses of value types.
1557+
// Note that adding a new kind of symbolic value may require handling its
1558+
// side-effects, especially if that symbolic value does not represent a
1559+
// value type.
1560+
assert(constKind == SymbolicValue::Address ||
1561+
constKind == SymbolicValue::Unknown ||
1562+
constKind == SymbolicValue::Metatype ||
1563+
constKind == SymbolicValue::Function ||
1564+
constKind == SymbolicValue::Integer ||
1565+
constKind == SymbolicValue::String ||
1566+
constKind == SymbolicValue::Aggregate ||
1567+
constKind == SymbolicValue::Enum ||
1568+
constKind == SymbolicValue::EnumWithPayload ||
1569+
constKind == SymbolicValue::UninitMemory);
1570+
1571+
if (constKind != SymbolicValue::Address) {
1572+
continue;
1573+
}
1574+
1575+
// If the address is only used @in_guaranteed or @in_constant, there
1576+
// can be no mutation through this address. Therefore, ignore it.
1577+
if (ApplyInst *applyInst = dyn_cast<ApplyInst>(inst)) {
1578+
ApplySite applySite(applyInst);
1579+
SILArgumentConvention convention =
1580+
applySite.getArgumentConvention(operand);
1581+
if (convention == SILArgumentConvention::Indirect_In_Guaranteed ||
1582+
convention == SILArgumentConvention::Indirect_In_Constant) {
1583+
continue;
1584+
}
1585+
}
1586+
1587+
// Write an unknown value into the address.
1588+
SmallVector<unsigned, 4> accessPath;
1589+
auto *memoryObject = constVal.getAddressValue(accessPath);
1590+
auto unknownValue = SymbolicValue::getUnknown(
1591+
inst, UnknownReason::MutatedByUnevaluatedInstruction, {},
1592+
evaluator.getAllocator());
1593+
1594+
auto memoryContent = memoryObject->getValue();
1595+
if (memoryContent.getKind() == SymbolicValue::Aggregate) {
1596+
memoryObject->setIndexedElement(accessPath, unknownValue,
1597+
evaluator.getAllocator());
1598+
} else {
1599+
memoryObject->setValue(unknownValue);
1600+
}
1601+
}
1602+
1603+
// Map the results of this instruction to unknown values.
1604+
for (auto result : inst->getResults()) {
1605+
internalState->setValue(
1606+
result, SymbolicValue::getUnknown(
1607+
inst, UnknownReason::ReturnedByUnevaluatedInstruction, {},
1608+
evaluator.getAllocator()));
1609+
}
1610+
1611+
// If we have a next instruction in the basic block return it.
1612+
// Otherwise, return None for the next instruction.
1613+
// Note that we can find the next instruction in the case of unconditional
1614+
// branches. But, there is no real need to do that as of now.
1615+
if (!isa<TermInst>(inst)) {
1616+
return {++instI, None};
1617+
}
1618+
return {None, None};
1619+
}
1620+
1621+
bool ConstExprStepEvaluator::isFailStopError(SymbolicValue errorVal) {
1622+
assert(errorVal.isUnknown());
1623+
1624+
switch (errorVal.getUnknownReason()) {
1625+
case UnknownReason::TooManyInstructions:
1626+
case UnknownReason::Loop:
1627+
case UnknownReason::Overflow:
1628+
case UnknownReason::Trap:
1629+
return true;
1630+
default:
1631+
return false;
15381632
}
1539-
return None;
15401633
}
15411634

15421635
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
1543-
ConstExprStepEvaluator::evaluate(SILBasicBlock::iterator instI,
1544-
bool includeInInstructionLimit) {
1545-
// Diagnose whether evaluating this instruction exceeds the instruction
1546-
// limit, unless asked to not include this instruction in the limit.
1547-
auto limitError =
1548-
incrementStepsAndCheckLimit(&(*instI), includeInInstructionLimit);
1549-
if (limitError) {
1550-
return {None, limitError.getValue()};
1636+
ConstExprStepEvaluator::tryEvaluateOrElseMakeEffectsNonConstant(
1637+
SILBasicBlock::iterator instI) {
1638+
1639+
auto evaluateResult = evaluate(instI);
1640+
Optional<SILBasicBlock::iterator> nextI = evaluateResult.first;
1641+
Optional<SymbolicValue> errorVal = evaluateResult.second;
1642+
1643+
if (!errorVal) {
1644+
assert(nextI);
1645+
return evaluateResult;
15511646
}
1552-
return internalState->evaluateInstructionAndGetNext(instI, visitedBlocks);
1647+
assert(!nextI);
1648+
1649+
if (isFailStopError(*errorVal)) {
1650+
return evaluateResult;
1651+
}
1652+
1653+
// Evaluation cannot fail on unconditional branches.
1654+
assert(!isa<BranchInst>(&(*instI)));
1655+
1656+
// Since the evaluation has failed, make the effects of this instruction
1657+
// unknown.
1658+
auto result = skipByMakingEffectsNonConstant(instI);
1659+
return {result.first, errorVal};
15531660
}
15541661

15551662
Optional<SymbolicValue>

0 commit comments

Comments
 (0)