Skip to content

Commit d912e33

Browse files
Merge pull request #24113 from ravikandhadai/constexpr-skip
[Const evaluator] Add support to "skip" instructions in step-wise evaluation
2 parents 3089c86 + b0e56f7 commit d912e33

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
@@ -277,8 +277,11 @@ SymbolicValue ConstExprFunctionState::computeConstantValue(SILValue value) {
277277

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

@@ -1559,28 +1562,132 @@ ConstExprStepEvaluator::ConstExprStepEvaluator(SymbolicValueAllocator &alloc,
15591562

15601563
ConstExprStepEvaluator::~ConstExprStepEvaluator() { delete internalState; }
15611564

1562-
Optional<SymbolicValue> ConstExprStepEvaluator::incrementStepsAndCheckLimit(
1563-
SILInstruction *inst, bool includeInInstructionLimit) {
1564-
if (includeInInstructionLimit && ++stepsEvaluated > ConstExprLimit) {
1565-
// Note that there is no call stack to associate with the unknown value
1566-
// as the interpreter is at the top-level here.
1567-
return SymbolicValue::getUnknown(inst, UnknownReason::TooManyInstructions,
1568-
{}, evaluator.getAllocator());
1565+
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
1566+
ConstExprStepEvaluator::evaluate(SILBasicBlock::iterator instI) {
1567+
// Reset `stepsEvaluated` to zero.
1568+
stepsEvaluated = 0;
1569+
return internalState->evaluateInstructionAndGetNext(instI, visitedBlocks);
1570+
}
1571+
1572+
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
1573+
ConstExprStepEvaluator::skipByMakingEffectsNonConstant(
1574+
SILBasicBlock::iterator instI) {
1575+
SILInstruction *inst = &(*instI);
1576+
1577+
// Set all constant state that could be mutated by the instruction
1578+
// to an unknown symbolic value.
1579+
for (auto &operand : inst->getAllOperands()) {
1580+
auto constValOpt = lookupConstValue(operand.get());
1581+
if (!constValOpt) {
1582+
continue;
1583+
}
1584+
auto constVal = constValOpt.getValue();
1585+
auto constKind = constVal.getKind();
1586+
1587+
// Skip can only be invoked on value types or addresses of value types.
1588+
// Note that adding a new kind of symbolic value may require handling its
1589+
// side-effects, especially if that symbolic value does not represent a
1590+
// value type.
1591+
assert(constKind == SymbolicValue::Address ||
1592+
constKind == SymbolicValue::Unknown ||
1593+
constKind == SymbolicValue::Metatype ||
1594+
constKind == SymbolicValue::Function ||
1595+
constKind == SymbolicValue::Integer ||
1596+
constKind == SymbolicValue::String ||
1597+
constKind == SymbolicValue::Aggregate ||
1598+
constKind == SymbolicValue::Enum ||
1599+
constKind == SymbolicValue::EnumWithPayload ||
1600+
constKind == SymbolicValue::UninitMemory);
1601+
1602+
if (constKind != SymbolicValue::Address) {
1603+
continue;
1604+
}
1605+
1606+
// If the address is only used @in_guaranteed or @in_constant, there
1607+
// can be no mutation through this address. Therefore, ignore it.
1608+
if (ApplyInst *applyInst = dyn_cast<ApplyInst>(inst)) {
1609+
ApplySite applySite(applyInst);
1610+
SILArgumentConvention convention =
1611+
applySite.getArgumentConvention(operand);
1612+
if (convention == SILArgumentConvention::Indirect_In_Guaranteed ||
1613+
convention == SILArgumentConvention::Indirect_In_Constant) {
1614+
continue;
1615+
}
1616+
}
1617+
1618+
// Write an unknown value into the address.
1619+
SmallVector<unsigned, 4> accessPath;
1620+
auto *memoryObject = constVal.getAddressValue(accessPath);
1621+
auto unknownValue = SymbolicValue::getUnknown(
1622+
inst, UnknownReason::MutatedByUnevaluatedInstruction, {},
1623+
evaluator.getAllocator());
1624+
1625+
auto memoryContent = memoryObject->getValue();
1626+
if (memoryContent.getKind() == SymbolicValue::Aggregate) {
1627+
memoryObject->setIndexedElement(accessPath, unknownValue,
1628+
evaluator.getAllocator());
1629+
} else {
1630+
memoryObject->setValue(unknownValue);
1631+
}
1632+
}
1633+
1634+
// Map the results of this instruction to unknown values.
1635+
for (auto result : inst->getResults()) {
1636+
internalState->setValue(
1637+
result, SymbolicValue::getUnknown(
1638+
inst, UnknownReason::ReturnedByUnevaluatedInstruction, {},
1639+
evaluator.getAllocator()));
1640+
}
1641+
1642+
// If we have a next instruction in the basic block return it.
1643+
// Otherwise, return None for the next instruction.
1644+
// Note that we can find the next instruction in the case of unconditional
1645+
// branches. But, there is no real need to do that as of now.
1646+
if (!isa<TermInst>(inst)) {
1647+
return {++instI, None};
1648+
}
1649+
return {None, None};
1650+
}
1651+
1652+
bool ConstExprStepEvaluator::isFailStopError(SymbolicValue errorVal) {
1653+
assert(errorVal.isUnknown());
1654+
1655+
switch (errorVal.getUnknownReason()) {
1656+
case UnknownReason::TooManyInstructions:
1657+
case UnknownReason::Loop:
1658+
case UnknownReason::Overflow:
1659+
case UnknownReason::Trap:
1660+
return true;
1661+
default:
1662+
return false;
15691663
}
1570-
return None;
15711664
}
15721665

15731666
std::pair<Optional<SILBasicBlock::iterator>, Optional<SymbolicValue>>
1574-
ConstExprStepEvaluator::evaluate(SILBasicBlock::iterator instI,
1575-
bool includeInInstructionLimit) {
1576-
// Diagnose whether evaluating this instruction exceeds the instruction
1577-
// limit, unless asked to not include this instruction in the limit.
1578-
auto limitError =
1579-
incrementStepsAndCheckLimit(&(*instI), includeInInstructionLimit);
1580-
if (limitError) {
1581-
return {None, limitError.getValue()};
1667+
ConstExprStepEvaluator::tryEvaluateOrElseMakeEffectsNonConstant(
1668+
SILBasicBlock::iterator instI) {
1669+
1670+
auto evaluateResult = evaluate(instI);
1671+
Optional<SILBasicBlock::iterator> nextI = evaluateResult.first;
1672+
Optional<SymbolicValue> errorVal = evaluateResult.second;
1673+
1674+
if (!errorVal) {
1675+
assert(nextI);
1676+
return evaluateResult;
15821677
}
1583-
return internalState->evaluateInstructionAndGetNext(instI, visitedBlocks);
1678+
assert(!nextI);
1679+
1680+
if (isFailStopError(*errorVal)) {
1681+
return evaluateResult;
1682+
}
1683+
1684+
// Evaluation cannot fail on unconditional branches.
1685+
assert(!isa<BranchInst>(&(*instI)));
1686+
1687+
// Since the evaluation has failed, make the effects of this instruction
1688+
// unknown.
1689+
auto result = skipByMakingEffectsNonConstant(instI);
1690+
return {result.first, errorVal};
15841691
}
15851692

15861693
Optional<SymbolicValue>

0 commit comments

Comments
 (0)