From 74ea504236fd8735c47c48d7e156c809dba5ed5d Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sun, 23 Jun 2024 22:22:44 +0200 Subject: [PATCH] feat: grow os stack on demand --- Cargo.lock | 45 +++++++++++++++++++ crates/jrsonnet-evaluator/Cargo.toml | 1 + crates/jrsonnet-evaluator/src/evaluate/mod.rs | 31 ++++++++++--- 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f21a208a..b5afcc81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -486,6 +486,7 @@ dependencies = [ "pathdiff", "rustc-hash", "serde", + "stacker", "static_assertions", "strsim", "thiserror", @@ -850,6 +851,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.36" @@ -1076,6 +1086,19 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1202,6 +1225,28 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/crates/jrsonnet-evaluator/Cargo.toml b/crates/jrsonnet-evaluator/Cargo.toml index 4b5a430b..d0905cb7 100644 --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -60,3 +60,4 @@ hi-doc = { workspace = true, optional = true } # Bigint num-bigint = { workspace = true, features = ["serde"], optional = true } derivative.workspace = true +stacker = "0.1.15" diff --git a/crates/jrsonnet-evaluator/src/evaluate/mod.rs b/crates/jrsonnet-evaluator/src/evaluate/mod.rs index 89e5cd1c..350def58 100644 --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -25,6 +25,25 @@ use crate::{ pub mod destructure; pub mod operator; +// This is the amount of bytes that need to be left on the stack before increasing the size. +// It must be at least as large as the stack required by any code that does not call +// `ensure_sufficient_stack`. +const RED_ZONE: usize = 100 * 1024; // 100k + +// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then +// on. This flag has performance relevant characteristics. Don't set it too high. +const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB + +/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations +/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit +/// from this. +/// +/// Should not be sprinkled around carelessly, as it causes a little bit of overhead. +#[inline] +pub fn ensure_sufficient_stack(f: impl FnOnce() -> R) -> R { + stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f) +} + pub fn evaluate_trivial(expr: &LocExpr) -> Option { fn is_trivial(expr: &LocExpr) -> bool { match expr.expr() { @@ -463,7 +482,7 @@ pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result { || format!("variable <{name}> access"), || ctx.binding(name.clone())?.evaluate(), )?, - Index { indexable, parts } => { + Index { indexable, parts } => ensure_sufficient_stack(|| { let mut parts = parts.iter(); let mut indexable = if matches!(indexable.expr(), Expr::Literal(LiteralType::Super)) { let part = parts.next().expect("at least part should exist"); @@ -574,8 +593,8 @@ pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result { (v, _) => bail!(CantIndexInto(v.value_type())), }; } - indexable - } + Ok(indexable) + })?, LocalExpr(bindings, returned) => { let mut new_bindings: GcHashMap> = GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum()); @@ -636,9 +655,9 @@ pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result { &evaluate(ctx.clone(), a)?, &Val::Obj(evaluate_object(ctx, b)?), )?, - Apply(value, args, tailstrict) => { - evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict)? - } + Apply(value, args, tailstrict) => ensure_sufficient_stack(|| { + evaluate_apply(ctx, value, args, CallLocation::new(&loc), *tailstrict) + })?, Function(params, body) => { evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone()) }