diff --git a/fix-codegen/src/disassembler.rs b/fix-codegen/src/disassembler.rs
index 5aba5c5..08e3d65 100644
--- a/fix-codegen/src/disassembler.rs
+++ b/fix-codegen/src/disassembler.rs
@@ -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();
}
}
}
diff --git a/fix-codegen/src/lib.rs b/fix-codegen/src/lib.rs
index b6f571a..6fdae83 100644
--- a/fix-codegen/src/lib.rs
+++ b/fix-codegen/src/lib.rs
@@ -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>,
- body: RawIrRef<'_>,
+ thunks: &[(ThunkId, RawIrRef<'ir>)],
+ param: &Option>,
+ 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<'_>)],
) {
diff --git a/fix-ir/src/downgrade.rs b/fix-ir/src/downgrade.rs
index 66e4b71..f7665e2 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, 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) -> 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;
@@ -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>,
{
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> {
+) -> Result, 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,
+ inherit_lookups: &HashMap, TextRange)>,
ctx: &mut Ctx,
) -> Result> {
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,
+ inherit_lookups: &HashMap, TextRange)>,
ctx: &mut Ctx,
) -> Result> {
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) => {
diff --git a/fix-ir/src/lib.rs b/fix-ir/src/lib.rs
index 15ee0c2..355b130 100644
--- a/fix-ir/src/lib.rs
+++ b/fix-ir/src/lib.rs
@@ -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;
-#[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> = as RefExt<'ir>>::IrRef;
+pub type RawIrRef<'ir> = as RefExt<'ir>>::IrRef;
+pub type GhostMaybeThunkRef<'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>` (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::, 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
+ 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;
+ type MaybeThunkRef = Self::Ref;
+}
+
+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 = &'ir GhostCell<'id, T>;
+}
+impl<'ir> Ref<'ir> for RawRef<'ir> {
+ type Ref = &'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>>,
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[>,
+ lhs: R::IrRef,
+ rhs: Vec<'ir, Attr>,
},
Select {
- expr: Ref,
- attrpath: Vec<'ir, Attr][>,
- default: Option][,
+ expr: R::IrRef,
+ attrpath: Vec<'ir, Attr>,
+ default: Option,
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>,
- 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>> {
+) -> 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);
diff --git a/fix-vm/src/bytecode_reader.rs b/fix-vm/src/bytecode_reader.rs
index abe35f8..31cd856 100644
--- a/fix-vm/src/bytecode_reader.rs
+++ b/fix-vm/src/bytecode_reader.rs
@@ -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)
}
}
}
diff --git a/fix-vm/src/lib.rs b/fix-vm/src/lib.rs
index 58bbaaf..f477bb2 100644
--- a/fix-vm/src/lib.rs
+++ b/fix-vm/src/lib.rs
@@ -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!(),
}
}
}
diff --git a/fix/src/lib.rs b/fix/src/lib.rs
index 69b26c6..f9b5176 100644
--- a/fix/src/lib.rs
+++ b/fix/src/lib.rs
@@ -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,
pub spans: Vec<(usize, rnix::TextRange)>,
pub thunk_count: usize,
- pub global_env: HashMap>>,
+ pub global_env: HashMap>>,
}
pub struct Evaluator {
@@ -85,13 +85,13 @@ impl Evaluator {
source: Source,
scope: &HashSet,
) -> Result {
- 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>,
+ extra_scope: Option>,
force_mode: ForceMode,
) -> Result {
let ip = {
@@ -175,14 +175,14 @@ impl<'a, R: VmRuntimeCtx> CompilerCtx<'a, R> {
fn compile_bytecode(
&mut self,
source: Source,
- extra_scope: Option,
+ extra_scope: Option,
) -> Result {
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) -> Result {
+ fn downgrade(&mut self, source: Source, extra_scope: Option) -> Result {
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>,
+ scopes: Vec>,
with_scope_count: u32,
arg_count: u32,
thunk_count: &'ctx mut usize,
thunk_scopes: Vec>,
}
-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>>,
- extra_scope: Option>,
+ global: &'ctx HashMap>>,
+ extra_scope: Option>,
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) -> 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 {
+ fn lookup(&self, sym: StringId, span: rnix::TextRange) -> Result> {
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>>),
+enum Scope<'ctx, 'id, 'ir> {
+ Global(&'ctx HashMap>>),
Repl(&'ctx HashSet),
ScopedImport(HashSet),
- Let(HashMap),
+ Let(HashMap>),
Param { sym: StringId, abs_layer: u8 },
}
+enum ExtraScope<'ctx> {
+ Repl(&'ctx HashSet),
+ ScopedImport(HashSet),
+}
+
+impl<'ctx> From> 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<'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`), 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<'ir>>(self.ir) }
}
}
]