difftreelog
refactor closure interpreter
in: master
14 files changed
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -7,7 +7,12 @@
use jrsonnet_gcmodule::{Cc, cc_dyn};
-use crate::{Context, Result, Thunk, Val, analyze::LExpr, function::NativeFn, typed::IntoUntyped};
+use crate::{
+ Context, Result, Thunk, Val,
+ analyze::{ClosureShape, LExpr},
+ function::NativeFn,
+ typed::IntoUntyped,
+};
mod spec;
pub use spec::{ArrayLike, *};
@@ -36,8 +41,8 @@
Self::new(())
}
- pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {
- Self::new(ExprArray::new(ctx, exprs))
+ pub fn expr(ctx: Context, shape: &ClosureShape, exprs: Rc<Vec<LExpr>>) -> Self {
+ Self::new(ExprArray::new(ctx, shape, exprs))
}
pub fn repeated(data: Self, repeats: u32) -> Option<Self> {
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth1use std::{2 any::Any,3 cell::RefCell,4 fmt::{self, Debug},5 mem::replace,6 rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::{IBytes, IStr};1112use super::ArrValue;13use crate::{14 Context, Error, ObjValue, Result, Thunk, Val,15 analyze::LExpr,16 error::ErrorKind::InfiniteRecursionDetected,17 evaluate::evaluate,18 function::NativeFn,19 typed::{IntoUntyped, Typed},20 val::ThunkValue,21};2223pub trait ArrayLike: Any + Trace + Debug {24 fn len(&self) -> u32;25 fn is_empty(&self) -> bool {26 self.len() == 027 }28 fn get(&self, index: u32) -> Result<Option<Val>>;29 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>>;3031 fn is_cheap(&self) -> bool {32 false33 }34}35trait ArrayCheap {36 fn get(&self, index: u32) -> Option<Val>;37 fn len(&self) -> u32;38}39impl<T> ArrayLike for T40where41 T: Any + Trace + Debug + ArrayCheap,42{43 fn len(&self) -> u32 {44 <T as ArrayCheap>::len(self)45 }4647 fn get(&self, index: u32) -> Result<Option<Val>> {48 Ok(<T as ArrayCheap>::get(self, index))49 }5051 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {52 <T as ArrayCheap>::get(self, index).map(Thunk::evaluated)53 }5455 fn is_cheap(&self) -> bool {56 true57 }58}5960impl ArrayCheap for () {61 fn len(&self) -> u32 {62 063 }64 fn get(&self, _index: u32) -> Option<Val> {65 None66 }67}6869#[derive(Debug, Trace)]70pub struct SliceArray {71 pub(crate) inner: ArrValue,72 pub(crate) from: u32,73 pub(crate) to: u32,74 pub(crate) step: u32,75}7677impl SliceArray {78 fn map_idx(&self, index: u32) -> u32 {79 self.from + self.step * index80 }81}82impl ArrayLike for SliceArray {83 fn len(&self) -> u32 {84 (self.to - self.from).div_ceil(self.step)85 }8687 fn get(&self, index: u32) -> Result<Option<Val>> {88 self.inner.get(self.map_idx(index))89 }9091 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {92 self.inner.get_lazy(self.map_idx(index))93 }9495 fn is_cheap(&self) -> bool {96 self.inner.is_cheap()97 }98}99100impl ArrayCheap for IBytes {101 fn len(&self) -> u32 {102 self.as_slice().len() as u32103 }104 fn get(&self, index: u32) -> Option<Val> {105 self.as_slice()106 .get(index as usize)107 .map(|v| Val::Num((*v).into()))108 }109}110111#[derive(Debug, Trace, Clone)]112enum ArrayThunk {113 Computed(Val),114 Errored(Error),115 Waiting,116 Pending,117}118119#[derive(Debug, Trace, Clone)]120pub struct ExprArray {121 ctx: Context,122 src: Rc<Vec<LExpr>>,123 cached: Cc<RefCell<Vec<ArrayThunk>>>,124}125impl ExprArray {126 pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {127 Self {128 ctx,129 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),130 src,131 }132 }133}134impl ArrayLike for ExprArray {135 fn len(&self) -> u32 {136 self.cached.borrow().len() as u32137 }138 fn get(&self, index: u32) -> Result<Option<Val>> {139 if index >= self.len() {140 return Ok(None);141 }142 match &self.cached.borrow()[index as usize] {143 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),144 ArrayThunk::Errored(e) => return Err(e.clone()),145 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),146 ArrayThunk::Waiting => {}147 }148149 let ArrayThunk::Waiting = replace(150 &mut self.cached.borrow_mut()[index as usize],151 ArrayThunk::Pending,152 ) else {153 unreachable!()154 };155156 let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;157 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());158 Ok(Some(new_value))159 }160 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {161 #[derive(Trace)]162 struct ExprArrThunk {163 expr: ExprArray,164 index: u32,165 }166 impl ThunkValue for ExprArrThunk {167 type Output = Val;168169 fn get(&self) -> Result<Self::Output> {170 self.expr171 .get(self.index)172 .transpose()173 .expect("index checked")174 }175 }176177 if index >= self.len() {178 return None;179 }180 match &self.cached.borrow()[index as usize] {181 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),182 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),183 ArrayThunk::Waiting | ArrayThunk::Pending => {}184 }185186 Some(Thunk::new(ExprArrThunk {187 expr: self.clone(),188 index,189 }))190 }191 fn is_cheap(&self) -> bool {192 false193 }194}195196#[derive(Trace, Debug)]197pub struct ExtendedArray {198 pub a: ArrValue,199 pub b: ArrValue,200 split: u32,201 len: u32,202}203impl ExtendedArray {204 pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {205 let a_len = a.len();206 let b_len = b.len();207 let len = a_len.checked_add(b_len)?;208 Some(Self {209 a,210 b,211 split: a_len,212 len,213 })214 }215}216217struct WithExactSize<I>(I, usize);218impl<I, T> Iterator for WithExactSize<I>219where220 I: Iterator<Item = T>,221{222 type Item = T;223224 fn next(&mut self) -> Option<Self::Item> {225 self.0.next()226 }227 fn nth(&mut self, n: usize) -> Option<Self::Item> {228 self.0.nth(n)229 }230 fn size_hint(&self) -> (usize, Option<usize>) {231 (self.1, Some(self.1))232 }233}234impl<I> DoubleEndedIterator for WithExactSize<I>235where236 I: DoubleEndedIterator,237{238 fn next_back(&mut self) -> Option<Self::Item> {239 self.0.next_back()240 }241 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {242 self.0.nth_back(n)243 }244}245impl<I> ExactSizeIterator for WithExactSize<I>246where247 I: Iterator,248{249 fn len(&self) -> usize {250 self.1251 }252}253impl ArrayLike for ExtendedArray {254 fn get(&self, index: u32) -> Result<Option<Val>> {255 if self.split > index {256 self.a.get(index)257 } else {258 self.b.get(index - self.split)259 }260 }261 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {262 if self.split > index {263 self.a.get_lazy(index)264 } else {265 self.b.get_lazy(index - self.split)266 }267 }268269 fn len(&self) -> u32 {270 self.len271 }272273 fn is_cheap(&self) -> bool {274 self.a.is_cheap() && self.b.is_cheap()275 }276}277278impl<T> ArrayLike for Vec<T>279where280 T: IntoUntyped + Trace + fmt::Debug,281 for<'a> &'a T: IntoUntyped,282{283 fn len(&self) -> u32 {284 self.as_slice().len().try_into().unwrap_or(u32::MAX)285 }286287 fn get(&self, index: u32) -> Result<Option<Val>> {288 let Some(elem) = self.as_slice().get(index as usize) else {289 return Ok(None);290 };291 IntoUntyped::into_untyped(elem).map(Some)292 }293294 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {295 let elem = self.as_slice().get(index as usize)?;296 Some(IntoUntyped::into_lazy_untyped(elem))297 }298299 fn is_cheap(&self) -> bool {300 !T::provides_lazy()301 }302}303304/// Inclusive range type305#[derive(Debug, Trace, PartialEq, Eq)]306pub struct RangeArray {307 start: i32,308 end: i32,309}310impl RangeArray {311 pub fn empty() -> Self {312 Self::new_exclusive(0, 0)313 }314 pub fn new_exclusive(start: i32, end: i32) -> Self {315 end.checked_sub(1)316 .map_or_else(Self::empty, |end| Self { start, end })317 }318 pub fn new_inclusive(start: i32, end: i32) -> Self {319 Self { start, end }320 }321 #[expect(322 clippy::cast_sign_loss,323 reason = "the math is valid with wrapping, sign loss works as intended"324 )]325 fn size(&self) -> u32 {326 (self.end as u32)327 .wrapping_sub(self.start as u32)328 .wrapping_add(1)329 }330 fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {331 WithExactSize(self.start..=self.end, self.size() as usize)332 }333}334impl ArrayCheap for RangeArray {335 fn get(&self, index: u32) -> Option<Val> {336 self.range().nth(index as usize).map(|i| Val::Num(i.into()))337 }338 fn len(&self) -> u32 {339 self.size()340 }341}342343#[derive(Debug, Trace)]344pub struct ReverseArray(pub ArrValue);345impl ArrayLike for ReverseArray {346 fn len(&self) -> u32 {347 self.0.len()348 }349350 fn get(&self, index: u32) -> Result<Option<Val>> {351 self.0.get(self.0.len() - index - 1)352 }353354 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {355 self.0.get_lazy(self.0.len() - index - 1)356 }357358 fn is_cheap(&self) -> bool {359 self.0.is_cheap()360 }361}362363#[derive(Trace, Clone, Debug)]364pub enum ArrayMapper {365 Plain(NativeFn!((Val) -> Val)),366 WithIndex(NativeFn!((u32, Val) -> Val)),367}368369#[derive(Trace, Debug, Clone)]370pub struct MappedArray {371 inner: ArrValue,372 cached: Cc<RefCell<Vec<ArrayThunk>>>,373 mapper: ArrayMapper,374}375impl MappedArray {376 pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {377 let len = inner.len();378 Self {379 inner,380 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),381 mapper,382 }383 }384 fn evaluate(&self, index: u32, value: Val) -> Result<Val> {385 match &self.mapper {386 ArrayMapper::Plain(f) => f.call(value),387 ArrayMapper::WithIndex(f) => f.call(index, value),388 }389 }390}391impl ArrayLike for MappedArray {392 fn len(&self) -> u32 {393 self.cached.borrow().len() as u32394 }395396 fn get(&self, index: u32) -> Result<Option<Val>> {397 if index >= self.len() {398 return Ok(None);399 }400 match &self.cached.borrow()[index as usize] {401 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),402 ArrayThunk::Errored(e) => return Err(e.clone()),403 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),404 ArrayThunk::Waiting => {}405 }406407 let ArrayThunk::Waiting = replace(408 &mut self.cached.borrow_mut()[index as usize],409 ArrayThunk::Pending,410 ) else {411 unreachable!()412 };413414 let val = self415 .inner416 .get(index)417 .transpose()418 .expect("index checked")419 .and_then(|r| self.evaluate(index, r));420421 let new_value = match val {422 Ok(v) => v,423 Err(e) => {424 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());425 return Err(e);426 }427 };428 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());429 Ok(Some(new_value))430 }431 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {432 #[derive(Trace)]433 struct MappedArrayThunk {434 arr: MappedArray,435 index: u32,436 }437 impl ThunkValue for MappedArrayThunk {438 type Output = Val;439440 fn get(&self) -> Result<Self::Output> {441 self.arr.get(self.index).transpose().expect("index checked")442 }443 }444445 if index >= self.len() {446 return None;447 }448 match &self.cached.borrow()[index as usize] {449 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),450 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),451 ArrayThunk::Waiting | ArrayThunk::Pending => {}452 }453454 Some(Thunk::new(MappedArrayThunk {455 arr: self.clone(),456 index,457 }))458 }459}460#[derive(Trace, Debug, Clone)]461pub struct MakeArray {462 cached: Cc<RefCell<Vec<ArrayThunk>>>,463 mapper: NativeFn!((u32,)->Val),464}465impl MakeArray {466 pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {467 Self {468 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),469 mapper,470 }471 }472}473impl ArrayLike for MakeArray {474 fn len(&self) -> u32 {475 self.cached.borrow().len() as u32476 }477478 fn get(&self, index: u32) -> Result<Option<Val>> {479 if index >= self.len() {480 return Ok(None);481 }482 match &self.cached.borrow()[index as usize] {483 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),484 ArrayThunk::Errored(e) => return Err(e.clone()),485 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),486 ArrayThunk::Waiting => {}487 }488489 let ArrayThunk::Waiting = replace(490 &mut self.cached.borrow_mut()[index as usize],491 ArrayThunk::Pending,492 ) else {493 unreachable!()494 };495496 let val = self.mapper.call(index as u32);497498 let new_value = match val {499 Ok(v) => v,500 Err(e) => {501 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());502 return Err(e);503 }504 };505 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());506 Ok(Some(new_value))507 }508 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {509 #[derive(Trace)]510 struct MakeArrayThunk {511 arr: MakeArray,512 index: u32,513 }514 impl ThunkValue for MakeArrayThunk {515 type Output = Val;516517 fn get(&self) -> Result<Self::Output> {518 self.arr.get(self.index).transpose().expect("index checked")519 }520 }521522 if index >= self.len() {523 return None;524 }525 match &self.cached.borrow()[index as usize] {526 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),527 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),528 ArrayThunk::Waiting | ArrayThunk::Pending => {}529 }530531 Some(Thunk::new(MakeArrayThunk {532 arr: self.clone(),533 index,534 }))535 }536}537538#[derive(Trace, Debug)]539pub struct RepeatedArray {540 data: ArrValue,541 repeats: u32,542 total_len: u32,543}544impl RepeatedArray {545 pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {546 let total_len = data.len().checked_mul(repeats)?;547 Some(Self {548 data,549 repeats,550 total_len,551 })552 }553 fn map_idx(&self, index: u32) -> Option<u32> {554 if index > self.total_len {555 return None;556 }557 Some(index % self.data.len())558 }559}560561impl ArrayLike for RepeatedArray {562 fn len(&self) -> u32 {563 self.total_len564 }565566 fn get(&self, index: u32) -> Result<Option<Val>> {567 let Some(idx) = self.map_idx(index) else {568 return Ok(None);569 };570 self.data.get(idx)571 }572573 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {574 let idx = self.map_idx(index)?;575 self.data.get_lazy(idx)576 }577578 fn is_cheap(&self) -> bool {579 self.data.is_cheap()580 }581}582583#[derive(Trace, Debug)]584pub struct PickObjectValues {585 obj: ObjValue,586 keys: Vec<IStr>,587}588589impl PickObjectValues {590 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {591 Self { obj, keys }592 }593}594595impl ArrayLike for PickObjectValues {596 fn len(&self) -> u32 {597 self.keys.len() as u32598 }599600 fn get(&self, index: u32) -> Result<Option<Val>> {601 let Some(key) = self.keys.as_slice().get(index as usize) else {602 return Ok(None);603 };604 Ok(Some(self.obj.get_or_bail(key.clone())?))605 }606607 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {608 let key = self.keys.as_slice().get(index as usize)?;609 Some(self.obj.get_lazy_or_bail(key.clone()))610 }611612 fn is_cheap(&self) -> bool {613 false614 }615}616617#[derive(Trace, Debug)]618pub struct PickObjectKeyValues {619 obj: ObjValue,620 keys: Vec<IStr>,621}622623impl PickObjectKeyValues {624 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {625 Self { obj, keys }626 }627}628629#[derive(Typed, IntoUntyped)]630pub struct KeyValue {631 key: IStr,632 value: Thunk<Val>,633}634635impl ArrayLike for PickObjectKeyValues {636 fn len(&self) -> u32 {637 self.keys.len() as u32638 }639640 fn get(&self, index: u32) -> Result<Option<Val>> {641 let Some(key) = self.keys.as_slice().get(index as usize) else {642 return Ok(None);643 };644 Ok(Some(645 KeyValue::into_untyped(KeyValue {646 key: key.clone(),647 value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),648 })649 .expect("convertible"),650 ))651 }652653 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {654 let key = self.keys.as_slice().get(index as usize)?;655 // Nothing can fail in the key part, yet value is still656 // lazy-evaluated657 Some(Thunk::evaluated(658 KeyValue::into_untyped(KeyValue {659 key: key.clone(),660 value: self.obj.get_lazy_or_bail(key.clone()),661 })662 .expect("convertible"),663 ))664 }665666 fn is_cheap(&self) -> bool {667 false668 }669}1use std::{2 any::Any,3 cell::RefCell,4 fmt::{self, Debug},5 mem::replace,6 rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::{IBytes, IStr};1112use super::ArrValue;13use crate::{14 Context, Error, ObjValue, Result, Thunk, Val,15 analyze::{ClosureShape, LExpr},16 error::ErrorKind::InfiniteRecursionDetected,17 evaluate::evaluate,18 function::NativeFn,19 typed::{IntoUntyped, Typed},20 val::ThunkValue,21};2223pub trait ArrayLike: Any + Trace + Debug {24 fn len(&self) -> u32;25 fn is_empty(&self) -> bool {26 self.len() == 027 }28 fn get(&self, index: u32) -> Result<Option<Val>>;29 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>>;3031 fn is_cheap(&self) -> bool {32 false33 }34}35trait ArrayCheap {36 fn get(&self, index: u32) -> Option<Val>;37 fn len(&self) -> u32;38}39impl<T> ArrayLike for T40where41 T: Any + Trace + Debug + ArrayCheap,42{43 fn len(&self) -> u32 {44 <T as ArrayCheap>::len(self)45 }4647 fn get(&self, index: u32) -> Result<Option<Val>> {48 Ok(<T as ArrayCheap>::get(self, index))49 }5051 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {52 <T as ArrayCheap>::get(self, index).map(Thunk::evaluated)53 }5455 fn is_cheap(&self) -> bool {56 true57 }58}5960impl ArrayCheap for () {61 fn len(&self) -> u32 {62 063 }64 fn get(&self, _index: u32) -> Option<Val> {65 None66 }67}6869#[derive(Debug, Trace)]70pub struct SliceArray {71 pub(crate) inner: ArrValue,72 pub(crate) from: u32,73 pub(crate) to: u32,74 pub(crate) step: u32,75}7677impl SliceArray {78 fn map_idx(&self, index: u32) -> u32 {79 self.from + self.step * index80 }81}82impl ArrayLike for SliceArray {83 fn len(&self) -> u32 {84 (self.to - self.from).div_ceil(self.step)85 }8687 fn get(&self, index: u32) -> Result<Option<Val>> {88 self.inner.get(self.map_idx(index))89 }9091 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {92 self.inner.get_lazy(self.map_idx(index))93 }9495 fn is_cheap(&self) -> bool {96 self.inner.is_cheap()97 }98}99100impl ArrayCheap for IBytes {101 fn len(&self) -> u32 {102 self.as_slice().len() as u32103 }104 fn get(&self, index: u32) -> Option<Val> {105 self.as_slice()106 .get(index as usize)107 .map(|v| Val::Num((*v).into()))108 }109}110111#[derive(Debug, Trace, Clone)]112enum ArrayThunk {113 Computed(Val),114 Errored(Error),115 Waiting,116 Pending,117}118119#[derive(Debug, Trace, Clone)]120pub struct ExprArray {121 ctx: Context,122 src: Rc<Vec<LExpr>>,123 cached: Cc<RefCell<Vec<ArrayThunk>>>,124}125impl ExprArray {126 pub fn new(outer: Context, shape: &ClosureShape, src: Rc<Vec<LExpr>>) -> Self {127 Self {128 ctx: Context::enter_using(&outer, shape),129 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),130 src,131 }132 }133}134impl ArrayLike for ExprArray {135 fn len(&self) -> u32 {136 self.cached.borrow().len() as u32137 }138 fn get(&self, index: u32) -> Result<Option<Val>> {139 if index >= self.len() {140 return Ok(None);141 }142 match &self.cached.borrow()[index as usize] {143 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),144 ArrayThunk::Errored(e) => return Err(e.clone()),145 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),146 ArrayThunk::Waiting => {}147 }148149 let ArrayThunk::Waiting = replace(150 &mut self.cached.borrow_mut()[index as usize],151 ArrayThunk::Pending,152 ) else {153 unreachable!()154 };155156 let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;157 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());158 Ok(Some(new_value))159 }160 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {161 #[derive(Trace)]162 struct ExprArrThunk {163 expr: ExprArray,164 index: u32,165 }166 impl ThunkValue for ExprArrThunk {167 type Output = Val;168169 fn get(&self) -> Result<Self::Output> {170 self.expr171 .get(self.index)172 .transpose()173 .expect("index checked")174 }175 }176177 if index >= self.len() {178 return None;179 }180 match &self.cached.borrow()[index as usize] {181 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),182 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),183 ArrayThunk::Waiting | ArrayThunk::Pending => {}184 }185186 Some(Thunk::new(ExprArrThunk {187 expr: self.clone(),188 index,189 }))190 }191 fn is_cheap(&self) -> bool {192 false193 }194}195196#[derive(Trace, Debug)]197pub struct ExtendedArray {198 pub a: ArrValue,199 pub b: ArrValue,200 split: u32,201 len: u32,202}203impl ExtendedArray {204 pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {205 let a_len = a.len();206 let b_len = b.len();207 let len = a_len.checked_add(b_len)?;208 Some(Self {209 a,210 b,211 split: a_len,212 len,213 })214 }215}216217struct WithExactSize<I>(I, usize);218impl<I, T> Iterator for WithExactSize<I>219where220 I: Iterator<Item = T>,221{222 type Item = T;223224 fn next(&mut self) -> Option<Self::Item> {225 self.0.next()226 }227 fn nth(&mut self, n: usize) -> Option<Self::Item> {228 self.0.nth(n)229 }230 fn size_hint(&self) -> (usize, Option<usize>) {231 (self.1, Some(self.1))232 }233}234impl<I> DoubleEndedIterator for WithExactSize<I>235where236 I: DoubleEndedIterator,237{238 fn next_back(&mut self) -> Option<Self::Item> {239 self.0.next_back()240 }241 fn nth_back(&mut self, n: usize) -> Option<Self::Item> {242 self.0.nth_back(n)243 }244}245impl<I> ExactSizeIterator for WithExactSize<I>246where247 I: Iterator,248{249 fn len(&self) -> usize {250 self.1251 }252}253impl ArrayLike for ExtendedArray {254 fn get(&self, index: u32) -> Result<Option<Val>> {255 if self.split > index {256 self.a.get(index)257 } else {258 self.b.get(index - self.split)259 }260 }261 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {262 if self.split > index {263 self.a.get_lazy(index)264 } else {265 self.b.get_lazy(index - self.split)266 }267 }268269 fn len(&self) -> u32 {270 self.len271 }272273 fn is_cheap(&self) -> bool {274 self.a.is_cheap() && self.b.is_cheap()275 }276}277278impl<T> ArrayLike for Vec<T>279where280 T: IntoUntyped + Trace + fmt::Debug,281 for<'a> &'a T: IntoUntyped,282{283 fn len(&self) -> u32 {284 self.as_slice().len().try_into().unwrap_or(u32::MAX)285 }286287 fn get(&self, index: u32) -> Result<Option<Val>> {288 let Some(elem) = self.as_slice().get(index as usize) else {289 return Ok(None);290 };291 IntoUntyped::into_untyped(elem).map(Some)292 }293294 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {295 let elem = self.as_slice().get(index as usize)?;296 Some(IntoUntyped::into_lazy_untyped(elem))297 }298299 fn is_cheap(&self) -> bool {300 !T::provides_lazy()301 }302}303304/// Inclusive range type305#[derive(Debug, Trace, PartialEq, Eq)]306pub struct RangeArray {307 start: i32,308 end: i32,309}310impl RangeArray {311 pub fn empty() -> Self {312 Self::new_exclusive(0, 0)313 }314 pub fn new_exclusive(start: i32, end: i32) -> Self {315 end.checked_sub(1)316 .map_or_else(Self::empty, |end| Self { start, end })317 }318 pub fn new_inclusive(start: i32, end: i32) -> Self {319 Self { start, end }320 }321 #[expect(322 clippy::cast_sign_loss,323 reason = "the math is valid with wrapping, sign loss works as intended"324 )]325 fn size(&self) -> u32 {326 (self.end as u32)327 .wrapping_sub(self.start as u32)328 .wrapping_add(1)329 }330 fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {331 WithExactSize(self.start..=self.end, self.size() as usize)332 }333}334impl ArrayCheap for RangeArray {335 fn get(&self, index: u32) -> Option<Val> {336 self.range().nth(index as usize).map(|i| Val::Num(i.into()))337 }338 fn len(&self) -> u32 {339 self.size()340 }341}342343#[derive(Debug, Trace)]344pub struct ReverseArray(pub ArrValue);345impl ArrayLike for ReverseArray {346 fn len(&self) -> u32 {347 self.0.len()348 }349350 fn get(&self, index: u32) -> Result<Option<Val>> {351 self.0.get(self.0.len() - index - 1)352 }353354 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {355 self.0.get_lazy(self.0.len() - index - 1)356 }357358 fn is_cheap(&self) -> bool {359 self.0.is_cheap()360 }361}362363#[derive(Trace, Clone, Debug)]364pub enum ArrayMapper {365 Plain(NativeFn!((Val) -> Val)),366 WithIndex(NativeFn!((u32, Val) -> Val)),367}368369#[derive(Trace, Debug, Clone)]370pub struct MappedArray {371 inner: ArrValue,372 cached: Cc<RefCell<Vec<ArrayThunk>>>,373 mapper: ArrayMapper,374}375impl MappedArray {376 pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {377 let len = inner.len();378 Self {379 inner,380 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),381 mapper,382 }383 }384 fn evaluate(&self, index: u32, value: Val) -> Result<Val> {385 match &self.mapper {386 ArrayMapper::Plain(f) => f.call(value),387 ArrayMapper::WithIndex(f) => f.call(index, value),388 }389 }390}391impl ArrayLike for MappedArray {392 fn len(&self) -> u32 {393 self.cached.borrow().len() as u32394 }395396 fn get(&self, index: u32) -> Result<Option<Val>> {397 if index >= self.len() {398 return Ok(None);399 }400 match &self.cached.borrow()[index as usize] {401 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),402 ArrayThunk::Errored(e) => return Err(e.clone()),403 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),404 ArrayThunk::Waiting => {}405 }406407 let ArrayThunk::Waiting = replace(408 &mut self.cached.borrow_mut()[index as usize],409 ArrayThunk::Pending,410 ) else {411 unreachable!()412 };413414 let val = self415 .inner416 .get(index)417 .transpose()418 .expect("index checked")419 .and_then(|r| self.evaluate(index, r));420421 let new_value = match val {422 Ok(v) => v,423 Err(e) => {424 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());425 return Err(e);426 }427 };428 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());429 Ok(Some(new_value))430 }431 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {432 #[derive(Trace)]433 struct MappedArrayThunk {434 arr: MappedArray,435 index: u32,436 }437 impl ThunkValue for MappedArrayThunk {438 type Output = Val;439440 fn get(&self) -> Result<Self::Output> {441 self.arr.get(self.index).transpose().expect("index checked")442 }443 }444445 if index >= self.len() {446 return None;447 }448 match &self.cached.borrow()[index as usize] {449 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),450 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),451 ArrayThunk::Waiting | ArrayThunk::Pending => {}452 }453454 Some(Thunk::new(MappedArrayThunk {455 arr: self.clone(),456 index,457 }))458 }459}460#[derive(Trace, Debug, Clone)]461pub struct MakeArray {462 cached: Cc<RefCell<Vec<ArrayThunk>>>,463 mapper: NativeFn!((u32,)->Val),464}465impl MakeArray {466 pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {467 Self {468 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),469 mapper,470 }471 }472}473impl ArrayLike for MakeArray {474 fn len(&self) -> u32 {475 self.cached.borrow().len() as u32476 }477478 fn get(&self, index: u32) -> Result<Option<Val>> {479 if index >= self.len() {480 return Ok(None);481 }482 match &self.cached.borrow()[index as usize] {483 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),484 ArrayThunk::Errored(e) => return Err(e.clone()),485 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),486 ArrayThunk::Waiting => {}487 }488489 let ArrayThunk::Waiting = replace(490 &mut self.cached.borrow_mut()[index as usize],491 ArrayThunk::Pending,492 ) else {493 unreachable!()494 };495496 let val = self.mapper.call(index as u32);497498 let new_value = match val {499 Ok(v) => v,500 Err(e) => {501 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());502 return Err(e);503 }504 };505 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());506 Ok(Some(new_value))507 }508 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {509 #[derive(Trace)]510 struct MakeArrayThunk {511 arr: MakeArray,512 index: u32,513 }514 impl ThunkValue for MakeArrayThunk {515 type Output = Val;516517 fn get(&self) -> Result<Self::Output> {518 self.arr.get(self.index).transpose().expect("index checked")519 }520 }521522 if index >= self.len() {523 return None;524 }525 match &self.cached.borrow()[index as usize] {526 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),527 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),528 ArrayThunk::Waiting | ArrayThunk::Pending => {}529 }530531 Some(Thunk::new(MakeArrayThunk {532 arr: self.clone(),533 index,534 }))535 }536}537538#[derive(Trace, Debug)]539pub struct RepeatedArray {540 data: ArrValue,541 repeats: u32,542 total_len: u32,543}544impl RepeatedArray {545 pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {546 let total_len = data.len().checked_mul(repeats)?;547 Some(Self {548 data,549 repeats,550 total_len,551 })552 }553 fn map_idx(&self, index: u32) -> Option<u32> {554 if index > self.total_len {555 return None;556 }557 Some(index % self.data.len())558 }559}560561impl ArrayLike for RepeatedArray {562 fn len(&self) -> u32 {563 self.total_len564 }565566 fn get(&self, index: u32) -> Result<Option<Val>> {567 let Some(idx) = self.map_idx(index) else {568 return Ok(None);569 };570 self.data.get(idx)571 }572573 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {574 let idx = self.map_idx(index)?;575 self.data.get_lazy(idx)576 }577578 fn is_cheap(&self) -> bool {579 self.data.is_cheap()580 }581}582583#[derive(Trace, Debug)]584pub struct PickObjectValues {585 obj: ObjValue,586 keys: Vec<IStr>,587}588589impl PickObjectValues {590 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {591 Self { obj, keys }592 }593}594595impl ArrayLike for PickObjectValues {596 fn len(&self) -> u32 {597 self.keys.len() as u32598 }599600 fn get(&self, index: u32) -> Result<Option<Val>> {601 let Some(key) = self.keys.as_slice().get(index as usize) else {602 return Ok(None);603 };604 Ok(Some(self.obj.get_or_bail(key.clone())?))605 }606607 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {608 let key = self.keys.as_slice().get(index as usize)?;609 Some(self.obj.get_lazy_or_bail(key.clone()))610 }611612 fn is_cheap(&self) -> bool {613 false614 }615}616617#[derive(Trace, Debug)]618pub struct PickObjectKeyValues {619 obj: ObjValue,620 keys: Vec<IStr>,621}622623impl PickObjectKeyValues {624 pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {625 Self { obj, keys }626 }627}628629#[derive(Typed, IntoUntyped)]630pub struct KeyValue {631 key: IStr,632 value: Thunk<Val>,633}634635impl ArrayLike for PickObjectKeyValues {636 fn len(&self) -> u32 {637 self.keys.len() as u32638 }639640 fn get(&self, index: u32) -> Result<Option<Val>> {641 let Some(key) = self.keys.as_slice().get(index as usize) else {642 return Ok(None);643 };644 Ok(Some(645 KeyValue::into_untyped(KeyValue {646 key: key.clone(),647 value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),648 })649 .expect("convertible"),650 ))651 }652653 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {654 let key = self.keys.as_slice().get(index as usize)?;655 // Nothing can fail in the key part, yet value is still656 // lazy-evaluated657 Some(Thunk::evaluated(658 KeyValue::into_untyped(KeyValue {659 key: key.clone(),660 value: self.obj.get_lazy_or_bail(key.clone()),661 })662 .expect("convertible"),663 ))664 }665666 fn is_cheap(&self) -> bool {667 false668 }669}crates/jrsonnet-evaluator/src/async_import.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/async_import.rs
+++ b/crates/jrsonnet-evaluator/src/async_import.rs
@@ -4,7 +4,7 @@
use jrsonnet_ir::{IStr, Source, SourcePath, visit::Visitor};
use rustc_hash::FxHashMap;
-use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, State};
+use crate::{AsPathLike, FileData, ImportResolver, ResolvePathOwned, Result, State};
pub struct Import {
path: ResolvePathOwned,
@@ -109,23 +109,23 @@
}
}
Job::ParseFile(path) => {
- if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path) {
- if file.parsed.is_none() {
- let Some(code) = file.get_string() else {
- continue;
- };
- let source = Source::new(path.clone(), code.clone());
- // If failed - then skip import
- file.parsed = crate::parse_jsonnet(&code, source).map(Rc::new).ok();
- if let Some(parsed) = &file.parsed {
- let mut imports = FoundImports(vec![]);
- imports.visit_expr(parsed);
- for import in imports.0 {
- queue.push(Job::ResolveImport {
- from: path.clone(),
- import,
- });
- }
+ if let Some(file) = s.0.file_cache.borrow_mut().get_mut(&path)
+ && file.parsed.is_none()
+ {
+ let Some(code) = file.get_string() else {
+ continue;
+ };
+ let source = Source::new(path.clone(), code.clone());
+ // If failed - then skip import
+ file.parsed = crate::parse_jsonnet(&code, source).map(Rc::new).ok();
+ if let Some(parsed) = &file.parsed {
+ let mut imports = FoundImports(vec![]);
+ imports.visit_expr(parsed);
+ for import in imports.0 {
+ queue.push(Job::ResolveImport {
+ from: path.clone(),
+ import,
+ });
}
}
}
crates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -1,151 +1,296 @@
-use std::{clone::Clone, fmt::Debug};
+use std::{
+ cell::{Cell, OnceCell, RefCell},
+ clone::Clone,
+ fmt::{self, Debug},
+};
use educe::Educe;
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
-use crate::{Pending, Result, SupThis, Thunk, Val, analyze::LocalId, error, error::ErrorKind::*};
+use crate::{
+ Result, SupThis, Thunk, Val,
+ analyze::{CaptureSlot, ClosureShape, LSlot, LocalId, LocalSlot},
+ bail, error,
+ error::ErrorKind::*,
+};
#[derive(Debug, Trace, Clone, Educe)]
#[educe(PartialEq)]
-pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] Cc<ContextInternal>);
+pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] pub(crate) Cc<ContextInternal>);
-#[derive(Debug, Trace, Clone)]
-struct ContextInternal {
- sup_this: Option<SupThis>,
- /// `bindings[i]` corresponds to `LocalId(offset + i)`.
- bindings: Vec<Option<Thunk<Val>>>,
- offset: u32,
- parent: Option<Context>,
+#[derive(Trace)]
+pub(crate) struct ContextInternal {
+ /// Immutable, packed at closure-create time.
+ pub(crate) captures: Cc<Vec<Thunk<Val>>>,
+ /// Filled during closure initialization
+ pub(crate) locals: Cc<LocalsFrame>,
+ pub(crate) sup_this: Option<SupThis>,
}
-impl Context {
- pub fn new_future() -> Pending<Self> {
- Pending::new()
+impl Debug for ContextInternal {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("ContextInternal")
+ .field("captures", &self.captures.len())
+ .field("locals", &self.locals)
+ .field("sup_this", &self.sup_this.is_some())
+ .finish()
}
+}
- pub fn sup_this(&self) -> Option<&SupThis> {
- self.0.sup_this.as_ref()
+#[derive(Trace, Debug)]
+pub(crate) struct IterFrame {
+ slots: Vec<RefCell<Option<Thunk<Val>>>>,
+ captured: Cell<bool>,
+}
+impl IterFrame {
+ pub fn new(n: u16) -> IterFrame {
+ let cells: Vec<RefCell<Option<Thunk<Val>>>> = (0..n).map(|_| RefCell::new(None)).collect();
+ IterFrame {
+ slots: cells,
+ captured: Cell::new(false),
+ }
}
-
- pub fn try_sup_this(&self) -> Result<SupThis> {
- self.0
- .sup_this
- .clone()
- .ok_or_else(|| error!(CantUseSelfSupOutsideOfObject))
+ pub fn set(&self, slot: LocalSlot, value: Thunk<Val>) {
+ *self.slots[slot.0 as usize].borrow_mut() = Some(value);
}
-
- /// Update binding in `CoW` fashion. Only useful for eager comprehension
- /// fast-path, as it requires Cc refcount to be 1; Use `ContextBuilder` otherwise.
- pub(crate) fn cow_fill_binding(&mut self, id: LocalId, value: Thunk<Val>) {
- let mut value = Some(Some(value));
+}
- self.0.update_with(|inner| {
- let local_idx = (id.0 - inner.offset) as usize;
- while inner.bindings.len() <= local_idx {
- inner.bindings.push(None);
+#[derive(Trace, Debug)]
+pub(crate) enum LocalsFrame {
+ Once1(OnceCell<Thunk<Val>>),
+ /// Letrec/function/object/for frames - slots are filled during frame setup
+ Once(Vec<OnceCell<Thunk<Val>>>),
+ /// Comp-eager fast-path, cells are reset per iteration for the unique frames (i.e for the non-capturing thunks)
+ Iter(IterFrame),
+}
+impl LocalsFrame {
+ pub fn set(&self, slot: LocalSlot, value: Thunk<Val>) {
+ match self {
+ LocalsFrame::Once1(cell) => {
+ debug_assert_eq!(slot.0, 0, "Once1 only holds slot 0");
+ cell.set(value)
+ .map_err(|_| ())
+ .expect("slot already filled");
}
- inner.bindings[local_idx] = value.take().expect("called once");
- });
- }
-
- pub fn binding(&self, id: LocalId) -> Option<Thunk<Val>> {
- let id_num = id.0;
- if id_num >= self.0.offset {
- let local_idx = (id_num - self.0.offset) as usize;
- if let Some(Some(thunk)) = self.0.bindings.get(local_idx) {
- return Some(thunk.clone());
+ LocalsFrame::Once(cells) => {
+ cells[slot.0 as usize]
+ .set(value)
+ .map_err(|_| ())
+ .expect("slot already filled");
}
+ LocalsFrame::Iter(_) => unreachable!("iter frame has different constructors"),
}
- if let Some(parent) = &self.0.parent {
- return parent.binding(id);
+ }
+}
+
+impl LocalsFrame {
+ pub(crate) fn new_once(n: u16) -> Cc<Self> {
+ if n == 1 {
+ return Cc::new(Self::Once1(OnceCell::new()));
}
- None
+ let cells: Vec<OnceCell<Thunk<Val>>> = (0..n).map(|_| OnceCell::new()).collect();
+ Cc::new(Self::Once(cells))
}
+}
- #[must_use]
- pub fn into_future(self, ctx: Pending<Self>) -> Self {
- {
- ctx.clone().fill(self);
+pub(crate) struct IterContext {
+ context: Context,
+}
+impl IterContext {
+ pub(crate) fn create(&self, build: impl FnOnce(&IterFrame)) -> Result<Context> {
+ if !Cc::is_unique(&self.context.0.locals) {
+ bail!(EagerCompspecCaptured);
+ }
+ let LocalsFrame::Iter(frame) = &*self.context.0.locals else {
+ unreachable!("IterContext is only created for Iter ctx");
+ };
+ if frame.captured.get() {
+ bail!(EagerCompspecCaptured);
}
- ctx.unwrap()
+ build(frame);
+ Ok(self.context.clone())
}
}
-#[derive(Clone)]
-pub struct ContextBuilder {
+#[derive(Trace, Clone)]
+pub(crate) struct PackedContext {
+ captures: Cc<Vec<Thunk<Val>>>,
+ n_locals: u16,
+}
+impl PackedContext {
+ pub fn enter(self, sup_this: SupThis, build: impl FnOnce(&LocalsFrame, &Context)) -> Context {
+ let locals = LocalsFrame::new_once(self.n_locals);
+ let val = Context(Cc::new(ContextInternal {
+ captures: self.captures.clone(),
+ locals,
+ sup_this: Some(sup_this),
+ }));
+ build(&val.0.locals, &val);
+ val
+ }
+}
+#[derive(Trace, Clone, Educe, Debug)]
+#[educe(PartialEq)]
+pub(crate) struct PackedContextSupThis {
+ #[educe(PartialEq(method = Cc::ptr_eq))]
+ captures: Cc<Vec<Thunk<Val>>>,
+ n_locals: u16,
sup_this: Option<SupThis>,
- bindings: Vec<Option<Thunk<Val>>>,
- offset: u32,
- parent: Option<Context>,
}
+impl PackedContextSupThis {
+ pub fn enter(self, build: impl FnOnce(&LocalsFrame, &Context)) -> Context {
+ let locals = LocalsFrame::new_once(self.n_locals);
+ let val = Context(Cc::new(ContextInternal {
+ captures: self.captures.clone(),
+ locals,
+ sup_this: self.sup_this,
+ }));
+ build(&val.0.locals, &val);
+ val
+ }
+}
-impl ContextBuilder {
- pub fn new() -> Self {
- Self {
+impl Context {
+ #[inline]
+ pub fn slot(&self, slot: LSlot) -> Thunk<Val> {
+ match slot {
+ LSlot::Local(i) => self.local(i),
+ LSlot::Capture(i) => self.capture(i),
+ }
+ }
+ /// Read a local slot from the shared locals frame.
+ ///
+ /// # Panics
+ /// If the slot has not yet been filled. The analyzer guarantees
+ /// that slot indices are in range and that letrec setup completes
+ /// before the first read. A panic indicates an analyzer/runtime
+ /// invariant violation, not a user error.
+ #[inline]
+ pub fn local(&self, slot: LocalSlot) -> Thunk<Val> {
+ match &*self.0.locals {
+ LocalsFrame::Once1(cell) => {
+ debug_assert_eq!(slot.0, 0, "Once1 only holds slot 0");
+ cell.get().expect("local read before letrec init").clone()
+ }
+ LocalsFrame::Once(cells) => cells[slot.0 as usize]
+ .get()
+ .expect("local read before letrec init")
+ .clone(),
+ LocalsFrame::Iter(cells) => cells.slots[slot.0 as usize]
+ .borrow()
+ .as_ref()
+ .expect("iter local read before iteration filled it")
+ .clone(),
+ }
+ }
+
+ /// Read a captured slot from this closure's capture pack.
+ #[inline]
+ pub fn capture(&self, slot: CaptureSlot) -> Thunk<Val> {
+ (*self.0.captures)[slot.0 as usize].clone()
+ }
+
+ pub fn sup_this(&self) -> Option<&SupThis> {
+ self.0.sup_this.as_ref()
+ }
+
+ pub fn try_sup_this(&self) -> Result<SupThis> {
+ self.0
+ .sup_this
+ .clone()
+ .ok_or_else(|| error!(CantUseSelfSupOutsideOfObject))
+ }
+
+ /// Build a root context: empty captures, externals filled into a
+ /// fresh Once locals frame in declaration order. Used once at
+ /// program entry to construct the context the analyzed root LIR
+ /// runs against.
+ pub(crate) fn root(externals: Vec<Thunk<Val>>) -> Self {
+ let n: u16 = externals
+ .len()
+ .try_into()
+ .expect("more than u16::MAX externals");
+ let cells: Vec<OnceCell<Thunk<Val>>> = externals
+ .into_iter()
+ .map(|t| {
+ let cell = OnceCell::new();
+ cell.set(t).map_err(|_| ()).expect("fresh cell");
+ cell
+ })
+ .collect();
+ debug_assert_eq!(cells.len(), n as usize);
+ let locals = Cc::new(LocalsFrame::Once(cells));
+ Self(Cc::new(ContextInternal {
+ captures: Cc::new(Vec::new()),
+ locals,
sup_this: None,
- bindings: Vec::new(),
- offset: 0,
- parent: None,
- }
+ }))
}
- pub(crate) fn extend(parent: Context, capacity: usize) -> Self {
- let offset = parent.0.offset + parent.0.bindings.len() as u32;
- Self {
- sup_this: parent.0.sup_this.clone(),
- bindings: Vec::with_capacity(capacity),
- offset,
- parent: Some(parent),
+ pub(crate) fn pack_captures(&self, shape: &ClosureShape) -> PackedContext {
+ PackedContext {
+ captures: Cc::new(pack_captures(self, &shape.captures)),
+ n_locals: shape.n_locals,
}
}
-
- pub(crate) fn bind(&mut self, id: LocalId, value: Thunk<Val>) {
- debug_assert!(
- id.0 >= self.offset,
- "cannot bind {id:?} below offset {}",
- self.offset,
- );
- let local_idx = (id.0 - self.offset) as usize;
- self.bindings.reserve(local_idx);
- while self.bindings.len() <= local_idx {
- self.bindings.push(None);
+ pub(crate) fn pack_captures_sup_this(&self, shape: &ClosureShape) -> PackedContextSupThis {
+ PackedContextSupThis {
+ captures: Cc::new(pack_captures(self, &shape.captures)),
+ n_locals: shape.n_locals,
+ sup_this: self.0.sup_this.clone(),
}
- self.bindings[local_idx] = Some(value);
}
- pub(crate) fn build(self) -> Context {
- Context(Cc::new(ContextInternal {
- sup_this: self.sup_this,
- bindings: self.bindings,
- offset: self.offset,
- parent: self.parent,
- }))
+ pub(crate) fn enter_iter(
+ parent: &Context,
+ shape: &ClosureShape,
+ cb: impl FnOnce(IterContext) -> Result<()>,
+ ) -> Result<()> {
+ let captures = Cc::new(pack_captures(parent, &shape.captures));
+ let locals = IterFrame::new(shape.n_locals);
+ cb(IterContext {
+ context: Self(Cc::new(ContextInternal {
+ captures,
+ locals: Cc::new(LocalsFrame::Iter(locals)),
+ sup_this: parent.0.sup_this.clone(),
+ })),
+ })
}
- pub(crate) fn build_sup_this(mut self, st: SupThis) -> Context {
- self.sup_this = Some(st);
- self.build()
+ pub(crate) fn enter_using(parent: &Context, shape: &ClosureShape) -> Self {
+ debug_assert_eq!(shape.n_locals, 0);
+ if shape.captures.is_empty() {
+ if let LocalsFrame::Iter(i) = &*parent.0.locals {
+ i.captured.set(true);
+ }
+ // Value never uses captures, thus evaluating it against the parent gives the same result
+ return parent.clone();
+ }
+ let captures = Cc::new(pack_captures(parent, &shape.captures));
+ Self(Cc::new(ContextInternal {
+ captures,
+ locals: parent.0.locals.clone(),
+ sup_this: parent.0.sup_this.clone(),
+ }))
}
}
-impl Default for ContextBuilder {
- fn default() -> Self {
- Self::new()
- }
+fn pack_captures(parent: &Context, sources: &[LSlot]) -> Vec<Thunk<Val>> {
+ sources.iter().map(|src| parent.slot(*src)).collect()
}
pub struct InitialContextBuilder {
- builder: ContextBuilder,
externals: Vec<(IStr, LocalId)>,
+ values: Vec<Thunk<Val>>,
next_id: u32,
}
impl InitialContextBuilder {
pub(crate) fn new() -> Self {
Self {
- builder: ContextBuilder::new(),
externals: Vec::new(),
+ values: Vec::new(),
next_id: 0,
}
}
@@ -155,11 +300,11 @@
let id = LocalId(self.next_id);
self.next_id += 1;
self.externals.push((name, id));
- self.builder.bind(id, value);
+ self.values.push(value);
}
- pub(crate) fn build(self) -> (ContextBuilder, Vec<(IStr, LocalId)>) {
- (self.builder, self.externals)
+ pub(crate) fn build(self) -> (Vec<(IStr, LocalId)>, Vec<Thunk<Val>>) {
+ (self.externals, self.values)
}
}
crates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -1,59 +1,6 @@
-use std::{cell::OnceCell, hash::Hasher, ptr::addr_of};
-
-use educe::Educe;
-use jrsonnet_gcmodule::{Cc, Trace};
-
-use crate::{Result, bail, error::ErrorKind::InfiniteRecursionDetected, val::ThunkValue};
-
-#[derive(Trace, Educe)]
-#[educe(Clone)]
-pub struct Pending<V: Trace + 'static>(pub Cc<OnceCell<V>>);
-impl<T: Trace + 'static> Pending<T> {
- pub fn new() -> Self {
- Self(Cc::new(OnceCell::new()))
- }
- pub fn new_filled(v: T) -> Self {
- let cell = OnceCell::new();
- let _ = cell.set(v);
- Self(Cc::new(cell))
- }
- /// # Panics
- /// If wrapper is filled already
- pub fn fill(self, value: T) {
- self.0
- .set(value)
- .map_err(|_| ())
- .expect("wrapper is filled already");
- }
-}
-impl<T: Trace + 'static + Clone> Pending<T> {
- /// # Panics
- /// If wrapper is not yet filled
- pub fn unwrap(&self) -> T {
- self.0.get().cloned().expect("pending was not filled")
- }
- pub fn try_get(&self) -> Option<T> {
- self.0.get().cloned()
- }
-}
+use std::{hash::Hasher, ptr::addr_of};
-impl<T: Trace + Clone> ThunkValue for Pending<T> {
- type Output = T;
-
- fn get(&self) -> Result<Self::Output> {
- let Some(value) = self.0.get() else {
- // TODO: Other error?
- bail!(InfiniteRecursionDetected);
- };
- Ok(value.clone())
- }
-}
-
-impl<T: Trace + 'static> Default for Pending<T> {
- fn default() -> Self {
- Self::new()
- }
-}
+use jrsonnet_gcmodule::Cc;
pub fn identity_hash<T, H: Hasher>(v: &Cc<T>, hasher: &mut H) {
hasher.write_usize(addr_of!(**v) as usize);
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -10,6 +10,7 @@
use crate::{
ObjValue, ResolvePathOwned,
+ analyze::Diagnostic,
function::{CallLocation, FunctionSignature, ParamName},
stdlib::format::FormatError,
typed::TypeLocError,
@@ -112,12 +113,14 @@
CantUseSelfSupOutsideOfObject,
#[error("static analysis errors: {}", .0.iter().map(|d| d.message.as_str()).collect::<Vec<_>>().join("; "))]
- StaticAnalysisError(Vec<crate::analyze::Diagnostic>),
+ StaticAnalysisError(Vec<Diagnostic>),
#[error("no super found")]
NoSuperFound,
#[error("for loop can only iterate over arrays")]
InComprehensionCanOnlyIterateOverArray,
+ #[error("(should not be visible) eager compspec evaluation failed due to captured context")]
+ EagerCompspecCaptured,
#[error("array out of bounds: {0} is not within [0,{1})")]
ArrayBoundsError(isize, u32),
@@ -394,12 +397,5 @@
};
($l:literal$(, $($tt:tt)*)?) => {
<$crate::error::Error as From<$crate::error::ErrorKind>>::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())
- };
-}
-
-#[macro_export]
-macro_rules! runtime_error {
- ($l:literal$(, $($tt:tt)*)?) => {
- $crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))
};
}
crates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
@@ -3,16 +3,19 @@
use jrsonnet_types::ValType;
use super::{
- destructure::{self, evaluate_locals, evaluate_locals_unbound},
+ destructure::{destruct, evaluate_locals_unbound, fill_letrec_binds},
evaluate_field_member_static, evaluate_field_member_unbound,
};
use crate::{
- Context, ContextBuilder, ObjValue, ObjValueBuilder, Pending, Result, Thunk, Val,
- analyze::{LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp, LocalId},
+ Context, ObjValue, ObjValueBuilder, Result, Thunk, Val,
+ analyze::{
+ ClosureShape, LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp,
+ LocalSlot,
+ },
arr::ArrValue,
bail,
error::ErrorKind::*,
- evaluate::evaluate,
+ evaluate::{evaluate, evaluate_trivial},
};
trait CompCollector {
@@ -22,6 +25,7 @@
struct EagerArrCollector<'a> {
out: &'a mut Vec<Val>,
+ value_shape: &'a ClosureShape,
value: &'a LExpr,
}
impl CompCollector for EagerArrCollector<'_> {
@@ -29,13 +33,23 @@
self.out.reserve(size_hint);
}
fn collect(&mut self, ctx: Context) -> Result<()> {
- self.out.push(evaluate(ctx, self.value)?);
+ if let Some(v) = evaluate_trivial(self.value) {
+ self.out.push(v);
+ return Ok(());
+ }
+ if let LExpr::Slot(slot) = self.value {
+ self.out.push(ctx.slot(*slot).evaluate()?);
+ return Ok(());
+ }
+ let env = Context::enter_using(&ctx, self.value_shape);
+ self.out.push(evaluate(env, self.value)?);
Ok(())
}
}
struct LazyArrCollector<'a> {
out: &'a mut Vec<Thunk<Val>>,
+ value_shape: &'a ClosureShape,
value: &'a Rc<LExpr>,
}
impl CompCollector for LazyArrCollector<'_> {
@@ -43,14 +57,24 @@
self.out.reserve(size_hint);
}
fn collect(&mut self, ctx: Context) -> Result<()> {
+ if let Some(v) = evaluate_trivial(self.value) {
+ self.out.push(Thunk::evaluated(v));
+ return Ok(());
+ }
+ if let LExpr::Slot(slot) = self.value.as_ref() {
+ self.out.push(ctx.slot(*slot));
+ return Ok(());
+ }
+ let env = Context::enter_using(&ctx, self.value_shape);
let value_expr = self.value.clone();
- self.out.push(Thunk!(move || evaluate(ctx, &value_expr)));
+ self.out.push(Thunk!(move || evaluate(env, &value_expr)));
Ok(())
}
}
struct ObjCompCollectorStatic<'a> {
builder: &'a mut ObjValueBuilder,
+ frame_shape: &'a ClosureShape,
locals: &'a [LBind],
field: &'a LFieldMember,
}
@@ -59,15 +83,23 @@
self.builder.reserve_fields(guaranteed);
}
fn collect(&mut self, inner_ctx: Context) -> Result<()> {
- let value_ctx = evaluate_locals(inner_ctx.clone(), self.locals);
+ // Build the object's A-frame fresh per iteration: captures from
+ // the comp's iter ctx, locals = `this` (slot 0, unfilled in the
+ // static path) + member-locals via letrec.
+ let value_ctx = inner_ctx
+ .pack_captures_sup_this(self.frame_shape)
+ .enter(|fill, ctx| {
+ fill_letrec_binds(fill, &ctx, self.locals);
+ });
evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)
}
}
struct ObjCompCollectorUnbound<'a> {
builder: &'a mut ObjValueBuilder,
+ frame_shape: Rc<ClosureShape>,
locals: Rc<Vec<LBind>>,
- this_id: Option<LocalId>,
+ this_slot: Option<LocalSlot>,
field: &'a LFieldMember,
}
impl CompCollector for ObjCompCollectorUnbound<'_> {
@@ -75,7 +107,12 @@
self.builder.reserve_fields(guaranteed);
}
fn collect(&mut self, inner_ctx: Context) -> Result<()> {
- let uctx = evaluate_locals_unbound(inner_ctx.clone(), self.locals.clone(), self.this_id);
+ let uctx = evaluate_locals_unbound(
+ &inner_ctx,
+ &self.frame_shape,
+ self.this_slot,
+ self.locals.clone(),
+ );
evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)
}
}
@@ -100,8 +137,9 @@
0,
&mut ObjCompCollectorUnbound {
builder: &mut builder,
+ frame_shape: comp.frame_shape.clone(),
locals: comp.locals.clone(),
- this_id: comp.this,
+ this_slot: comp.this,
field: &comp.field,
},
)?;
@@ -114,6 +152,7 @@
0,
&mut ObjCompCollectorStatic {
builder: &mut builder,
+ frame_shape: &comp.frame_shape,
locals: &comp.locals,
field: &comp.field,
},
@@ -126,7 +165,9 @@
pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {
let cached_overs = cache_overs(&ctx, &comp.compspecs)?;
- // In eager evaluation, Context is not captured, thus updates in CoW fashion will likely to success
+ // Eager fast-path: when the comp has only `if` and `for { destruct: Full(_) }`
+ // specs, allocate one Iter A-frame per for-spec and re-set the slot
+ // per iteration as long as the frame's refcount stays at 1.
'eager: {
let mut out = Vec::new();
@@ -147,6 +188,7 @@
0,
&mut EagerArrCollector {
out: &mut out,
+ value_shape: &comp.value_shape,
value: &comp.value,
},
)
@@ -166,6 +208,7 @@
0,
&mut LazyArrCollector {
out: &mut items,
+ value_shape: &comp.value_shape,
value: &comp.value,
},
)?;
@@ -220,7 +263,12 @@
evaluate_compspecs_eager(ctx, specs, cached_overs, idx + 1, 0, collector)?;
}
}
- LCompSpec::For { destruct, over, .. } => {
+ LCompSpec::For {
+ frame_shape,
+ destruct,
+ over,
+ ..
+ } => {
let arr = if let Some(cached) = &cached_overs[idx] {
cached.clone()
} else {
@@ -232,21 +280,24 @@
};
let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;
match destruct {
- LDestruct::Full(id) => {
- let id = *id;
- let mut inner_ctx = ContextBuilder::extend(ctx, 1).build();
- for (i, item) in arr.iter().enumerate() {
- // TODO: reuse one ContextBuilder for full evaluate_compspecs pipeline
- inner_ctx.cow_fill_binding(id, Thunk::evaluated(item?));
- evaluate_compspecs_eager(
- inner_ctx.clone(),
- specs,
- cached_overs,
- idx + 1,
- if i == 0 { inner_reserve } else { 0 },
- collector,
- )?;
- }
+ LDestruct::Full(slot) => {
+ Context::enter_iter(&ctx, frame_shape, |it| {
+ for (i, item) in arr.iter().enumerate() {
+ let item = item?;
+ let ctx = it.create(|f| {
+ f.set(*slot, Thunk::evaluated(item));
+ })?;
+ evaluate_compspecs_eager(
+ ctx,
+ specs,
+ cached_overs,
+ idx + 1,
+ if i == 0 { inner_reserve } else { 0 },
+ collector,
+ )?;
+ }
+ Ok(())
+ })?;
}
// TODO: Should not be eager? CoW won't work here
#[cfg(feature = "exp-destruct")]
@@ -283,7 +334,12 @@
evaluate_compspecs(ctx, specs, cached_overs, idx + 1, 0, collector)?;
}
}
- LCompSpec::For { destruct, over, .. } => {
+ LCompSpec::For {
+ frame_shape,
+ destruct: dst,
+ over,
+ ..
+ } => {
let arr = if let Some(cached) = &cached_overs[idx] {
cached.clone()
} else {
@@ -295,16 +351,10 @@
};
let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;
for (i, item) in arr.iter().enumerate() {
- let item_val = item?;
- let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);
- let fctx = Pending::new();
- destructure::destruct(
- destruct,
- Thunk::evaluated(item_val),
- fctx.clone(),
- &mut inner_builder,
- );
- let inner_ctx = inner_builder.build().into_future(fctx);
+ let item = item?;
+ let inner_ctx = ctx.pack_captures_sup_this(frame_shape).enter(|fill, ctx| {
+ destruct(dst, fill, Thunk::evaluated(item), &ctx);
+ });
evaluate_compspecs(
inner_ctx,
specs,
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -3,8 +3,10 @@
use jrsonnet_gcmodule::Trace;
use crate::{
- Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,
- analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},
+ Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,
+ analyze::{
+ ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LExpr, LLocalExpr, LocalSlot,
+ },
bail,
evaluate::evaluate,
};
@@ -15,9 +17,9 @@
rest: Option<&LDestructRest>,
end: &[LDestruct],
+ fill: &LocalsFrame,
value: Thunk<Val>,
- fctx: Pending<Context>,
- builder: &mut ContextBuilder,
+ a_ctx: &Context,
) {
let min_len = start.len() + end.len();
let has_rest = rest.is_some();
@@ -44,19 +46,19 @@
let full = full.clone();
destruct(
d,
+ fill,
Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),
- fctx.clone(),
- builder,
+ a_ctx,
);
}
let start_len = start.len() as u32;
let end_len = end.len() as u32;
- if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {
+ if let Some(LDestructRest::Keep(slot)) = rest {
let full = full.clone();
- builder.bind(
- *id,
+ fill.set(
+ *slot,
Thunk!(move || {
let full = full.evaluate()?;
let to = full.len() - end_len;
@@ -73,14 +75,14 @@
let full = full.clone();
destruct(
d,
+ fill,
Thunk!(move || {
let full = full.evaluate()?;
Ok(full
.get(full.len() - end_len + i as u32)?
.expect("length is checked"))
}),
- fctx.clone(),
- builder,
+ a_ctx,
);
}
}
@@ -90,9 +92,9 @@
fields: &[LDestructField],
rest: Option<&LDestructRest>,
+ fill: &LocalsFrame,
value: Thunk<Val>,
- fctx: Pending<Context>,
- builder: &mut ContextBuilder,
+ a_ctx: &Context,
) {
use jrsonnet_interner::IStr;
use rustc_hash::FxHashSet;
@@ -124,10 +126,10 @@
Ok(obj)
});
- if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {
+ if let Some(LDestructRest::Keep(slot)) = rest {
let full = full.clone();
- builder.bind(
- *id,
+ fill.set(
+ *slot,
Thunk!(move || {
let full = full.evaluate()?;
let mut out = ObjValueBuilder::new();
@@ -140,121 +142,100 @@
for field in fields {
let field_name = field.name.clone();
- let default: Option<(Pending<Context>, Rc<LExpr>)> =
- field.default.as_ref().map(|e| (fctx.clone(), e.clone()));
+ let default_thunk: Option<Thunk<Val>> = field
+ .default
+ .as_ref()
+ .map(|(shape, expr)| build_b_thunk(a_ctx, shape, expr.clone()));
+
let field_full = full.clone();
let value_thunk = Thunk!(move || {
let obj = field_full.evaluate()?;
obj.get(field_name)?.map_or_else(
- || {
- let (fctx, expr) = default.as_ref().expect("shape is checked");
- evaluate(fctx.unwrap(), expr)
- },
+ || default_thunk.as_ref().expect("shape is checked").evaluate(),
Ok,
)
});
if let Some(into) = &field.into {
- destruct(into, value_thunk, fctx.clone(), builder);
+ destruct(into, fill, value_thunk, a_ctx);
} else {
unreachable!("analyzer lowers object-destruct shorthands into `into`");
}
}
}
-/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one
-/// binding per [`LocalId`] the pattern introduces.
-///
-/// `fctx` is needed for object-destruct defaults (feature `exp-destruct`).
#[allow(unused_variables)]
-pub fn destruct(
- d: &LDestruct,
- value: Thunk<Val>,
- fctx: Pending<Context>,
- builder: &mut ContextBuilder,
-) {
+pub fn destruct(d: &LDestruct, fill: &LocalsFrame, value: Thunk<Val>, a_ctx: &Context) {
match d {
- LDestruct::Full(id) => builder.bind(*id, value),
+ LDestruct::Full(slot) => fill.set(*slot, value),
#[cfg(feature = "exp-destruct")]
LDestruct::Skip => {}
#[cfg(feature = "exp-destruct")]
LDestruct::Array { start, rest, end } => {
- destruct_array(start, rest.as_ref(), end, value, fctx, builder)
+ destruct_array(start, rest.as_ref(), end, fill, value, a_ctx)
}
#[cfg(feature = "exp-destruct")]
- LDestruct::Object { fields, rest } => {
- destruct_object(fields, rest.as_ref(), value, fctx, builder)
- }
+ LDestruct::Object { fields, rest } => destruct_object(fields, rest.as_ref(), fill, value, a_ctx),
}
}
-/// Bind one [`LBind`] as a lazy thunk that evaluates in the given
-/// future context. Mirrors the old `evaluate_dest` — one entry per
-/// binding in a `local … ;` frame.
-pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {
- let value = bind.value.clone();
- let fctx_clone = fctx.clone();
- let thunk = Thunk!(move || {
- let ctx = fctx_clone.unwrap();
- evaluate(ctx, &value)
- });
- destruct(&bind.destruct, thunk, fctx, builder);
+pub fn build_b_thunk(a_ctx: &Context, shape: &ClosureShape, expr: Rc<LExpr>) -> Thunk<Val> {
+ let env = Context::enter_using(a_ctx, shape);
+ Thunk!(move || evaluate(env, &expr))
+}
+pub fn build_b_thunk_uno(a_ctx: &Context, shape: Rc<(ClosureShape, LExpr)>) -> Thunk<Val> {
+ let env = Context::enter_using(a_ctx, &shape.0);
+ Thunk!(move || evaluate(env, &shape.1))
}
-/// Bind each LBind's value as a lazy thunk. Mutually recursive locals
-/// resolve lazily through the shared Pending<Context>.
-pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {
- if binds.is_empty() {
- return parent;
- }
- let fctx = Context::new_future();
- let mut builder =
- ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());
+pub fn fill_letrec_binds(fill: &LocalsFrame, ctx: &Context, binds: &[LBind]) {
for bind in binds {
- evaluate_dest(bind, fctx.clone(), &mut builder);
+ let value_thunk = build_b_thunk(ctx, &bind.value_shape, bind.value.clone());
+ destruct(&bind.destruct, fill, value_thunk, ctx);
}
- builder.build().into_future(fctx)
}
+pub fn evaluate_local_expr(parent: Context, l: &LLocalExpr) -> Result<Val> {
+ let ctx = parent
+ .pack_captures_sup_this(&l.frame_shape)
+ .enter(|fill, ctx| {
+ fill_letrec_binds(fill, ctx, &l.binds);
+ });
+ evaluate(ctx, &l.body)
+}
+
pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}
impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}
pub fn evaluate_locals_unbound(
- fctx: Context,
+ outer: &Context,
+ frame_shape: &ClosureShape,
+ this_slot: Option<LocalSlot>,
locals: Rc<Vec<LBind>>,
- this_id: Option<LocalId>,
) -> impl CloneableUnbound<Context> {
#[derive(Trace, Clone)]
struct UnboundLocals {
- fctx: Context,
+ captures: PackedContext,
+ this_slot: Option<LocalSlot>,
locals: Rc<Vec<LBind>>,
- this_id: Option<LocalId>,
}
impl Unbound for UnboundLocals {
type Bound = Context;
fn bind(&self, sup_this: SupThis) -> Result<Context> {
- let parent = self.fctx.clone();
-
- let fctx = Context::new_future();
- let mut builder = ContextBuilder::extend(
- parent,
- self.locals.iter().map(|b| b.destruct.ids().len()).sum(),
- );
- for b in self.locals.iter() {
- evaluate_dest(b, fctx.clone(), &mut builder);
- }
- if let Some(this_id) = self.this_id {
- builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));
- }
- let ctx = builder.build_sup_this(sup_this).into_future(fctx);
- Ok(ctx)
+ Ok(self.captures.clone().enter(sup_this, |fill, ctx| {
+ if let Some(slot) = self.this_slot {
+ let this_obj = ctx.sup_this().expect("sup_this set above").this().clone();
+ fill.set(slot, Thunk::evaluated(Val::Obj(this_obj)));
+ }
+ fill_letrec_binds(fill, ctx, &self.locals);
+ }))
}
}
UnboundLocals {
- fctx,
+ captures: outer.pack_captures(frame_shape),
+ this_slot,
locals,
- this_id,
}
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -7,21 +7,22 @@
use self::{
compspec::{evaluate_arr_comp, evaluate_obj_comp},
- destructure::{evaluate_locals, evaluate_locals_unbound},
+ destructure::{build_b_thunk_uno, evaluate_local_expr, evaluate_locals_unbound},
operator::evaluate_binary_op_special,
};
use crate::{
Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _, SupThis,
Unbound, Val,
analyze::{
- LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction, LIndexPart, LObjBody,
- LObjMembers,
+ ClosureShape, LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction,
+ LIndexPart, LObjAsserts, LObjBody, LObjMembers, LSlot,
},
- bail,
+ arr::ArrValue,
+ bail, error,
error::{ErrorKind::*, suggest_object_fields},
- evaluate::operator::evaluate_unary_op,
+ evaluate::{destructure::fill_letrec_binds, operator::evaluate_unary_op},
function::{CallLocation, FuncDesc, FuncVal, prepared::PreparedFuncVal},
- in_frame, runtime_error,
+ in_frame,
typed::FromUntyped as _,
val::{CachedUnbound, Thunk},
with_state,
@@ -62,11 +63,10 @@
})
}
-/// Evaluate a method definition.
pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {
Val::Func(FuncVal::Normal(Cc::new(FuncDesc {
name,
- ctx,
+ body_captures: ctx.pack_captures_sup_this(&func.body_shape),
func: func.clone(),
})))
}
@@ -91,6 +91,15 @@
}
pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {
+ match &*expr {
+ LExpr::Slot(LSlot::Local(i)) => return Ok(ctx.local(*i)),
+ LExpr::Slot(LSlot::Capture(i)) => return Ok(ctx.capture(*i)),
+ _ => {
+ if let Some(v) = evaluate_trivial(&expr) {
+ return Ok(Thunk::evaluated(v));
+ }
+ }
+ }
Ok(if tailstrict {
Thunk::evaluated(evaluate(ctx, &expr)?)
} else {
@@ -106,40 +115,21 @@
}
}
-pub fn evaluate_named(name: &IStr, ctx: Context, expr: &LExpr) -> Result<Val> {
- if let LExpr::Function(f) = &expr {
- return Ok(evaluate_method(
- ctx,
- f.name.clone().unwrap_or_else(|| name.clone()),
- f,
- ));
- }
- evaluate(ctx, expr)
-}
-
pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {
Ok(match expr {
LExpr::Null => Val::Null,
LExpr::Bool(b) => Val::Bool(*b),
LExpr::Str(s) => Val::string(s.clone()),
LExpr::Num(n) => Val::Num(*n),
- LExpr::Local(id) => {
- let Some(thunk) = ctx.binding(*id) else {
- bail!("should not happen: unbound local {id:?}");
- };
- thunk.evaluate()?
- }
+ LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,
LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),
- LExpr::Arr(items) => Val::Arr(crate::arr::ArrValue::expr(ctx, items.clone())),
+ LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),
LExpr::UnaryOp(op, value) => {
let value = evaluate(ctx, value)?;
evaluate_unary_op(*op, &value)?
}
LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,
- LExpr::LocalExpr { binds, body } => {
- let ctx = evaluate_locals(ctx, binds);
- evaluate(ctx, body)?
- }
+ LExpr::LocalExpr(local_expr) => evaluate_local_expr(ctx, local_expr)?,
LExpr::IfElse {
cond,
cond_then,
@@ -176,6 +166,7 @@
func.name.clone().unwrap_or_else(names::anonymous),
func,
),
+ LExpr::IdentityFunction => Val::Func(FuncVal::identity()),
LExpr::Apply {
applicable,
args,
@@ -240,8 +231,7 @@
let n = v.as_num().ok_or_else(|| -> crate::Error {
TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()
})?;
- BoundedUsize::new(n as usize)
- .ok_or_else(|| runtime_error!("slice step must be >= 1"))
+ BoundedUsize::new(n as usize).ok_or_else(|| error!("slice step must be >= 1"))
})
.transpose()?;
Val::from(indexable.slice(start, end, step)?)
@@ -278,7 +268,32 @@
bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))
};
+ if func.is_identity() && args.names.is_empty() && args.unnamed.len() == 1 {
+ return evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?.evaluate();
+ }
+
let name = func.name();
+
+ if args.names.is_empty() && args.unnamed.len() == 1 && func.params().len() == 1 {
+ use crate::function::prepared::PreparedCall;
+ let prepared_inline = PreparedCall::empty();
+ let arg = evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?;
+ let arg_slice = std::slice::from_ref(&arg);
+ return in_frame(
+ loc,
+ || format!("function <{name}> call"),
+ || {
+ func.evaluate_prepared(
+ &prepared_inline,
+ CallLocation::native(),
+ arg_slice,
+ &[],
+ tailstrict,
+ )
+ },
+ );
+ }
+
let unnamed = args
.unnamed
.iter()
@@ -286,6 +301,26 @@
.map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))
.collect::<Result<Vec<_>>>()?;
+ // Fast path: positional-only multi-arg call fully covering the
+ // params, no defaults.
+ if args.names.is_empty() && unnamed.len() == func.params().len() {
+ use crate::function::prepared::PreparedCall;
+ let prepared_inline = PreparedCall::empty();
+ return in_frame(
+ loc,
+ || format!("function <{name}> call"),
+ || {
+ func.evaluate_prepared(
+ &prepared_inline,
+ CallLocation::native(),
+ &unnamed,
+ &[],
+ tailstrict,
+ )
+ },
+ );
+ }
+
let named = args
.values
.iter()
@@ -302,7 +337,7 @@
}
fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {
- let mut value = if let LExpr::Super = indexable {
+ let mut value = if matches!(indexable, LExpr::Super) {
let sup_this = ctx.try_sup_this()?;
// First part must be evaluated to get the super field name
if parts.is_empty() {
@@ -422,13 +457,15 @@
#[derive(Trace)]
struct UnboundValue<B: Trace> {
uctx: B,
- value: Rc<LExpr>,
+ value: Rc<(ClosureShape, LExpr)>,
name: IStr,
}
impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {
type Bound = Val;
fn bind(&self, sup_this: SupThis) -> Result<Val> {
- evaluate(self.uctx.bind(sup_this)?, &self.value)
+ let a_ctx = self.uctx.bind(sup_this)?;
+ let b_ctx = Context::enter_using(&a_ctx, &self.value.0);
+ evaluate(b_ctx, &self.value.1)
}
}
@@ -468,12 +505,12 @@
return Ok(());
};
- let value = value.clone();
+ let thunk = build_b_thunk_uno(&value_ctx, value.clone());
builder
.field(name)
.with_add(*plus)
.with_visibility(*visibility)
- .try_thunk(Thunk!(move || { evaluate(value_ctx, &value) }))?;
+ .try_thunk(thunk)?;
Ok(())
}
@@ -491,34 +528,33 @@
if needs_unbound {
let uctx = CachedUnbound::new(evaluate_locals_unbound(
- ctx.clone(),
- members.locals.clone(),
+ &ctx,
+ &members.frame_shape,
members.this,
+ members.locals.clone(),
));
for field in &members.fields {
evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;
}
- if !members.asserts.is_empty() {
+ if let Some(asserts_block) = &members.asserts {
builder.assert(evaluate_object_assertions_unbound(
uctx,
- members.asserts.clone(),
+ asserts_block.clone(),
));
}
} else {
- let field_ctx = ctx;
- let value_ctx = evaluate_locals(field_ctx.clone(), &members.locals);
+ let a_ctx = ctx
+ .pack_captures_sup_this(&members.frame_shape)
+ .enter(|fill, ctx| {
+ fill_letrec_binds(fill, &ctx, &members.locals);
+ });
for field in &members.fields {
- evaluate_field_member_static(
- &mut builder,
- field_ctx.clone(),
- value_ctx.clone(),
- field,
- )?;
+ evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;
}
- if !members.asserts.is_empty() {
+ if let Some(asserts_block) = &members.asserts {
builder.assert(evaluate_object_assertions_static(
- value_ctx,
- members.asserts.clone(),
+ a_ctx,
+ asserts_block.clone(),
));
}
}
@@ -529,7 +565,7 @@
pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {
let LAssertStmt { cond, message } = assertion;
let assertion_result = in_frame(
- CallLocation::native(),
+ CallLocation::new(&cond.span),
|| "assertion condition".to_owned(),
|| bool::from_untyped(evaluate(ctx.clone(), cond)?),
)?;
@@ -550,18 +586,19 @@
fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(
uctx: B,
- asserts: Rc<Vec<LAssertStmt>>,
+ asserts: Rc<LObjAsserts>,
) -> impl ObjectAssertion {
#[derive(Trace)]
struct ObjectAssert<B: Trace> {
uctx: B,
- asserts: Rc<Vec<LAssertStmt>>,
+ asserts: Rc<LObjAsserts>,
}
impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {
fn run(&self, sup_this: SupThis) -> Result<()> {
- let ctx = self.uctx.bind(sup_this)?;
- for assert in &*self.asserts {
- evaluate_assert(ctx.clone(), assert)?;
+ let a_ctx = self.uctx.bind(sup_this)?;
+ let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);
+ for assert in &self.asserts.asserts {
+ evaluate_assert(assert_env.clone(), assert)?;
}
Ok(())
}
@@ -569,21 +606,25 @@
ObjectAssert { uctx, asserts }
}
fn evaluate_object_assertions_static(
- ctx: Context,
- asserts: Rc<Vec<LAssertStmt>>,
+ a_ctx: Context,
+ asserts: Rc<LObjAsserts>,
) -> impl ObjectAssertion {
#[derive(Trace)]
struct ObjectAssert {
- ctx: Context,
- asserts: Rc<Vec<LAssertStmt>>,
+ assert_env: Context,
+ asserts: Rc<LObjAsserts>,
}
impl ObjectAssertion for ObjectAssert {
fn run(&self, _sup_this: SupThis) -> Result<()> {
- for assert in &*self.asserts {
- evaluate_assert(self.ctx.clone(), assert)?;
+ for assert in &self.asserts.asserts {
+ evaluate_assert(self.assert_env.clone(), assert)?;
}
Ok(())
}
}
- ObjectAssert { ctx, asserts }
+ let assert_env = Context::enter_using(&a_ctx, &asserts.shape);
+ ObjectAssert {
+ assert_env,
+ asserts,
+ }
}
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -11,9 +11,12 @@
prepared::{PreparedCall, parse_prepared_builtin_call},
};
use crate::{
- Context, ContextBuilder, Result, Thunk, Val,
- analyze::{LDestruct, LExpr, LFunction},
- evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},
+ PackedContextSupThis, Result, Thunk, Val,
+ analyze::LFunction,
+ evaluate::{
+ destructure::{build_b_thunk, destruct},
+ ensure_sufficient_stack, evaluate, evaluate_trivial,
+ },
function::builtin::BuiltinFunc,
};
@@ -56,16 +59,7 @@
/// { a() = ... }
/// ```
pub name: IStr,
- /// Context, in which this function was evaluated.
- ///
- /// # Example
- /// In
- /// ```jsonnet
- /// local a = 2;
- /// function() ...
- /// ```
- /// context will contain `a`.
- pub ctx: Context,
+ pub(crate) body_captures: PackedContextSupThis,
#[educe(PartialEq(method = Rc::ptr_eq))]
pub func: Rc<LFunction>,
@@ -82,44 +76,34 @@
named: &[Thunk<Val>],
prepared: &PreparedCall,
) -> Result<Val> {
- let has_defaults = !prepared.defaults().is_empty();
- let mut builder = ContextBuilder::extend(self.ctx.clone(), self.func.params.len());
-
- let fctx = Context::new_future();
- for (param_idx, thunk) in unnamed.iter().enumerate() {
- destruct(
- &self.func.params[param_idx].destruct,
- thunk.clone(),
- fctx.clone(),
- &mut builder,
- );
- }
-
- for &(param_idx, arg_idx) in prepared.named() {
- destruct(
- &self.func.params[param_idx].destruct,
- named[arg_idx].clone(),
- fctx.clone(),
- &mut builder,
- );
- }
+ let body_ctx = self.body_captures.clone().enter(|fill, ctx| {
+ // Place each provided arg-thunk into its destructured slots.
+ for (param_idx, thunk) in unnamed.iter().enumerate() {
+ destruct(
+ &self.func.params[param_idx].destruct,
+ fill,
+ thunk.clone(),
+ &ctx,
+ );
+ }
+ for &(param_idx, arg_idx) in prepared.named() {
+ destruct(
+ &self.func.params[param_idx].destruct,
+ fill,
+ named[arg_idx].clone(),
+ &ctx,
+ );
+ }
- if has_defaults {
for ¶m_idx in prepared.defaults() {
let param = &self.func.params[param_idx];
- if let Some(default_expr) = ¶m.default {
- let default_expr = default_expr.clone();
- let fctxc = fctx.clone();
- let thunk = Thunk!(move || {
- let ctx = fctxc.unwrap();
- evaluate(ctx, &default_expr)
- });
- destruct(¶m.destruct, thunk, fctx.clone(), &mut builder);
- }
+ let (shape, expr) = param.default.as_ref().expect("default exists");
+ let thunk = build_b_thunk(&ctx, shape, expr.clone());
+ destruct(¶m.destruct, fill, thunk, &ctx);
}
- };
- let ctx = builder.build().into_future(fctx);
- ensure_sufficient_stack(|| evaluate(ctx, &self.func.body))
+ });
+
+ ensure_sufficient_stack(|| evaluate(body_ctx, &self.func.body))
}
pub fn evaluate_trivial(&self) -> Option<Val> {
@@ -157,6 +141,10 @@
Self::Builtin(BuiltinFunc::new(builtin))
}
+ pub fn identity() -> Self {
+ Self::builtin(builtin_id {})
+ }
+
pub fn params(&self) -> FunctionSignature {
match self {
Self::Builtin(i) => i.params(),
@@ -193,27 +181,12 @@
}
/// Is this function an identity function.
- ///
- /// Currently only works for builtin `std.id`, aka `Self::Id` value, and `function(x) x`.
///
/// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too
pub fn is_identity(&self) -> bool {
match self {
Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),
- Self::Normal(desc) => {
- if desc.func.params.len() != 1 {
- return false;
- }
- let param = &desc.func.params[0];
- if param.default.is_some() {
- return false;
- }
- #[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]
- let LDestruct::Full(id) = ¶m.destruct else {
- return false;
- };
- matches!(&*desc.func.body, LExpr::Local(v) if v == id)
- }
+ Self::Normal(_) => false,
}
}
crates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/prepared.rs
+++ b/crates/jrsonnet-evaluator/src/function/prepared.rs
@@ -46,6 +46,12 @@
pub fn defaults(&self) -> &[usize] {
&self.defaults
}
+ pub const fn empty() -> Self {
+ Self {
+ named: Vec::new(),
+ defaults: Vec::new(),
+ }
+ }
}
pub fn prepare_call(
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -12,7 +12,7 @@
};
use crate::{
- Error as JrError, ObjValue, ObjValueBuilder, Result, Val, in_description_frame, runtime_error,
+ Error as JrError, ObjValue, ObjValueBuilder, Result, Val, error, in_description_frame,
};
impl<'de> Deserialize<'de> for Val {
@@ -629,6 +629,6 @@
where
T: std::fmt::Display,
{
- runtime_error!("serde: {msg}")
+ error!("serde: {msg}")
}
}
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -58,6 +58,7 @@
pub use val::{Thunk, Val};
pub mod analyze;
+use self::analyze::{LExpr, analyze_root};
use crate::gc::WithCapacityExt as _;
#[allow(clippy::needless_return)]
@@ -408,12 +409,15 @@
file.evaluating = true;
// Dropping file cache guard here, as evaluation may use this map too
drop(file_cache);
- let (ctx, externals) = self.create_default_context(file_name.clone()).build();
- let report = analyze::analyze_root(&parsed, externals);
+ let (externals, thunks) = self.create_default_context(file_name).build();
+ let report = analyze_root(&parsed, externals);
if report.errored {
return Err(StaticAnalysisError(report.diagnostics_list).into());
}
- let res = evaluate::evaluate(ctx.build(), &report.lir);
+ debug_assert_eq!(report.root_shape.n_locals as usize, thunks.len());
+ debug_assert!(report.root_shape.captures.is_empty());
+ let ctx = Context::root(thunks);
+ let res = evaluate::evaluate(ctx, &report.lir);
let mut file_cache = self.file_cache();
let mut file = file_cache.entry(path);
@@ -501,33 +505,66 @@
}
}
+pub struct PreparedSnippet {
+ lir: LExpr,
+ thunks: Vec<Thunk<Val>>,
+}
+
/// Raw methods evaluate passed values but don't perform TLA execution
impl State {
- /// Parses and evaluates the given snippet
- pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
- self.evaluate_snippet_with(name, code, &())
- }
- /// Parses and evaluates the given snippet with custom context modifier
- pub fn evaluate_snippet_with(
+ /// Parses and analyses the given snippet with a custom context
+ /// modifier.
+ pub fn prepare_snippet_with(
&self,
name: impl Into<IStr>,
code: impl Into<IStr>,
context_initializer: &dyn ContextInitializer,
- ) -> Result<Val> {
+ ) -> Result<PreparedSnippet> {
let code = code.into();
let source = Source::new_virtual(name.into(), code.clone());
let parsed = parse_jsonnet(&code, source.clone()).map_err(|e| ImportSyntaxError {
path: source.clone(),
error: Box::new(e),
})?;
- let (ctx, externals) = self
- .create_default_context_with(source.clone(), context_initializer)
+ let (externals, thunks) = self
+ .create_default_context_with(source, context_initializer)
.build();
- let report = analyze::analyze_root(&parsed, externals);
+ let report = analyze_root(&parsed, externals);
if report.errored {
return Err(StaticAnalysisError(report.diagnostics_list).into());
}
- evaluate::evaluate(ctx.build(), &report.lir)
+ debug_assert_eq!(report.root_shape.n_locals as usize, thunks.len());
+ debug_assert!(report.root_shape.captures.is_empty());
+ Ok(PreparedSnippet {
+ lir: report.lir,
+ thunks,
+ })
+ }
+ /// Parses and analyses the given snippet
+ pub fn prepare_snippet(
+ &self,
+ name: impl Into<IStr>,
+ code: impl Into<IStr>,
+ ) -> Result<PreparedSnippet> {
+ self.prepare_snippet_with(name, code, &())
+ }
+ pub fn evaluate_prepared_snippet(&self, prepared: &PreparedSnippet) -> Result<Val> {
+ let ctx = Context::root(prepared.thunks.clone());
+ evaluate::evaluate(ctx, &prepared.lir)
+ }
+ /// Parses and evaluates the given snippet with custom context modifier
+ pub fn evaluate_snippet_with(
+ &self,
+ name: impl Into<IStr>,
+ code: impl Into<IStr>,
+ context_initializer: &dyn ContextInitializer,
+ ) -> Result<Val> {
+ let prepared = self.prepare_snippet_with(name, code, context_initializer)?;
+ self.evaluate_prepared_snippet(&prepared)
+ }
+ /// Parses and evaluates the given snippet
+ pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
+ self.evaluate_snippet_with(name, code, &())
}
}
tests/benches/cpp_test_suite.rsdiffbeforeafterboth--- a/tests/benches/cpp_test_suite.rs
+++ b/tests/benches/cpp_test_suite.rs
@@ -1,10 +1,8 @@
-use std::{collections::HashMap, fs::read_dir, hint::black_box, path::Path};
+use std::{collections::HashMap, fs, fs::read_dir, hint::black_box, path::Path};
use criterion::{Criterion, criterion_group, criterion_main};
use jrsonnet_evaluator::{
- FileImportResolver, State, apply_tla,
- manifest::{BlackBoxFormat, JsonFormat},
- stack::limit_stack_depth,
+ FileImportResolver, State, apply_tla, manifest::JsonFormat, stack::limit_stack_depth,
trace::PathResolver,
};
@@ -12,31 +10,37 @@
static GLOBAL: mimallocator::Mimalloc = mimallocator::Mimalloc;
fn bench_entry(c: &mut Criterion, path: &Path) {
- c.bench_function(
- path.file_name()
- .expect("file path")
- .to_str()
- .expect("name is utf-8"),
- |b| {
- let _stack = limit_stack_depth(200_000);
+ let name = path
+ .file_name()
+ .expect("file path")
+ .to_str()
+ .expect("name is utf-8")
+ .to_owned();
+ let code = fs::read_to_string(path).expect("read bench source");
- let mut s = State::builder();
+ c.bench_function(&name, |b| {
+ let _stack = limit_stack_depth(200_000);
- s.context_initializer(jrsonnet_stdlib::ContextInitializer::new(
- PathResolver::Absolute,
- ))
- .import_resolver(FileImportResolver::new(vec![]));
+ let mut s = State::builder();
+ s.context_initializer(jrsonnet_stdlib::ContextInitializer::new(
+ PathResolver::Absolute,
+ ))
+ .import_resolver(FileImportResolver::new(vec![]));
+ let s = s.build();
+ let _entered = s.enter();
- let s = s.build();
- let _s = s.enter();
+ // Parse + analysis happen once; each iter only measures
+ // evaluation + manifestation.
+ let prepared = s
+ .prepare_snippet(name.clone(), code.clone())
+ .expect("prepared");
- b.iter(|| {
- let imported = s.import(path).expect("evaluated");
- let res = apply_tla(&HashMap::new(), imported).expect("tla applied");
- black_box(res.manifest(JsonFormat::cli(3)).expect("manifested"));
- });
- },
- );
+ b.iter(|| {
+ let imported = s.evaluate_prepared_snippet(&prepared).expect("evaluated");
+ let res = apply_tla(&HashMap::new(), imported).expect("tla applied");
+ black_box(res.manifest(JsonFormat::cli(3)).expect("manifested"));
+ });
+ });
}
fn criterion_benchmark(c: &mut Criterion) {
for entry in read_dir("go_builtin_benchmarks").expect("dir exists") {