From 06e73fc9be3b8a871efee92d47acb7f8a13e908e Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Sat, 2 May 2026 14:57:52 +0800 Subject: [PATCH] avoid thunking trivial values --- fix-codegen/src/disassembler.rs | 3 + fix-codegen/src/lib.rs | 21 +++++- fix-ir/src/downgrade.rs | 113 +++++++++++++++++--------------- fix-ir/src/lib.rs | 70 +++++++++++++++----- fix-vm/src/bytecode_reader.rs | 4 ++ fix-vm/src/lib.rs | 8 +++ fix/src/lib.rs | 102 ++++++++++++++++------------ 7 files changed, 208 insertions(+), 113 deletions(-) diff --git a/fix-codegen/src/disassembler.rs b/fix-codegen/src/disassembler.rs index 08e3d65..bde0b35 100644 --- a/fix-codegen/src/disassembler.rs +++ b/fix-codegen/src/disassembler.rs @@ -93,6 +93,9 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> { self.read_u8(); self.read_u32(); } + BuiltinConst => { + self.read_u32(); + } Builtins => {} ReplBinding | ScopedImportBinding | WithLookup => { self.read_u32(); diff --git a/fix-codegen/src/lib.rs b/fix-codegen/src/lib.rs index 6fdae83..4cf3903 100644 --- a/fix-codegen/src/lib.rs +++ b/fix-codegen/src/lib.rs @@ -116,6 +116,7 @@ pub enum OperandType { Const, BigInt, Local, + BuiltinConst, Builtins, ReplBinding, ScopedImportBinding, @@ -147,6 +148,7 @@ pub enum InlineOperand { Const(Const), BigInt(i64), Local { layer: u8, local: u32 }, + BuiltinConst(StringId), Builtins, ReplBinding(StringId), ScopedImportBinding(StringId), @@ -191,8 +193,13 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { Arg { layer } => InlineOperand::Local { layer, local: 0 }, Builtin(id) => { let (_, arity) = BUILTINS[id as usize]; - InlineOperand::Const(Const::PrimOp { id, arity, dispatch_ip: id.entry_phase().ip() }) - }, + InlineOperand::Const(Const::PrimOp { + id, + arity, + dispatch_ip: id.entry_phase().ip(), + }) + } + BuiltinConst(id) => InlineOperand::BuiltinConst(id), Builtins => InlineOperand::Builtins, ReplBinding(id) => InlineOperand::ReplBinding(id), ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id), @@ -218,6 +225,10 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.emit_u8(layer); self.emit_u32(local); } + BuiltinConst(id) => { + self.emit_u8(OperandType::BuiltinConst as u8); + self.emit_str_id(id); + } Builtins => { self.emit_u8(OperandType::Builtins as u8); } @@ -698,6 +709,12 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> { self.emit_op(Op::LoadBuiltin); self.emit_u8(id as u8); } + BuiltinConst(id) => self.emit_select( + &Ir::Builtins, + &[Attr::Str(id, TextRange::default())], + None, + TextRange::default(), + ), Builtins => self.emit_op(Op::LoadBuiltins), ReplBinding(name) => { self.emit_op(Op::LoadReplBinding); diff --git a/fix-ir/src/downgrade.rs b/fix-ir/src/downgrade.rs index f7665e2..eb51d42 100644 --- a/fix-ir/src/downgrade.rs +++ b/fix-ir/src/downgrade.rs @@ -39,12 +39,12 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T, E: std::fmt::Display> } pub trait DowngradeContext<'id: 'ir, 'ir> { - fn new_expr(&self, expr: Ir<'ir, GhostRef<'id, 'ir>>) -> IrRef<'id, 'ir>; - fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> GhostMaybeThunkRef<'id, 'ir>; + fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir>; + fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir>; fn intern_string(&mut self, sym: impl AsRef) -> StringId; fn resolve_sym(&self, id: StringId) -> Symbol<'_>; - fn lookup(&self, sym: StringId, span: TextRange) -> Result>; + fn lookup(&self, sym: StringId, span: TextRange) -> Result>; fn get_current_source(&self) -> Source; @@ -53,11 +53,11 @@ pub trait DowngradeContext<'id: 'ir, 'ir> { F: FnOnce(&mut Self) -> R; fn with_let_scope(&mut self, bindings: &[StringId], f: F) -> Result where - F: FnOnce(&mut Self) -> Result<(Vec<'ir, IrRef<'id, 'ir>>, R)>; + F: FnOnce(&mut Self) -> Result<(Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>, R)>; fn with_with_scope(&mut self, f: F) -> R where F: FnOnce(&mut Self) -> R; - fn with_thunk_scope(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>) + fn with_thunk_scope(&mut self, f: F) -> (R, Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>) where F: FnOnce(&mut Self) -> R; @@ -65,11 +65,11 @@ pub trait DowngradeContext<'id: 'ir, 'ir> { } pub trait Downgrade<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> { - fn downgrade(self, ctx: &mut Ctx) -> Result>; + fn downgrade(self, ctx: &mut Ctx) -> Result>; } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for Expr { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { use Expr::*; match self { Apply(apply) => apply.downgrade(ctx), @@ -114,7 +114,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Assert { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let assertion = self.condition().require(ctx, span)?; let assertion_raw = assertion.to_string(); @@ -130,7 +130,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::IfElse { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let cond = self.condition().require(ctx, span)?.downgrade(ctx)?; let consq = self.body().require(ctx, span)?.downgrade(ctx)?; @@ -142,7 +142,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo macro_rules! path { ($ty:ident) => { impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::$ty { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { downgrade_path(self.parts(), ctx) } } @@ -152,7 +152,7 @@ path!(PathAbs); path!(PathRel); path!(PathHome); impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::PathSearch { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let path = { let temp = self.content().require(ctx, span)?; @@ -180,7 +180,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Str { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let normalized = self.normalized_parts(); let is_single_literal = normalized.len() == 1 && matches!(normalized.first(), Some(ast::InterpolPart::Literal(_))); @@ -210,7 +210,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Literal { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let expr = match self.kind() { ast::LiteralKind::Integer(int) => Ir::Int(int.value().require(ctx, span)?), @@ -225,7 +225,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Ident { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let text = self.ident_token().require(ctx, span)?.to_string(); let sym = ctx.intern_string(text); @@ -235,7 +235,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::AttrSet { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let rec = self.rec_token().is_some(); if !rec { let attrs = downgrade_attrs(self, ctx)?; @@ -251,7 +251,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::List { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let bump = ctx.bump(); let items = self .items() @@ -265,7 +265,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::BinOp { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let lhs = self.lhs().require(ctx, span)?.downgrade(ctx)?; let rhs = self.rhs().require(ctx, span)?.downgrade(ctx)?; @@ -275,7 +275,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::HasAttr { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let lhs = self.expr().require(ctx, span)?.downgrade(ctx)?; let rhs = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?; @@ -284,7 +284,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::UnaryOp { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let rhs = self.expr().require(ctx, span)?.downgrade(ctx)?; let kind = self.operator().require(ctx, span)?.into(); @@ -293,7 +293,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Select { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let expr = self.expr().require(ctx, span)?.downgrade(ctx)?; let attrpath = downgrade_attrpath(self.attrpath().require(ctx, span)?, ctx)?; @@ -312,7 +312,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LegacyLet { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump()); let attrset_expr = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| { @@ -341,7 +341,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::LetIn { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let entries: Vec<'ir, _> = self.entries().collect_in(ctx.bump()); let span = self.syntax().text_range(); let body_expr = self.body().require(ctx, span)?; @@ -351,7 +351,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::With { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let namespace = self.namespace().require(ctx, span)?.downgrade(ctx)?; let namespace = ctx.maybe_thunk(namespace); @@ -370,14 +370,14 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Lambda { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let raw_param = self.param().require(ctx, span)?; let body_ast = self.body().require(ctx, span)?; struct Ret<'id, 'ir> { param: Option>, - body: IrRef<'id, 'ir>, + body: GhostRoIrRef<'id, 'ir>, } let (ret, thunks) = ctx.with_thunk_scope(|ctx| { @@ -434,7 +434,7 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo } impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> for ast::Apply { - fn downgrade(self, ctx: &mut Ctx) -> Result> { + fn downgrade(self, ctx: &mut Ctx) -> Result> { let span = self.syntax().text_range(); let func = self.lambda().require(ctx, span)?.downgrade(ctx)?; let arg = self.argument().require(ctx, span)?.downgrade(ctx)?; @@ -836,8 +836,15 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) - } struct FinalizedAttrSet<'id, 'ir> { - stcs: HashMap<'ir, StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>, - dyns: Vec<'ir, (IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)>, + stcs: HashMap<'ir, StringId, (GhostRoMaybeThunkRef<'id, 'ir>, TextRange)>, + dyns: Vec< + 'ir, + ( + GhostRoIrRef<'id, 'ir>, + GhostRoMaybeThunkRef<'id, 'ir>, + TextRange, + ), + >, } fn downgrade_attrs<'id, 'ir>( @@ -852,7 +859,7 @@ fn downgrade_attrs<'id, 'ir>( fn downgrade_attr<'id, 'ir>( attr: ast::Attr, ctx: &mut impl DowngradeContext<'id, 'ir>, -) -> Result>> { +) -> Result>> { use ast::Attr::*; use ast::InterpolPart::*; match attr { @@ -913,7 +920,7 @@ fn downgrade_attr<'id, 'ir>( fn downgrade_attrpath<'id, 'ir>( attrpath: ast::Attrpath, ctx: &mut impl DowngradeContext<'id, 'ir>, -) -> Result>>> { +) -> Result>>> { let bump = ctx.bump(); attrpath .attrs() @@ -922,7 +929,7 @@ fn downgrade_attrpath<'id, 'ir>( } struct PatternBindings<'id, 'ir> { - body: IrRef<'id, 'ir>, + body: GhostRoIrRef<'id, 'ir>, required: Vec<'ir, (StringId, TextRange)>, optional: Vec<'ir, (StringId, TextRange)>, } @@ -931,7 +938,7 @@ fn downgrade_pattern_bindings<'id, 'ir, Ctx>( pat_entries: impl Iterator, alias: Option, ctx: &mut Ctx, - body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result>, + body_fn: impl FnOnce(&mut Ctx, &[StringId]) -> Result>, ) -> Result> where Ctx: DowngradeContext<'id, 'ir>, @@ -990,6 +997,7 @@ where } let arg = ctx.new_expr(Ir::Arg { layer: 0 }); + let arg_thunk = ctx.maybe_thunk(arg); ctx.with_let_scope(&keys, |ctx| { let vals = params .into_iter() @@ -1001,21 +1009,16 @@ where span, } = param; let default = default.map(|default| default.downgrade(ctx)).transpose()?; - // let default = if let Some(default) = default { - // let default = default.clone().downgrade(ctx)?; - // Some(ctx.maybe_thunk(default)) - // } else { - // None - // }; - Ok(ctx.new_expr(Ir::Select { + let expr = ctx.new_expr(Ir::Select { expr: arg, attrpath: Vec::from_iter_in([Attr::Str(sym, sym_span)], bump), default, span, - })) + }); + Ok(ctx.maybe_thunk(expr)) }) - .chain(alias.into_iter().map(|_| Ok(arg))) + .chain(alias.into_iter().map(|_| Ok(arg_thunk))) .collect_in::>(bump)?; let body = body_fn(ctx, &keys)?; @@ -1035,10 +1038,10 @@ fn downgrade_let_bindings<'id, 'ir, Ctx, F>( entries: Vec<'ir, ast::Entry>, ctx: &mut Ctx, body_fn: F, -) -> Result> +) -> Result> where Ctx: DowngradeContext<'id, 'ir>, - F: FnOnce(&mut Ctx, &[StringId]) -> Result>, + F: FnOnce(&mut Ctx, &[StringId]) -> Result>, { downgrade_rec_attrs_impl::<_, _, false>(entries, ctx, |ctx, binding_keys, _dyns| { body_fn(ctx, binding_keys) @@ -1048,7 +1051,7 @@ where fn downgrade_rec_bindings<'id, 'ir, Ctx>( entries: Vec<'ir, ast::Entry>, ctx: &mut Ctx, -) -> Result> +) -> Result> where Ctx: DowngradeContext<'id, 'ir>, { @@ -1069,14 +1072,18 @@ fn downgrade_rec_attrs_impl<'id, 'ir, Ctx, F, const ALLOW_DYN: bool>( entries: Vec<'ir, ast::Entry>, ctx: &mut Ctx, body_fn: F, -) -> Result> +) -> Result> where Ctx: DowngradeContext<'id, 'ir>, F: FnOnce( &mut Ctx, &[StringId], - &[(IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)], - ) -> Result>, + &[( + GhostRoIrRef<'id, 'ir>, + GhostRoMaybeThunkRef<'id, 'ir>, + TextRange, + )], + ) -> Result>, { let mut pending = PendingAttrSet::new_in(ctx.bump()); pending.collect_entries(entries.iter().cloned(), ctx)?; @@ -1091,7 +1098,7 @@ where let vals = { let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump()); for sym in &keys { - temp.push(ctx.new_expr(Ir::MaybeThunk(finalized.stcs.get(sym).expect("WTF").0))); + temp.push(finalized.stcs.get(sym).expect("WTF").0); } temp }; @@ -1102,7 +1109,7 @@ where fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>( entries: &[ast::Entry], ctx: &mut Ctx, -) -> Result, TextRange)>> { +) -> Result, TextRange)>> { let mut inherit_lookups = HashMap::new_in(ctx.bump()); for entry in entries { if let ast::Entry::Inherit(inherit) = entry @@ -1142,7 +1149,7 @@ fn collect_binding_syms<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, const AL fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( pending: PendingAttrSet, - inherit_lookups: &HashMap, TextRange)>, + inherit_lookups: &HashMap, TextRange)>, ctx: &mut Ctx, ) -> Result> { let mut stcs = HashMap::new_in(ctx.bump()); @@ -1170,9 +1177,9 @@ fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_D fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>( value: PendingValue, - inherit_lookups: &HashMap, TextRange)>, + inherit_lookups: &HashMap, TextRange)>, ctx: &mut Ctx, -) -> Result> { +) -> Result> { match value { PendingValue::Expr(expr) => expr.downgrade(ctx), PendingValue::InheritFrom(from_expr, sym, span) => { @@ -1212,7 +1219,7 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW fn downgrade_path<'id, 'ir>( parts: impl IntoIterator>, ctx: &mut impl DowngradeContext<'id, 'ir>, -) -> Result> { +) -> Result> { let bump = ctx.bump(); let parts = parts .into_iter() diff --git a/fix-ir/src/lib.rs b/fix-ir/src/lib.rs index dcc0d89..cd55c99 100644 --- a/fix-ir/src/lib.rs +++ b/fix-ir/src/lib.rs @@ -14,14 +14,16 @@ pub mod downgrade; pub type HashMap<'ir, K, V> = hashbrown::HashMap; -pub type IrRef<'id, 'ir> = as RefExt<'ir>>::IrRef; +pub type GhostIrRef<'id, 'ir> = as RefExt<'ir>>::IrRef; +pub type GhostRoIrRef<'id, 'ir> = as RefExt<'ir>>::IrRef; pub type RawIrRef<'ir> = as RefExt<'ir>>::IrRef; pub type GhostMaybeThunkRef<'id, 'ir> = as RefExt<'ir>>::MaybeThunkRef; +pub type GhostRoMaybeThunkRef<'id, 'ir> = as RefExt<'ir>>::MaybeThunkRef; -impl<'id, 'ir> Ir<'ir, GhostRef<'id, 'ir>> { +impl<'id, 'ir> Ir<'ir, GhostRoRef<'id, 'ir>> { /// Freeze a mutable IR reference into a read-only one, consuming the /// `GhostToken` to prevent any further mutation. - pub fn freeze(this: IrRef<'id, 'ir>, _: GhostToken<'id>) -> RawIrRef<'ir> { + pub fn freeze(this: GhostRoIrRef<'id, 'ir>, _: GhostToken<'id>) -> RawIrRef<'ir> { // SAFETY: The transmute is sound because: // - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`, so // `&'ir GhostCell<'id, T>` and `&'ir T` have identical layout. @@ -39,7 +41,39 @@ impl<'id, 'ir> Ir<'ir, GhostRef<'id, 'ir>> { // Consuming the `GhostToken` guarantees no `borrow_mut` calls can // occur afterwards, so the shared `&Ir` references reachable from a // `RawIrRef<'ir>` can never alias with mutable references. - unsafe { std::mem::transmute::, RawIrRef<'ir>>(this) } + unsafe { std::mem::transmute::, RawIrRef<'ir>>(this) } + } +} + +#[repr(transparent)] +pub struct GhostRoCell<'id, T: ?Sized>(GhostCell<'id, T>); + +impl<'id, T> From> for GhostRoCell<'id, T> { + fn from(value: GhostCell<'id, T>) -> Self { + Self(value) + } +} + +impl<'id, T: ?Sized> From<&GhostCell<'id, T>> for &GhostRoCell<'id, T> { + fn from(value: &GhostCell<'id, T>) -> Self { + // SAFETY: `GhostRoCell` is `#[repr(transparent)]` over `GhostCell` + // TODO: document mutability + unsafe { std::mem::transmute(value) } + } +} + +impl<'id, T: ?Sized> From<&T> for &GhostRoCell<'id, T> { + fn from(value: &T) -> Self { + // SAFETY: `GhostRoCell` is `#[repr(transparent)]` over `GhostCell`, + // which is `#[repr(transparent)]` over `T` + // TODO: document mutability + unsafe { std::mem::transmute(value) } + } +} + +impl<'id, T: ?Sized> GhostRoCell<'id, T> { + pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a T { + self.0.borrow(token) } } @@ -55,13 +89,13 @@ pub enum MaybeThunk { Thunk(ThunkId), Arg { layer: u8 }, Builtin(BuiltinId), + BuiltinConst(StringId), Builtins, ReplBinding(StringId), ScopedImportBinding(StringId), WithLookup(StringId), } - pub trait Ref<'ir> { type Ref where @@ -80,11 +114,15 @@ impl<'ir, T: Ref<'ir> + 'ir> RefExt<'ir> for T { } pub struct GhostRef<'id, 'ir>(PhantomData<&'ir GhostCell<'id, ()>>); +pub struct GhostRoRef<'id, 'ir>(PhantomData<&'ir GhostRoCell<'id, ()>>); pub struct RawRef<'ir>(PhantomData<&'ir ()>); impl<'id, 'ir> Ref<'ir> for GhostRef<'id, 'ir> { type Ref = &'ir GhostCell<'id, T>; } +impl<'id, 'ir> Ref<'ir> for GhostRoRef<'id, 'ir> { + type Ref = &'ir GhostRoCell<'id, T>; +} impl<'ir> Ref<'ir> for RawRef<'ir> { type Ref = &'ir T; } @@ -285,38 +323,38 @@ pub struct Param<'ir> { pub fn new_global_env( strings: &mut DefaultStringInterner, -) -> hashbrown::HashMap>> { +) -> hashbrown::HashMap { let mut global_env = hashbrown::HashMap::new(); let builtins_sym = StringId(strings.get_or_intern("builtins")); - global_env.insert(builtins_sym, Ir::Builtins); + global_env.insert(builtins_sym, MaybeThunk::Builtins); for (idx, &(name, _)) in BUILTINS.iter().enumerate() { let id = BuiltinId::try_from_primitive(idx as u8).expect("infallible"); let name = StringId(strings.get_or_intern(name)); - global_env.insert(name, Ir::Builtin(id)); + global_env.insert(name, MaybeThunk::Builtin(id)); } let consts = [ ( "__currentSystem", - Ir::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))), + MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("currentSystem"))), ), - ("__langVersion", Ir::Int(6)), + ("__langVersion", MaybeThunk::Int(6)), ( "__nixVersion", - Ir::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))), + MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("nixVersion"))), ), ( "__storeDir", - Ir::BuiltinConst(StringId(strings.get_or_intern("storeDir"))), + MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("storeDir"))), ), ( "__nixPath", - Ir::BuiltinConst(StringId(strings.get_or_intern("nixPath"))), + MaybeThunk::BuiltinConst(StringId(strings.get_or_intern("nixPath"))), ), - ("null", Ir::Null), - ("true", Ir::Bool(true)), - ("false", Ir::Bool(false)), + ("null", MaybeThunk::Null), + ("true", MaybeThunk::Bool(true)), + ("false", MaybeThunk::Bool(false)), ]; for (name, ir) in consts { diff --git a/fix-vm/src/bytecode_reader.rs b/fix-vm/src/bytecode_reader.rs index 31cd856..cb0ee86 100644 --- a/fix-vm/src/bytecode_reader.rs +++ b/fix-vm/src/bytecode_reader.rs @@ -112,6 +112,10 @@ impl<'a> BytecodeReader<'a> { let idx = self.read_u32(); OperandData::Local { layer, idx } } + OperandType::BuiltinConst => { + let id = self.read_string_id(); + OperandData::BuiltinConst(id) + } OperandType::Builtins => OperandData::Builtins, OperandType::ReplBinding => { let id = self.read_string_id(); diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs index f477bb2..cfb65cf 100644 --- a/fix-vm/src/lib.rs +++ b/fix-vm/src/lib.rs @@ -229,6 +229,7 @@ pub(crate) enum OperandData { Const(StaticValue), BigInt(i64), Local { layer: u8, idx: u32 }, + BuiltinConst(StringId), Builtins, ReplBinding(StringId), ScopedImportBinding(StringId), @@ -249,6 +250,13 @@ impl OperandData { } cur.borrow().locals[idx as usize] } + #[allow(clippy::unwrap_used)] + BuiltinConst(id) => root + .builtins + .as_gc::() + .unwrap() + .lookup(id) + .unwrap(), Builtins => root.builtins, ReplBinding(_id) => todo!(), ScopedImportBinding(_id) => todo!(), diff --git a/fix/src/lib.rs b/fix/src/lib.rs index f9b5176..0c99292 100644 --- a/fix/src/lib.rs +++ b/fix/src/lib.rs @@ -8,7 +8,10 @@ use fix_codegen::{BytecodeContext, InstructionPtr, Op}; use fix_common::{StringId, Symbol}; use fix_error::{Error, Result, Source}; use fix_ir::downgrade::{Downgrade as _, DowngradeContext}; -use fix_ir::{GhostMaybeThunkRef, GhostRef, Ir, IrRef, MaybeThunk, RawIrRef, RawRef, ThunkId}; +use fix_ir::{ + GhostMaybeThunkRef, GhostRoIrRef, GhostRoMaybeThunkRef, GhostRoRef, Ir, MaybeThunk, RawIrRef, + ThunkId, +}; use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx}; use ghost_cell::{GhostCell, GhostToken}; use hashbrown::{HashMap, HashSet}; @@ -30,7 +33,7 @@ pub struct CodeState { pub sources: Vec, pub spans: Vec<(usize, rnix::TextRange)>, pub thunk_count: usize, - pub global_env: HashMap>>, + pub global_env: HashMap, } pub struct Evaluator { @@ -323,7 +326,7 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { bump: &'ir Bump, token: GhostToken<'id>, runtime: &'ctx mut R, - global: &'ctx HashMap>>, + global: &'ctx HashMap, extra_scope: Option>, thunk_count: &'ctx mut usize, source: Source, @@ -347,11 +350,11 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir, R> { - fn new_expr(&self, expr: Ir<'ir, GhostRef<'id, 'ir>>) -> IrRef<'id, 'ir> { - self.bump.alloc(GhostCell::new(expr)) + fn new_expr(&self, expr: Ir<'ir, GhostRoRef<'id, 'ir>>) -> GhostRoIrRef<'id, 'ir> { + self.bump.alloc(GhostCell::new(expr).into()) } - fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> GhostMaybeThunkRef<'id, 'ir> { + fn maybe_thunk(&mut self, ir: GhostRoIrRef<'id, 'ir>) -> GhostRoMaybeThunkRef<'id, 'ir> { use MaybeThunk::*; let expr = (|| { let expr = match *ir.borrow(&self.token) { @@ -366,7 +369,7 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> Ir::MaybeThunk(thunk) => return Some(thunk), _ => return None, }; - Some(self.bump.alloc(GhostCell::new(expr))) + Some(self.bump.alloc(GhostCell::new(expr).into())) })(); if let Some(thunk) = expr { return thunk; @@ -376,8 +379,8 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> self.thunk_scopes .last_mut() .expect("no active cache scope") - .add_binding(id, ir, &self.token); - self.bump.alloc(GhostCell::new(Thunk(id))) + .add_binding(id, ir); + self.bump.alloc(GhostCell::new(Thunk(id)).into()) } fn intern_string(&mut self, sym: impl AsRef) -> StringId { @@ -388,39 +391,35 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> self.runtime.resolve_string(id).into() } - fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result> { + fn lookup( + &self, + sym: StringId, + span: rnix::TextRange, + ) -> Result> { for scope in self.scopes.iter().rev() { match scope { &Scope::Global(global_scope) => { - use MaybeThunk::*; if let Some(expr) = global_scope.get(&sym) { - let val = match expr { - Ir::Builtins => Builtins, - Ir::Builtin(s) => Builtin(*s), - Ir::Bool(b) => Bool(*b), - Ir::Null => Null, - _ => unreachable!("globals should only contain leaf IR nodes"), - }; - return Ok(self.bump.alloc(GhostCell::new(val))); + return Ok(expr.into()); } } &Scope::Repl(repl_bindings) => { if repl_bindings.contains(&sym) { return Ok(self .bump - .alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)))); + .alloc(GhostCell::new(MaybeThunk::ReplBinding(sym)).into())); } } Scope::ScopedImport(scoped_bindings) => { if scoped_bindings.contains(&sym) { return Ok(self .bump - .alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)))); + .alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym)).into())); } } Scope::Let(let_scope) => { if let Some(&expr) = let_scope.get(&sym) { - return Ok(expr); + return Ok(expr.into()); } } &Scope::Param { @@ -431,14 +430,18 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> let layers: u8 = self.thunk_scopes.len().try_into().expect("scope too deep!"); let layer = layers - abs_layer; - return Ok(self.bump.alloc(GhostCell::new(MaybeThunk::Arg { layer }))); + return Ok(self + .bump + .alloc(GhostCell::new(MaybeThunk::Arg { layer }).into())); } } } } if self.with_scope_count > 0 { - Ok(self.bump.alloc(GhostCell::new(MaybeThunk::WithLookup(sym)))) + Ok(self + .bump + .alloc(GhostCell::new(MaybeThunk::WithLookup(sym)).into())) } else { Err(Error::downgrade_error( format!("'{}' not found", self.resolve_sym(sym)), @@ -454,28 +457,40 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> fn with_let_scope(&mut self, keys: &[StringId], f: F) -> Result where - F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, Ret)>, + F: FnOnce( + &mut Self, + ) -> Result<( + bumpalo::collections::Vec<'ir, GhostRoMaybeThunkRef<'id, 'ir>>, + Ret, + )>, { let base = *self.thunk_count; *self.thunk_count = self .thunk_count .checked_add(keys.len()) .expect("thunk id overflow"); - let scope = { - let mut scope = HashMap::new(); - for (offset, &key) in keys.iter().enumerate() { - scope.insert(key, &*self.bump.alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(base + offset))))); - } - scope - }; + let handles = (base..base + keys.len()) + .map(|id| { + &*self + .bump + .alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(id)))) + }) + .collect::>(); + let scope = keys.iter().copied().zip(handles.iter().copied()).collect(); self.scopes.push(Scope::Let(scope)); - let (vals, ret) = { - let mut guard = ScopeGuard { ctx: self }; - f(guard.as_ctx())? - }; + let (vals, ret) = { f(self)? }; + self.scopes.pop(); assert_eq!(keys.len(), vals.len()); let scope = self.thunk_scopes.last_mut().expect("no active thunk scope"); - scope.extend_bindings((base..base + keys.len()).map(ThunkId).zip(vals)); + for (i, (val, handle)) in vals.into_iter().zip(handles).enumerate() { + let thunk = *val.borrow(&self.token); + *handle.borrow_mut(&mut self.token) = thunk; + let id = ThunkId(base + i); + let ir_ref = self + .bump + .alloc(GhostCell::new(Ir::MaybeThunk(handle.into())).into()); + scope.add_binding(id, ir_ref); + } Ok(ret) } @@ -506,7 +521,7 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> f: F, ) -> ( Ret, - bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>, + bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>, ) where F: FnOnce(&mut Self) -> Ret, @@ -546,7 +561,7 @@ impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { } struct ThunkScope<'id, 'ir> { - bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>, + bindings: bumpalo::collections::Vec<'ir, (ThunkId, GhostRoIrRef<'id, 'ir>)>, } impl<'id, 'ir> ThunkScope<'id, 'ir> { @@ -556,17 +571,20 @@ impl<'id, 'ir> ThunkScope<'id, 'ir> { } } - fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, _token: &GhostToken<'id>) { + fn add_binding(&mut self, id: ThunkId, ir: GhostRoIrRef<'id, 'ir>) { self.bindings.push((id, ir)); } - fn extend_bindings(&mut self, iter: impl IntoIterator)>) { + fn extend_bindings( + &mut self, + iter: impl IntoIterator)>, + ) { self.bindings.extend(iter); } } enum Scope<'ctx, 'id, 'ir> { - Global(&'ctx HashMap>>), + Global(&'ctx HashMap), Repl(&'ctx HashSet), ScopedImport(HashSet), Let(HashMap>),