git.delta.rocks / jrsonnet / refs/commits / 87a7e44fcfaf

difftreelog

perf destruct capacity hints

Yaroslav Bolyukin2022-11-09parent: #06bf1b3.patch.diff
in: master

2 files changed

modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function/parse.rs
1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9	destructure::destruct,10	error::{Error::*, Result},11	evaluate_named,12	gc::GcHashMap,13	tb, throw,14	val::ThunkValue,15	Context, Pending, Thunk, Val,16};1718#[derive(Trace)]19struct EvaluateNamedThunk {20	ctx: Pending<Context>,21	name: IStr,22	value: LocExpr,23}2425impl ThunkValue for EvaluateNamedThunk {26	type Output = Val;27	fn get(self: Box<Self>) -> Result<Val> {28		evaluate_named(self.ctx.unwrap(), &self.value, self.name)29	}30}3132/// Creates correct [context](Context) for function body evaluation returning error on invalid call.33///34/// ## Parameters35/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)36/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)37/// * `params`: function parameters' definition38/// * `args`: passed function arguments39/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily40pub fn parse_function_call(41	ctx: Context,42	body_ctx: Context,43	params: &ParamsDesc,44	args: &dyn ArgsLike,45	tailstrict: bool,46) -> Result<Context> {47	let mut passed_args = GcHashMap::with_capacity(params.len());48	if args.unnamed_len() > params.len() {49		throw!(TooManyArgsFunctionHas(50			params.len(),51			params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()52		))53	}5455	let mut filled_named = 0;56	let mut filled_positionals = 0;5758	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {59		let name = params[id].0.clone();60		destruct(61			&name,62			arg,63			Pending::new_filled(ctx.clone()),64			&mut passed_args,65		)?;66		filled_positionals += 1;67		Ok(())68	})?;6970	args.named_iter(ctx, tailstrict, &mut |name, value| {71		// FIXME: O(n) for arg existence check72		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {73			throw!(UnknownFunctionParameter((name as &str).to_owned()));74		}75		if passed_args.insert(name.clone(), value).is_some() {76			throw!(BindingParameterASecondTime(name.clone()));77		}78		filled_named += 1;79		Ok(())80	})?;8182	if filled_named + filled_positionals < params.len() {83		// Some args are unset, but maybe we have defaults for them84		// Default values should be created in newly created context85		let fctx = Context::new_future();86		let mut defaults =87			GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8889		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {90			if let Some(name) = param.0.name() {91				if passed_args.contains_key(&name) {92					continue;93				}94			} else if idx < filled_positionals {95				continue;96			}9798			destruct(99				&param.0,100				Thunk::new(tb!(EvaluateNamedThunk {101					ctx: fctx.clone(),102					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),103					value: param.1.clone().expect("default exists"),104				})),105				fctx.clone(),106				&mut defaults,107			)?;108			if param.0.name().is_some() {109				filled_named += 1;110			} else {111				filled_positionals += 1;112			}113		}114115		// Some args still weren't filled116		if filled_named + filled_positionals != params.len() {117			for param in params.iter().skip(args.unnamed_len()) {118				let mut found = false;119				args.named_names(&mut |name| {120					if Some(name) == param.0.name().as_ref() {121						found = true;122					}123				});124				if !found {125					throw!(FunctionParameterNotBoundInCall(126						param.0.clone().name(),127						params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()128					));129				}130			}131			unreachable!();132		}133134		Ok(body_ctx135			.extend(passed_args, None, None, None)136			.extend(defaults, None, None, None)137			.into_future(fctx))138	} else {139		let body_ctx = body_ctx.extend(passed_args, None, None, None);140		Ok(body_ctx)141	}142}143144/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead145///146/// ## Parameters147/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)148/// * `params`: function parameters' definition149/// * `args`: passed function arguments150/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily151pub fn parse_builtin_call(152	ctx: Context,153	params: &[BuiltinParam],154	args: &dyn ArgsLike,155	tailstrict: bool,156) -> Result<Vec<Option<Thunk<Val>>>> {157	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];158	if args.unnamed_len() > params.len() {159		throw!(TooManyArgsFunctionHas(160			params.len(),161			params162				.iter()163				.map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))164				.collect()165		))166	}167168	let mut filled_args = 0;169170	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {171		passed_args[id] = Some(arg);172		filled_args += 1;173		Ok(())174	})?;175176	args.named_iter(ctx, tailstrict, &mut |name, arg| {177		// FIXME: O(n) for arg existence check178		let id = params179			.iter()180			.position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))181			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;182		if replace(&mut passed_args[id], Some(arg)).is_some() {183			throw!(BindingParameterASecondTime(name.clone()));184		}185		filled_args += 1;186		Ok(())187	})?;188189	if filled_args < params.len() {190		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {191			if passed_args[id].is_some() {192				continue;193			}194			filled_args += 1;195		}196197		// Some args still wasn't filled198		if filled_args != params.len() {199			for param in params.iter().skip(args.unnamed_len()) {200				let mut found = false;201				args.named_names(&mut |name| {202					if param203						.name204						.as_ref()205						.map_or(false, |v| v as &str == name as &str)206					{207						found = true;208					}209				});210				if !found {211					throw!(FunctionParameterNotBoundInCall(212						param.name.as_ref().map(|v| v.as_ref().into()),213						params214							.iter()215							.map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))216							.collect()217					));218				}219			}220			unreachable!();221		}222	}223	Ok(passed_args)224}225226/// Creates Context, which has all argument default values applied227/// and with unbound values causing error to be returned228pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {229	#[derive(Trace)]230	struct DependsOnUnbound(IStr, ParamsDesc);231	impl ThunkValue for DependsOnUnbound {232		type Output = Val;233		fn get(self: Box<Self>) -> Result<Val> {234			Err(FunctionParameterNotBoundInCall(235				Some(self.0.clone()),236				self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),237			)238			.into())239		}240	}241242	let fctx = Context::new_future();243244	let mut bindings = GcHashMap::new();245246	for param in params.iter() {247		if let Some(v) = &param.1 {248			destruct(249				&param.0.clone(),250				Thunk::new(tb!(EvaluateNamedThunk {251					ctx: fctx.clone(),252					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),253					value: v.clone(),254				})),255				fctx.clone(),256				&mut bindings,257			)?;258		} else {259			destruct(260				&param.0,261				Thunk::new(tb!(DependsOnUnbound(262					param.0.name().unwrap_or_else(|| "<destruct>".into()),263					params.clone()264				))),265				fctx.clone(),266				&mut bindings,267			)?;268		}269	}270271	Ok(body_ctx272		.extend(bindings, None, None, None)273		.into_future(fctx))274}
after · crates/jrsonnet-evaluator/src/function/parse.rs
1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9	destructure::destruct,10	error::{Error::*, Result},11	evaluate_named,12	gc::GcHashMap,13	tb, throw,14	val::ThunkValue,15	Context, Pending, Thunk, Val,16};1718#[derive(Trace)]19struct EvaluateNamedThunk {20	ctx: Pending<Context>,21	name: IStr,22	value: LocExpr,23}2425impl ThunkValue for EvaluateNamedThunk {26	type Output = Val;27	fn get(self: Box<Self>) -> Result<Val> {28		evaluate_named(self.ctx.unwrap(), &self.value, self.name)29	}30}3132/// Creates correct [context](Context) for function body evaluation returning error on invalid call.33///34/// ## Parameters35/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)36/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)37/// * `params`: function parameters' definition38/// * `args`: passed function arguments39/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily40pub fn parse_function_call(41	ctx: Context,42	body_ctx: Context,43	params: &ParamsDesc,44	args: &dyn ArgsLike,45	tailstrict: bool,46) -> Result<Context> {47	let mut passed_args =48		GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());49	if args.unnamed_len() > params.len() {50		throw!(TooManyArgsFunctionHas(51			params.len(),52			params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()53		))54	}5556	let mut filled_named = 0;57	let mut filled_positionals = 0;5859	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {60		let name = params[id].0.clone();61		destruct(62			&name,63			arg,64			Pending::new_filled(ctx.clone()),65			&mut passed_args,66		)?;67		filled_positionals += 1;68		Ok(())69	})?;7071	args.named_iter(ctx, tailstrict, &mut |name, value| {72		// FIXME: O(n) for arg existence check73		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {74			throw!(UnknownFunctionParameter((name as &str).to_owned()));75		}76		if passed_args.insert(name.clone(), value).is_some() {77			throw!(BindingParameterASecondTime(name.clone()));78		}79		filled_named += 1;80		Ok(())81	})?;8283	if filled_named + filled_positionals < params.len() {84		// Some args are unset, but maybe we have defaults for them85		// Default values should be created in newly created context86		let fctx = Context::new_future();87		let mut defaults = GcHashMap::with_capacity(88			params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()89				- filled_named - filled_positionals,90		);9192		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {93			if let Some(name) = param.0.name() {94				if passed_args.contains_key(&name) {95					continue;96				}97			} else if idx < filled_positionals {98				continue;99			}100101			destruct(102				&param.0,103				Thunk::new(tb!(EvaluateNamedThunk {104					ctx: fctx.clone(),105					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),106					value: param.1.clone().expect("default exists"),107				})),108				fctx.clone(),109				&mut defaults,110			)?;111			if param.0.name().is_some() {112				filled_named += 1;113			} else {114				filled_positionals += 1;115			}116		}117118		// Some args still weren't filled119		if filled_named + filled_positionals != params.len() {120			for param in params.iter().skip(args.unnamed_len()) {121				let mut found = false;122				args.named_names(&mut |name| {123					if Some(name) == param.0.name().as_ref() {124						found = true;125					}126				});127				if !found {128					throw!(FunctionParameterNotBoundInCall(129						param.0.clone().name(),130						params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()131					));132				}133			}134			unreachable!();135		}136137		Ok(body_ctx138			.extend(passed_args, None, None, None)139			.extend(defaults, None, None, None)140			.into_future(fctx))141	} else {142		let body_ctx = body_ctx.extend(passed_args, None, None, None);143		Ok(body_ctx)144	}145}146147/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead148///149/// ## Parameters150/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)151/// * `params`: function parameters' definition152/// * `args`: passed function arguments153/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily154pub fn parse_builtin_call(155	ctx: Context,156	params: &[BuiltinParam],157	args: &dyn ArgsLike,158	tailstrict: bool,159) -> Result<Vec<Option<Thunk<Val>>>> {160	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];161	if args.unnamed_len() > params.len() {162		throw!(TooManyArgsFunctionHas(163			params.len(),164			params165				.iter()166				.map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))167				.collect()168		))169	}170171	let mut filled_args = 0;172173	args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {174		passed_args[id] = Some(arg);175		filled_args += 1;176		Ok(())177	})?;178179	args.named_iter(ctx, tailstrict, &mut |name, arg| {180		// FIXME: O(n) for arg existence check181		let id = params182			.iter()183			.position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))184			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;185		if replace(&mut passed_args[id], Some(arg)).is_some() {186			throw!(BindingParameterASecondTime(name.clone()));187		}188		filled_args += 1;189		Ok(())190	})?;191192	if filled_args < params.len() {193		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {194			if passed_args[id].is_some() {195				continue;196			}197			filled_args += 1;198		}199200		// Some args still wasn't filled201		if filled_args != params.len() {202			for param in params.iter().skip(args.unnamed_len()) {203				let mut found = false;204				args.named_names(&mut |name| {205					if param206						.name207						.as_ref()208						.map_or(false, |v| v as &str == name as &str)209					{210						found = true;211					}212				});213				if !found {214					throw!(FunctionParameterNotBoundInCall(215						param.name.as_ref().map(|v| v.as_ref().into()),216						params217							.iter()218							.map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))219							.collect()220					));221				}222			}223			unreachable!();224		}225	}226	Ok(passed_args)227}228229/// Creates Context, which has all argument default values applied230/// and with unbound values causing error to be returned231pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {232	#[derive(Trace)]233	struct DependsOnUnbound(IStr, ParamsDesc);234	impl ThunkValue for DependsOnUnbound {235		type Output = Val;236		fn get(self: Box<Self>) -> Result<Val> {237			Err(FunctionParameterNotBoundInCall(238				Some(self.0.clone()),239				self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),240			)241			.into())242		}243	}244245	let fctx = Context::new_future();246247	let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());248249	for param in params.iter() {250		if let Some(v) = &param.1 {251			destruct(252				&param.0.clone(),253				Thunk::new(tb!(EvaluateNamedThunk {254					ctx: fctx.clone(),255					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),256					value: v.clone(),257				})),258				fctx.clone(),259				&mut bindings,260			)?;261		} else {262			destruct(263				&param.0,264				Thunk::new(tb!(DependsOnUnbound(265					param.0.name().unwrap_or_else(|| "<destruct>".into()),266					params.clone()267				))),268				fctx.clone(),269				&mut bindings,270			)?;271		}272	}273274	Ok(body_ctx275		.extend(bindings, None, None, None)276		.into_future(fctx))277}
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -228,6 +228,39 @@
 			_ => None,
 		}
 	}
+	pub fn capacity_hint(&self) -> usize {
+		#[cfg(feature = "exp-destruct")]
+		fn cap_rest(rest: &Option<DestructRest>) -> usize {
+			match rest {
+				Some(DestructRest::Keep(_)) => 1,
+				Some(DestructRest::Drop) => 0,
+				None => 0,
+			}
+		}
+		match self {
+			Self::Full(_) => 1,
+			#[cfg(feature = "exp-destruct")]
+			Self::Skip => 0,
+			#[cfg(feature = "exp-destruct")]
+			Self::Array { start, rest, end } => {
+				start.iter().map(Destruct::capacity_hint).sum::<usize>()
+					+ end.iter().map(Destruct::capacity_hint).sum::<usize>()
+					+ cap_rest(rest)
+			}
+			#[cfg(feature = "exp-destruct")]
+			Self::Object { fields, rest } => {
+				let mut out = 0;
+				for (_, into, _) in fields {
+					match into {
+						Some(v) => out += v.capacity_hint(),
+						// Field is destructured to default name
+						None => out += 1,
+					}
+				}
+				out + cap_rest(rest)
+			}
+		}
+	}
 }
 
 #[cfg_attr(feature = "structdump", derive(Codegen))]
@@ -244,6 +277,14 @@
 		value: LocExpr,
 	},
 }
+impl BindSpec {
+	pub fn capacity_hint(&self) -> usize {
+		match self {
+			BindSpec::Field { into, .. } => into.capacity_hint(),
+			BindSpec::Function { .. } => 1,
+		}
+	}
+}
 
 #[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]