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