refactor: use GAT in enum Ir

This commit is contained in:
2026-05-01 20:18:00 +08:00
parent 0df38f374f
commit 5dd160cc43
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)]
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
View File
@@ -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
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> {
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
View File
@@ -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);
+15 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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) }
}
}