Skip to content

Commit 94101d1

Browse files
jnthntatumcopybara-github
authored andcommitted
Track iterator stack limit separately from comprehension slots.
PiperOrigin-RevId: 1000446433
1 parent 9cef3a1 commit 94101d1

16 files changed

Lines changed: 242 additions & 29 deletions

‎eval/compiler/BUILD‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ cc_test(
204204
":cel_expression_builder_flat_impl",
205205
":comprehension_vulnerability_check",
206206
":flat_expr_builder",
207+
"//base:ast",
208+
"//common:source",
209+
"//common:value",
210+
"//common:value_testing",
211+
"//eval/eval:evaluator_core",
207212
"//eval/public:activation",
208213
"//eval/public:builtin_func_registrar",
209214
"//eval/public:cel_attribute",
@@ -213,10 +218,15 @@ cc_test(
213218
"//eval/public/containers:container_backed_list_impl",
214219
"//eval/public/testing:matchers",
215220
"//eval/testutil:test_message_cc_proto",
221+
"//extensions:bindings_ext",
216222
"//internal:testing",
217223
"//parser",
224+
"//parser:parser_interface",
225+
"//runtime:activation",
218226
"//runtime:runtime_options",
227+
"//runtime/internal:runtime_env",
219228
"//runtime/internal:runtime_env_testing",
229+
"//testutil:test_macros",
220230
"@com_google_absl//absl/status",
221231
"@com_google_absl//absl/strings",
222232
"@com_google_cel_spec//proto/cel/expr:syntax_cc_proto",

‎eval/compiler/constant_folding.cc‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,9 @@ class ConstantFoldingExtension : public ProgramOptimizer {
7575
const TypeProvider& type_provider)
7676
: shared_arena_(std::move(shared_arena)),
7777
shared_message_factory_(std::move(shared_message_factory)),
78-
state_(kDefaultStackLimit, kComprehensionSlotCount, type_provider,
79-
descriptor_pool, message_factory, arena) {}
78+
state_(kDefaultStackLimit, kComprehensionSlotCount,
79+
/*iterator_stack_size=*/0, type_provider, descriptor_pool,
80+
message_factory, arena) {}
8081

8182
absl::Status OnPreVisit(google::api::expr::runtime::PlannerContext& context,
8283
const Expr& node) override;

‎eval/compiler/flat_expr_builder.cc‎

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,12 @@ class FlatExprVisitor;
105105
// Helper for bookkeeping variables mapped to indexes.
106106
class IndexManager {
107107
public:
108-
IndexManager() : next_free_slot_(0), max_slot_count_(0) {}
108+
IndexManager()
109+
: next_free_slot_(0),
110+
max_slot_count_(0),
111+
iter_stack_count_(0),
112+
max_iter_stack_count_(0),
113+
lazy_iter_stack_count_(0) {}
109114

110115
size_t ReserveSlots(size_t n) {
111116
size_t result = next_free_slot_;
@@ -121,11 +126,30 @@ class IndexManager {
121126
return next_free_slot_;
122127
}
123128

129+
void ReserveIterStack() {
130+
++iter_stack_count_;
131+
max_iter_stack_count_ = std::max(max_iter_stack_count_, iter_stack_count_);
132+
}
133+
void ReleaseIterStack() { --iter_stack_count_; }
134+
void AddLazyIterStack() { ++lazy_iter_stack_count_; }
135+
124136
size_t max_slot_count() const { return max_slot_count_; }
125137

138+
// Returns the maximum number of iterator stack slots required to evaluate the
139+
// expression by tracking the maximum comprehension nesting in the main
140+
// expression and dedicating a slot for any comprehensions evaluated lazily.
141+
// This is not as tight as possible, but is a correct upper bound estimate and
142+
// is tight for small expressions.
143+
size_t IterStackRequirement() const {
144+
return max_iter_stack_count_ + lazy_iter_stack_count_;
145+
}
146+
126147
private:
127148
size_t next_free_slot_;
128149
size_t max_slot_count_;
150+
size_t iter_stack_count_;
151+
size_t max_iter_stack_count_;
152+
size_t lazy_iter_stack_count_;
129153
};
130154

131155
// Helper for computing jump offsets.
@@ -1145,21 +1169,36 @@ class FlatExprVisitor : public cel::AstVisitor {
11451169
slot_count = 0;
11461170
}
11471171
}
1172+
11481173
// If this is in the scope of an optimized bind accu-init, account the slots
11491174
// to the outermost bind-init scope.
11501175
//
11511176
// The init expression is effectively inlined at the first usage in the
11521177
// critical path (which is unknown at plan time), so the used slots need to
11531178
// be dedicated for the entire scope of that bind.
1179+
bool in_bind_init = false;
11541180
for (ComprehensionStackRecord& record : comprehension_stack_) {
11551181
if (record.in_accu_init && record.is_optimizable_bind) {
11561182
record.slot_count += slot_count;
11571183
slot_count = 0;
1184+
in_bind_init = true;
11581185
break;
11591186
}
11601187
// If no bind init subexpression, account normally.
11611188
}
11621189

1190+
// Iterator frames are releasable if not in a bind (no iterator push), and
1191+
// not in a lazy subexpression (which may evaluate in different implicit
1192+
// scopes).
1193+
const bool is_stack_releasable =
1194+
!is_bind && !in_bind_init && (!block_.has_value() || !block_->in);
1195+
1196+
if (is_stack_releasable) {
1197+
index_manager_.ReserveIterStack();
1198+
} else if (!is_bind) {
1199+
index_manager_.AddLazyIterStack();
1200+
}
1201+
11631202
comprehension_stack_.push_back(
11641203
{&expr, &comprehension, iter_slot, iter2_slot, accu_slot, slot_count,
11651204
/*subexpression=*/-1,
@@ -1172,6 +1211,7 @@ class FlatExprVisitor : public cel::AstVisitor {
11721211
options_.enable_comprehension_mutable_map,
11731212
options_.short_circuiting),
11741213
/*.is_optimizable_bind=*/is_bind,
1214+
/*.is_stack_releasable=*/is_stack_releasable,
11751215
/*.iter_var_in_scope=*/false,
11761216
/*.iter_var2_in_scope=*/false,
11771217
/*.accu_var_in_scope=*/false,
@@ -1198,6 +1238,10 @@ class FlatExprVisitor : public cel::AstVisitor {
11981238
record.visitor->PostVisit(&expr);
11991239

12001240
index_manager_.ReleaseSlots(record.slot_count);
1241+
if (record.is_stack_releasable) {
1242+
ABSL_DCHECK(!record.is_optimizable_bind);
1243+
index_manager_.ReleaseIterStack();
1244+
}
12011245
comprehension_stack_.pop_back();
12021246
}
12031247

@@ -1499,6 +1543,10 @@ class FlatExprVisitor : public cel::AstVisitor {
14991543

15001544
size_t slot_count() const { return index_manager_.max_slot_count(); }
15011545

1546+
size_t iter_stack_count() const {
1547+
return index_manager_.IterStackRequirement();
1548+
}
1549+
15021550
void AddOptimizer(std::unique_ptr<ProgramOptimizer> optimizer) {
15031551
program_optimizers_.push_back(std::move(optimizer));
15041552
}
@@ -1530,6 +1578,7 @@ class FlatExprVisitor : public cel::AstVisitor {
15301578
bool is_optimizable_list_append;
15311579
bool is_optimizable_map_insert;
15321580
bool is_optimizable_bind;
1581+
bool is_stack_releasable;
15331582
bool iter_var_in_scope;
15341583
bool iter_var2_in_scope;
15351584
bool accu_var_in_scope;
@@ -2298,8 +2347,8 @@ absl::StatusOr<FlatExpression> FlatExprBuilder::CreateExpressionImpl(
22982347
FlattenExpressionTable(program_builder, execution_path);
22992348

23002349
return FlatExpression(std::move(execution_path), std::move(subexpressions),
2301-
visitor.slot_count(), GetTypeProvider(), options_,
2302-
std::move(arena));
2350+
visitor.slot_count(), visitor.iter_stack_count(),
2351+
GetTypeProvider(), options_, std::move(arena));
23032352
}
23042353
const cel::TypeProvider& FlatExprBuilder::GetTypeProvider() const {
23052354
return use_legacy_type_provider_

‎eval/compiler/flat_expr_builder_comprehensions_test.cc‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,22 @@
1414
* limitations under the License.
1515
*/
1616

17+
#include <memory>
18+
#include <utility>
1719
#include <vector>
1820

1921
#include "cel/expr/syntax.pb.h"
2022
#include "google/protobuf/field_mask.pb.h"
2123
#include "absl/status/status.h"
2224
#include "absl/strings/string_view.h"
25+
#include "base/ast.h"
26+
#include "common/source.h"
27+
#include "common/value.h"
28+
#include "common/value_testing.h"
2329
#include "eval/compiler/cel_expression_builder_flat_impl.h"
2430
#include "eval/compiler/comprehension_vulnerability_check.h"
2531
#include "eval/compiler/flat_expr_builder.h"
32+
#include "eval/eval/evaluator_core.h"
2633
#include "eval/public/activation.h"
2734
#include "eval/public/builtin_func_registrar.h"
2835
#include "eval/public/cel_attribute.h"
@@ -32,10 +39,15 @@
3239
#include "eval/public/containers/container_backed_list_impl.h"
3340
#include "eval/public/testing/matchers.h"
3441
#include "eval/testutil/test_message.pb.h"
42+
#include "extensions/bindings_ext.h"
3543
#include "internal/testing.h"
3644
#include "parser/parser.h"
45+
#include "parser/parser_interface.h"
46+
#include "runtime/activation.h"
47+
#include "runtime/internal/runtime_env.h"
3748
#include "runtime/internal/runtime_env_testing.h"
3849
#include "runtime/runtime_options.h"
50+
#include "testutil/test_macros.h"
3951
#include "google/protobuf/arena.h"
4052
#include "google/protobuf/text_format.h"
4153

@@ -623,6 +635,119 @@ TEST_F(CelExpressionBuilderFlatImplComprehensionsTest,
623635
HasSubstr("Unexpected iter_var access in trivial comprehension")));
624636
}
625637

638+
TEST_F(CelExpressionBuilderFlatImplComprehensionsTest,
639+
BindInsideComprehensionWithComprehensionInInitIterStackCount) {
640+
std::unique_ptr<cel::ParserBuilder> parser_builder = cel::NewParserBuilder();
641+
ASSERT_THAT(
642+
parser_builder->AddLibrary(cel::extensions::BindingsParserLibrary()),
643+
IsOk());
644+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<cel::Parser> parser,
645+
parser_builder->Build());
646+
647+
ASSERT_OK_AND_ASSIGN(
648+
std::unique_ptr<cel::Source> source,
649+
parser->PrepareSource(
650+
"[[1, 2], [3, 4]].map(l, cel.bind(filtered, l.filter(x, x > 1), "
651+
"size(filtered) > 0 ? filtered[0] : 0)) == [2, 3]"));
652+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<cel::Ast> ast, parser->Parse(*source));
653+
654+
cel::RuntimeOptions options = GetRuntimeOptions();
655+
std::shared_ptr<cel::runtime_internal::RuntimeEnv> env =
656+
NewTestingRuntimeEnv();
657+
CelExpressionBuilderFlatImpl builder(env, options);
658+
ASSERT_THAT(RegisterBuiltinFunctions(builder.GetRegistry()), IsOk());
659+
660+
ASSERT_OK_AND_ASSIGN(
661+
FlatExpression flat_expr,
662+
builder.flat_expr_builder().CreateExpressionImpl(std::move(ast),
663+
/*issues=*/nullptr));
664+
EXPECT_EQ(flat_expr.iterator_stack_size(), 2);
665+
666+
cel::Activation activation;
667+
google::protobuf::Arena arena;
668+
FlatExpressionEvaluatorState state = flat_expr.MakeEvaluatorState(
669+
env->descriptor_pool.get(), env->MutableMessageFactory(), &arena);
670+
ASSERT_OK_AND_ASSIGN(
671+
cel::Value result,
672+
flat_expr.EvaluateWithCallback(activation, /*embedder_context=*/nullptr,
673+
EvaluationListener(), state));
674+
EXPECT_THAT(result, cel::test::BoolValueIs(true));
675+
}
676+
677+
TEST_F(CelExpressionBuilderFlatImplComprehensionsTest,
678+
BlockWithComprehensionInBindingAndBodyIterStackCount) {
679+
std::unique_ptr<cel::ParserBuilder> parser_builder = cel::NewParserBuilder();
680+
ASSERT_THAT(parser_builder->AddLibrary(cel::test::TestMacrosParserLibrary()),
681+
IsOk());
682+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<cel::Parser> parser,
683+
parser_builder->Build());
684+
685+
ASSERT_OK_AND_ASSIGN(
686+
std::unique_ptr<cel::Source> source,
687+
parser->PrepareSource(
688+
"cel.block([[1, 2, 3].filter(x, x > 1), [1, 2, 3].filter(x, x < 3)], "
689+
"[1, 2].map(y, y in cel.index(0) || y in cel.index(1))) == [true, "
690+
"true]"));
691+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<cel::Ast> ast, parser->Parse(*source));
692+
693+
cel::RuntimeOptions options = GetRuntimeOptions();
694+
std::shared_ptr<cel::runtime_internal::RuntimeEnv> env =
695+
NewTestingRuntimeEnv();
696+
CelExpressionBuilderFlatImpl builder(env, options);
697+
ASSERT_THAT(RegisterBuiltinFunctions(builder.GetRegistry()), IsOk());
698+
699+
ASSERT_OK_AND_ASSIGN(
700+
FlatExpression flat_expr,
701+
builder.flat_expr_builder().CreateExpressionImpl(std::move(ast),
702+
/*issues=*/nullptr));
703+
EXPECT_EQ(flat_expr.iterator_stack_size(), 3);
704+
705+
cel::Activation activation;
706+
google::protobuf::Arena arena;
707+
FlatExpressionEvaluatorState state = flat_expr.MakeEvaluatorState(
708+
env->descriptor_pool.get(), env->MutableMessageFactory(), &arena);
709+
ASSERT_OK_AND_ASSIGN(
710+
cel::Value result,
711+
flat_expr.EvaluateWithCallback(activation, /*embedder_context=*/nullptr,
712+
EvaluationListener(), state));
713+
EXPECT_THAT(result, cel::test::BoolValueIs(true));
714+
}
715+
716+
TEST_F(CelExpressionBuilderFlatImplComprehensionsTest,
717+
NonNestedComprehensionsIterStackCount) {
718+
std::unique_ptr<cel::ParserBuilder> parser_builder = cel::NewParserBuilder();
719+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<cel::Parser> parser,
720+
parser_builder->Build());
721+
722+
ASSERT_OK_AND_ASSIGN(
723+
std::unique_ptr<cel::Source> source,
724+
parser->PrepareSource(
725+
"[1, 2].map(x, x + 1) + [3, 4].filter(y, y > 3) == [2, 3, 4]"));
726+
ASSERT_OK_AND_ASSIGN(std::unique_ptr<cel::Ast> ast, parser->Parse(*source));
727+
728+
cel::RuntimeOptions options = GetRuntimeOptions();
729+
std::shared_ptr<cel::runtime_internal::RuntimeEnv> env =
730+
NewTestingRuntimeEnv();
731+
CelExpressionBuilderFlatImpl builder(env, options);
732+
ASSERT_THAT(RegisterBuiltinFunctions(builder.GetRegistry()), IsOk());
733+
734+
ASSERT_OK_AND_ASSIGN(
735+
FlatExpression flat_expr,
736+
builder.flat_expr_builder().CreateExpressionImpl(std::move(ast),
737+
/*issues=*/nullptr));
738+
EXPECT_EQ(flat_expr.iterator_stack_size(), 1);
739+
740+
cel::Activation activation;
741+
google::protobuf::Arena arena;
742+
FlatExpressionEvaluatorState state = flat_expr.MakeEvaluatorState(
743+
env->descriptor_pool.get(), env->MutableMessageFactory(), &arena);
744+
ASSERT_OK_AND_ASSIGN(
745+
cel::Value result,
746+
flat_expr.EvaluateWithCallback(activation, /*embedder_context=*/nullptr,
747+
EvaluationListener(), state));
748+
EXPECT_THAT(result, cel::test::BoolValueIs(true));
749+
}
750+
626751
} // namespace
627752

628753
} // namespace google::api::expr::runtime

‎eval/eval/BUILD‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -927,6 +927,7 @@ cc_library(
927927
deps = [
928928
"//common:value",
929929
"@com_google_absl//absl/base:nullability",
930+
"@com_google_absl//absl/container:inlined_vector",
930931
"@com_google_absl//absl/log:absl_check",
931932
],
932933
)

‎eval/eval/equality_steps_test.cc‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ TEST(IterativeTest, PartialAttrUnknown) {
8181

8282
FlatExpressionEvaluatorState state(
8383
/*value_stack_size=*/5,
84-
/*comprehension_slot_count=*/0, type_provider,
84+
/*comprehension_slot_count=*/0, /*iterator_stack_size=*/0, type_provider,
8585
cel::internal::GetTestingDescriptorPool(),
8686
cel::internal::GetTestingMessageFactory(), &arena);
8787

@@ -113,7 +113,7 @@ TEST(IterativeTest, PartialAttrUnknownDisabled) {
113113

114114
FlatExpressionEvaluatorState state(
115115
/*value_stack_size=*/5,
116-
/*comprehension_slot_count=*/0, type_provider,
116+
/*comprehension_slot_count=*/0, /*iterator_stack_size=*/0, type_provider,
117117
cel::internal::GetTestingDescriptorPool(),
118118
cel::internal::GetTestingMessageFactory(), &arena);
119119

@@ -183,7 +183,7 @@ TEST_P(EqualsTest, Iterative) {
183183

184184
FlatExpressionEvaluatorState state(
185185
/*value_stack_size=*/5,
186-
/*comprehension_slot_count=*/0, type_provider,
186+
/*comprehension_slot_count=*/0, /*iterator_stack_size=*/0, type_provider,
187187
cel::internal::GetTestingDescriptorPool(),
188188
cel::internal::GetTestingMessageFactory(), &arena);
189189

@@ -328,7 +328,7 @@ TEST_P(InTest, Iterative) {
328328

329329
FlatExpressionEvaluatorState state(
330330
/*value_stack_size=*/5,
331-
/*comprehension_slot_count=*/0, type_provider,
331+
/*comprehension_slot_count=*/0, /*iterator_stack_size=*/0, type_provider,
332332
cel::internal::GetTestingDescriptorPool(),
333333
cel::internal::GetTestingMessageFactory(), &arena);
334334

‎eval/eval/evaluator_core.cc‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,16 +222,16 @@ FlatExpressionEvaluatorState FlatExpression::MakeEvaluatorState(
222222
google::protobuf::MessageFactory* absl_nonnull message_factory,
223223
google::protobuf::Arena* absl_nonnull arena) const {
224224
return FlatExpressionEvaluatorState(path_.size(), comprehension_slots_size_,
225-
type_provider_, descriptor_pool,
226-
message_factory, arena);
225+
iterator_stack_size_, type_provider_,
226+
descriptor_pool, message_factory, arena);
227227
}
228228

229229
FlatExpressionEvaluatorState FlatExpression::MakeEvaluatorState(
230230
const google::protobuf::DescriptorPool* absl_nonnull descriptor_pool,
231231
google::protobuf::MessageFactory* absl_nullable message_factory) const {
232232
return FlatExpressionEvaluatorState(path_.size(), comprehension_slots_size_,
233-
type_provider_, descriptor_pool,
234-
message_factory);
233+
iterator_stack_size_, type_provider_,
234+
descriptor_pool, message_factory);
235235
}
236236

237237
absl::StatusOr<cel::Value> FlatExpression::EvaluateWithCallback(

0 commit comments

Comments
 (0)