Files
regorus/src/builtins/aggregates.rs
Anand Krishnamoorthi fd59bb5a91 feat(memory): Allocator-backed global memory limits (#544)
Policy evaluation at scale needs to be able to set memory limits
so that a bad policy does not hog memory or to ensure that
policy evaluation itself does not use too much memory which could
cause other components to suffer.

This PR introduces capability to set and enforce global memory limits.
It also lays the groundwork for enabling per evaluation limits in future.

Once a global memory limit is set, Regorus maintains per thread counters
to track memory activity (allocation, deallocation) of a thread.
These counters are periodically flushed to global memory counters.
Per thread counters avoid the contention that updating global counters
on each alloc/free would cause.

Policy evaluation periodically checks these counters and raises errors
if allocated memory has exceeded the configured limit.

Currently memory limit capability is exposed only to FFI and C#.

Also update mimalloc to v2.2.6

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-01-24 07:08:54 +05:30

147 lines
4.7 KiB
Rust

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(clippy::pattern_type_mismatch)]
use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{enforce_limit, ensure_args_count, ensure_numeric};
use crate::lexer::Span;
use crate::number::Number;
use crate::value::Value;
use crate::*;
use anyhow::{bail, Result};
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
m.insert("count", (count, 1));
m.insert("max", (max, 1));
m.insert("min", (min, 1));
m.insert("product", (product, 1));
m.insert("sort", (sort, 1));
m.insert("sum", (sum, 1));
}
fn count(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
ensure_args_count(span, "count", params, args, 1)?;
Ok(Value::from(Number::from(match &args[0] {
Value::Array(a) => a.len(),
Value::Set(a) => a.len(),
Value::Object(a) => a.len(),
Value::String(a) => a.encode_utf16().count(),
a if strict => {
let span = params[0].span();
bail!(span.error(
format!("`count` requires array/object/set/string argument. Got `{a}`.").as_str()
))
}
_ => return Ok(Value::Undefined),
})))
}
fn max(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "max", params, args, 1)?;
Ok(match &args[0] {
Value::Array(a) if a.is_empty() => Value::Undefined,
Value::Array(a) => a.iter().max().unwrap().clone(),
Value::Set(a) if a.is_empty() => Value::Undefined,
Value::Set(a) => a.iter().max().unwrap().clone(),
a => {
let span = params[0].span();
bail!(span.error(format!("`max` requires array/set argument. Got `{a}`.").as_str()))
}
})
}
fn min(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "min", params, args, 1)?;
Ok(match &args[0] {
Value::Array(a) if a.is_empty() => Value::Undefined,
Value::Array(a) => a.iter().min().unwrap().clone(),
Value::Set(a) if a.is_empty() => Value::Undefined,
Value::Set(a) => a.iter().min().unwrap().clone(),
a => {
let span = params[0].span();
bail!(span.error(format!("`min` requires array/set argument. Got `{a}`.").as_str()))
}
})
}
fn product(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "product", params, args, 1)?;
let mut v = Number::from(1_u64);
Ok(Value::from(match &args[0] {
Value::Array(a) => {
for e in a.iter() {
v.mul_assign(&ensure_numeric("product", &params[0], e)?)?;
}
v
}
Value::Set(a) => {
for e in a.iter() {
v.mul_assign(&ensure_numeric("product", &params[0], e)?)?;
}
v
}
a => {
let span = params[0].span();
bail!(span.error(format!("`product` requires array/set argument. Got `{a}`.").as_str()))
}
}))
}
fn sort(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "sort", params, args, 1)?;
Ok(match &args[0] {
Value::Array(a) => {
let mut ac = (**a).clone();
ac.sort();
Value::from(ac)
}
// Sorting a set produces array.
Value::Set(a) => {
let mut items = Vec::with_capacity(a.len());
for value in a.iter() {
items.push(value.clone());
// Guard array growth while materializing the sorted set.
enforce_limit()?;
}
Value::from(items)
}
a => {
let span = params[0].span();
bail!(span.error(format!("`sort` requires array/set argument. Got `{a}`.").as_str()))
}
})
}
fn sum(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "sum", params, args, 1)?;
let mut v = Number::from(0_u64);
Ok(Value::from(match &args[0] {
Value::Array(a) => {
for e in a.iter() {
v.add_assign(&ensure_numeric("sum", &params[0], e)?)?;
}
v
}
Value::Set(a) => {
for e in a.iter() {
v.add_assign(&ensure_numeric("sum", &params[0], e)?)?;
}
v
}
a => {
let span = params[0].span();
bail!(span.error(format!("`sum` requires array/set argument. Got `{a}`.").as_str()))
}
}))
}