refactor: use GAT in enum Ir
This commit is contained in:
@@ -79,19 +79,23 @@ impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||
|
||||
#[inline(always)]
|
||||
fn read_operand_data(&mut self) {
|
||||
use OperandType::*;
|
||||
let tag = self.read_u8();
|
||||
let ty = OperandType::try_from_primitive(tag).expect("invalid operand type");
|
||||
match ty {
|
||||
OperandType::Const => {
|
||||
Const => {
|
||||
self.read_u32();
|
||||
}
|
||||
OperandType::Local => {
|
||||
BigInt => {
|
||||
self.read_i64();
|
||||
}
|
||||
Local => {
|
||||
self.read_u8();
|
||||
self.read_u32();
|
||||
}
|
||||
OperandType::Builtins => {}
|
||||
OperandType::BigInt => {
|
||||
self.read_i64();
|
||||
Builtins => {}
|
||||
ReplBinding | ScopedImportBinding | WithLookup => {
|
||||
self.read_u32();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+109
-36
@@ -1,6 +1,4 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use fix_builtins::BuiltinId;
|
||||
use fix_builtins::{BUILTINS, BuiltinId};
|
||||
use fix_common::StringId;
|
||||
use fix_ir::{Attr, BinOpKind, Ir, MaybeThunk, Param, RawIrRef, ThunkId, UnOpKind};
|
||||
use hashbrown::HashMap;
|
||||
@@ -116,9 +114,12 @@ struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
||||
#[derive(Debug, Clone, Copy, TryFromPrimitive)]
|
||||
pub enum OperandType {
|
||||
Const,
|
||||
BigInt,
|
||||
Local,
|
||||
Builtins,
|
||||
BigInt,
|
||||
ReplBinding,
|
||||
ScopedImportBinding,
|
||||
WithLookup,
|
||||
}
|
||||
|
||||
pub enum Const {
|
||||
@@ -126,6 +127,7 @@ pub enum Const {
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
String(StringId),
|
||||
Path(StringId),
|
||||
PrimOp {
|
||||
id: BuiltinId,
|
||||
arity: u8,
|
||||
@@ -143,9 +145,12 @@ pub enum AttrKeyType {
|
||||
|
||||
pub enum InlineOperand {
|
||||
Const(Const),
|
||||
BigInt(i64),
|
||||
Local { layer: u8, local: u32 },
|
||||
Builtins,
|
||||
BigInt(i64),
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
WithLookup(StringId),
|
||||
}
|
||||
|
||||
pub fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> InstructionPtr {
|
||||
@@ -164,9 +169,9 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn inline_maybe_thunk(&self, val: MaybeThunk) -> InlineOperand {
|
||||
fn inline_maybe_thunk(&self, val: &MaybeThunk) -> InlineOperand {
|
||||
use MaybeThunk::*;
|
||||
match val {
|
||||
match *val {
|
||||
Int(x) => {
|
||||
if let Ok(x) = x.try_into() {
|
||||
InlineOperand::Const(Const::Smi(x))
|
||||
@@ -178,37 +183,55 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
Bool(b) => InlineOperand::Const(Const::Bool(b)),
|
||||
Null => InlineOperand::Const(Const::Null),
|
||||
Str(id) => InlineOperand::Const(Const::String(id)),
|
||||
Path(id) => InlineOperand::Const(Const::String(id)),
|
||||
Thunk(id) => {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
InlineOperand::Local { layer, local }
|
||||
}
|
||||
Arg { layer } => InlineOperand::Local {
|
||||
layer,
|
||||
local: 0,
|
||||
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() })
|
||||
},
|
||||
_ => 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);
|
||||
match operand {
|
||||
InlineOperand::Const(val) => {
|
||||
Const(val) => {
|
||||
let idx = self.ctx.add_constant(val);
|
||||
self.emit_u8(OperandType::Const as u8);
|
||||
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(layer);
|
||||
self.emit_u32(local);
|
||||
}
|
||||
InlineOperand::Builtins => {
|
||||
Builtins => {
|
||||
self.emit_u8(OperandType::Builtins as u8);
|
||||
}
|
||||
InlineOperand::BigInt(val) => {
|
||||
self.emit_u8(OperandType::BigInt as u8);
|
||||
self.emit_i64(val);
|
||||
ReplBinding(id) => {
|
||||
self.emit_u8(OperandType::ReplBinding as u8);
|
||||
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 {
|
||||
match ir.deref() {
|
||||
match ir {
|
||||
Ir::With { 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 } => {
|
||||
@@ -361,7 +384,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
ir: RawIrRef<'ir>,
|
||||
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
|
||||
) {
|
||||
match ir.deref() {
|
||||
match ir {
|
||||
Ir::With { thunks, body, .. } => {
|
||||
for &(id, inner) in thunks.iter() {
|
||||
out.push((id, inner));
|
||||
@@ -422,7 +445,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
|
||||
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
||||
match ir.deref() {
|
||||
match ir {
|
||||
&Ir::TopLevel { body, ref thunks } => {
|
||||
let with_thunk_count = self.count_with_thunks(body);
|
||||
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<'_>) {
|
||||
match ir.deref() {
|
||||
match ir {
|
||||
&Ir::Int(x) => {
|
||||
if x <= i32::MAX as i64 {
|
||||
if let Ok(x) = x.try_into() {
|
||||
self.emit_op(Op::PushSmi);
|
||||
self.emit_i32(x as i32);
|
||||
self.emit_i32(x);
|
||||
} else {
|
||||
self.emit_op(Op::PushBigInt);
|
||||
self.emit_i64(x);
|
||||
@@ -567,10 +590,6 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
} => {
|
||||
self.emit_select(expr, attrpath, default, span);
|
||||
}
|
||||
&Ir::Thunk(id) => {
|
||||
let (layer, local) = self.resolve_thunk(id);
|
||||
self.emit_load(layer, local);
|
||||
}
|
||||
Ir::Builtins => {
|
||||
self.emit_op(Op::LoadBuiltins);
|
||||
}
|
||||
@@ -580,7 +599,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
}
|
||||
&Ir::BuiltinConst(id) => {
|
||||
self.emit_select(
|
||||
RawIrRef(&Ir::Builtins),
|
||||
&Ir::Builtins,
|
||||
&[Attr::Str(id, TextRange::default())],
|
||||
None,
|
||||
TextRange::default(),
|
||||
@@ -642,6 +661,60 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
self.emit_op(Op::LookupWith);
|
||||
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,
|
||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||
param: &Option<Param<'_>>,
|
||||
body: RawIrRef<'_>,
|
||||
thunks: &[(ThunkId, RawIrRef<'ir>)],
|
||||
param: &Option<Param<'ir>>,
|
||||
body: RawIrRef<'ir>,
|
||||
) {
|
||||
let with_thunk_count = self.count_with_thunks(body);
|
||||
let total_slots = thunks.len() + with_thunk_count;
|
||||
@@ -793,8 +866,8 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
fn emit_attrset(
|
||||
&mut self,
|
||||
stcs: &fix_ir::HashMap<'_, StringId, (MaybeThunk, TextRange)>,
|
||||
dyns: &[(RawIrRef<'_>, MaybeThunk, TextRange)],
|
||||
stcs: &fix_ir::HashMap<'_, StringId, (&MaybeThunk, TextRange)>,
|
||||
dyns: &[(RawIrRef<'_>, &MaybeThunk, TextRange)],
|
||||
) {
|
||||
if stcs.is_empty() && dyns.is_empty() {
|
||||
self.emit_op(Op::MakeEmptyAttrs);
|
||||
@@ -913,7 +986,7 @@ impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||
|
||||
fn emit_with(
|
||||
&mut self,
|
||||
namespace: MaybeThunk,
|
||||
namespace: &MaybeThunk,
|
||||
body: RawIrRef<'_>,
|
||||
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||
) {
|
||||
|
||||
+15
-13
@@ -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, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir>;
|
||||
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> MaybeThunk;
|
||||
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 intern_string(&mut self, sym: impl AsRef<str>) -> StringId;
|
||||
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;
|
||||
|
||||
@@ -229,7 +229,8 @@ impl<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>> Downgrade<'id, 'ir, Ctx> fo
|
||||
let span = self.syntax().text_range();
|
||||
let text = self.ident_token().require(ctx, span)?.to_string();
|
||||
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> {
|
||||
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
|
||||
dyns: Vec<'ir, (IrRef<'id, 'ir>, MaybeThunk, TextRange)>,
|
||||
stcs: HashMap<'ir, StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||
dyns: Vec<'ir, (IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||
}
|
||||
|
||||
fn downgrade_attrs<'id, 'ir>(
|
||||
@@ -1074,7 +1075,7 @@ where
|
||||
F: FnOnce(
|
||||
&mut Ctx,
|
||||
&[StringId],
|
||||
&[(IrRef<'id, 'ir>, MaybeThunk, TextRange)],
|
||||
&[(IrRef<'id, 'ir>, GhostMaybeThunkRef<'id, 'ir>, TextRange)],
|
||||
) -> Result<IrRef<'id, 'ir>>,
|
||||
{
|
||||
let mut pending = PendingAttrSet::new_in(ctx.bump());
|
||||
@@ -1090,7 +1091,7 @@ where
|
||||
let vals = {
|
||||
let mut temp = Vec::with_capacity_in(keys.len(), ctx.bump());
|
||||
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
|
||||
};
|
||||
@@ -1101,7 +1102,7 @@ where
|
||||
fn collect_inherit_lookups<'id: 'ir, 'ir, Ctx: DowngradeContext<'id, 'ir>>(
|
||||
entries: &[ast::Entry],
|
||||
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());
|
||||
for entry in entries {
|
||||
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>(
|
||||
pending: PendingAttrSet,
|
||||
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
|
||||
inherit_lookups: &HashMap<StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<FinalizedAttrSet<'id, 'ir>> {
|
||||
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>(
|
||||
value: PendingValue,
|
||||
inherit_lookups: &HashMap<StringId, (MaybeThunk, TextRange)>,
|
||||
inherit_lookups: &HashMap<StringId, (GhostMaybeThunkRef<'id, 'ir>, TextRange)>,
|
||||
ctx: &mut Ctx,
|
||||
) -> Result<IrRef<'id, 'ir>> {
|
||||
match value {
|
||||
@@ -1185,9 +1186,10 @@ fn finalize_pending_value<'id, 'ir, Ctx: DowngradeContext<'id, 'ir>, const ALLOW
|
||||
}
|
||||
PendingValue::InheritScope(sym, span) => {
|
||||
if let Some(&(expr, _)) = inherit_lookups.get(&sym) {
|
||||
Ok(expr.to_ir(ctx))
|
||||
Ok(ctx.new_expr(Ir::MaybeThunk(expr)))
|
||||
} 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) => {
|
||||
|
||||
+79
-93
@@ -1,5 +1,5 @@
|
||||
use std::hash::Hash;
|
||||
use std::ops::Deref;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use bumpalo::Bump;
|
||||
use bumpalo::collections::Vec;
|
||||
@@ -10,56 +10,36 @@ use num_enum::TryFromPrimitive as _;
|
||||
use rnix::{TextRange, ast};
|
||||
use string_interner::DefaultStringInterner;
|
||||
|
||||
use crate::downgrade::DowngradeContext;
|
||||
|
||||
pub mod downgrade;
|
||||
|
||||
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>);
|
||||
|
||||
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)
|
||||
}
|
||||
pub type IrRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::IrRef;
|
||||
pub type RawIrRef<'ir> = <RawRef<'ir> as RefExt<'ir>>::IrRef;
|
||||
pub type GhostMaybeThunkRef<'id, 'ir> = <GhostRef<'id, 'ir> as RefExt<'ir>>::MaybeThunkRef;
|
||||
|
||||
impl<'id, 'ir> Ir<'ir, GhostRef<'id, 'ir>> {
|
||||
/// Freeze a mutable IR reference into a read-only one, consuming the
|
||||
/// `GhostToken` to prevent any further mutation.
|
||||
///
|
||||
/// # Safety
|
||||
/// The transmute is sound because:
|
||||
/// - `GhostCell<'id, T>` is `#[repr(transparent)]` over `T`
|
||||
/// - `IrRef<'id, 'ir>` is `#[repr(transparent)]` over
|
||||
/// `&'ir GhostCell<'id, Ir<'ir, Self>>`
|
||||
/// - `RawIrRef<'ir>` is `#[repr(transparent)]` over `&'ir Ir<'ir, Self>`
|
||||
/// - `Ir<'ir, Ref>` is `#[repr(C)]` and both ref types are pointer-sized
|
||||
///
|
||||
/// Consuming the `GhostToken` guarantees no `borrow_mut` calls can occur
|
||||
/// afterwards, so the shared `&Ir` references from `RawIrRef::Deref` can
|
||||
/// never alias with mutable references.
|
||||
pub fn freeze(self, _token: GhostToken<'id>) -> RawIrRef<'ir> {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
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
|
||||
pub fn freeze(this: IrRef<'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.
|
||||
// - `Ir<'ir, R>` is `#[repr(C)]`, and for every field that depends on
|
||||
// `R`, instantiating `R = GhostRef<'id, 'ir>` vs `R = RawRef<'ir>`
|
||||
// produces types of identical layout:
|
||||
// - `R::IrRef` becomes `&'ir GhostCell<'id, Ir<…>>` vs `&'ir Ir<…>`
|
||||
// - `R::MaybeThunkRef` becomes `&'ir GhostCell<'id, MaybeThunk>`
|
||||
// vs `&'ir MaybeThunk`
|
||||
// - `R::Ref<Ir<'ir, R>>` (used in `ConcatStrings::parts`) reduces
|
||||
// to the same case as `R::IrRef`
|
||||
// - Therefore `IrRef<'id, 'ir>` and `RawIrRef<'ir>` are both
|
||||
// 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
|
||||
// `RawIrRef<'ir>` can never alias with mutable references.
|
||||
unsafe { std::mem::transmute::<IrRef<'id, 'ir>, RawIrRef<'ir>>(this) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,102 +61,108 @@ pub enum MaybeThunk {
|
||||
WithLookup(StringId),
|
||||
}
|
||||
|
||||
impl MaybeThunk {
|
||||
fn to_ir<'id, 'ir>(self, ctx: &mut impl DowngradeContext<'id, 'ir>) -> IrRef<'id, 'ir> {
|
||||
use MaybeThunk::*;
|
||||
let ir = match self {
|
||||
Int(x) => Ir::Int(x),
|
||||
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 Ref<'ir> {
|
||||
type Ref<T>
|
||||
where
|
||||
T: '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)]
|
||||
#[derive(Debug)]
|
||||
pub enum Ir<'ir, Ref> {
|
||||
pub enum Ir<'ir, R: RefExt<'ir> + ?Sized + 'ir> {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Null,
|
||||
Str(StringId),
|
||||
Path(Ref),
|
||||
Path(R::IrRef),
|
||||
AttrSet {
|
||||
stcs: HashMap<'ir, StringId, (MaybeThunk, TextRange)>,
|
||||
dyns: Vec<'ir, (Ref, MaybeThunk, TextRange)>,
|
||||
stcs: HashMap<'ir, StringId, (R::MaybeThunkRef, TextRange)>,
|
||||
dyns: Vec<'ir, (R::IrRef, R::MaybeThunkRef, TextRange)>,
|
||||
},
|
||||
List {
|
||||
items: Vec<'ir, MaybeThunk>,
|
||||
items: Vec<'ir, R::MaybeThunkRef>,
|
||||
},
|
||||
ConcatStrings {
|
||||
parts: Vec<'ir, Ref>,
|
||||
parts: Vec<'ir, R::Ref<Ir<'ir, R>>>,
|
||||
force_string: bool,
|
||||
},
|
||||
|
||||
// OPs
|
||||
UnOp {
|
||||
rhs: Ref,
|
||||
rhs: R::IrRef,
|
||||
kind: UnOpKind,
|
||||
},
|
||||
BinOp {
|
||||
lhs: Ref,
|
||||
rhs: Ref,
|
||||
lhs: R::IrRef,
|
||||
rhs: R::IrRef,
|
||||
kind: BinOpKind,
|
||||
},
|
||||
HasAttr {
|
||||
lhs: Ref,
|
||||
rhs: Vec<'ir, Attr<Ref>>,
|
||||
lhs: R::IrRef,
|
||||
rhs: Vec<'ir, Attr<R::IrRef>>,
|
||||
},
|
||||
Select {
|
||||
expr: Ref,
|
||||
attrpath: Vec<'ir, Attr<Ref>>,
|
||||
default: Option<Ref>,
|
||||
expr: R::IrRef,
|
||||
attrpath: Vec<'ir, Attr<R::IrRef>>,
|
||||
default: Option<R::IrRef>,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
// Conditionals
|
||||
If {
|
||||
cond: Ref,
|
||||
consq: Ref,
|
||||
alter: Ref,
|
||||
cond: R::IrRef,
|
||||
consq: R::IrRef,
|
||||
alter: R::IrRef,
|
||||
},
|
||||
Assert {
|
||||
assertion: Ref,
|
||||
expr: Ref,
|
||||
assertion: R::IrRef,
|
||||
expr: R::IrRef,
|
||||
assertion_raw: String,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
With {
|
||||
namespace: MaybeThunk,
|
||||
body: Ref,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
namespace: R::MaybeThunkRef,
|
||||
body: R::IrRef,
|
||||
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
|
||||
},
|
||||
WithLookup(StringId),
|
||||
|
||||
// Function related
|
||||
Func {
|
||||
body: Ref,
|
||||
body: R::IrRef,
|
||||
param: Option<Param<'ir>>,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
|
||||
},
|
||||
Arg {
|
||||
layer: u8,
|
||||
},
|
||||
Call {
|
||||
func: Ref,
|
||||
arg: MaybeThunk,
|
||||
func: R::IrRef,
|
||||
arg: R::MaybeThunkRef,
|
||||
span: TextRange,
|
||||
},
|
||||
|
||||
@@ -187,10 +173,10 @@ pub enum Ir<'ir, Ref> {
|
||||
|
||||
// Misc
|
||||
TopLevel {
|
||||
body: Ref,
|
||||
thunks: Vec<'ir, (ThunkId, Ref)>,
|
||||
body: R::IrRef,
|
||||
thunks: Vec<'ir, (ThunkId, R::IrRef)>,
|
||||
},
|
||||
Thunk(ThunkId),
|
||||
MaybeThunk(R::MaybeThunkRef),
|
||||
CurPos(TextRange),
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
@@ -299,7 +285,7 @@ pub struct Param<'ir> {
|
||||
|
||||
pub fn new_global_env(
|
||||
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 builtins_sym = StringId(strings.get_or_intern("builtins"));
|
||||
global_env.insert(builtins_sym, Ir::Builtins);
|
||||
|
||||
@@ -103,15 +103,27 @@ impl<'a> BytecodeReader<'a> {
|
||||
let id = self.read_u32();
|
||||
OperandData::Const(ctx.get_const(id))
|
||||
}
|
||||
OperandType::BigInt => {
|
||||
let val = self.read_i64();
|
||||
OperandData::BigInt(val)
|
||||
}
|
||||
OperandType::Local => {
|
||||
let layer = self.read_u8();
|
||||
let idx = self.read_u32();
|
||||
OperandData::Local { layer, idx }
|
||||
}
|
||||
OperandType::Builtins => OperandData::Builtins,
|
||||
OperandType::BigInt => {
|
||||
let val = self.read_i64();
|
||||
OperandData::BigInt(val)
|
||||
OperandType::ReplBinding => {
|
||||
let id = self.read_string_id();
|
||||
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
@@ -227,16 +227,21 @@ pub struct Vm<'gc> {
|
||||
|
||||
pub(crate) enum OperandData {
|
||||
Const(StaticValue),
|
||||
BigInt(i64),
|
||||
Local { layer: u8, idx: u32 },
|
||||
Builtins,
|
||||
BigInt(i64),
|
||||
ReplBinding(StringId),
|
||||
ScopedImportBinding(StringId),
|
||||
WithLookup(StringId),
|
||||
}
|
||||
|
||||
impl OperandData {
|
||||
pub(crate) fn resolve<'gc>(&self, mc: &Mutation<'gc>, root: &Vm<'gc>) -> Value<'gc> {
|
||||
use OperandData::*;
|
||||
match *self {
|
||||
OperandData::Const(sv) => sv.into(),
|
||||
OperandData::Local { layer, idx } => {
|
||||
Const(sv) => sv.into(),
|
||||
BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
||||
Local { layer, idx } => {
|
||||
let mut cur = root.env;
|
||||
for _ in 0..layer {
|
||||
let prev = cur.borrow().prev.expect("env chain too short");
|
||||
@@ -244,8 +249,10 @@ impl OperandData {
|
||||
}
|
||||
cur.borrow().locals[idx as usize]
|
||||
}
|
||||
OperandData::Builtins => root.builtins,
|
||||
OperandData::BigInt(val) => Value::new_gc(Gc::new(mc, val)),
|
||||
Builtins => root.builtins,
|
||||
ReplBinding(_id) => todo!(),
|
||||
ScopedImportBinding(_id) => todo!(),
|
||||
WithLookup(_id) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+91
-58
@@ -8,7 +8,7 @@ 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::{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 ghost_cell::{GhostCell, GhostToken};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
@@ -30,7 +30,7 @@ pub struct CodeState {
|
||||
pub sources: Vec<Source>,
|
||||
pub spans: Vec<(usize, rnix::TextRange)>,
|
||||
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 {
|
||||
@@ -85,13 +85,13 @@ impl Evaluator {
|
||||
source: Source,
|
||||
scope: &HashSet<StringId>,
|
||||
) -> 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>(
|
||||
&'ctx mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
extra_scope: Option<ExtraScope<'ctx>>,
|
||||
force_mode: ForceMode,
|
||||
) -> Result<fix_common::Value> {
|
||||
let ip = {
|
||||
@@ -175,14 +175,14 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
||||
fn compile_bytecode(
|
||||
&mut self,
|
||||
source: Source,
|
||||
extra_scope: Option<Scope>,
|
||||
extra_scope: Option<ExtraScope>,
|
||||
) -> Result<InstructionPtr> {
|
||||
let root = self.downgrade(source, extra_scope)?;
|
||||
let ip = fix_codegen::compile_bytecode(root.as_ref(), self);
|
||||
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");
|
||||
|
||||
self.code.sources.push(source.clone());
|
||||
@@ -202,7 +202,7 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
|
||||
token,
|
||||
self.runtime,
|
||||
&self.code.global_env,
|
||||
extra_scope,
|
||||
extra_scope.map(Into::into),
|
||||
&mut self.code.thunk_count,
|
||||
source,
|
||||
);
|
||||
@@ -245,6 +245,7 @@ impl<'a, R: VmRuntimeCtx> BytecodeContext for CompilerCtx<'a, R> {
|
||||
Float(x) => StaticValue::new_float(x),
|
||||
Bool(x) => StaticValue::new_inline(x),
|
||||
String(x) => StaticValue::new_inline(x),
|
||||
Path(_) => todo!(),
|
||||
PrimOp {
|
||||
id,
|
||||
arity,
|
||||
@@ -310,34 +311,20 @@ struct DowngradeCtx<'ctx, 'id, 'ir, R: VmRuntimeCtx> {
|
||||
token: GhostToken<'id>,
|
||||
runtime: &'ctx mut R,
|
||||
source: Source,
|
||||
scopes: Vec<Scope<'ctx>>,
|
||||
scopes: Vec<Scope<'ctx, 'id, 'ir>>,
|
||||
with_scope_count: u32,
|
||||
arg_count: u32,
|
||||
thunk_count: &'ctx mut usize,
|
||||
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> {
|
||||
fn new(
|
||||
bump: &'ir Bump,
|
||||
token: GhostToken<'id>,
|
||||
runtime: &'ctx mut R,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx>>,
|
||||
global: &'ctx HashMap<StringId, Ir<'static, RawRef<'static>>>,
|
||||
extra_scope: Option<Scope<'ctx, 'id, 'ir>>,
|
||||
thunk_count: &'ctx mut usize,
|
||||
source: Source,
|
||||
) -> 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>
|
||||
for DowngradeCtx<'ctx, 'id, 'ir, R>
|
||||
{
|
||||
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
|
||||
fn new_expr(&self, expr: Ir<'ir, GhostRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
|
||||
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::*;
|
||||
match *ir.borrow(&self.token) {
|
||||
Ir::Builtin(x) => return Builtin(x),
|
||||
Ir::Int(x) => return Int(x),
|
||||
Ir::Float(x) => return Float(x),
|
||||
Ir::Bool(x) => return Bool(x),
|
||||
Ir::Str(x) => return Str(x),
|
||||
Ir::Thunk(x) => return Thunk(x),
|
||||
Ir::Arg { layer } => return Arg { layer },
|
||||
Ir::Builtins => return Builtins,
|
||||
Ir::Null => return Null,
|
||||
_ => (),
|
||||
let expr = (|| {
|
||||
let expr = match *ir.borrow(&self.token) {
|
||||
Ir::Builtin(x) => Builtin(x),
|
||||
Ir::Int(x) => Int(x),
|
||||
Ir::Float(x) => Float(x),
|
||||
Ir::Bool(x) => Bool(x),
|
||||
Ir::Str(x) => Str(x),
|
||||
Ir::Arg { layer } => Arg { layer },
|
||||
Ir::Builtins => Builtins,
|
||||
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);
|
||||
*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()
|
||||
.expect("no active cache scope")
|
||||
.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 {
|
||||
@@ -395,7 +388,7 @@ 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<MaybeThunk> {
|
||||
fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result<GhostMaybeThunkRef<'id, 'ir>> {
|
||||
for scope in self.scopes.iter().rev() {
|
||||
match scope {
|
||||
&Scope::Global(global_scope) => {
|
||||
@@ -408,22 +401,26 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
Ir::Null => Null,
|
||||
_ => unreachable!("globals should only contain leaf IR nodes"),
|
||||
};
|
||||
return Ok(val);
|
||||
return Ok(self.bump.alloc(GhostCell::new(val)));
|
||||
}
|
||||
}
|
||||
&Scope::Repl(repl_bindings) => {
|
||||
if repl_bindings.contains(&sym) {
|
||||
return Ok(MaybeThunk::ReplBinding(sym));
|
||||
return Ok(self
|
||||
.bump
|
||||
.alloc(GhostCell::new(MaybeThunk::ReplBinding(sym))));
|
||||
}
|
||||
}
|
||||
Scope::ScopedImport(scoped_bindings) => {
|
||||
if scoped_bindings.contains(&sym) {
|
||||
return Ok(MaybeThunk::ScopedImportBinding(sym));
|
||||
return Ok(self
|
||||
.bump
|
||||
.alloc(GhostCell::new(MaybeThunk::ScopedImportBinding(sym))));
|
||||
}
|
||||
}
|
||||
Scope::Let(let_scope) => {
|
||||
if let Some(&expr) = let_scope.get(&sym) {
|
||||
return Ok(MaybeThunk::Thunk(expr));
|
||||
return Ok(expr);
|
||||
}
|
||||
}
|
||||
&Scope::Param {
|
||||
@@ -434,14 +431,14 @@ 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(MaybeThunk::Arg { layer });
|
||||
return Ok(self.bump.alloc(GhostCell::new(MaybeThunk::Arg { layer })));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.with_scope_count > 0 {
|
||||
Ok(MaybeThunk::WithLookup(sym))
|
||||
Ok(self.bump.alloc(GhostCell::new(MaybeThunk::WithLookup(sym))))
|
||||
} else {
|
||||
Err(Error::downgrade_error(
|
||||
format!("'{}' not found", self.resolve_sym(sym)),
|
||||
@@ -464,11 +461,14 @@ impl<'ctx: 'ir, 'id, 'ir, R: VmRuntimeCtx> DowngradeContext<'id, 'ir>
|
||||
.thunk_count
|
||||
.checked_add(keys.len())
|
||||
.expect("thunk id overflow");
|
||||
let iter = keys
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(offset, &key)| (key, ThunkId(base + offset)));
|
||||
self.scopes.push(Scope::Let(iter.collect()));
|
||||
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
|
||||
};
|
||||
self.scopes.push(Scope::Let(scope));
|
||||
let (vals, ret) = {
|
||||
let mut guard = ScopeGuard { ctx: self };
|
||||
f(guard.as_ctx())?
|
||||
@@ -538,8 +538,10 @@ impl<'id, 'ir, 'ctx: 'ir, R: VmRuntimeCtx> DowngradeCtx<'ctx, 'id, 'ir, R> {
|
||||
.pop()
|
||||
.expect("no thunk scope left???")
|
||||
.bindings;
|
||||
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
|
||||
Ok(ir.freeze(self.token))
|
||||
Ok(Ir::freeze(
|
||||
self.new_expr(Ir::TopLevel { body, thunks }),
|
||||
self.token,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,14 +565,29 @@ impl<'id, 'ir> ThunkScope<'id, 'ir> {
|
||||
}
|
||||
}
|
||||
|
||||
enum Scope<'ctx> {
|
||||
Global(&'ctx HashMap<StringId, Ir<'static, RawIrRef<'static>>>),
|
||||
enum Scope<'ctx, 'id, 'ir> {
|
||||
Global(&'ctx HashMap<StringId, Ir<'static, RawRef<'static>>>),
|
||||
Repl(&'ctx HashSet<StringId>),
|
||||
ScopedImport(HashSet<StringId>),
|
||||
Let(HashMap<StringId, ThunkId>),
|
||||
Let(HashMap<StringId, GhostMaybeThunkRef<'id, 'ir>>),
|
||||
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> {
|
||||
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir, R>,
|
||||
}
|
||||
@@ -593,15 +610,31 @@ struct 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 {
|
||||
Self {
|
||||
_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) },
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ref(&self) -> RawIrRef<'_> {
|
||||
self.ir
|
||||
fn as_ref<'ir>(&'ir self) -> RawIrRef<'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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user