Skip to content

Commit bcafb78

Browse files
Merge pull request #27208 from ravikandhadai/constexpr-fragment-check
[Constant Evaluator][Tests] Add tests to check constant evaluability of Swift code snippets
2 parents fa5b0f3 + 032442d commit bcafb78

File tree

8 files changed

+1108
-17
lines changed

8 files changed

+1108
-17
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ PASS(ConditionForwarding, "condition-forwarding",
112112
"Conditional Branch Forwarding to Fold SIL switch_enum")
113113
PASS(ConstantEvaluatorTester, "test-constant-evaluator",
114114
"Test constant evaluator")
115+
PASS(ConstantEvaluableSubsetChecker, "test-constant-evaluable-subset",
116+
"Test Swift code snippets expected to be constant evaluable")
115117
PASS(CopyForwarding, "copy-forwarding",
116118
"Copy Forwarding to Remove Redundant Copies")
117119
PASS(CopyPropagation, "copy-propagation",

include/swift/SILOptimizer/Utils/ConstExpr.h

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,17 @@ class ConstExprEvaluator {
4848
/// The current call stack, used for providing accurate diagnostics.
4949
llvm::SmallVector<SourceLoc, 4> callStack;
5050

51+
/// When set to true, keep track of all functions called during an evaluation.
52+
bool trackCallees;
53+
/// Functions called during the evaluation. This is an auxiliary information
54+
/// provided to the clients.
55+
llvm::SmallPtrSet<SILFunction *, 2> calledFunctions;
56+
5157
void operator=(const ConstExprEvaluator &) = delete;
5258

5359
public:
54-
explicit ConstExprEvaluator(SymbolicValueAllocator &alloc);
60+
explicit ConstExprEvaluator(SymbolicValueAllocator &alloc,
61+
bool trackCallees = false);
5562
~ConstExprEvaluator();
5663

5764
explicit ConstExprEvaluator(const ConstExprEvaluator &other);
@@ -75,12 +82,23 @@ class ConstExprEvaluator {
7582
/// This is done in code that is not necessarily itself a constexpr
7683
/// function. The results are added to the results list which is a parallel
7784
/// structure to the input values.
78-
///
79-
/// TODO: Return information about which callees were found to be
80-
/// constexprs, which would allow the caller to delete dead calls to them
81-
/// that occur after after folding them.
8285
void computeConstantValues(ArrayRef<SILValue> values,
8386
SmallVectorImpl<SymbolicValue> &results);
87+
88+
void recordCalledFunctionIfEnabled(SILFunction *callee) {
89+
if (trackCallees) {
90+
calledFunctions.insert(callee);
91+
}
92+
}
93+
94+
/// If the evaluator was initialized with \c trackCallees enabled, return the
95+
/// SIL functions encountered during the evaluations performed with this
96+
/// evaluator. The returned functions include those that were called but
97+
/// failed to complete successfully.
98+
const SmallPtrSetImpl<SILFunction *> &getFuncsCalledDuringEvaluation() const {
99+
assert(trackCallees && "evaluator not configured to track callees");
100+
return calledFunctions;
101+
}
84102
};
85103

86104
/// A constant-expression evaluator that can be used to step through a control
@@ -106,7 +124,7 @@ class ConstExprStepEvaluator {
106124
/// Constructs a step evaluator given an allocator and a non-null pointer to a
107125
/// SILFunction.
108126
explicit ConstExprStepEvaluator(SymbolicValueAllocator &alloc,
109-
SILFunction *fun);
127+
SILFunction *fun, bool trackCallees = false);
110128
~ConstExprStepEvaluator();
111129

112130
/// Evaluate an instruction in the current interpreter state.
@@ -173,6 +191,15 @@ class ConstExprStepEvaluator {
173191
/// Note that 'skipByMakingEffectsNonConstant' operation is not considered
174192
/// as an evaluation.
175193
unsigned instructionsEvaluatedByLastEvaluation() { return stepsEvaluated; }
194+
195+
/// If the evaluator was initialized with \c trackCallees enabled, return the
196+
/// SIL functions encountered during the evaluations performed with this
197+
/// evaluator. The returned functions include those that were called but
198+
/// failed to complete successfully. Targets of skipped apply instructions
199+
/// will not be included in the returned set.
200+
const SmallPtrSetImpl<SILFunction *> &getFuncsCalledDuringEvaluation() {
201+
return evaluator.getFuncsCalledDuringEvaluation();
202+
}
176203
};
177204

178205
bool isKnownConstantEvaluableFunction(SILFunction *fun);

lib/SILOptimizer/UtilityPasses/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ silopt_register_sources(
1010
ComputeDominanceInfo.cpp
1111
ComputeLoopInfo.cpp
1212
ConstantEvaluatorTester.cpp
13+
ConstantEvaluableSubsetChecker.cpp
1314
EpilogueARCMatcherDumper.cpp
1415
EpilogueRetainReleaseMatcherDumper.cpp
1516
EscapeAnalysisDumper.cpp
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//===--ConstantEvaluableSubsetChecker.cpp - Test Constant Evaluable Swift--===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
// This file implements a pass for checking the constant evaluability of Swift
14+
// code snippets. This pass is only used in tests and is not part of the
15+
// compilation pipeline.
16+
17+
#define DEBUG_TYPE "sil-constant-evaluable-subset-checker"
18+
#include "swift/AST/DiagnosticsSIL.h"
19+
#include "swift/AST/Module.h"
20+
#include "swift/Demangling/Demangle.h"
21+
#include "swift/SIL/CFG.h"
22+
#include "swift/SIL/SILConstants.h"
23+
#include "swift/SIL/SILInstruction.h"
24+
#include "swift/SILOptimizer/PassManager/Passes.h"
25+
#include "swift/SILOptimizer/PassManager/Transforms.h"
26+
#include "swift/SILOptimizer/Utils/ConstExpr.h"
27+
28+
using namespace swift;
29+
30+
namespace {
31+
32+
static const StringRef constantEvaluableSemanticsAttr = "constant_evaluable";
33+
static const StringRef testDriverSemanticsAttr = "test_driver";
34+
35+
template <typename... T, typename... U>
36+
static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc,
37+
Diag<T...> diag, U &&... args) {
38+
return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
39+
}
40+
41+
static std::string demangleSymbolName(StringRef name) {
42+
Demangle::DemangleOptions options;
43+
options.QualifyEntities = false;
44+
return Demangle::demangleSymbolAsString(name, options);
45+
}
46+
47+
/// A SILModule pass that invokes the constant evaluator on all functions in a
48+
/// SILModule with the semantics attribute "test_driver". Each "test_driver"
49+
/// must invoke one or more functions in the module annotated as
50+
/// "constant_evaluable" with constant arguments.
51+
class ConstantEvaluableSubsetChecker : public SILModuleTransform {
52+
53+
llvm::SmallPtrSet<SILFunction *, 4> constantEvaluableFunctions;
54+
llvm::SmallPtrSet<SILFunction *, 4> evaluatedFunctions;
55+
56+
/// Evaluate the body of \c fun with the constant evaluator. \c fun must be
57+
/// annotated as "test_driver" and must invoke one or more functions annotated
58+
/// as "constant_evaluable" with constant arguments. Emit diagnostics if the
59+
/// evaluation of any "constant_evaluable" function called in the body of
60+
/// \c fun fails.
61+
void constantEvaluateDriver(SILFunction *fun) {
62+
ASTContext &astContext = fun->getASTContext();
63+
64+
// Create a step evaluator and run it on the function.
65+
SymbolicValueBumpAllocator allocator;
66+
ConstExprStepEvaluator stepEvaluator(allocator, fun,
67+
/*trackCallees*/ true);
68+
69+
for (auto currI = fun->getEntryBlock()->begin();;) {
70+
auto *inst = &(*currI);
71+
72+
if (isa<ReturnInst>(inst))
73+
break;
74+
75+
auto *applyInst = dyn_cast<ApplyInst>(inst);
76+
SILFunction *callee = nullptr;
77+
if (applyInst) {
78+
callee = applyInst->getReferencedFunctionOrNull();
79+
}
80+
81+
Optional<SILBasicBlock::iterator> nextInstOpt;
82+
Optional<SymbolicValue> errorVal;
83+
84+
if (!applyInst || !callee ||
85+
!callee->hasSemanticsAttr(constantEvaluableSemanticsAttr)) {
86+
std::tie(nextInstOpt, errorVal) =
87+
stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
88+
assert(nextInstOpt && "non-constant control flow in the test driver");
89+
currI = nextInstOpt.getValue();
90+
continue;
91+
}
92+
93+
// Here, a function annotated as "constant_evaluable" is called.
94+
llvm::errs() << "@" << demangleSymbolName(callee->getName()) << "\n";
95+
std::tie(nextInstOpt, errorVal) =
96+
stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
97+
98+
if (errorVal) {
99+
SourceLoc instLoc = inst->getLoc().getSourceLoc();
100+
diagnose(astContext, instLoc, diag::not_constant_evaluable);
101+
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
102+
}
103+
104+
if (!nextInstOpt) {
105+
break; // This instruction should be the end of the test driver.
106+
}
107+
currI = nextInstOpt.getValue();
108+
}
109+
110+
// Record functions that were called during this evaluation to detect
111+
// whether the test drivers in the SILModule cover all function annotated
112+
// as "constant_evaluable".
113+
for (SILFunction *callee : stepEvaluator.getFuncsCalledDuringEvaluation()) {
114+
evaluatedFunctions.insert(callee);
115+
}
116+
}
117+
118+
void run() override {
119+
SILModule *module = getModule();
120+
assert(module);
121+
122+
for (SILFunction &fun : *module) {
123+
// Record functions annotated as constant evaluable.
124+
if (fun.hasSemanticsAttr(constantEvaluableSemanticsAttr)) {
125+
constantEvaluableFunctions.insert(&fun);
126+
continue;
127+
}
128+
129+
// Evaluate test drivers.
130+
if (!fun.hasSemanticsAttr(testDriverSemanticsAttr))
131+
continue;
132+
constantEvaluateDriver(&fun);
133+
}
134+
135+
// Assert that every function annotated as "constant_evaluable" was convered
136+
// by a test driver.
137+
bool error = false;
138+
for (SILFunction *constEvalFun : constantEvaluableFunctions) {
139+
if (!evaluatedFunctions.count(constEvalFun)) {
140+
llvm::errs() << "Error: function "
141+
<< demangleSymbolName(constEvalFun->getName());
142+
llvm::errs() << " annotated as constant evaluable";
143+
llvm::errs() << " does not have a test driver"
144+
<< "\n";
145+
error = true;
146+
}
147+
}
148+
assert(!error);
149+
}
150+
};
151+
152+
} // end anonymous namespace
153+
154+
SILTransform *swift::createConstantEvaluableSubsetChecker() {
155+
return new ConstantEvaluableSubsetChecker();
156+
}

lib/SILOptimizer/Utils/ConstExpr.cpp

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ ConstExprFunctionState::computeCallResult(ApplyInst *apply) {
898898
UnknownReason::InvalidOperandValue);
899899

900900
SILFunction *callee = calleeFn.getFunctionValue();
901+
evaluator.recordCalledFunctionIfEnabled(callee);
901902

902903
// If this is a well-known function, do not step into it.
903904
if (auto wellKnownFunction = classifyFunction(callee))
@@ -1644,8 +1645,9 @@ evaluateAndCacheCall(SILFunction &fn, SubstitutionMap substitutionMap,
16441645
// ConstExprEvaluator implementation.
16451646
//===----------------------------------------------------------------------===//
16461647

1647-
ConstExprEvaluator::ConstExprEvaluator(SymbolicValueAllocator &alloc)
1648-
: allocator(alloc) {}
1648+
ConstExprEvaluator::ConstExprEvaluator(SymbolicValueAllocator &alloc,
1649+
bool trackCallees)
1650+
: allocator(alloc), trackCallees(trackCallees) {}
16491651

16501652
ConstExprEvaluator::~ConstExprEvaluator() {}
16511653

@@ -1661,14 +1663,10 @@ SymbolicValue ConstExprEvaluator::getUnknown(SILNode *node,
16611663
getAllocator());
16621664
}
16631665

1664-
/// Analyze the specified values to determine if they are constant values. This
1665-
/// is done in code that is not necessarily itself a constexpr function. The
1666+
/// Analyze the specified values to determine if they are constant values. This
1667+
/// is done in code that is not necessarily itself a constexpr function. The
16661668
/// results are added to the results list which is a parallel structure to the
16671669
/// input values.
1668-
///
1669-
/// TODO: Return information about which callees were found to be
1670-
/// constexprs, which would allow the caller to delete dead calls to them
1671-
/// that occur after folding them.
16721670
void ConstExprEvaluator::computeConstantValues(
16731671
ArrayRef<SILValue> values, SmallVectorImpl<SymbolicValue> &results) {
16741672
unsigned numInstEvaluated = 0;
@@ -1688,9 +1686,10 @@ void ConstExprEvaluator::computeConstantValues(
16881686
//===----------------------------------------------------------------------===//
16891687

16901688
ConstExprStepEvaluator::ConstExprStepEvaluator(SymbolicValueAllocator &alloc,
1691-
SILFunction *fun)
1692-
: evaluator(alloc), internalState(new ConstExprFunctionState(
1693-
evaluator, fun, {}, stepsEvaluated)) {
1689+
SILFunction *fun,
1690+
bool trackCallees)
1691+
: evaluator(alloc, trackCallees), internalState(new ConstExprFunctionState(
1692+
evaluator, fun, {}, stepsEvaluated)) {
16941693
assert(fun);
16951694
}
16961695

0 commit comments

Comments
 (0)