refactor: use GhostCell to provide interior mutability in Ir

This commit is contained in:
2026-02-24 16:52:33 +08:00
parent c24d6a8bb3
commit 843ae6cfb4
6 changed files with 1133 additions and 1017 deletions

7
Cargo.lock generated
View File

@@ -1302,6 +1302,12 @@ dependencies = [
"wasip3",
]
[[package]]
name = "ghost-cell"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8449d342b1c67f49169e92e71deb7b9b27f30062301a16dbc27a4cc8d2351b7"
[[package]]
name = "gimli"
version = "0.32.3"
@@ -2057,6 +2063,7 @@ dependencies = [
"ere",
"fastwebsockets",
"flate2",
"ghost-cell",
"hashbrown 0.16.1",
"hex",
"http",

View File

@@ -74,6 +74,7 @@ hyper-util = { version = "0.1", features = ["tokio"], optional = true }
http-body-util = { version = "0.1", optional = true }
http = { version = "1", optional = true }
uuid = { version = "1", features = ["v4"], optional = true }
ghost-cell = "0.2.6"
[features]
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]

View File

@@ -1,6 +1,9 @@
use std::fmt::{self, Write as _};
use std::ops::Deref;
use std::path::Path;
use rnix::TextRange;
use crate::ir::*;
use crate::value::Symbol;
@@ -26,7 +29,7 @@ macro_rules! code {
};
}
pub(crate) fn compile<const SCOPED: bool>(expr: &Ir, ctx: &impl CodegenContext) -> String {
pub(crate) fn compile<const SCOPED: bool>(expr: RawIrRef<'_>, ctx: &impl CodegenContext) -> String {
let mut buf = CodeBuffer::with_capacity(8192);
code!(
@@ -186,59 +189,84 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Symbol<'_> {
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> {
impl<Ctx: CodegenContext> Compile<Ctx> for RawIrRef<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
match self {
match self.deref() {
Ir::Int(int) => {
code!(buf, "{}n", int.inner);
code!(buf, "{}n", int);
}
Ir::Float(float) => {
code!(buf, "{}", float.inner);
code!(buf, "{}", float);
}
Ir::Bool(bool) => {
code!(buf, "{}", bool.inner);
code!(buf, "{}", bool);
}
Ir::Null(_) => {
Ir::Null => {
code!(buf, ctx; "null");
}
Ir::Str(s) => {
code!(buf, ctx; quoted(&s.inner));
code!(buf, ctx; quoted(s));
}
Ir::Path(p) => {
// Nix.resolvePath
code!(buf, ctx; "$r(_d," p.expr ")");
code!(buf, ctx; "$r(_d," p ")");
}
Ir::If { cond, consq, alter } => {
code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")");
}
&Ir::BinOp { lhs, rhs, kind } => compile_binop(lhs, rhs, kind, ctx, buf),
&Ir::UnOp { rhs, kind } => compile_unop(rhs, kind, ctx, buf),
&Ir::Func {
body,
ref param,
arg,
ref thunks,
} => compile_func(arg, thunks, param, body, ctx, buf),
Ir::AttrSet { stcs, dyns } => compile_attrset(stcs, dyns, ctx, buf),
Ir::List { items } => compile_list(items, ctx, buf),
Ir::Call { func, arg, span } => {
code!(buf, ctx;
"$c("
func
","
arg
","
span
")"
);
}
Ir::If(x) => x.compile(ctx, buf),
Ir::BinOp(x) => x.compile(ctx, buf),
Ir::UnOp(x) => x.compile(ctx, buf),
Ir::Func(x) => x.compile(ctx, buf),
Ir::AttrSet(x) => x.compile(ctx, buf),
Ir::List(x) => x.compile(ctx, buf),
Ir::Call(x) => x.compile(ctx, buf),
Ir::Arg(x) => {
code!(buf, "a{}", x.inner.0);
code!(buf, "a{}", x.0);
}
Ir::TopLevel(x) => x.compile(ctx, buf),
Ir::Select(x) => x.compile(ctx, buf),
&Ir::Thunk(Thunk { inner: expr_id, .. }) => {
code!(buf, "e{}", expr_id.0);
&Ir::TopLevel { body, ref thunks } => compile_toplevel(body, thunks, ctx, buf),
&Ir::Select {
expr,
ref attrpath,
default,
span,
} => compile_select(expr, attrpath, default, span, ctx, buf),
Ir::Thunk(ThunkId(id)) => {
code!(buf, "e{}", id);
}
Ir::Builtins(_) => {
Ir::Builtins => {
// Nix.builtins
code!(buf, ctx; "$b");
}
&Ir::Builtin(Builtin { inner: name, .. }) => {
&Ir::Builtin(name) => {
// Nix.builtins
code!(buf, ctx; "$b.get(" ctx.get_sym(name) ")");
}
Ir::ConcatStrings(x) => x.compile(ctx, buf),
Ir::HasAttr(x) => x.compile(ctx, buf),
&Ir::Assert(Assert {
&Ir::ConcatStrings {
ref parts,
force_string,
} => compile_concat_strings(parts, force_string, ctx, buf),
&Ir::HasAttr { lhs, ref rhs } => compile_has_attr(lhs, rhs, ctx, buf),
Ir::Assert {
assertion,
expr,
ref assertion_raw,
assertion_raw,
span: assert_span,
}) => {
} => {
// Nix.assert
code!(buf, ctx;
"$a("
@@ -252,19 +280,23 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> {
")"
);
}
Ir::CurPos(cur_pos) => {
Ir::CurPos(span) => {
// Nix.mkPos
code!(buf, ctx; "$mp(" cur_pos.span ")");
code!(buf, ctx; "$mp(" span ")");
}
&Ir::ReplBinding(ReplBinding { inner: name, .. }) => {
&Ir::ReplBinding(name) => {
// Nix.getReplBinding
code!(buf, ctx; "$gb(" ctx.get_sym(name) ")");
}
&Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => {
&Ir::ScopedImportBinding(name) => {
code!(buf, ctx; "_s.get(" ctx.get_sym(name) ")");
}
Ir::With(x) => x.compile(ctx, buf),
&Ir::WithLookup(WithLookup { inner: name, .. }) => {
&Ir::With {
namespace,
body,
ref thunks,
} => compile_with(namespace, body, thunks, ctx, buf),
&Ir::WithLookup(name) => {
// Nix.lookupWith
code!(buf, ctx; "$l(" ctx.get_sym(name) ",_w)");
}
@@ -272,161 +304,132 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir<'_> {
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for If<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let &If {
cond,
consq,
alter,
span: _,
} = self;
fn compile_binop<'ir>(
lhs: RawIrRef<'ir>,
rhs: RawIrRef<'ir>,
kind: BinOpKind,
ctx: &impl CodegenContext,
buf: &mut CodeBuffer,
) {
use BinOpKind::*;
match kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
let op_func = match kind {
Add => "$oa",
Sub => "$os",
Mul => "$om",
Div => "$od",
Eq => "$oe",
Neq => "!$oe",
Lt => "$ol",
Gt => "$og",
Leq => "!$og",
Geq => "!$ol",
Con => "$oc",
Upd => "$ou",
_ => unreachable!(),
};
// Nix.forceBool
code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")");
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for BinOp<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
use BinOpKind::*;
let lhs = self.lhs;
let rhs = self.rhs;
match self.kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
let op_func = match self.kind {
Add => "$oa",
Sub => "$os",
Mul => "$om",
Div => "$od",
Eq => "$oe",
Neq => "!$oe",
Lt => "$ol",
Gt => "$og",
Leq => "!$og",
Geq => "!$ol",
Con => "$oc",
Upd => "$ou",
_ => unreachable!(),
};
code!(
buf, ctx;
op_func "(" lhs "," rhs ")"
);
}
And => {
code!(
buf, ctx;
"$fb(" lhs ")" "&&" "$fb(" rhs ")"
);
}
Or => {
code!(
buf, ctx;
"$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
Impl => {
code!(
buf, ctx;
"!$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
PipeL => {
code!(buf, ctx; "$c(" rhs "," lhs ")");
}
PipeR => {
code!(buf, ctx; "$c(" lhs "," rhs ")");
}
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for UnOp<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
use UnOpKind::*;
let rhs = self.rhs;
match self.kind {
Neg => {
// 0 - rhs
code!(buf, ctx; "$os(0n," rhs ")");
}
Not => {
code!(buf, ctx; "!$fb(" rhs ")");
}
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Func<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let Ir::Arg(Arg {
inner: ArgId(id), ..
}) = self.arg
else {
// TODO:
unreachable!()
};
let has_thunks = !self.thunks.is_empty();
if let Some(Param {
required,
optional,
ellipsis,
}) = &self.param
{
code!(buf, "$mf(a{}=>", id);
if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else {
code!(buf, ctx; "(" self.body ")");
}
code!(buf, ctx;
",["
joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym));
})
"],["
joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym));
})
"],new Map(["
joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),"
ellipsis
")"
code!(
buf, ctx;
op_func "(" lhs "," rhs ")"
);
} else {
code!(buf, "a{}=>", id);
if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else {
code!(buf, ctx; "(" self.body ")");
}
}
And => {
code!(
buf, ctx;
"$fb(" lhs ")" "&&" "$fb(" rhs ")"
);
}
Or => {
code!(
buf, ctx;
"$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
Impl => {
code!(
buf, ctx;
"!$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
PipeL => {
code!(buf, ctx; "$c(" rhs "," lhs ")");
}
PipeR => {
code!(buf, ctx; "$c(" lhs "," rhs ")");
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Call<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
fn compile_unop(
rhs: RawIrRef<'_>,
kind: UnOpKind,
ctx: &impl CodegenContext,
buf: &mut CodeBuffer,
) {
use UnOpKind::*;
match kind {
Neg => {
// 0 - rhs
code!(buf, ctx; "$os(0n," rhs ")");
}
Not => {
code!(buf, ctx; "!$fb(" rhs ")");
}
}
}
fn compile_func<'ir, Ctx: CodegenContext>(
ArgId(id): ArgId,
thunks: &[(ThunkId, RawIrRef<'ir>)],
param: &Option<Param<'ir>>,
body: RawIrRef<'ir>,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
let has_thunks = !thunks.is_empty();
if let Some(Param {
required,
optional,
ellipsis,
}) = &param
{
code!(buf, "$mf(a{}=>", id);
if has_thunks {
code!(buf, ctx; "{" thunks "return " body "}");
} else {
code!(buf, ctx; "(" body ")");
}
code!(buf, ctx;
"$c("
self.func
","
self.arg
","
self.span
",["
joined(required.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym));
})
"],["
joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym));
})
"],new Map(["
joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),"
ellipsis
")"
);
} else {
code!(buf, "a{}=>", id);
if has_thunks {
code!(buf, ctx; "{" thunks "return " body "}");
} else {
code!(buf, ctx; "(" body ")");
}
}
}
impl<'ir, Ctx: CodegenContext> Compile<Ctx> for [(ThunkId, IrRef<'ir>)] {
impl<'ir, Ctx: CodegenContext> Compile<Ctx> for [(ThunkId, RawIrRef<'ir>)] {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if self.is_empty() {
return;
@@ -443,156 +446,173 @@ impl<'ir, Ctx: CodegenContext> Compile<Ctx> for [(ThunkId, IrRef<'ir>)] {
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for TopLevel<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if self.thunks.is_empty() {
self.body.compile(ctx, buf);
} else {
code!(buf, ctx; "(()=>{" self.thunks "return " self.body "})()");
}
fn compile_toplevel<'ir, Ctx: CodegenContext>(
body: RawIrRef<'ir>,
thunks: &[(ThunkId, RawIrRef<'ir>)],
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if thunks.is_empty() {
body.compile(ctx, buf);
} else {
code!(buf, ctx; "(()=>{" thunks "return " body "})()");
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for With<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
let namespace = self.namespace;
let body = self.body;
let has_thunks = !self.thunks.is_empty();
if has_thunks {
code!(buf, ctx; "((_w)=>{" self.thunks "return " body "})({env:" namespace ",last:_w})");
} else {
code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})");
}
fn compile_with<'ir>(
namespace: RawIrRef<'ir>,
body: RawIrRef<'ir>,
thunks: &[(ThunkId, RawIrRef<'ir>)],
ctx: &impl CodegenContext,
buf: &mut CodeBuffer,
) {
let has_thunks = !thunks.is_empty();
if has_thunks {
code!(buf, ctx; "((_w)=>{" thunks "return " body "})({env:" namespace ",last:_w})");
} else {
code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})");
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for Select<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if let Some(default) = self.default {
code!(buf, ctx;
"$sd("
self.expr
",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; *expr_id),
}
})
"],"
default
","
self.span
")"
);
} else {
code!(buf, ctx;
"$s("
self.expr
",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr, _) => code!(buf, ctx; expr),
}
})
"],"
self.span
")"
);
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if !self.dyns.is_empty() {
code!(buf, ctx;
"$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),{dynKeys:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; key);
})
"],dynVals:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
code!(buf, ctx; val);
})
"],dynSpans:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
code!(buf, ctx; attr_span);
})
"]})"
);
} else if !self.stcs.is_empty() {
code!(buf, ctx;
"$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]))"
);
} else {
code!(buf, ctx; "$e");
}
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for List<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
fn compile_select<'ir, Ctx: CodegenContext>(
expr: RawIrRef<'ir>,
attrpath: &[Attr<RawIrRef<'ir>>],
default: Option<RawIrRef<'ir>>,
span: TextRange,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if let Some(default) = default {
code!(buf, ctx;
"["
joined(self.items.iter(), ",", |ctx: &Ctx, buf, item| {
code!(buf, ctx; item);
})
"]"
);
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"$cs(["
joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| {
code!(buf, ctx; part);
})
"]," self.force_string ")"
);
}
}
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr<'_> {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"$h("
self.lhs
"$sd("
expr
",["
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| {
joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr_id, _) => code!(buf, ctx; *expr_id),
}
})
"],"
default
","
span
")"
);
} else {
code!(buf, ctx;
"$s("
expr
",["
joined(attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr, _) => code!(buf, ctx; expr),
}
})
"])"
"],"
span
")"
);
}
}
fn compile_attrset<'ir, Ctx: CodegenContext>(
stcs: &HashMap<'ir, SymId, (RawIrRef<'ir>, TextRange)>,
dyns: &[(RawIrRef<'ir>, RawIrRef<'ir>, TextRange)],
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
if !dyns.is_empty() {
code!(buf, ctx;
"$ma(new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]),{dynKeys:["
joined(dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; key);
})
"],dynVals:["
joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
code!(buf, ctx; val);
})
"],dynSpans:["
joined(dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
code!(buf, ctx; attr_span);
})
"]})"
);
} else if !stcs.is_empty() {
code!(buf, ctx;
"$ma(new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(val, _))| {
let key = ctx.get_sym(sym);
code!(
buf, ctx;
"[" key "," val "]"
);
})
"]),new Map(["
joined(stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"]))"
);
} else {
code!(buf, ctx; "$e");
}
}
fn compile_list<Ctx: CodegenContext>(items: &[RawIrRef<'_>], ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"["
joined(items.iter(), ",", |ctx: &Ctx, buf, item| {
code!(buf, ctx; item);
})
"]"
);
}
fn compile_concat_strings<Ctx: CodegenContext>(
parts: &[RawIrRef<'_>],
force_string: bool,
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
code!(buf, ctx;
"$cs(["
joined(parts.iter(), ",", |ctx: &Ctx, buf, part| {
code!(buf, ctx; part);
})
"]," force_string ")"
);
}
fn compile_has_attr<'ir, Ctx: CodegenContext>(
lhs: RawIrRef<'ir>,
rhs: &[Attr<RawIrRef<'ir>>],
ctx: &Ctx,
buf: &mut CodeBuffer,
) {
code!(buf, ctx;
"$h("
lhs
",["
joined(rhs.iter(), ",", |ctx: &Ctx, buf, attr| {
match attr {
Attr::Str(sym, _) => code!(buf, ctx; ctx.get_sym(*sym)),
Attr::Dynamic(expr, _) => code!(buf, ctx; expr),
}
})
"])"
);
}

View File

@@ -1,18 +1,17 @@
use std::cell::UnsafeCell;
use std::hash::BuildHasher;
use std::path::Path;
use bumpalo::Bump;
use hashbrown::{DefaultHashBuilder, HashMap, HashSet};
use ghost_cell::{GhostCell, GhostToken};
use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
use rnix::TextRange;
use string_interner::DefaultStringInterner;
use crate::codegen::{CodegenContext, compile};
use crate::downgrade::*;
use crate::error::{Error, Result, Source};
use crate::ir::{
Arg, ArgId, Bool, Builtin, Ir, IrKey, IrRef, Null, ReplBinding, ScopedImportBinding, SymId,
Thunk, ThunkId, ToIr as _, WithLookup,
};
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq};
#[cfg(feature = "inspector")]
use crate::runtime::inspector::InspectorServer;
use crate::runtime::{Runtime, RuntimeContext};
@@ -184,38 +183,36 @@ impl Context {
struct Ctx {
symbols: DefaultStringInterner,
global: HashMap<SymId, Ir<'static>>,
global: HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
sources: Vec<Source>,
store: DaemonStore,
spans: UnsafeCell<Vec<(usize, TextRange)>>,
thunk_count: usize,
}
/// Owns the bump allocator and a read-only reference into it.
///
/// # Safety
/// The `ir` field points into `_bump`'s storage. We use `'static` as a sentinel
/// lifetime because the struct owns the backing memory. The `as_ref` method
/// re-binds the lifetime to `&self`, preventing use-after-free.
struct OwnedIr {
_bump: Bump,
ir: &'static Ir<'static>,
ir: RawIrRef<'static>,
}
impl OwnedIr {
fn as_ref<'ir>(&'ir self) -> IrRef<'ir> {
fn as_ref(&self) -> RawIrRef<'_> {
self.ir
}
}
impl Ctx {
fn new() -> Result<Self> {
use crate::ir::{Builtins, ToIr as _};
let mut symbols = DefaultStringInterner::new();
let mut global = HashMap::new();
let builtins_sym = symbols.get_or_intern("builtins");
global.insert(
builtins_sym,
Builtins {
span: TextRange::default(),
}
.to_ir(),
);
global.insert(builtins_sym, Ir::Builtins);
let free_globals = [
"abort",
@@ -239,38 +236,14 @@ impl Ctx {
"toString",
];
let consts = [
(
"true",
Bool {
inner: true,
span: rnix::TextRange::default(),
}
.to_ir(),
),
(
"false",
Bool {
inner: false,
span: rnix::TextRange::default(),
}
.to_ir(),
),
(
"null",
Null {
span: rnix::TextRange::default(),
}
.to_ir(),
),
("true", Ir::Bool(true)),
("false", Ir::Bool(false)),
("null", Ir::Null),
];
for name in free_globals {
let name = symbols.get_or_intern(name);
let value = Builtin {
inner: name,
span: rnix::TextRange::default(),
}
.to_ir();
let value = Ir::Builtin(name);
global.insert(name, value);
}
for (name, value) in consts {
@@ -291,14 +264,16 @@ impl Ctx {
})
}
fn downgrade_ctx<'ctx, 'ir>(
fn downgrade_ctx<'ctx, 'id, 'ir>(
&'ctx mut self,
bump: &'ir Bump,
extra_scope: Option<Scope<'ctx, 'ir>>,
) -> DowngradeCtx<'ctx, 'ir> {
token: GhostToken<'id>,
extra_scope: Option<Scope<'ctx>>,
) -> DowngradeCtx<'ctx, 'id, 'ir> {
let source = self.get_current_source();
DowngradeCtx::new(
bump,
token,
&mut self.symbols,
&self.global,
extra_scope,
@@ -325,7 +300,7 @@ impl Ctx {
fn downgrade<'ctx>(
&'ctx mut self,
source: Source,
extra_scope: Option<Scope<'ctx, 'static>>,
extra_scope: Option<Scope<'ctx>>,
) -> Result<OwnedIr> {
tracing::debug!("Parsing Nix expression");
@@ -340,17 +315,19 @@ impl Ctx {
.expr()
.ok_or_else(|| Error::parse_error("unexpected EOF".into()))?;
let bump = Bump::new();
let ir = self
.downgrade_ctx(&bump, extra_scope)
.downgrade_toplevel(expr)?;
let ir = unsafe { std::mem::transmute::<IrRef<'_>, IrRef<'static>>(ir) };
Ok(OwnedIr { _bump: bump, ir })
GhostToken::new(|token| {
let ir = self
.downgrade_ctx(&bump, token, extra_scope)
.downgrade_toplevel(expr)?;
let ir = unsafe { std::mem::transmute::<RawIrRef<'_>, RawIrRef<'static>>(ir) };
Ok(OwnedIr { _bump: bump, ir })
})
}
fn compile<'ctx>(
&'ctx mut self,
source: Source,
extra_scope: Option<Scope<'ctx, 'static>>,
extra_scope: Option<Scope<'ctx>>,
) -> Result<String> {
let root = self.downgrade(source, extra_scope)?;
tracing::debug!("Generating JavaScript code");
@@ -426,98 +403,104 @@ impl RuntimeContext for Ctx {
}
}
enum Scope<'ctx, 'ir> {
Global(&'ctx HashMap<SymId, Ir<'static>>),
enum Scope<'ctx> {
Global(&'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>),
Repl(&'ctx HashSet<SymId>),
ScopedImport(HashSet<SymId>),
Let(HashMap<SymId, ThunkId>),
Param(SymId, IrRef<'ir>),
Param(SymId, ArgId),
}
struct ScopeGuard<'a, 'ctx, 'ir> {
ctx: &'a mut DowngradeCtx<'ctx, 'ir>,
struct ScopeGuard<'a, 'ctx, 'id, 'ir> {
ctx: &'a mut DowngradeCtx<'ctx, 'id, 'ir>,
}
impl Drop for ScopeGuard<'_, '_, '_> {
impl Drop for ScopeGuard<'_, '_, '_, '_> {
fn drop(&mut self) {
self.ctx.scopes.pop();
}
}
impl<'ir, 'ctx> ScopeGuard<'_, 'ctx, 'ir> {
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'ir> {
impl<'id, 'ir, 'ctx> ScopeGuard<'_, 'ctx, 'id, 'ir> {
fn as_ctx(&mut self) -> &mut DowngradeCtx<'ctx, 'id, 'ir> {
self.ctx
}
}
struct ThunkScope<'ir> {
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>,
cache: HashMap<IrKey<'ir>, ThunkId>,
struct ThunkScope<'id, 'ir> {
bindings: bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
cache: HashTable<(IrRef<'id, 'ir>, ThunkId)>,
hasher: DefaultHashBuilder,
}
impl<'ir> ThunkScope<'ir> {
impl<'id, 'ir> ThunkScope<'id, 'ir> {
fn new_in(bump: &'ir Bump) -> Self {
Self {
bindings: bumpalo::collections::Vec::new_in(bump),
cache: HashMap::new(),
cache: HashTable::new(),
hasher: DefaultHashBuilder::default(),
}
}
fn lookup_cache(&self, key: IrKey<'ir>) -> Option<ThunkId> {
self.cache.get(&key).copied()
fn lookup_cache(&self, key: IrRef<'id, 'ir>, token: &GhostToken<'id>) -> Option<ThunkId> {
let hash = self.hasher.hash_one(IrKey(key, token));
self.cache
.find(hash, |&(ir, _)| ir_content_eq(key, ir, token))
.map(|&(_, id)| id)
}
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'ir>) {
fn add_binding(&mut self, id: ThunkId, ir: IrRef<'id, 'ir>, token: &GhostToken<'id>) {
self.bindings.push((id, ir));
let hash = self.hasher.hash_one(IrKey(ir, token));
self.cache.insert_unique(hash, (ir, id), |&(ir, _)| {
self.hasher.hash_one(IrKey(ir, token))
});
}
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'ir>)>) {
fn extend_bindings(&mut self, iter: impl IntoIterator<Item = (ThunkId, IrRef<'id, 'ir>)>) {
self.bindings.extend(iter);
}
fn add_cache(&mut self, key: IrKey<'ir>, cache: ThunkId) {
self.cache.insert(key, cache);
}
}
struct DowngradeCtx<'ctx, 'ir> {
struct DowngradeCtx<'ctx, 'id, 'ir> {
bump: &'ir Bump,
token: GhostToken<'id>,
symbols: &'ctx mut DefaultStringInterner,
source: Source,
scopes: Vec<Scope<'ctx, 'ir>>,
scopes: Vec<Scope<'ctx>>,
with_scope_count: usize,
arg_count: usize,
thunk_count: &'ctx mut usize,
thunk_scopes: Vec<ThunkScope<'ir>>,
thunk_scopes: Vec<ThunkScope<'id, 'ir>>,
}
fn should_thunk(ir: IrRef<'_>) -> bool {
fn should_thunk<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>) -> bool {
!matches!(
ir,
ir.borrow(token),
Ir::Builtin(_)
| Ir::Builtins(_)
| Ir::Builtins
| Ir::Int(_)
| Ir::Float(_)
| Ir::Bool(_)
| Ir::Null(_)
| Ir::Null
| Ir::Str(_)
| Ir::Thunk(_)
)
}
impl<'ctx, 'ir> DowngradeCtx<'ctx, 'ir> {
impl<'ctx, 'id, 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
fn new(
bump: &'ir Bump,
token: GhostToken<'id>,
symbols: &'ctx mut DefaultStringInterner,
global: &'ctx HashMap<SymId, Ir<'static>>,
extra_scope: Option<Scope<'ctx, 'ir>>,
global: &'ctx HashMap<SymId, Ir<'static, RawIrRef<'static>>>,
extra_scope: Option<Scope<'ctx>>,
thunk_count: &'ctx mut usize,
source: Source,
) -> Self {
Self {
bump,
token,
symbols,
source,
scopes: std::iter::once(Scope::Global(global))
@@ -531,44 +514,38 @@ impl<'ctx, 'ir> DowngradeCtx<'ctx, 'ir> {
}
}
impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
fn new_expr(&self, expr: Ir<'ir>) -> IrRef<'ir> {
self.bump.alloc(expr)
impl<'ctx: 'ir, 'id, 'ir> DowngradeContext<'id, 'ir> for DowngradeCtx<'ctx, 'id, 'ir> {
fn new_expr(&self, expr: Ir<'ir, IrRef<'id, 'ir>>) -> IrRef<'id, 'ir> {
IrRef::new(self.bump.alloc(GhostCell::new(expr)))
}
fn new_arg(&mut self, span: TextRange) -> IrRef<'ir> {
fn new_arg(&mut self) -> ArgId {
self.arg_count += 1;
self.bump.alloc(
Arg {
inner: ArgId(self.arg_count - 1),
span,
}
.to_ir(),
)
ArgId(self.arg_count - 1)
}
fn maybe_thunk(&mut self, ir: IrRef<'ir>) -> IrRef<'ir> {
if should_thunk(ir) {
let scope = self.thunk_scopes.last_mut().expect("no active cache scope");
let key = IrKey(ir);
if let Some(id) = scope.lookup_cache(key) {
return self.new_expr(
Thunk {
inner: id,
span: ir.span(),
}
.to_ir(),
);
}
let span = ir.span();
let id = ThunkId(*self.thunk_count);
*self.thunk_count += 1;
scope.add_binding(id, ir);
scope.add_cache(key, id);
self.new_expr(Thunk { inner: id, span }.to_ir())
} else {
ir
fn maybe_thunk(&mut self, ir: IrRef<'id, 'ir>) -> IrRef<'id, 'ir> {
if !should_thunk(ir, &self.token) {
return ir;
}
let cached = self
.thunk_scopes
.last()
.expect("no active cache scope")
.lookup_cache(ir, &self.token);
if let Some(id) = cached {
return IrRef::alloc(self.bump, Ir::Thunk(id));
}
let id = ThunkId(*self.thunk_count);
*self.thunk_count = self.thunk_count.checked_add(1).expect("thunk id overflow");
self.thunk_scopes
.last_mut()
.expect("no active cache scope")
.add_binding(id, ir, &self.token);
IrRef::alloc(self.bump, Ir::Thunk(id))
}
fn new_sym(&mut self, sym: String) -> SymId {
@@ -579,39 +556,46 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
self.symbols.resolve(id).expect("no symbol found").into()
}
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'ir>> {
fn lookup(&self, sym: SymId, span: TextRange) -> Result<IrRef<'id, 'ir>> {
for scope in self.scopes.iter().rev() {
match scope {
&Scope::Global(global_scope) => {
if let Some(expr) = global_scope.get(&sym) {
return Ok(expr);
let ir = match expr {
Ir::Builtins => Ir::Builtins,
Ir::Builtin(s) => Ir::Builtin(*s),
Ir::Bool(b) => Ir::Bool(*b),
Ir::Null => Ir::Null,
_ => unreachable!("globals should only contain leaf IR nodes"),
};
return Ok(self.new_expr(ir));
}
}
&Scope::Repl(repl_bindings) => {
if repl_bindings.contains(&sym) {
return Ok(self.new_expr(ReplBinding { inner: sym, span }.to_ir()));
return Ok(self.new_expr(Ir::ReplBinding(sym)));
}
}
Scope::ScopedImport(scoped_bindings) => {
if scoped_bindings.contains(&sym) {
return Ok(self.new_expr(ScopedImportBinding { inner: sym, span }.to_ir()));
return Ok(self.new_expr(Ir::ScopedImportBinding(sym)));
}
}
Scope::Let(let_scope) => {
if let Some(&expr) = let_scope.get(&sym) {
return Ok(self.new_expr(Thunk { inner: expr, span }.to_ir()));
return Ok(self.new_expr(Ir::Thunk(expr)));
}
}
&Scope::Param(param_sym, expr) => {
&Scope::Param(param_sym, id) => {
if param_sym == sym {
return Ok(expr);
return Ok(self.new_expr(Ir::Arg(id)));
}
}
}
}
if self.with_scope_count > 0 {
Ok(self.new_expr(WithLookup { inner: sym, span }.to_ir()))
Ok(self.new_expr(Ir::WithLookup(sym)))
} else {
Err(Error::downgrade_error(
format!("'{}' not found", self.get_sym(sym)),
@@ -627,14 +611,19 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
fn with_let_scope<F, R>(&mut self, keys: &[SymId], f: F) -> Result<R>
where
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'ir>>, R)>,
F: FnOnce(&mut Self) -> Result<(bumpalo::collections::Vec<'ir, IrRef<'id, 'ir>>, R)>,
{
let base = *self.thunk_count;
*self.thunk_count += keys.len();
let iter = keys
.iter()
.enumerate()
.map(|(offset, &key)| (key, ThunkId(base + offset)));
*self.thunk_count = self
.thunk_count
.checked_add(keys.len())
.expect("thunk id overflow");
let iter = keys.iter().enumerate().map(|(offset, &key)| {
(
key,
ThunkId(unsafe { base.checked_add(offset).unwrap_unchecked() }),
)
});
self.scopes.push(Scope::Let(iter.collect()));
let (vals, ret) = {
let mut guard = ScopeGuard { ctx: self };
@@ -646,7 +635,7 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
Ok(ret)
}
fn with_param_scope<F, R>(&mut self, param: SymId, arg: IrRef<'ir>, f: F) -> R
fn with_param_scope<F, R>(&mut self, param: SymId, arg: ArgId, f: F) -> R
where
F: FnOnce(&mut Self) -> R,
{
@@ -668,7 +657,10 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
fn with_thunk_scope<F, R>(
&mut self,
f: F,
) -> (R, bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'ir>)>)
) -> (
R,
bumpalo::collections::Vec<'ir, (ThunkId, IrRef<'id, 'ir>)>,
)
where
F: FnOnce(&mut Self) -> R,
{
@@ -688,16 +680,15 @@ impl<'ctx: 'ir, 'ir> DowngradeContext<'ir> for DowngradeCtx<'ctx, 'ir> {
}
}
impl<'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'ir> {
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<IrRef<'ir>> {
use crate::ir::TopLevel;
impl<'id, 'ir, 'ctx: 'ir> DowngradeCtx<'ctx, 'id, 'ir> {
fn downgrade_toplevel(mut self, root: rnix::ast::Expr) -> Result<RawIrRef<'ir>> {
let body = root.downgrade(&mut self)?;
let span = body.span();
let thunks = self
.thunk_scopes
.pop()
.expect("no thunk scope left???")
.bindings;
Ok(self.new_expr(TopLevel { body, thunks, span }.to_ir()))
let ir = IrRef::alloc(self.bump, Ir::TopLevel { body, thunks });
Ok(ir.freeze(self.token))
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,54 +1,151 @@
use std::hash::{Hash, Hasher};
use std::{
hash::{Hash, Hasher},
ops::Deref,
};
use bumpalo::{Bump, boxed::Box, collections::Vec};
use ghost_cell::{GhostCell, GhostToken};
use rnix::{TextRange, ast};
use string_interner::symbol::SymbolU32;
use nix_js_macros::ir;
pub type HashMap<'ir, K, V> = hashbrown::HashMap<K, V, hashbrown::DefaultHashBuilder, &'ir Bump>;
pub type IrRef<'ir> = &'ir Ir<'ir>;
ir! {
Ir<'ir>;
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct IrRef<'id, 'ir>(&'ir GhostCell<'id, Ir<'ir, Self>>);
// Literals
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)))
}
/// 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) }
}
}
impl<'id, 'ir> Deref for IrRef<'id, 'ir> {
type Target = GhostCell<'id, Ir<'ir, IrRef<'id, 'ir>>>;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[repr(transparent)]
#[derive(Clone, Copy)]
pub struct RawIrRef<'ir>(&'ir Ir<'ir, Self>);
impl<'ir> Deref for RawIrRef<'ir> {
type Target = Ir<'ir, RawIrRef<'ir>>;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[repr(C)]
pub enum Ir<'ir, Ref> {
Int(i64),
Float(f64),
Bool(bool),
Null,
Str { inner: Box<'ir, String> },
AttrSet { stcs: HashMap<'ir, SymId, (IrRef<'ir>, TextRange)>, dyns: Vec<'ir, (IrRef<'ir>, IrRef<'ir>, TextRange)> },
List { items: Vec<'ir, IrRef<'ir>> },
Path { expr: IrRef<'ir> },
ConcatStrings { parts: Vec<'ir, IrRef<'ir>>, force_string: bool },
Str(Box<'ir, String>),
AttrSet {
stcs: HashMap<'ir, SymId, (Ref, TextRange)>,
dyns: Vec<'ir, (Ref, Ref, TextRange)>,
},
List {
items: Vec<'ir, Ref>,
},
Path(Ref),
ConcatStrings {
parts: Vec<'ir, Ref>,
force_string: bool,
},
// OPs
UnOp { rhs: IrRef<'ir>, kind: UnOpKind },
BinOp { lhs: IrRef<'ir>, rhs: IrRef<'ir>, kind: BinOpKind },
HasAttr { lhs: IrRef<'ir>, rhs: Vec<'ir, Attr<'ir>> },
Select { expr: IrRef<'ir>, attrpath: Vec<'ir, Attr<'ir>>, default: Option<IrRef<'ir>> },
UnOp {
rhs: Ref,
kind: UnOpKind,
},
BinOp {
lhs: Ref,
rhs: Ref,
kind: BinOpKind,
},
HasAttr {
lhs: Ref,
rhs: Vec<'ir, Attr<Ref>>,
},
Select {
expr: Ref,
attrpath: Vec<'ir, Attr<Ref>>,
default: Option<Ref>,
span: TextRange,
},
// Conditionals
If { cond: IrRef<'ir>, consq: IrRef<'ir>, alter: IrRef<'ir> },
Assert { assertion: IrRef<'ir>, expr: IrRef<'ir>, assertion_raw: String },
If {
cond: Ref,
consq: Ref,
alter: Ref,
},
Assert {
assertion: Ref,
expr: Ref,
assertion_raw: String,
span: TextRange,
},
With { namespace: IrRef<'ir>, body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
With {
namespace: Ref,
body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
WithLookup(SymId),
// Function related
Func { body: IrRef<'ir>, param: Option<Param<'ir>>, arg: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
Func {
body: Ref,
param: Option<Param<'ir>>,
arg: ArgId,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
Arg(ArgId),
Call { func: IrRef<'ir>, arg: IrRef<'ir> },
Call {
func: Ref,
arg: Ref,
span: TextRange,
},
// Builtins
Builtins,
Builtin(SymId),
// Misc
TopLevel { body: IrRef<'ir>, thunks: Vec<'ir, (ThunkId, IrRef<'ir>)> },
TopLevel {
body: Ref,
thunks: Vec<'ir, (ThunkId, Ref)>,
},
Thunk(ThunkId),
CurPos,
CurPos(TextRange),
ReplBinding(SymId),
ScopedImportBinding(SymId),
}
@@ -66,17 +163,17 @@ pub struct ArgId(pub usize);
/// Represents a key in an attribute path.
#[allow(unused)]
#[derive(Debug)]
pub enum Attr<'ir> {
pub enum Attr<Ref> {
/// A dynamic attribute key, which is an expression that must evaluate to a string.
/// Example: `attrs.${key}`
Dynamic(IrRef<'ir>, TextRange),
Dynamic(Ref, TextRange),
/// A static attribute key.
/// Example: `attrs.key`
Str(SymId, TextRange),
}
/// The kinds of binary operations supported in Nix.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum BinOpKind {
// Arithmetic
Add,
@@ -133,7 +230,7 @@ impl From<ast::BinOpKind> for BinOpKind {
}
/// The kinds of unary operations.
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum UnOpKind {
Neg, // Negation (`-`)
Not, // Logical not (`!`)
@@ -157,33 +254,41 @@ pub struct Param<'ir> {
}
#[derive(Clone, Copy)]
pub(crate) struct IrKey<'ir>(pub IrRef<'ir>);
pub(crate) struct IrKey<'id, 'ir, 'a>(pub IrRef<'id, 'ir>, pub &'a GhostToken<'id>);
impl std::hash::Hash for IrKey<'_> {
impl std::hash::Hash for IrKey<'_, '_, '_> {
fn hash<H: Hasher>(&self, state: &mut H) {
ir_content_hash(self.0, state);
ir_content_hash(self.0, self.1, state);
}
}
impl PartialEq for IrKey<'_> {
impl PartialEq for IrKey<'_, '_, '_> {
fn eq(&self, other: &Self) -> bool {
ir_content_eq(self.0, other.0)
ir_content_eq(self.0, other.0, self.1)
}
}
impl Eq for IrKey<'_> {}
impl Eq for IrKey<'_, '_, '_> {}
fn attr_content_hash(attr: &Attr<'_>, state: &mut impl Hasher) {
fn attr_content_hash<'id>(
attr: &Attr<IrRef<'id, '_>>,
token: &GhostToken<'id>,
state: &mut impl Hasher,
) {
core::mem::discriminant(attr).hash(state);
match attr {
Attr::Dynamic(expr, _) => ir_content_hash(expr, state),
Attr::Dynamic(expr, _) => ir_content_hash(*expr, token, state),
Attr::Str(sym, _) => sym.hash(state),
}
}
fn attr_content_eq(a: &Attr<'_>, b: &Attr<'_>) -> bool {
fn attr_content_eq<'id, 'ir>(
a: &Attr<IrRef<'id, 'ir>>,
b: &Attr<IrRef<'id, 'ir>>,
token: &GhostToken<'id>,
) -> bool {
match (a, b) {
(Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(ae, be),
(Attr::Dynamic(ae, _), Attr::Dynamic(be, _)) => ir_content_eq(*ae, *be, token),
(Attr::Str(a, _), Attr::Str(b, _)) => a == b,
_ => false,
}
@@ -215,230 +320,364 @@ fn param_content_eq(a: &Param<'_>, b: &Param<'_>) -> bool {
.all(|((a, _), (b, _))| a == b)
}
fn thunks_content_hash(thunks: &[(ThunkId, IrRef<'_>)], state: &mut impl Hasher) {
fn thunks_content_hash<'id>(
thunks: &[(ThunkId, IrRef<'id, '_>)],
token: &GhostToken<'id>,
state: &mut impl Hasher,
) {
thunks.len().hash(state);
for (id, ir) in thunks {
for &(id, ir) in thunks {
id.hash(state);
ir_content_hash(ir, state);
ir_content_hash(ir, token, state);
}
}
fn thunks_content_eq(a: &[(ThunkId, IrRef<'_>)], b: &[(ThunkId, IrRef<'_>)]) -> bool {
fn thunks_content_eq<'id, 'ir>(
a: &[(ThunkId, IrRef<'id, 'ir>)],
b: &[(ThunkId, IrRef<'id, 'ir>)],
token: &GhostToken<'id>,
) -> bool {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|((ai, ae), (bi, be))| ai == bi && ir_content_eq(ae, be))
.all(|(&(ai, ae), &(bi, be))| ai == bi && ir_content_eq(ae, be, token))
}
fn ir_content_hash(ir: &Ir<'_>, state: &mut impl Hasher) {
fn ir_content_hash<'id>(ir: IrRef<'id, '_>, token: &GhostToken<'id>, state: &mut impl Hasher) {
let ir = ir.borrow(token);
core::mem::discriminant(ir).hash(state);
match ir {
Ir::Int(x) => x.inner.hash(state),
Ir::Float(x) => x.inner.to_bits().hash(state),
Ir::Bool(x) => x.inner.hash(state),
Ir::Null(_) => {}
Ir::Str(x) => x.inner.hash(state),
Ir::AttrSet(x) => {
x.stcs.len().hash(state);
Ir::Int(x) => x.hash(state),
Ir::Float(x) => x.to_bits().hash(state),
Ir::Bool(x) => x.hash(state),
Ir::Null => {}
Ir::Str(x) => x.hash(state),
Ir::AttrSet { stcs, dyns } => {
stcs.len().hash(state);
let mut combined: u64 = 0;
for (&key, (val, _)) in x.stcs.iter() {
for (&key, &(val, _)) in stcs.iter() {
let mut h = std::hash::DefaultHasher::new();
key.hash(&mut h);
ir_content_hash(val, &mut h);
ir_content_hash(val, token, &mut h);
combined = combined.wrapping_add(h.finish());
}
combined.hash(state);
x.dyns.len().hash(state);
for (k, v, _) in x.dyns.iter() {
ir_content_hash(k, state);
ir_content_hash(v, state);
dyns.len().hash(state);
for &(k, v, _) in dyns.iter() {
ir_content_hash(k, token, state);
ir_content_hash(v, token, state);
}
}
Ir::List(x) => {
x.items.len().hash(state);
for item in x.items.iter() {
ir_content_hash(item, state);
Ir::List { items } => {
items.len().hash(state);
for &item in items.iter() {
ir_content_hash(item, token, state);
}
}
Ir::HasAttr(x) => {
ir_content_hash(x.lhs, state);
x.rhs.len().hash(state);
for attr in x.rhs.iter() {
attr_content_hash(attr, state);
Ir::HasAttr { lhs, rhs } => {
ir_content_hash(*lhs, token, state);
rhs.len().hash(state);
for attr in rhs.iter() {
attr_content_hash(attr, token, state);
}
}
Ir::BinOp(x) => {
ir_content_hash(x.lhs, state);
ir_content_hash(x.rhs, state);
x.kind.hash(state);
&Ir::BinOp { lhs, rhs, kind } => {
ir_content_hash(lhs, token, state);
ir_content_hash(rhs, token, state);
kind.hash(state);
}
Ir::UnOp(x) => {
ir_content_hash(x.rhs, state);
x.kind.hash(state);
&Ir::UnOp { rhs, kind } => {
ir_content_hash(rhs, token, state);
kind.hash(state);
}
Ir::Select(x) => {
ir_content_hash(x.expr, state);
x.attrpath.len().hash(state);
for attr in x.attrpath.iter() {
attr_content_hash(attr, state);
Ir::Select {
expr,
attrpath,
default,
..
} => {
ir_content_hash(*expr, token, state);
attrpath.len().hash(state);
for attr in attrpath.iter() {
attr_content_hash(attr, token, state);
}
x.default.is_some().hash(state);
if let Some(d) = x.default {
ir_content_hash(d, state);
default.is_some().hash(state);
if let Some(d) = default {
ir_content_hash(*d, token, state);
}
}
Ir::If(x) => {
ir_content_hash(x.cond, state);
ir_content_hash(x.consq, state);
ir_content_hash(x.alter, state);
&Ir::If { cond, consq, alter } => {
ir_content_hash(cond, token, state);
ir_content_hash(consq, token, state);
ir_content_hash(alter, token, state);
}
Ir::Call(x) => {
ir_content_hash(x.func, state);
ir_content_hash(x.arg, state);
&Ir::Call { func, arg, .. } => {
ir_content_hash(func, token, state);
ir_content_hash(arg, token, state);
}
Ir::Assert(x) => {
ir_content_hash(x.assertion, state);
ir_content_hash(x.expr, state);
x.assertion_raw.hash(state);
Ir::Assert {
assertion,
expr,
assertion_raw,
..
} => {
ir_content_hash(*assertion, token, state);
ir_content_hash(*expr, token, state);
assertion_raw.hash(state);
}
Ir::ConcatStrings(x) => {
x.force_string.hash(state);
x.parts.len().hash(state);
for part in x.parts.iter() {
ir_content_hash(part, state);
Ir::ConcatStrings {
force_string,
parts,
} => {
force_string.hash(state);
parts.len().hash(state);
for &part in parts.iter() {
ir_content_hash(part, token, state);
}
}
Ir::Path(x) => ir_content_hash(x.expr, state),
Ir::Func(x) => {
ir_content_hash(x.body, state);
ir_content_hash(x.arg, state);
x.param.is_some().hash(state);
if let Some(p) = &x.param {
&Ir::Path(expr) => ir_content_hash(expr, token, state),
Ir::Func {
body,
arg,
param,
thunks,
} => {
ir_content_hash(*body, token, state);
arg.hash(state);
param.is_some().hash(state);
if let Some(p) = param {
param_content_hash(p, state);
}
thunks_content_hash(&x.thunks, state);
thunks_content_hash(thunks, token, state);
}
Ir::TopLevel(x) => {
ir_content_hash(x.body, state);
thunks_content_hash(&x.thunks, state);
Ir::TopLevel { body, thunks } => {
ir_content_hash(*body, token, state);
thunks_content_hash(thunks, token, state);
}
Ir::Arg(x) => x.inner.hash(state),
Ir::Thunk(x) => x.inner.hash(state),
Ir::Builtins(_) => {}
Ir::Builtin(x) => x.inner.hash(state),
Ir::CurPos(x) => x.span.hash(state),
Ir::ReplBinding(x) => x.inner.hash(state),
Ir::ScopedImportBinding(x) => x.inner.hash(state),
Ir::With(x) => {
ir_content_hash(x.namespace, state);
ir_content_hash(x.body, state);
thunks_content_hash(&x.thunks, state);
Ir::Arg(x) => x.hash(state),
Ir::Thunk(x) => x.hash(state),
Ir::Builtins => {}
Ir::Builtin(x) => x.hash(state),
Ir::CurPos(x) => x.hash(state),
Ir::ReplBinding(x) => x.hash(state),
Ir::ScopedImportBinding(x) => x.hash(state),
&Ir::With {
namespace,
body,
ref thunks,
} => {
ir_content_hash(namespace, token, state);
ir_content_hash(body, token, state);
thunks_content_hash(thunks, token, state);
}
Ir::WithLookup(x) => x.inner.hash(state),
Ir::WithLookup(x) => x.hash(state),
}
}
fn ir_content_eq(a: &Ir<'_>, b: &Ir<'_>) -> bool {
std::ptr::eq(a, b)
|| match (a, b) {
(Ir::Int(a), Ir::Int(b)) => a.inner == b.inner,
(Ir::Float(a), Ir::Float(b)) => a.inner.to_bits() == b.inner.to_bits(),
(Ir::Bool(a), Ir::Bool(b)) => a.inner == b.inner,
(Ir::Null(_), Ir::Null(_)) => true,
(Ir::Str(a), Ir::Str(b)) => *a.inner == *b.inner,
(Ir::AttrSet(a), Ir::AttrSet(b)) => {
a.stcs.len() == b.stcs.len()
&& a.dyns.len() == b.dyns.len()
&& a.stcs.iter().all(|(&k, (v, _))| {
b.stcs.get(&k).is_some_and(|(bv, _)| ir_content_eq(v, bv))
pub(crate) fn ir_content_eq<'id, 'ir>(
a: IrRef<'id, 'ir>,
b: IrRef<'id, 'ir>,
token: &GhostToken<'id>,
) -> bool {
std::ptr::eq(a.0, b.0)
|| match (a.borrow(token), b.borrow(token)) {
(Ir::Int(a), Ir::Int(b)) => a == b,
(Ir::Float(a), Ir::Float(b)) => a.to_bits() == b.to_bits(),
(Ir::Bool(a), Ir::Bool(b)) => a == b,
(Ir::Null, Ir::Null) => true,
(Ir::Str(a), Ir::Str(b)) => **a == **b,
(
Ir::AttrSet {
stcs: a_stcs,
dyns: a_dyns,
},
Ir::AttrSet {
stcs: b_stcs,
dyns: b_dyns,
},
) => {
a_stcs.len() == b_stcs.len()
&& a_dyns.len() == b_dyns.len()
&& a_stcs.iter().all(|(&k, &(av, _))| {
b_stcs
.get(&k)
.is_some_and(|&(bv, _)| ir_content_eq(av, bv, token))
})
&& a.dyns
&& a_dyns
.iter()
.zip(b.dyns.iter())
.all(|((ak, av, _), (bk, bv, _))| {
ir_content_eq(ak, bk) && ir_content_eq(av, bv)
.zip(b_dyns.iter())
.all(|(&(ak, av, _), &(bk, bv, _))| {
ir_content_eq(ak, bk, token) && ir_content_eq(av, bv, token)
})
}
(Ir::List(a), Ir::List(b)) => {
a.items.len() == b.items.len()
&& a.items
.iter()
.zip(b.items.iter())
.all(|(a, b)| ir_content_eq(a, b))
(Ir::List { items: a }, Ir::List { items: b }) => {
a.len() == b.len()
&& a.iter()
.zip(b.iter())
.all(|(&a, &b)| ir_content_eq(a, b, token))
}
(Ir::HasAttr(a), Ir::HasAttr(b)) => {
ir_content_eq(a.lhs, b.lhs)
&& a.rhs.len() == b.rhs.len()
&& a.rhs
(Ir::HasAttr { lhs: al, rhs: ar }, Ir::HasAttr { lhs: bl, rhs: br }) => {
ir_content_eq(*al, *bl, token)
&& ar.len() == br.len()
&& ar
.iter()
.zip(b.rhs.iter())
.all(|(a, b)| attr_content_eq(a, b))
.zip(br.iter())
.all(|(a, b)| attr_content_eq(a, b, token))
}
(Ir::BinOp(a), Ir::BinOp(b)) => {
a.kind == b.kind && ir_content_eq(a.lhs, b.lhs) && ir_content_eq(a.rhs, b.rhs)
(
&Ir::BinOp {
lhs: al,
rhs: ar,
kind: ak,
},
&Ir::BinOp {
lhs: bl,
rhs: br,
kind: bk,
},
) => ak == bk && ir_content_eq(al, bl, token) && ir_content_eq(ar, br, token),
(&Ir::UnOp { rhs: ar, kind: ak }, &Ir::UnOp { rhs: br, kind: bk }) => {
ak == bk && ir_content_eq(ar, br, token)
}
(Ir::UnOp(a), Ir::UnOp(b)) => a.kind == b.kind && ir_content_eq(a.rhs, b.rhs),
(Ir::Select(a), Ir::Select(b)) => {
ir_content_eq(a.expr, b.expr)
&& a.attrpath.len() == b.attrpath.len()
&& a.attrpath
(
Ir::Select {
expr: ae,
attrpath: aa,
default: ad,
..
},
Ir::Select {
expr: be,
attrpath: ba,
default: bd,
..
},
) => {
ir_content_eq(*ae, *be, token)
&& aa.len() == ba.len()
&& aa
.iter()
.zip(b.attrpath.iter())
.all(|(a, b)| attr_content_eq(a, b))
&& match (a.default, b.default) {
(Some(a), Some(b)) => ir_content_eq(a, b),
.zip(ba.iter())
.all(|(a, b)| attr_content_eq(a, b, token))
&& match (ad, bd) {
(Some(a), Some(b)) => ir_content_eq(*a, *b, token),
(None, None) => true,
_ => false,
}
}
(Ir::If(a), Ir::If(b)) => {
ir_content_eq(a.cond, b.cond)
&& ir_content_eq(a.consq, b.consq)
&& ir_content_eq(a.alter, b.alter)
(
&Ir::If {
cond: ac,
consq: acs,
alter: aa,
},
&Ir::If {
cond: bc,
consq: bcs,
alter: ba,
},
) => {
ir_content_eq(ac, bc, token)
&& ir_content_eq(acs, bcs, token)
&& ir_content_eq(aa, ba, token)
}
(Ir::Call(a), Ir::Call(b)) => {
ir_content_eq(a.func, b.func) && ir_content_eq(a.arg, b.arg)
}
(Ir::Assert(a), Ir::Assert(b)) => {
a.assertion_raw == b.assertion_raw
&& ir_content_eq(a.assertion, b.assertion)
&& ir_content_eq(a.expr, b.expr)
}
(Ir::ConcatStrings(a), Ir::ConcatStrings(b)) => {
a.force_string == b.force_string
&& a.parts.len() == b.parts.len()
&& a.parts
(
&Ir::Call {
func: af, arg: aa, ..
},
&Ir::Call {
func: bf, arg: ba, ..
},
) => ir_content_eq(af, bf, token) && ir_content_eq(aa, ba, token),
(
Ir::Assert {
assertion: aa,
expr: ae,
assertion_raw: ar,
..
},
Ir::Assert {
assertion: ba,
expr: be,
assertion_raw: br,
..
},
) => ar == br && ir_content_eq(*aa, *ba, token) && ir_content_eq(*ae, *be, token),
(
Ir::ConcatStrings {
force_string: af,
parts: ap,
},
Ir::ConcatStrings {
force_string: bf,
parts: bp,
},
) => {
af == bf
&& ap.len() == bp.len()
&& ap
.iter()
.zip(b.parts.iter())
.all(|(a, b)| ir_content_eq(a, b))
.zip(bp.iter())
.all(|(&a, &b)| ir_content_eq(a, b, token))
}
(Ir::Path(a), Ir::Path(b)) => ir_content_eq(a.expr, b.expr),
(Ir::Func(a), Ir::Func(b)) => {
ir_content_eq(a.body, b.body)
&& ir_content_eq(a.arg, b.arg)
&& match (&a.param, &b.param) {
(&Ir::Path(a), &Ir::Path(b)) => ir_content_eq(a, b, token),
(
Ir::Func {
body: ab,
arg: aa,
param: ap,
thunks: at,
},
Ir::Func {
body: bb,
arg: ba,
param: bp,
thunks: bt,
},
) => {
ir_content_eq(*ab, *bb, token)
&& aa == ba
&& match (ap, bp) {
(Some(a), Some(b)) => param_content_eq(a, b),
(None, None) => true,
_ => false,
}
&& thunks_content_eq(&a.thunks, &b.thunks)
&& thunks_content_eq(at, bt, token)
}
(Ir::TopLevel(a), Ir::TopLevel(b)) => {
ir_content_eq(a.body, b.body) && thunks_content_eq(&a.thunks, &b.thunks)
(
Ir::TopLevel {
body: ab,
thunks: at,
},
Ir::TopLevel {
body: bb,
thunks: bt,
},
) => ir_content_eq(*ab, *bb, token) && thunks_content_eq(at, bt, token),
(Ir::Arg(a), Ir::Arg(b)) => a == b,
(Ir::Thunk(a), Ir::Thunk(b)) => a == b,
(Ir::Builtins, Ir::Builtins) => true,
(Ir::Builtin(a), Ir::Builtin(b)) => a == b,
(Ir::CurPos(a), Ir::CurPos(b)) => a == b,
(Ir::ReplBinding(a), Ir::ReplBinding(b)) => a == b,
(Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a == b,
(
Ir::With {
namespace: a_ns,
body: a_body,
thunks: a_thunks,
},
Ir::With {
namespace: b_ns,
body: b_body,
thunks: b_thunks,
},
) => {
ir_content_eq(*a_ns, *b_ns, token)
&& ir_content_eq(*a_body, *b_body, token)
&& thunks_content_eq(a_thunks, b_thunks, token)
}
(Ir::Arg(a), Ir::Arg(b)) => a.inner == b.inner,
(Ir::Thunk(a), Ir::Thunk(b)) => a.inner == b.inner,
(Ir::Builtins(_), Ir::Builtins(_)) => true,
(Ir::Builtin(a), Ir::Builtin(b)) => a.inner == b.inner,
(Ir::CurPos(a), Ir::CurPos(b)) => a.span == b.span,
(Ir::ReplBinding(a), Ir::ReplBinding(b)) => a.inner == b.inner,
(Ir::ScopedImportBinding(a), Ir::ScopedImportBinding(b)) => a.inner == b.inner,
(Ir::With(a), Ir::With(b)) => {
ir_content_eq(a.namespace, b.namespace)
&& ir_content_eq(a.body, b.body)
&& thunks_content_eq(&a.thunks, &b.thunks)
}
(Ir::WithLookup(a), Ir::WithLookup(b)) => a.inner == b.inner,
(Ir::WithLookup(a), Ir::WithLookup(b)) => a == b,
_ => false,
}
}