Coverage Report

Created: 2026-10-09 06:57

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/proc/self/cwd/eval/compiler/constant_folding.cc
Line
Count
Source
1
// Copyright 2019 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//     https://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "eval/compiler/constant_folding.h"
16
17
#include <cstddef>
18
#include <memory>
19
#include <utility>
20
#include <vector>
21
22
#include "absl/base/nullability.h"
23
#include "absl/status/status.h"
24
#include "absl/status/statusor.h"
25
#include "base/builtins.h"
26
#include "base/type_provider.h"
27
#include "common/ast.h"
28
#include "common/constant.h"
29
#include "common/expr.h"
30
#include "common/value.h"
31
#include "eval/compiler/flat_expr_builder_extensions.h"
32
#include "eval/compiler/resolver.h"
33
#include "eval/eval/evaluator_core.h"
34
#include "internal/status_macros.h"
35
#include "runtime/activation.h"
36
#include "runtime/internal/convert_constant.h"
37
#include "google/protobuf/arena.h"
38
#include "google/protobuf/descriptor.h"
39
#include "google/protobuf/message.h"
40
41
namespace cel::runtime_internal {
42
43
namespace {
44
45
using ::cel::Expr;
46
using ::cel::builtin::kAnd;
47
using ::cel::builtin::kOr;
48
using ::cel::builtin::kTernary;
49
using ::cel::runtime_internal::ConvertConstant;
50
using ::google::api::expr::runtime::EvaluationListener;
51
using ::google::api::expr::runtime::ExecutionFrame;
52
using ::google::api::expr::runtime::ExecutionPath;
53
using ::google::api::expr::runtime::ExecutionPathView;
54
using ::google::api::expr::runtime::ExpressionStep;
55
using ::google::api::expr::runtime::FlatExpressionEvaluatorState;
56
using ::google::api::expr::runtime::PlannerContext;
57
using ::google::api::expr::runtime::ProgramOptimizer;
58
using ::google::api::expr::runtime::ProgramOptimizerFactory;
59
using ::google::api::expr::runtime::Resolver;
60
61
enum class IsConst {
62
  kConditional,
63
  kNonConst,
64
};
65
66
class ConstantFoldingExtension : public ProgramOptimizer {
67
 public:
68
  ConstantFoldingExtension(
69
      const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
70
      absl_nullable std::shared_ptr<google::protobuf::Arena> shared_arena,
71
      google::protobuf::Arena* absl_nonnull arena,
72
      absl_nullable std::shared_ptr<google::protobuf::MessageFactory>
73
          shared_message_factory,
74
      google::protobuf::MessageFactory* absl_nonnull message_factory,
75
      const TypeProvider& type_provider)
76
0
      : shared_arena_(std::move(shared_arena)),
77
0
        shared_message_factory_(std::move(shared_message_factory)),
78
0
        state_(kDefaultStackLimit, kComprehensionSlotCount, type_provider,
79
0
               descriptor_pool, message_factory, arena) {}
80
81
  absl::Status OnPreVisit(google::api::expr::runtime::PlannerContext& context,
82
                          const Expr& node) override;
83
  absl::Status OnPostVisit(google::api::expr::runtime::PlannerContext& context,
84
                           const Expr& node) override;
85
86
 private:
87
  // Most constant folding evaluations are simple
88
  // binary operators.
89
  static constexpr size_t kDefaultStackLimit = 4;
90
91
  // Comprehensions are not evaluated -- the current implementation can't detect
92
  // if the comprehension variables are only used in a const way.
93
  static constexpr size_t kComprehensionSlotCount = 0;
94
95
  absl_nullable std::shared_ptr<google::protobuf::Arena> shared_arena_;
96
  [[maybe_unused]]
97
  absl_nullable std::shared_ptr<google::protobuf::MessageFactory> shared_message_factory_;
98
  Activation empty_;
99
  FlatExpressionEvaluatorState state_;
100
101
  std::vector<IsConst> is_const_;
102
};
103
104
0
IsConst IsConstExpr(const Expr& expr, const Resolver& resolver) {
105
0
  switch (expr.kind_case()) {
106
0
    case ExprKindCase::kConstant:
107
0
      return IsConst::kConditional;
108
0
    case ExprKindCase::kIdentExpr:
109
0
      return IsConst::kNonConst;
110
0
    case ExprKindCase::kComprehensionExpr:
111
      // Not yet supported, need to identify whether range and
112
      // iter vars are compatible with const folding.
113
0
      return IsConst::kNonConst;
114
0
    case ExprKindCase::kStructExpr:
115
0
      return IsConst::kNonConst;
116
0
    case ExprKindCase::kMapExpr:
117
      // Empty maps are rare and not currently supported as they may eventually
118
      // have similar issues to empty list when used within comprehensions or
119
      // macros.
120
0
      if (expr.map_expr().entries().empty()) {
121
0
        return IsConst::kNonConst;
122
0
      }
123
0
      return IsConst::kConditional;
124
0
    case ExprKindCase::kListExpr:
125
0
      if (expr.list_expr().elements().empty()) {
126
        // Don't fold for empty list to allow comprehension
127
        // list append optimization.
128
0
        return IsConst::kNonConst;
129
0
      }
130
0
      return IsConst::kConditional;
131
0
    case ExprKindCase::kSelectExpr:
132
0
      return IsConst::kConditional;
133
0
    case ExprKindCase::kCallExpr: {
134
0
      const auto& call = expr.call_expr();
135
      // Short Circuiting operators not yet supported.
136
0
      if (call.function() == kAnd || call.function() == kOr ||
137
0
          call.function() == kTernary) {
138
0
        return IsConst::kNonConst;
139
0
      }
140
      // For now we skip constant folding for cel.@block. We do not yet setup
141
      // slots. When we enable constant folding for comprehensions (like
142
      // cel.bind), we can address cel.@block.
143
0
      if (call.function() == "cel.@block") {
144
0
        return IsConst::kNonConst;
145
0
      }
146
147
0
      int arg_len = call.args().size() + (call.has_target() ? 1 : 0);
148
      // Check for any lazy overloads (activation dependant)
149
0
      if (!resolver
150
0
               .FindLazyOverloads(call.function(), call.has_target(), arg_len)
151
0
               .empty()) {
152
0
        return IsConst::kNonConst;
153
0
      }
154
155
0
      auto overloads =
156
0
          resolver.FindOverloads(call.function(), call.has_target(), arg_len);
157
      // Check for any contextual overloads. If there are any, we cowardly
158
      // avoid constant folding instead of trying to check if one of the
159
      // overloads would be safe to use.
160
0
      for (const auto& overload : overloads) {
161
0
        if (overload.descriptor.is_contextual()) {
162
0
          return IsConst::kNonConst;
163
0
        }
164
0
      }
165
166
0
      return IsConst::kConditional;
167
0
    }
168
0
    case ExprKindCase::kUnspecifiedExpr:
169
0
    default:
170
0
      return IsConst::kNonConst;
171
0
  }
172
0
}
173
174
absl::Status ConstantFoldingExtension::OnPreVisit(PlannerContext& context,
175
0
                                                  const Expr& node) {
176
0
  IsConst is_const = IsConstExpr(node, context.resolver());
177
0
  is_const_.push_back(is_const);
178
179
0
  return absl::OkStatus();
180
0
}
181
182
absl::Status ConstantFoldingExtension::OnPostVisit(PlannerContext& context,
183
0
                                                   const Expr& node) {
184
0
  if (is_const_.empty()) {
185
0
    return absl::InternalError("ConstantFoldingExtension called out of order.");
186
0
  }
187
188
0
  IsConst is_const = is_const_.back();
189
0
  is_const_.pop_back();
190
191
0
  if (is_const == IsConst::kNonConst) {
192
    // update parent
193
0
    if (!is_const_.empty()) {
194
0
      is_const_.back() = IsConst::kNonConst;
195
0
    }
196
0
    return absl::OkStatus();
197
0
  }
198
0
  ExecutionPathView subplan = context.GetSubplan(node);
199
0
  if (subplan.empty()) {
200
    // This subexpression is already optimized out or suppressed.
201
0
    return absl::OkStatus();
202
0
  }
203
  // copy string to managed handle if backed by the original program.
204
0
  Value value;
205
0
  if (node.has_const_expr()) {
206
0
    CEL_ASSIGN_OR_RETURN(value,
207
0
                         ConvertConstant(node.const_expr(), state_.arena()));
208
0
  } else {
209
0
    ExecutionFrame frame(subplan, empty_, context.options(), state_);
210
0
    state_.Reset();
211
    // Update stack size to accommodate sub expression.
212
    // This only results in a vector resize if the new maxsize is greater than
213
    // the current capacity.
214
0
    state_.value_stack().SetMaxSize(subplan.size());
215
216
0
    auto result = frame.Evaluate();
217
    // If this would be a runtime error, then don't adjust the program plan, but
218
    // rather allow the error to occur at runtime to preserve the evaluation
219
    // contract with non-constant folding use cases.
220
0
    if (!result.ok()) {
221
0
      return absl::OkStatus();
222
0
    }
223
0
    value = *result;
224
0
    if (value->Is<UnknownValue>()) {
225
0
      return absl::OkStatus();
226
0
    }
227
0
  }
228
229
  // Otherwise make a stack machine plan.
230
0
  ExecutionPath new_plan;
231
0
  new_plan.push_back(ExpressionStep::MakeConstant(value, node.id()));
232
233
0
  return context.ReplaceSubplan(node, std::move(new_plan));
234
0
}
235
236
}  // namespace
237
238
ProgramOptimizerFactory CreateConstantFoldingOptimizer(
239
    absl_nullable std::shared_ptr<google::protobuf::Arena> arena,
240
0
    absl_nullable std::shared_ptr<google::protobuf::MessageFactory> message_factory) {
241
0
  return
242
0
      [shared_arena = std::move(arena),
243
0
       shared_message_factory = std::move(message_factory)](
244
0
          PlannerContext& context,
245
0
          const Ast&) -> absl::StatusOr<std::unique_ptr<ProgramOptimizer>> {
246
        // If one was explicitly provided during planning or none was explicitly
247
        // provided during configuration, request one from the planning context.
248
        // Otherwise use the one provided during configuration.
249
0
        google::protobuf::Arena* absl_nonnull arena =
250
0
            context.HasExplicitArena() || shared_arena == nullptr
251
0
                ? context.MutableArena()
252
0
                : shared_arena.get();
253
0
        google::protobuf::MessageFactory* absl_nonnull message_factory =
254
0
            context.HasExplicitMessageFactory() ||
255
0
                    shared_message_factory == nullptr
256
0
                ? context.MutableMessageFactory()
257
0
                : shared_message_factory.get();
258
0
        return std::make_unique<ConstantFoldingExtension>(
259
0
            context.descriptor_pool(), shared_arena, arena,
260
0
            shared_message_factory, message_factory, context.type_reflector());
261
0
      };
262
0
}
263
264
}  // namespace cel::runtime_internal