refactor: use GAT in enum Ir

This commit is contained in:
2026-05-01 20:18:00 +08:00
parent 6659b22dce
commit 47b1344ebe
7 changed files with 331 additions and 214 deletions
+9 -5
View File
@@ -79,19 +79,23 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
#[inline(always)] #[inline(always)]
fn read_operand_data(&mut self) { fn read_operand_data(&mut self) {
use OperandType::*;
let tag = self.read_u8(); let tag = self.read_u8();
let ty = OperandType::try_from_primitive(tag).expect("invalid operand type"); let ty = OperandType::try_from_primitive(tag).expect("invalid operand type");
match ty { match ty {
OperandType::Const => { Const => {
self.read_u32(); self.read_u32();
} }
OperandType::Local => { BigInt => {
self.read_i64();
}
Local => {
self.read_u8(); self.read_u8();
self.read_u32(); self.read_u32();
} }
OperandType::Builtins => {} Builtins => {}
OperandType::BigInt => { ReplBinding | ScopedImportBinding | WithLookup => {
self.read_i64(); self.read_u32();
} }
} }
} }
+109 -36
View File
@@ -1,6 +1,4 @@
use std::ops::Deref; use fix_builtins::{BUILTINS, BuiltinId};
use fix_builtins::BuiltinId;
use fix_common::StringId; use fix_common::StringId;
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind}; use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
use hashbrown::HashMap; use hashbrown::HashMap;
@@ -116,9 +114,12 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
#[derive(Debug, Clone, Copy, TryFromPrimitive)] #[derive(Debug, Clone, Copy, TryFromPrimitive)]
pub enum OperandType { pub enum OperandType {
Const, Const,
BigInt,
Local, Local,
Builtins, Builtins,
BigInt, ReplBinding,
ScopedImportBinding,
WithLookup,
} }
pub enum Const { pub enum Const {
@@ -126,6 +127,7 @@ pub enum Const {
Float(f64), Float(f64),
Bool(bool), Bool(bool),
String(StringId), String(StringId),
Path(StringId),
PrimOp { PrimOp {
id: BuiltinId, id: BuiltinId,
arity: u8, arity: u8,
@@ -143,9 +145,12 @@ pub enum AttrKeyType {
pub enum InlineOperand { pub enum InlineOperand {
Const(Const), Const(Const),
BigInt(i64),
Local { layer: u8, local: u32 }, Local { layer: u8, local: u32 },
Builtins, Builtins,
BigInt(i64), ReplBinding(StringId),
ScopedImportBinding(StringId),
WithLookup(StringId),
} }
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr { pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
@@ -164,9 +169,9 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
#[must_use] #[must_use]
fn inline_maybe_thunk(&self, val: MaybeThunk) -> InlineOperand { fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand {
use MaybeThunk::*; use MaybeThunk::*;
match val { match *val {
Int(x) => { Int(x) => {
if let Ok(x) = x.try_into() { if let Ok(x) = x.try_into() {
InlineOperand::Const(Const::Smi(x)) InlineOperand::Const(Const::Smi(x))
@@ -178,37 +183,55 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
Bool(b) => InlineOperand::Const(Const::Bool(b)), Bool(b) => InlineOperand::Const(Const::Bool(b)),
Null => InlineOperand::Const(Const::Null), Null => InlineOperand::Const(Const::Null),
Str(id) => InlineOperand::Const(Const::String(id)), Str(id) => InlineOperand::Const(Const::String(id)),
Path(id) => InlineOperand::Const(Const::String(id)),
Thunk(id) => { Thunk(id) => {
let (layer, local) = self.resolve_thunk(id); let (layer, local) = self.resolve_thunk(id);
InlineOperand::Local { layer, local } InlineOperand::Local { layer, local }
} }
Arg { layer } => InlineOperand::Local { Arg { layer } => InlineOperand::Local { layer, local: 0 },
layer, Builtin(id) => {
local: 0, let (_, arity) = BUILTINS[id as usize];
InlineOperand::Const(Const::PrimOp { id, arity, dispatch_ip: id.entry_phase().ip() })
}, },
_ => todo!(), Builtins => InlineOperand::Builtins,
ReplBinding(id) => InlineOperand::ReplBinding(id),
ScopedImportBinding(id) => InlineOperand::ScopedImportBinding(id),
WithLookup(id) => InlineOperand::WithLookup(id),
} }
} }
fn emit_maybe_thunk(&mut self, val: MaybeThunk) { fn emit_maybe_thunk(&mut self, val: &MaybeThunk) {
use InlineOperand::*;
let operand = self.inline_maybe_thunk(val); let operand = self.inline_maybe_thunk(val);
match operand { match operand {
InlineOperand::Const(val) => { Const(val) => {
let idx = self.ctx.add_constant(val); let idx = self.ctx.add_constant(val);
self.emit_u8(OperandType::Const as u8); self.emit_u8(OperandType::Const as u8);
self.emit_u32(idx); self.emit_u32(idx);
} }
InlineOperand::Local { layer, local } => { BigInt(val) => {
self.emit_u8(OperandType::BigInt as u8);
self.emit_i64(val);
}
Local { layer, local } => {
self.emit_u8(OperandType::Local as u8); self.emit_u8(OperandType::Local as u8);
self.emit_u8(layer); self.emit_u8(layer);
self.emit_u32(local); self.emit_u32(local);
} }
InlineOperand::Builtins => { Builtins => {
self.emit_u8(OperandType::Builtins as u8); self.emit_u8(OperandType::Builtins as u8);
} }
InlineOperand::BigInt(val) => { ReplBinding(id) => {
self.emit_u8(OperandType::BigInt as u8); self.emit_u8(OperandType::ReplBinding as u8);
self.emit_i64(val); self.emit_str_id(id);
}
ScopedImportBinding(id) => {
self.emit_u8(OperandType::ScopedImportBinding as u8);
self.emit_str_id(id);
}
WithLookup(id) => {
self.emit_u8(OperandType::WithLookup as u8);
self.emit_str_id(id);
} }
} }
} }
@@ -315,7 +338,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize { fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize {
match ir.deref() { match ir {
Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body), Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body),
Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body), Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body),
Ir::If { cond, consq, alter } => { Ir::If { cond, consq, alter } => {
@@ -361,7 +384,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
ir: RawIrRef<'ir>, ir: RawIrRef<'ir>,
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>, out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
) { ) {
match ir.deref() { match ir {
Ir::With { thunks, body, .. } => { Ir::With { thunks, body, .. } => {
for &(id, inner) in thunks.iter() { for &(id, inner) in thunks.iter() {
out.push((id, inner)); out.push((id, inner));
@@ -422,7 +445,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) { fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
match ir.deref() { match ir {
&Ir::TopLevel { body, ref thunks } => { &Ir::TopLevel { body, ref thunks } => {
let with_thunk_count = self.count_with_thunks(body); let with_thunk_count = self.count_with_thunks(body);
let total_slots = thunks.len() + with_thunk_count; let total_slots = thunks.len() + with_thunk_count;
@@ -468,11 +491,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
fn emit_expr(&mut self, ir: RawIrRef<'_>) { fn emit_expr(&mut self, ir: RawIrRef<'_>) {
match ir.deref() { match ir {
&Ir::Int(x) => { &Ir::Int(x) => {
if x <= i32::MAX as i64 { if let Ok(x) = x.try_into() {
self.emit_op(Op::PushSmi); self.emit_op(Op::PushSmi);
self.emit_i32(x as i32); self.emit_i32(x);
} else { } else {
self.emit_op(Op::PushBigInt); self.emit_op(Op::PushBigInt);
self.emit_i64(x); self.emit_i64(x);
@@ -567,10 +590,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} => { } => {
self.emit_select(expr, attrpath, default, span); self.emit_select(expr, attrpath, default, span);
} }
&Ir::Thunk(id) => {
let (layer, local) = self.resolve_thunk(id);
self.emit_load(layer, local);
}
Ir::Builtins => { Ir::Builtins => {
self.emit_op(Op::LoadBuiltins); self.emit_op(Op::LoadBuiltins);
} }
@@ -580,7 +599,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
&Ir::BuiltinConst(id) => { &Ir::BuiltinConst(id) => {
self.emit_select( self.emit_select(
RawIrRef(&Ir::Builtins), &Ir::Builtins,
&[Attr::Str(id, TextRange::default())], &[Attr::Str(id, TextRange::default())],
None, None,
TextRange::default(), TextRange::default(),
@@ -642,6 +661,60 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
self.emit_op(Op::LookupWith); self.emit_op(Op::LookupWith);
self.emit_str_id(name); self.emit_str_id(name);
} }
&Ir::MaybeThunk(thunk) => {
use MaybeThunk::*;
match *thunk {
Int(x) => {
if let Ok(x) = x.try_into() {
self.emit_op(Op::PushSmi);
self.emit_i32(x);
} else {
self.emit_op(Op::PushBigInt);
self.emit_i64(x);
}
}
Float(x) => {
self.emit_op(Op::PushFloat);
self.emit_f64(x);
}
Bool(true) => self.emit_op(Op::PushTrue),
Bool(false) => self.emit_op(Op::PushFalse),
Null => self.emit_op(Op::PushNull),
Str(id) => {
self.emit_op(Op::PushString);
self.emit_str_id(id);
}
Path(id) => {
self.emit_op(Op::PushString);
self.emit_str_id(id);
self.emit_op(Op::ResolvePath);
}
Thunk(id) => {
let (layer, local) = self.resolve_thunk(id);
self.emit_load(layer, local);
}
Arg { layer } => self.emit_load(layer, 0),
Builtin(id) => {
self.emit_op(Op::LoadBuiltin);
self.emit_u8(id as u8);
}
Builtins => self.emit_op(Op::LoadBuiltins),
ReplBinding(name) => {
self.emit_op(Op::LoadReplBinding);
self.emit_str_id(name);
}
ScopedImportBinding(name) => {
self.emit_op(Op::LoadScopedBinding);
self.emit_str_id(name);
}
WithLookup(name) => {
// TODO: specialize shallow with lookups
self.emit_op(Op::PrepareWith);
self.emit_op(Op::LookupWith);
self.emit_str_id(name);
}
}
}
} }
} }
@@ -739,11 +812,11 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
} }
} }
fn emit_func( fn emit_func<'ir>(
&mut self, &mut self,
thunks: &[(ThunkId, RawIrRef<'_>)], thunks: &[(ThunkId, RawIrRef<'ir>)],
param: &Option<Param<'_>>, param: &Option<Param<'ir>>,
body: RawIrRef<'_>, body: RawIrRef<'ir>,
) { ) {
let with_thunk_count = self.count_with_thunks(body); let with_thunk_count = self.count_with_thunks(body);
let total_slots = thunks.len() + with_thunk_count; let total_slots = thunks.len() + with_thunk_count;
@@ -793,8 +866,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_attrset( fn emit_attrset(
&mut self, &mut self,
stcs: &fix_ir::HashMap<'_, StringId, (MaybeThunk, TextRange)>, stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
dyns: &[(RawIrRef<'_>, MaybeThunk, TextRange)], dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)],
) { ) {
if stcs.is_empty() && dyns.is_empty() { if stcs.is_empty() && dyns.is_empty() {
self.emit_op(Op::MakeEmptyAttrs); self.emit_op(Op::MakeEmptyAttrs);
@@ -913,7 +986,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
fn emit_with( fn emit_with(
&mut self, &mut self,
namespace: MaybeThunk, namespace: &MaybeThunk,
body: RawIrRef<'_>, body: RawIrRef<'_>,
thunks: &[(ThunkId, RawIrRef<'_>)], thunks: &[(ThunkId, RawIrRef<'_>)],
) { ) {
+15 -13
View File
@@ -39,12 +39,12 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>, T, E: std::fmt::Display>
} }
pub trait DowngradeContext<'id: 'ir, 'ir> { pub trait DowngradeContext<'id: 'ir, 'ir> {
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>; fn new_expr(&self, expr: Ir<'ir, GhostRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk; fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> GhostMaybeThunkRef<'id, 'ir>;
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId; fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId;
fn resolve_sym(&self, id: StringId) -> Symbol<'_>; fn resolve_sym(&self, id: StringId) -> Symbol<'_>;
fn lookup(&self, sym: StringId, span: TextRange) -> Result<MaybeThunk>; fn lookup(&self, sym: StringId, span: TextRange) -> Result<GhostMaybeThunkRef<'id, 'ir>>;
fn get_current_source(&self) -> Source; fn get_current_source(&self) -> Source;
@@ -229,7 +229,8 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
let span = self.syntax().text_range(); let span = self.syntax().text_range();
let text = self.ident_token().require(ctx, span)?.to_string(); let text = self.ident_token().require(ctx, span)?.to_string();
let sym = ctx.intern_string(text); let sym = ctx.intern_string(text);
ctx.lookup(sym, span).map(|thunk| thunk.to_ir(ctx)) ctx.lookup(sym, span)
.map(|thunk| ctx.new_expr(Ir::MaybeThunk(thunk)))
} }
} }
@@ -835,8 +836,8 @@ fn make_attrpath_value_entry<'ir>(path: Vec<'ir, ast::Attr>, value: ast::Expr) -
} }
struct FinalizedAttrSet<'id, 'ir> { struct FinalizedAttrSet<'id, 'ir> {
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>, stcs: HashMap<'ir, StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
dyns: Vec<'ir, (IrRef<'id, 'ir>, MaybeThunk, TextRange)>, dyns: Vec<'ir, (IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
} }
fn downgrade_attrs<'id, 'ir>( fn downgrade_attrs<'id, 'ir>(
@@ -1074,7 +1075,7 @@ where
F: FnOnce( F: FnOnce(
&mut Ctx, &mut Ctx,
&[StringId], &[StringId],
&[(IrRef<'id, 'ir>, MaybeThunk, TextRange)], &[(IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)],
) -> Result<IrRef<'id, 'ir>>, ) -> Result<IrRef<'id, 'ir>>,
{ {
let mut pending = PendingAttrSet::new_in(ctx.bump()); let mut pending = PendingAttrSet::new_in(ctx.bump());
@@ -1090,7 +1091,7 @@ where
let vals = { let vals = {
let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump()); let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump());
for sym in &keys { for sym in &keys {
temp.push(finalized.stcs.get(sym).expect("WTF").0.to_ir(ctx)); temp.push(ctx.new_expr(Ir::MaybeThunk(finalized.stcs.get(sym).expect("WTF").0)));
} }
temp temp
}; };
@@ -1101,7 +1102,7 @@ where
fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>( fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
entries: &[ast::Entry], entries: &[ast::Entry],
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<HashMap<'ir, StringId, (MaybeThunk, TextRange)>> { ) -> Result<HashMap<'ir, StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>> {
let mut inherit_lookups = HashMap::new_in(ctx.bump()); let mut inherit_lookups = HashMap::new_in(ctx.bump());
for entry in entries { for entry in entries {
if let ast::Entry::Inherit(inherit) = entry if let ast::Entry::Inherit(inherit) = entry
@@ -1141,7 +1142,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>( fn finalize_pending_set<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
pending: PendingAttrSet, pending: PendingAttrSet,
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>, inherit_lookups: &HashMap<StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<FinalizedAttrSet<'id, 'ir>> { ) -> Result<FinalizedAttrSet<'id, 'ir>> {
let mut stcs = HashMap::new_in(ctx.bump()); let mut stcs = HashMap::new_in(ctx.bump());
@@ -1169,7 +1170,7 @@ 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>( fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW_DYN: bool>(
value: PendingValue, value: PendingValue,
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>, inherit_lookups: &HashMap<StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
ctx: &mut Ctx, ctx: &mut Ctx,
) -> Result<IrRef<'id, 'ir>> { ) -> Result<IrRef<'id, 'ir>> {
match value { match value {
@@ -1185,9 +1186,10 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
} }
PendingValue::InheritScope(sym, span) => { PendingValue::InheritScope(sym, span) => {
if let Some(&(expr, _)) = inherit_lookups.get(&sym) { if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
Ok(expr.to_ir(ctx)) Ok(ctx.new_expr(Ir::MaybeThunk(expr)))
} else { } else {
ctx.lookup(sym, span).map(|val| val.to_ir(ctx)) ctx.lookup(sym, span)
.map(|val| ctx.new_expr(Ir::MaybeThunk(val)))
} }
} }
PendingValue::Set(set) => { PendingValue::Set(set) => {
+79 -93
View File
@@ -1,5 +1,5 @@
use std::hash::Hash; use std::hash::Hash;
use std::ops::Deref; use std::marker::PhantomData;
use bumpalo::Bump; use bumpalo::Bump;
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
@@ -10,56 +10,36 @@ use num_enum::TryFromPrimitive as _;
use rnix::{TextRange, ast}; use rnix::{TextRange, ast};
use string_interner::DefaultStringInterner; use string_interner::DefaultStringInterner;
use crate::downgrade::DowngradeContext;
pub mod downgrade; pub mod downgrade;
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>; pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
#[repr(transparent)] pub type IrRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::IrRef;
#[derive(Clone, Copy)] pub type RawIrRef<'ir> = <RawRef<'ir> as RefExt<'ir>>::IrRef;
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>); pub type GhostMaybeThunkRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::MaybeThunkRef;
impl<'id, 'ir> IrRef<'id, 'ir> {
pub fn new(ir: &'ir GhostCell<'id, Ir<'ir, Self>>) -> Self {
Self(ir)
}
pub fn alloc(bump: &'ir Bump, ir: Ir<'ir, Self>) -> Self {
Self(bump.alloc(GhostCell::new(ir)))
}
pub fn borrow<'a>(&'a self, token: &'a GhostToken<'id>) -> &'a Ir<'ir, Self> {
self.0.borrow(token)
}
impl<'id, 'ir> Ir<'ir, GhostRef<'id, 'ir>> {
/// Freeze a mutable IR reference into a read-only one, consuming the /// Freeze a mutable IR reference into a read-only one, consuming the
/// `GhostToken` to prevent any further mutation. /// `GhostToken` to prevent any further mutation.
/// pub fn freeze(this: IrRef<'id, 'ir>, _: GhostToken<'id>) -> RawIrRef<'ir> {
/// # Safety // SAFETY: The transmute is sound because:
/// The transmute is sound because: // - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`, so
/// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T` // `&'ir GhostCell<'id, T>` and `&'ir T` have identical layout.
/// - `IrRef<'id, 'ir>` is `#[repr(transparent)]` over // - `Ir<'ir, R>` is `#[repr(C)]`, and for every field that depends on
/// `&'ir GhostCell<'id, Ir<'ir, Self>>` // `R`, instantiating `R = GhostRef<'id, 'ir>` vs `R = RawRef<'ir>`
/// - `RawIrRef<'ir>` is `#[repr(transparent)]` over `&'ir Ir<'ir, Self>` // produces types of identical layout:
/// - `Ir<'ir, Ref>` is `#[repr(C)]` and both ref types are pointer-sized // - `R::IrRef` becomes `&'ir GhostCell<'id, Ir<...>>` vs `&'ir Ir<...>`
/// // - `R::MaybeThunkRef` becomes `&'ir GhostCell<'id, MaybeThunk>`
/// Consuming the `GhostToken` guarantees no `borrow_mut` calls can occur // vs `&'ir MaybeThunk`
/// afterwards, so the shared `&Ir` references from `RawIrRef::Deref` can // - `R::Ref<Ir<'ir, R>>` (used in `ConcatStrings::parts`) reduces
/// never alias with mutable references. // to the same case as `R::IrRef`
pub fn freeze(self, _token: GhostToken<'id>) -> RawIrRef<'ir> { // - Therefore `IrRef<'id, 'ir>` and `RawIrRef<'ir>` are both
unsafe { std::mem::transmute(self) } // pointer-sized references with the same layout.
} //
} // Consuming the `GhostToken` guarantees no `borrow_mut` calls can
// occur afterwards, so the shared `&Ir` references reachable from a
#[repr(transparent)] // `RawIrRef<'ir>` can never alias with mutable references.
#[derive(Clone, Copy, Debug)] unsafe { std::mem::transmute::<IrRef<'id, 'ir>, RawIrRef<'ir>>(this) }
pub struct RawIrRef<'ir>(pub &'ir Ir<'ir, Self>);
impl<'ir> Deref for RawIrRef<'ir> {
type Target = Ir<'ir, RawIrRef<'ir>>;
fn deref(&self) -> &Self::Target {
self.0
} }
} }
@@ -81,102 +61,108 @@ pub enum MaybeThunk {
WithLookup(StringId), WithLookup(StringId),
} }
impl MaybeThunk {
fn to_ir<'id, 'ir>(self, ctx: &mut impl DowngradeContext<'id, 'ir>) -> IrRef<'id, 'ir> { pub trait Ref<'ir> {
use MaybeThunk::*; type Ref<T>
let ir = match self { where
Int(x) => Ir::Int(x), T: 'ir;
Float(x) => Ir::Float(x),
Bool(x) => Ir::Bool(x),
Null => Ir::Null,
Str(x) => Ir::Str(x),
Path(x) => Ir::Path(ctx.new_expr(Ir::Str(x))),
Thunk(x) => Ir::Thunk(x),
Arg { layer } => Ir::Arg { layer },
Builtin(x) => Ir::Builtin(x),
Builtins => Ir::Builtins,
ReplBinding(x) => Ir::ReplBinding(x),
ScopedImportBinding(x) => Ir::ScopedImportBinding(x),
WithLookup(x) => Ir::WithLookup(x),
};
ctx.new_expr(ir)
} }
pub trait RefExt<'ir>: Ref<'ir> {
type Ir;
type IrRef;
type MaybeThunkRef;
}
impl<'ir, T: Ref<'ir> + 'ir> RefExt<'ir> for T {
type Ir = Ir<'ir, Self>;
type IrRef = Self::Ref<Self::Ir>;
type MaybeThunkRef = Self::Ref<MaybeThunk>;
}
pub struct GhostRef<'id, 'ir>(PhantomData<&'ir GhostCell<'id, ()>>);
pub struct RawRef<'ir>(PhantomData<&'ir ()>);
impl<'id, 'ir> Ref<'ir> for GhostRef<'id, 'ir> {
type Ref<T: 'ir> = &'ir GhostCell<'id, T>;
}
impl<'ir> Ref<'ir> for RawRef<'ir> {
type Ref<T: 'ir> = &'ir T;
} }
#[repr(C)] #[repr(C)]
#[derive(Debug)] #[derive(Debug)]
pub enum Ir<'ir, Ref> { pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
Int(i64), Int(i64),
Float(f64), Float(f64),
Bool(bool), Bool(bool),
Null, Null,
Str(StringId), Str(StringId),
Path(Ref), Path(R::IrRef),
AttrSet { AttrSet {
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>, stcs: HashMap<'ir, StringId, (R::MaybeThunkRef, TextRange)>,
dyns: Vec<'ir, (Ref, MaybeThunk, TextRange)>, dyns: Vec<'ir, (R::IrRef, R::MaybeThunkRef, TextRange)>,
}, },
List { List {
items: Vec<'ir, MaybeThunk>, items: Vec<'ir, R::MaybeThunkRef>,
}, },
ConcatStrings { ConcatStrings {
parts: Vec<'ir, Ref>, parts: Vec<'ir, R::Ref<Ir<'ir, R>>>,
force_string: bool, force_string: bool,
}, },
// OPs // OPs
UnOp { UnOp {
rhs: Ref, rhs: R::IrRef,
kind: UnOpKind, kind: UnOpKind,
}, },
BinOp { BinOp {
lhs: Ref, lhs: R::IrRef,
rhs: Ref, rhs: R::IrRef,
kind: BinOpKind, kind: BinOpKind,
}, },
HasAttr { HasAttr {
lhs: Ref, lhs: R::IrRef,
rhs: Vec<'ir, Attr<Ref>>, rhs: Vec<'ir, Attr<R::IrRef>>,
}, },
Select { Select {
expr: Ref, expr: R::IrRef,
attrpath: Vec<'ir, Attr<Ref>>, attrpath: Vec<'ir, Attr<R::IrRef>>,
default: Option<Ref>, default: Option<R::IrRef>,
span: TextRange, span: TextRange,
}, },
// Conditionals // Conditionals
If { If {
cond: Ref, cond: R::IrRef,
consq: Ref, consq: R::IrRef,
alter: Ref, alter: R::IrRef,
}, },
Assert { Assert {
assertion: Ref, assertion: R::IrRef,
expr: Ref, expr: R::IrRef,
assertion_raw: String, assertion_raw: String,
span: TextRange, span: TextRange,
}, },
With { With {
namespace: MaybeThunk, namespace: R::MaybeThunkRef,
body: Ref, body: R::IrRef,
thunks: Vec<'ir, (ThunkId, Ref)>, thunks: Vec<'ir, (ThunkId, R::IrRef)>,
}, },
WithLookup(StringId), WithLookup(StringId),
// Function related // Function related
Func { Func {
body: Ref, body: R::IrRef,
param: Option<Param<'ir>>, param: Option<Param<'ir>>,
thunks: Vec<'ir, (ThunkId, Ref)>, thunks: Vec<'ir, (ThunkId, R::IrRef)>,
}, },
Arg { Arg {
layer: u8, layer: u8,
}, },
Call { Call {
func: Ref, func: R::IrRef,
arg: MaybeThunk, arg: R::MaybeThunkRef,
span: TextRange, span: TextRange,
}, },
@@ -187,10 +173,10 @@ pub enum Ir<'ir, Ref> {
// Misc // Misc
TopLevel { TopLevel {
body: Ref, body: R::IrRef,
thunks: Vec<'ir, (ThunkId, Ref)>, thunks: Vec<'ir, (ThunkId, R::IrRef)>,
}, },
Thunk(ThunkId), MaybeThunk(R::MaybeThunkRef),
CurPos(TextRange), CurPos(TextRange),
ReplBinding(StringId), ReplBinding(StringId),
ScopedImportBinding(StringId), ScopedImportBinding(StringId),
@@ -299,7 +285,7 @@ pub struct Param<'ir> {
pub fn new_global_env( pub fn new_global_env(
strings: &mut DefaultStringInterner, strings: &mut DefaultStringInterner,
) -> hashbrown::HashMap<StringId, Ir<'static, RawIrRef<'static>>> { ) -> hashbrown::HashMap<StringId, Ir<'static, RawRef<'static>>> {
let mut global_env = hashbrown::HashMap::new(); let mut global_env = hashbrown::HashMap::new();
let builtins_sym = StringId(strings.get_or_intern("builtins")); let builtins_sym = StringId(strings.get_or_intern("builtins"));
global_env.insert(builtins_sym, Ir::Builtins); global_env.insert(builtins_sym, Ir::Builtins);
+15 -3
View File
@@ -103,15 +103,27 @@ impl<'a> BytecodeReader<'a> {
let id = self.read_u32(); let id = self.read_u32();
OperandData::Const(ctx.get_const(id)) OperandData::Const(ctx.get_const(id))
} }
OperandType::BigInt => {
let val = self.read_i64();
OperandData::BigInt(val)
}
OperandType::Local => { OperandType::Local => {
let layer = self.read_u8(); let layer = self.read_u8();
let idx = self.read_u32(); let idx = self.read_u32();
OperandData::Local { layer, idx } OperandData::Local { layer, idx }
} }
OperandType::Builtins => OperandData::Builtins, OperandType::Builtins => OperandData::Builtins,
OperandType::BigInt => { OperandType::ReplBinding => {
let val = self.read_i64(); let id = self.read_string_id();
OperandData::BigInt(val) OperandData::ReplBinding(id)
}
OperandType::ScopedImportBinding => {
let id = self.read_string_id();
OperandData::ScopedImportBinding(id)
}
OperandType::WithLookup => {
let id = self.read_string_id();
OperandData::WithLookup(id)
} }
} }
} }
+12 -5
View File
@@ -227,16 +227,21 @@ pub struct Vm<'gc> {
pub(crate) enum OperandData { pub(crate) enum OperandData {
Const(StaticValue), Const(StaticValue),
BigInt(i64),
Local { layer: u8, idx: u32 }, Local { layer: u8, idx: u32 },
Builtins, Builtins,
BigInt(i64), ReplBinding(StringId),
ScopedImportBinding(StringId),
WithLookup(StringId),
} }
impl OperandData { impl OperandData {
pub(crate) fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'gc>) -> Value<'gc> { pub(crate) fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'gc>) -> Value<'gc> {
use OperandData::*;
match *self { match *self {
OperandData::Const(sv) => sv.into(), Const(sv) => sv.into(),
OperandData::Local { layer, idx } => { BigInt(val) => Value::new_gc(Gc::new(mc, val)),
Local { layer, idx } => {
let mut cur = root.env; let mut cur = root.env;
for _ in 0..layer { for _ in 0..layer {
let prev = cur.borrow().prev.expect("env chain too short"); let prev = cur.borrow().prev.expect("env chain too short");
@@ -244,8 +249,10 @@ impl OperandData {
} }
cur.borrow().locals[idx as usize] cur.borrow().locals[idx as usize]
} }
OperandData::Builtins => root.builtins, Builtins => root.builtins,
OperandData::BigInt(val) => Value::new_gc(Gc::new(mc, val)), ReplBinding(_id) => todo!(),
ScopedImportBinding(_id) => todo!(),
WithLookup(_id) => todo!(),
} }
} }
} }
+91 -58
View File
@@ -8,7 +8,7 @@ use fix_codegen::{BytecodeContext, InstructionPtr, Op};
use fix_common::{StringId, Symbol}; use fix_common::{StringId, Symbol};
use fix_error::{Error, Result, Source}; use fix_error::{Error, Result, Source};
use fix_ir::downgrade::{Downgrade as _, DowngradeContext}; use fix_ir::downgrade::{Downgrade as _, DowngradeContext};
use fix_ir::{Ir, IrRef, MaybeThunk, RawIrRef, ThunkId}; use fix_ir::{GhostMaybeThunkRef, GhostRef, Ir, IrRef, MaybeThunk, RawIrRef, RawRef, ThunkId};
use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx}; use fix_vm::{ForceMode, StaticValue, Vm, VmCode, VmContext, VmRuntimeCtx};
use ghost_cell::{GhostCell, GhostToken}; use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
@@ -30,7 +30,7 @@ pub struct CodeState {
pub sources: Vec<Source>, pub sources: Vec<Source>,
pub spans: Vec<(usize, rnix::TextRange)>, pub spans: Vec<(usize, rnix::TextRange)>,
pub thunk_count: usize, pub thunk_count: usize,
pub global_env: HashMap<StringId, Ir<'static, RawIrRef<'static>>>, pub global_env: HashMap<StringId, Ir<'static, RawRef<'static>>>,
} }
pub struct Evaluator { pub struct Evaluator {
@@ -85,13 +85,13 @@ impl Evaluator {
source: Source, source: Source,
scope: &HashSet<StringId>, scope: &HashSet<StringId>,
) -> Result<fix_common::Value> { ) -> Result<fix_common::Value> {
self.do_eval(source, Some(Scope::Repl(scope)), ForceMode::Shallow) self.do_eval(source, Some(ExtraScope::Repl(scope)), ForceMode::Shallow)
} }
fn do_eval<'ctx>( fn do_eval<'ctx>(
&'ctx mut self, &'ctx mut self,
source: Source, source: Source,
extra_scope: Option<Scope<'ctx>>, extra_scope: Option<ExtraScope<'ctx>>,
force_mode: ForceMode, force_mode: ForceMode,
) -> Result<fix_common::Value> { ) -> Result<fix_common::Value> {
let ip = { let ip = {
@@ -175,14 +175,14 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
fn compile_bytecode( fn compile_bytecode(
&mut self, &mut self,
source: Source, source: Source,
extra_scope: Option<Scope>, extra_scope: Option<ExtraScope>,
) -> Result<InstructionPtr> { ) -> Result<InstructionPtr> {
let root = self.downgrade(source, extra_scope)?; let root = self.downgrade(source, extra_scope)?;
let ip = fix_codegen::compile_bytecode(root.as_ref(), self); let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
Ok(ip) Ok(ip)
} }
fn downgrade(&mut self, source: Source, extra_scope: Option<Scope>) -> Result<OwnedIr> { fn downgrade(&mut self, source: Source, extra_scope: Option<ExtraScope>) -> Result<OwnedIr> {
tracing::debug!("Parsing Nix expression"); tracing::debug!("Parsing Nix expression");
self.code.sources.push(source.clone()); self.code.sources.push(source.clone());
@@ -202,7 +202,7 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
token, token,
self.runtime, self.runtime,
&self.code.global_env, &self.code.global_env,
extra_scope, extra_scope.map(Into::into),
&mut self.code.thunk_count, &mut self.code.thunk_count,
source, source,
); );
@@ -245,6 +245,7 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
Float(x) => StaticValue::new_float(x), Float(x) => StaticValue::new_float(x),
Bool(x) => StaticValue::new_inline(x), Bool(x) => StaticValue::new_inline(x),
String(x) => StaticValue::new_inline(x), String(x) => StaticValue::new_inline(x),
Path(_) => todo!(),
PrimOp { PrimOp {
id, id,
arity, arity,
@@ -310,34 +311,20 @@ struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
token: GhostToken<'id>, token: GhostToken<'id>,
runtime: &'ctx mut R, runtime: &'ctx mut R,
source: Source, source: Source,
scopes: Vec<Scope<'ctx>>, scopes: Vec<Scope<'ctx, 'id, 'ir>>,
with_scope_count: u32, with_scope_count: u32,
arg_count: u32, arg_count: u32,
thunk_count: &'ctx mut usize, thunk_count: &'ctx mut usize,
thunk_scopes: Vec<ThunkScope<'id, 'ir>>, thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
} }
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
!matches!(
ir.borrow(token),
Ir::Builtin(_)
| Ir::Builtins
| Ir::Int(_)
| Ir::Float(_)
| Ir::Bool(_)
| Ir::Null
| Ir::Str(_)
| Ir::Thunk(_)
)
}
impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> { impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
fn new( fn new(
bump: &'ir Bump, bump: &'ir Bump,
token: GhostToken<'id>, token: GhostToken<'id>,
runtime: &'ctx mut R, runtime: &'ctx mut R,
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>, global: &'ctx HashMap<StringId, Ir<'static, RawRef<'static>>>,
extra_scope: Option<Scope<'ctx>>, extra_scope: Option<Scope<'ctx, 'id, 'ir>>,
thunk_count: &'ctx mut usize, thunk_count: &'ctx mut usize,
source: Source, source: Source,
) -> Self { ) -> Self {
@@ -360,23 +347,29 @@ impl<'ctx, 'id, 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir> impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
for DowngradeCtx<'ctx, 'id, 'ir, R> for DowngradeCtx<'ctx, 'id, 'ir, R>
{ {
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> { fn new_expr(&self, expr: Ir<'ir, GhostRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
IrRef::new(self.bump.alloc(GhostCell::new(expr))) self.bump.alloc(GhostCell::new(expr))
} }
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk { fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> GhostMaybeThunkRef<'id, 'ir> {
use MaybeThunk::*; use MaybeThunk::*;
match *ir.borrow(&self.token) { let expr = (|| {
Ir::Builtin(x) => return Builtin(x), let expr = match *ir.borrow(&self.token) {
Ir::Int(x) => return Int(x), Ir::Builtin(x) => Builtin(x),
Ir::Float(x) => return Float(x), Ir::Int(x) => Int(x),
Ir::Bool(x) => return Bool(x), Ir::Float(x) => Float(x),
Ir::Str(x) => return Str(x), Ir::Bool(x) => Bool(x),
Ir::Thunk(x) => return Thunk(x), Ir::Str(x) => Str(x),
Ir::Arg { layer } => return Arg { layer }, Ir::Arg { layer } => Arg { layer },
Ir::Builtins => return Builtins, Ir::Builtins => Builtins,
Ir::Null => return Null, Ir::Null => Null,
_ => (), Ir::MaybeThunk(thunk) => return Some(thunk),
_ => return None,
};
Some(self.bump.alloc(GhostCell::new(expr)))
})();
if let Some(thunk) = expr {
return thunk;
} }
let id = ThunkId(*self.thunk_count); let id = ThunkId(*self.thunk_count);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow"); *self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
@@ -384,7 +377,7 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
.last_mut() .last_mut()
.expect("no active cache scope") .expect("no active cache scope")
.add_binding(id, ir, &self.token); .add_binding(id, ir, &self.token);
Thunk(id) self.bump.alloc(GhostCell::new(Thunk(id)))
} }
fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId { fn intern_string(&mut self, sym: impl AsRef<str>) -> StringId {
@@ -395,7 +388,7 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
self.runtime.resolve_string(id).into() self.runtime.resolve_string(id).into()
} }
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<MaybeThunk> { fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<GhostMaybeThunkRef<'id, 'ir>> {
for scope in self.scopes.iter().rev() { for scope in self.scopes.iter().rev() {
match scope { match scope {
&Scope::Global(global_scope) => { &Scope::Global(global_scope) => {
@@ -408,22 +401,26 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
Ir::Null => Null, Ir::Null => Null,
_ => unreachable!("globals should only contain leaf IR nodes"), _ => unreachable!("globals should only contain leaf IR nodes"),
}; };
return Ok(val); return Ok(self.bump.alloc(GhostCell::new(val)));
} }
} }
&Scope::Repl(repl_bindings) => { &Scope::Repl(repl_bindings) => {
if repl_bindings.contains(&sym) { if repl_bindings.contains(&sym) {
return Ok(MaybeThunk::ReplBinding(sym)); return Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym))));
} }
} }
Scope::ScopedImport(scoped_bindings) => { Scope::ScopedImport(scoped_bindings) => {
if scoped_bindings.contains(&sym) { if scoped_bindings.contains(&sym) {
return Ok(MaybeThunk::ScopedImportBinding(sym)); return Ok(self
.bump
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym))));
} }
} }
Scope::Let(let_scope) => { Scope::Let(let_scope) => {
if let Some(&expr) = let_scope.get(&sym) { if let Some(&expr) = let_scope.get(&sym) {
return Ok(MaybeThunk::Thunk(expr)); return Ok(expr);
} }
} }
&Scope::Param { &Scope::Param {
@@ -434,14 +431,14 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
let layers: u8 = let layers: u8 =
self.thunk_scopes.len().try_into().expect("scope too deep!"); self.thunk_scopes.len().try_into().expect("scope too deep!");
let layer = layers - abs_layer; let layer = layers - abs_layer;
return Ok(MaybeThunk::Arg { layer }); return Ok(self.bump.alloc(GhostCell::new(MaybeThunk::Arg { layer })));
} }
} }
} }
} }
if self.with_scope_count > 0 { if self.with_scope_count > 0 {
Ok(MaybeThunk::WithLookup(sym)) Ok(self.bump.alloc(GhostCell::new(MaybeThunk::WithLookup(sym))))
} else { } else {
Err(Error::downgrade_error( Err(Error::downgrade_error(
format!("'{}' not found", self.resolve_sym(sym)), format!("'{}' not found", self.resolve_sym(sym)),
@@ -464,11 +461,14 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
.thunk_count .thunk_count
.checked_add(keys.len()) .checked_add(keys.len())
.expect("thunk id overflow"); .expect("thunk id overflow");
let iter = keys let scope = {
.iter() let mut scope = HashMap::new();
.enumerate() for (offset, &key) in keys.iter().enumerate() {
.map(|(offset, &key)| (key, ThunkId(base + offset))); scope.insert(key, &*self.bump.alloc(GhostCell::new(MaybeThunk::Thunk(ThunkId(base + offset)))));
self.scopes.push(Scope::Let(iter.collect())); }
scope
};
self.scopes.push(Scope::Let(scope));
let (vals, ret) = { let (vals, ret) = {
let mut guard = ScopeGuard { ctx: self }; let mut guard = ScopeGuard { ctx: self };
f(guard.as_ctx())? f(guard.as_ctx())?
@@ -538,8 +538,10 @@ impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
.pop() .pop()
.expect("no thunk scope left???") .expect("no thunk scope left???")
.bindings; .bindings;
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks }); Ok(Ir::freeze(
Ok(ir.freeze(self.token)) self.new_expr(Ir::TopLevel { body, thunks }),
self.token,
))
} }
} }
@@ -563,14 +565,29 @@ impl<'id, 'ir> ThunkScope<'id, 'ir> {
} }
} }
enum Scope<'ctx> { enum Scope<'ctx, 'id, 'ir> {
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>), Global(&'ctx HashMap<StringId, Ir<'static, RawRef<'static>>>),
Repl(&'ctx HashSet<StringId>), Repl(&'ctx HashSet<StringId>),
ScopedImport(HashSet<StringId>), ScopedImport(HashSet<StringId>),
Let(HashMap<StringId, ThunkId>), Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
Param { sym: StringId, abs_layer: u8 }, Param { sym: StringId, abs_layer: u8 },
} }
enum ExtraScope<'ctx> {
Repl(&'ctx HashSet<StringId>),
ScopedImport(HashSet<StringId>),
}
impl<'ctx> From<ExtraScope<'ctx>> for Scope<'ctx, '_, '_> {
fn from(value: ExtraScope<'ctx>) -> Self {
use ExtraScope::*;
match value {
ScopedImport(scope) => Scope::ScopedImport(scope),
Repl(scope) => Scope::Repl(scope),
}
}
}
struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> { struct ScopeGuard<'a, 'ctx, 'id, 'ir, R: VmRuntimeCtx> {
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>, ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>,
} }
@@ -593,15 +610,31 @@ struct OwnedIr {
} }
impl OwnedIr { impl OwnedIr {
/// # Safety
/// `ir` must be an allocation backed by `bump`. The reference's
/// lifetime is extended to `'static` as a placeholder; the stored IR
/// must only be re-borrowed via [`OwnedIr::as_ref`], which narrows
/// the lifetime back to that of the `&self` borrow. Moving `bump`
/// into the struct keeps the underlying allocation live for the
/// lifetime of the `OwnedIr`.
unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self { unsafe fn new(ir: RawIrRef<'_>, bump: Bump) -> Self {
Self { Self {
_bump: bump, _bump: bump,
// SAFETY: see function docs - caller guarantees `ir` is in `bump`,
// and the `'static` lifetime is a placeholder narrowed by `as_ref`.
ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) }, ir: unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) },
} }
} }
fn as_ref(&self) -> RawIrRef<'_> { fn as_ref<'ir>(&'ir self) -> RawIrRef<'ir> {
self.ir // SAFETY: narrows the placeholder `'static` lifetime stored in
// `self.ir` down to `'ir = &'ir self`. Lifetime shortening is
// logically sound for covariant positions; the transmute is only
// needed because `RawRef<'ir>` carries `'ir` through a GAT
// (`Ref::Ref<T>`), which prevents the compiler from inferring
// covariance automatically. The bump arena that backs the IR is
// owned by `self._bump`, so the data is live for at least `'ir`.
unsafe { std::mem::transmute::<RawIrRef<'static>, RawIrRef<'ir>>(self.ir) }
} }
} }