File: ConstantEvaluableSubsetChecker.cpp

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (197 lines) | stat: -rw-r--r-- 7,474 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
//===--ConstantEvaluableSubsetChecker.cpp - Test Constant Evaluable Swift--===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

// This file implements a pass for checking the constant evaluability of Swift
// code snippets. This pass is only used in tests and is not part of the
// compilation pipeline.

#define DEBUG_TYPE "sil-constant-evaluable-subset-checker"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Module.h"
#include "swift/Demangling/Demangle.h"
#include "swift/SIL/CFG.h"
#include "swift/SIL/SILConstants.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SILOptimizer/PassManager/Passes.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/ConstExpr.h"

using namespace swift;

namespace {

static const StringRef testDriverSemanticsAttr = "test_driver";

template <typename... T, typename... U>
static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc,
                                   Diag<T...> diag, U &&... args) {
  return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
}

static std::string demangleSymbolName(StringRef name) {
  Demangle::DemangleOptions options;
  options.QualifyEntities = false;
  return Demangle::demangleSymbolAsString(name, options);
}

/// A SILModule pass that invokes the constant evaluator on all functions in a
/// SILModule with the semantics attribute "test_driver". Each "test_driver"
/// must invoke one or more functions in the module annotated as
/// "constant_evaluable" with constant arguments.
class ConstantEvaluableSubsetChecker : public SILModuleTransform {

  llvm::SmallPtrSet<SILFunction *, 4> constantEvaluableFunctions;
  llvm::SmallPtrSet<SILFunction *, 4> evaluatedFunctions;

  /// Evaluate the body of \c fun with the constant evaluator. \c fun must be
  /// annotated as "test_driver" and must invoke one or more functions annotated
  /// as "constant_evaluable" with constant arguments. Emit diagnostics if the
  /// evaluation of any  "constant_evaluable" function called in the body of
  /// \c fun fails.
  void constantEvaluateDriver(SILFunction *fun) {
    ASTContext &astContext = fun->getASTContext();

    // Create a step evaluator and run it on the function.
    SymbolicValueBumpAllocator allocator;
    ConstExprStepEvaluator stepEvaluator(allocator, fun,
                                         getOptions().AssertConfig,
                                         /*trackCallees*/ true);
    bool previousEvaluationHadFatalError = false;

    for (auto currI = fun->getEntryBlock()->begin();;) {
      auto *inst = &(*currI);

      if (isa<ReturnInst>(inst))
        break;

      auto *applyInst = dyn_cast<ApplyInst>(inst);
      SILFunction *callee = nullptr;
      if (applyInst) {
        callee = applyInst->getReferencedFunctionOrNull();
      }

      std::optional<SILBasicBlock::iterator> nextInstOpt;
      std::optional<SymbolicValue> errorVal;

      if (!applyInst || !callee || !isConstantEvaluable(callee)) {

        // Ignore these instructions if we had a fatal error already.
        if (previousEvaluationHadFatalError) {
          if (isa<TermInst>(inst)) {
            assert(false && "non-constant control flow in the test driver");
          }
          ++currI;
          continue;
        }

        std::tie(nextInstOpt, errorVal) =
            stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
        if (!nextInstOpt) {
          // This indicates an error in the test driver.
          errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
          assert(false && "non-constant control flow in the test driver");
        }
        currI = nextInstOpt.value();
        continue;
      }

      assert(!previousEvaluationHadFatalError &&
             "cannot continue evaluation of test driver as previous call "
             "resulted in non-skippable evaluation error.");

      // Here, a function annotated as "constant_evaluable" is called.
      llvm::errs() << "@" << demangleSymbolName(callee->getName()) << "\n";
      std::tie(nextInstOpt, errorVal) =
          stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);

      if (errorVal) {
        SourceLoc instLoc = inst->getLoc().getSourceLoc();
        diagnose(astContext, instLoc, diag::not_constant_evaluable);
        errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
      }

      if (nextInstOpt) {
        currI = nextInstOpt.value();
        continue;
      }

      // Here, a non-skippable error like "instruction-limit exceeded" has been
      // encountered during evaluation. Proceed to the next instruction but
      // ensure that an assertion failure occurs if there is any instruction
      // to evaluate after this instruction.
      ++currI;
      previousEvaluationHadFatalError = true;
    }

    // For every function seen during the evaluation of this constant evaluable
    // function:
    // 1. Record it so as to detect whether the test drivers in the SILModule
    // cover all function annotated as "constant_evaluable".
    //
    // 2. If the callee is annotated as constant_evaluable and is imported from
    // a different Swift module (other than stdlib), check that the function is
    // marked as Onone. Otherwise, it could have been optimized, which will
    // break constant evaluability.
    for (SILFunction *callee : stepEvaluator.getFuncsCalledDuringEvaluation()) {
      evaluatedFunctions.insert(callee);

      SILModule &calleeModule = callee->getModule();
      if (callee->isAvailableExternally() &&
          hasConstantEvaluableAnnotation(callee) &&
          callee->getOptimizationMode() != OptimizationMode::NoOptimization) {
        diagnose(calleeModule.getASTContext(),
                 callee->getLocation().getSourceLoc(),
                 diag::constexpr_imported_func_not_onone,
                 demangleSymbolName(callee->getName()));
      }
    }
  }

  void run() override {
    SILModule *module = getModule();
    assert(module);

    for (SILFunction &fun : *module) {
      // Record functions annotated as constant evaluable.
      if (hasConstantEvaluableAnnotation(&fun)) {
        constantEvaluableFunctions.insert(&fun);
        continue;
      }

      // Evaluate test drivers.
      if (!fun.hasSemanticsAttr(testDriverSemanticsAttr))
        continue;
      constantEvaluateDriver(&fun);
    }

    // Assert that every function annotated as "constant_evaluable" was covered
    // by a test driver.
    bool error = false;
    for (SILFunction *constEvalFun : constantEvaluableFunctions) {
      if (!evaluatedFunctions.count(constEvalFun)) {
        llvm::errs() << "Error: function "
                     << demangleSymbolName(constEvalFun->getName());
        llvm::errs() << " annotated as constant evaluable";
        llvm::errs() << " does not have a test driver"
                     << "\n";
        error = true;
      }
    }
    assert(!error);
  }
};

} // end anonymous namespace

SILTransform *swift::createConstantEvaluableSubsetChecker() {
  return new ConstantEvaluableSubsetChecker();
}