optimize: generate shorter code

This commit is contained in:
2026-02-18 20:57:21 +08:00
parent 04dcadfd61
commit 42031edac1
13 changed files with 234 additions and 246 deletions

2
.gitignore vendored
View File

@@ -9,4 +9,4 @@ profile.json.gz
prof.json
*.cpuprofile
*.cpuprofile.gz
*v8.log
*v8.log*

8
Cargo.lock generated
View File

@@ -1986,7 +1986,7 @@ dependencies = [
[[package]]
name = "nix-compat"
version = "0.1.0"
source = "git+https://git.snix.dev/snix/snix.git#db30e92b30e18ca4d813206ac1b60d1f670adb8c"
source = "git+https://git.snix.dev/snix/snix.git#1b37f68842a7e5e226d9dc009e9a90d400c5fb14"
dependencies = [
"bitflags",
"bstr",
@@ -2009,7 +2009,7 @@ dependencies = [
[[package]]
name = "nix-compat-derive"
version = "0.1.0"
source = "git+https://git.snix.dev/snix/snix.git#db30e92b30e18ca4d813206ac1b60d1f670adb8c"
source = "git+https://git.snix.dev/snix/snix.git#1b37f68842a7e5e226d9dc009e9a90d400c5fb14"
dependencies = [
"proc-macro2",
"quote",
@@ -3271,9 +3271,9 @@ dependencies = [
[[package]]
name = "syn-match"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783c4140d7ed89f37116e865b49e5a9fdd28608b9071a9dd1e158b50fc0a31fc"
checksum = "54b8f0a9004d6aafa6a588602a1119e6cdaacec9921aa1605383e6e7d6258fd6"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -154,6 +154,6 @@ export const unsafeGetAttrPos =
return null;
}
const span = positions.get(name) as string;
const span = positions.get(name) as number;
return mkPos(span);
};

View File

@@ -8,7 +8,7 @@ import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
import { CatchableError, isNixPath } from "./types";
interface StackFrame {
span: string;
span: number;
message: string;
}
@@ -32,7 +32,7 @@ function enrichError(error: unknown): Error {
return err;
}
const pushContext = (message: string, span: string): void => {
const pushContext = (message: string, span: number): void => {
if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift();
}
@@ -43,7 +43,7 @@ const popContext = (): void => {
callStack.pop();
};
export const withContext = <T>(message: string, span: string, fn: () => T): T => {
export const withContext = <T>(message: string, span: number, fn: () => T): T => {
pushContext(message, span);
try {
return fn();
@@ -142,8 +142,8 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
return mkPath(resolved);
};
export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => {
if (span) {
export const select = (obj: NixValue, attrpath: NixValue[], span?: number): NixValue => {
if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift();
}
@@ -190,9 +190,9 @@ export const selectWithDefault = (
obj: NixValue,
attrpath: NixValue[],
defaultVal: NixValue,
span?: string,
span?: number,
): NixValue => {
if (span) {
if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift();
}
@@ -263,8 +263,8 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
return attrs.has(forceStringValue(attrpath[attrpath.length - 1]));
};
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
if (span) {
export const call = (func: NixValue, arg: NixValue, span?: number): NixValue => {
if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift();
}
@@ -282,19 +282,19 @@ export const call = (func: NixValue, arg: NixValue, span?: string): NixValue =>
};
function callImpl(func: NixValue, arg: NixValue): NixValue {
const forcedFunc = force(func);
if (typeof forcedFunc === "function") {
forcedFunc.args?.check(arg);
return forcedFunc(arg);
const forced = force(func);
if (typeof forced === "function") {
forced.args?.check(arg);
return forced(arg);
}
if (forcedFunc instanceof Map && forcedFunc.has("__functor")) {
const functor = forceFunction(forcedFunc.get("__functor") as NixValue);
return call(functor(forcedFunc), arg);
if (forced instanceof Map && forced.has("__functor")) {
const functor = forceFunction(forced.get("__functor") as NixValue);
return call(callImpl(functor, forced), arg);
}
throw new Error(`attempt to call something which is not a function but ${typeOf(forcedFunc)}`);
throw new Error(`attempt to call something which is not a function but ${typeOf(forced)}`);
}
export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string, span: string): NixValue => {
export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string, span: number): NixValue => {
if (forceBool(assertion)) {
return expr;
}
@@ -304,14 +304,7 @@ export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string
throw "unreachable";
};
export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => {
if (forceBool(cond)) {
return consq;
}
return alter;
};
export const mkPos = (span: string): NixAttrs => {
export const mkPos = (span: number): NixAttrs => {
return new Map(Object.entries(Deno.core.ops.op_decode_span(span)));
};

View File

@@ -5,7 +5,6 @@
*/
import { builtins, PRIMOP_METADATA } from "./builtins";
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
import {
assert,
call,
@@ -16,40 +15,31 @@ import {
resolvePath,
select,
selectWithDefault,
withContext,
} from "./helpers";
import { op } from "./operators";
import { HAS_CONTEXT } from "./string-context";
import {
createThunk,
DEBUG_THUNKS,
force,
forceDeep,
forceShallow,
IS_CYCLE,
IS_THUNK,
isThunk,
} from "./thunk";
import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk";
import { forceBool } from "./type-assert";
import { ATTR_POSITIONS, IS_PATH, mkAttrs, mkAttrsWithPos, mkFunction, type NixValue } from "./types";
import { IS_PATH, mkAttrs, mkFunction, type NixValue } from "./types";
export type NixRuntime = typeof Nix;
const replBindings: Map<string, NixValue> = new Map();
export const Nix = {
createThunk,
force,
forceShallow,
forceDeep,
forceBool,
isThunk,
IS_THUNK,
IS_CYCLE,
HAS_CONTEXT,
IS_PATH,
PRIMOP_METADATA,
DEBUG_THUNKS,
createThunk,
force,
forceBool,
forceShallow,
forceDeep,
assert,
call,
hasAttr,
@@ -57,20 +47,13 @@ export const Nix = {
selectWithDefault,
lookupWith,
resolvePath,
coerceToString,
concatStringsWithContext,
StringCoercionMode,
mkAttrs,
mkAttrsWithPos,
mkFunction,
mkPos,
ATTR_POSITIONS,
withContext,
op,
builtins,
PRIMOP_METADATA,
replBindings,
setReplBinding: (name: string, value: NixValue) => {
@@ -80,3 +63,29 @@ export const Nix = {
};
globalThis.Nix = Nix;
globalThis.$t = createThunk;
globalThis.$f = force;
globalThis.$fb = forceBool;
globalThis.$a = assert;
globalThis.$c = call;
globalThis.$h = hasAttr;
globalThis.$s = select;
globalThis.$sd = selectWithDefault;
globalThis.$l = lookupWith;
globalThis.$r = resolvePath;
globalThis.$cs = concatStringsWithContext;
globalThis.$ma = mkAttrs;
globalThis.$mf = mkFunction;
globalThis.$mp = mkPos;
globalThis.$gb = Nix.getReplBinding;
globalThis.$oa = op.add;
globalThis.$os = op.sub;
globalThis.$om = op.mul;
globalThis.$od = op.div;
globalThis.$oe = op.eq;
globalThis.$ol = op.lt;
globalThis.$og = op.gt;
globalThis.$oc = op.concat;
globalThis.$ou = op.update;
globalThis.$b = builtins;

View File

@@ -27,15 +27,15 @@ export type NixNull = null;
export const ATTR_POSITIONS = Symbol("attrPositions");
export type NixList = NixValue[];
export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, string> };
export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, number> };
export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs };
export class NixArgs {
required: string[];
optional: string[];
allowed: Set<string>;
ellipsis: boolean;
positions: Map<string, string>;
constructor(required: string[], optional: string[], positions: Map<string, string>, ellipsis: boolean) {
positions: Map<string, number>;
constructor(required: string[], optional: string[], positions: Map<string, number>, ellipsis: boolean) {
this.required = required;
this.optional = optional;
this.positions = positions;
@@ -64,7 +64,7 @@ export const mkFunction = (
f: (arg: NixValue) => NixValue,
required: string[],
optional: string[],
positions: Map<string, string>,
positions: Map<string, number>,
ellipsis: boolean,
): NixFunction => {
const func: NixFunction = f;
@@ -72,23 +72,10 @@ export const mkFunction = (
return func;
};
export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]): NixAttrs => {
const len = keys.length;
for (let i = 0; i < len; i++) {
const key = force(keys[i]);
if (key === null) {
continue;
}
const str = forceStringNoCtx(key);
attrs.set(str, values[i]);
}
return attrs;
};
export const mkAttrsWithPos = (
export const mkAttrs = (
attrs: NixAttrs,
positions: Map<string, string>,
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] },
positions: Map<string, number>,
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: number[] },
): NixAttrs => {
if (dyns) {
const len = dyns.dynKeys.length;

View File

@@ -1,8 +1,50 @@
import type { NixRuntime } from "..";
import type { builtins } from "../builtins";
import type { FetchGitResult, FetchTarballResult, FetchUrlResult } from "../builtins/io";
import type {
assert,
call,
concatStringsWithContext,
hasAttr,
lookupWith,
mkPos,
resolvePath,
select,
selectWithDefault,
} from "../helpers";
import type { op } from "../operators";
import type { createThunk, force } from "../thunk";
import type { forceBool } from "../type-assert";
import type { mkAttrs, mkFunction } from "../types";
declare global {
var Nix: NixRuntime;
var $t: typeof createThunk;
var $f: typeof force;
var $fb: typeof forceBool;
var $a: typeof assert;
var $c: typeof call;
var $h: typeof hasAttr;
var $s: typeof select;
var $sd: typeof selectWithDefault;
var $l: typeof lookupWith;
var $r: typeof resolvePath;
var $cs: typeof concatStringsWithContext;
var $ma: typeof mkAttrs;
var $mf: typeof mkFunction;
var $mp: typeof mkPos;
var $oa: typeof op.add;
var $os: typeof op.sub;
var $om: typeof op.mul;
var $od: typeof op.div;
var $oe: typeof op.eq;
var $ol: typeof op.lt;
var $og: typeof op.gt;
var $oc: typeof op.concat;
var $ou: typeof op.update;
var $b: typeof builtins;
var $gb: typeof Nix.getReplBinding;
namespace Deno {
namespace core {
namespace ops {
@@ -20,11 +62,7 @@ declare global {
function op_make_placeholder(output: string): string;
function op_store_path(path: string): string;
function op_convert_hash(input: {
hash: string;
hashAlgo: string | null;
toHashFormat: string;
}): string;
function op_convert_hash(hash: string, hashAlgo: string | null, toHashFormat: string): string;
function op_hash_string(algo: string, data: string): string;
function op_hash_file(algo: string, path: string): string;
function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string };
@@ -43,7 +81,7 @@ declare global {
includePaths: string[],
): string;
function op_decode_span(span: string): {
function op_decode_span(span: number): {
file: string | null;
line: number | null;
column: number | null;

View File

@@ -31,14 +31,10 @@ pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
code!(&mut buf, ctx; "(()=>{");
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
}
code!(&mut buf, ctx;
"const __currentDir="
"const _d="
quoted(&ctx.get_current_dir().display().to_string())
";const __with=null;return "
",_w=null;return "
expr
"})()");
@@ -48,16 +44,12 @@ pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String {
let mut buf = CodeBuffer::with_capacity(8192);
code!(&mut buf, ctx; "((__scope)=>{");
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
}
code!(&mut buf, ctx; "((_s)=>{");
code!(&mut buf, ctx;
"const __currentDir="
"const _d="
quoted(&ctx.get_current_dir().display().to_string())
";return "
",_w=null;return "
expr
"})"
);
@@ -191,13 +183,7 @@ where
impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(
buf,
"\"{}:{}:{}\"",
ctx.get_current_source_id(),
usize::from(self.start()),
usize::from(self.end())
);
code!(buf, "{}", ctx.register_span(*self));
}
}
@@ -207,6 +193,7 @@ pub(crate) trait CodegenContext {
fn get_current_dir(&self) -> &Path;
fn get_store_dir(&self) -> &str;
fn get_current_source_id(&self) -> usize;
fn register_span(&self, range: rnix::TextRange) -> usize;
}
impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
@@ -240,7 +227,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
code!(buf, ctx; quoted(&s.val));
}
Ir::Path(p) => {
code!(buf, ctx; "Nix.resolvePath(__currentDir," ctx.get_ir(p.expr) ")");
// Nix.resolvePath
code!(buf, ctx; "$r(_d," ctx.get_ir(p.expr) ")");
}
Ir::If(x) => x.compile(ctx, buf),
Ir::BinOp(x) => x.compile(ctx, buf),
@@ -258,11 +246,13 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
code!(buf, "expr{}", expr_id.0);
}
Ir::Builtins(_) => {
code!(buf, ctx; "Nix.builtins");
// Nix.builtins
code!(buf, ctx; "$b");
}
&Ir::Builtin(Builtin { inner: name, .. }) => {
// Nix.builtins
code!(buf, ctx;
"Nix.builtins.get("
"$b.get("
ctx.get_sym(name)
")"
);
@@ -275,50 +265,51 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
ref assertion_raw,
span: assert_span,
}) => {
let assertion_ir = ctx.get_ir(assertion);
let assertion_span = assertion_ir.span();
let assertion = ctx.get_ir(assertion);
// Nix.assert
code!(buf, ctx;
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\","
assertion_span
",()=>("
assertion_ir
")),"
ctx.get_ir(expr)
","
quoted(assertion_raw)
","
assert_span
"$a("
assertion
","
ctx.get_ir(expr)
","
quoted(assertion_raw)
","
assert_span
")"
);
}
Ir::CurPos(cur_pos) => {
// Nix.mkPos
code!(buf, ctx;
"Nix.mkPos("
cur_pos.span
"$mp("
cur_pos.span
")"
);
}
&Ir::ReplBinding(ReplBinding { inner: name, .. }) => {
// Nix.getReplBinding
code!(buf, ctx;
"Nix.getReplBinding("
"$gb("
ctx.get_sym(name)
")"
);
}
&Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => {
code!(buf, ctx;
"__scope.get("
"_s.get("
ctx.get_sym(name)
")"
);
}
Ir::WithExpr(x) => x.compile(ctx, buf),
&Ir::WithLookup(WithLookup { inner: name, .. }) => {
// Nix.lookupWith
code!(buf, ctx;
"Nix.lookupWith("
"$l("
ctx.get_sym(name)
",__with)"
",_w)"
);
}
}
@@ -333,13 +324,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for If {
alter,
span: _,
} = self;
let cond_ir = ctx.get_ir(cond);
let cond_span = cond_ir.span();
let cond = ctx.get_ir(cond);
code!(buf, ctx;
"(Nix.withContext(\"while evaluating a branch condition\"," cond_span ",()=>Nix.forceBool(" cond_ir ")))"
"?(" consq "):(" alter ")"
);
// Nix.forceBool
code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")");
}
}
@@ -352,65 +340,50 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
match self.kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => {
let op_name = match self.kind {
Add => "+",
Sub => "-",
Mul => "*",
Div => "/",
Eq => "==",
Neq => "!=",
Lt => "<",
Gt => ">",
Leq => "<=",
Geq => ">=",
Con => "++",
Upd => "//",
_ => unreachable!(),
};
let op_func = match self.kind {
Add => "Nix.op.add",
Sub => "Nix.op.sub",
Mul => "Nix.op.mul",
Div => "Nix.op.div",
Eq => "Nix.op.eq",
Neq => "Nix.op.neq",
Lt => "Nix.op.lt",
Gt => "Nix.op.gt",
Leq => "Nix.op.lte",
Geq => "Nix.op.gte",
Con => "Nix.op.concat",
Upd => "Nix.op.update",
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;
"Nix.withContext(\"while evaluating the " op_name " operator\"," self.span ",()=>(" op_func "(" lhs "," rhs ")))"
op_func "(" lhs "," rhs ")"
);
}
And => {
code!(
buf, ctx;
"Nix.withContext(\"while evaluating the && operator\"," self.span ",()=>(Nix.forceBool(" lhs ")&&Nix.forceBool(" rhs ")))"
"$fb(" lhs ")" "&&" "$fb(" rhs ")"
);
}
Or => {
code!(
buf, ctx;
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
"$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
Impl => {
code!(
buf, ctx;
"Nix.withContext(\"while evaluating the -> operator\"," self.span ",()=>(!Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))"
"!$fb(" lhs ")" "||" "$fb(" rhs ")"
);
}
PipeL => {
code!(buf, ctx; "Nix.call(" rhs "," lhs ")");
code!(buf, ctx; "$c(" rhs "," lhs ")");
}
PipeR => {
code!(buf, ctx; "Nix.call(" lhs "," rhs ")");
code!(buf, ctx; "$c(" lhs "," rhs ")");
}
}
}
@@ -422,10 +395,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
let rhs = ctx.get_ir(self.rhs);
match self.kind {
Neg => {
code!(buf, ctx; "Nix.op.sub(0n," rhs ")");
code!(buf, ctx; "$os(0n," rhs ")");
}
Not => {
code!(buf, ctx; "Nix.op.bnot(" ctx.get_ir(self.rhs) ")");
code!(buf, ctx; "!$fb(" ctx.get_ir(self.rhs) ")");
}
}
}
@@ -443,7 +416,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
ellipsis,
}) = &self.param
{
code!(buf, "Nix.mkFunction(arg{}=>", id);
code!(buf, "$mf(arg{}=>", id);
if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else {
@@ -458,11 +431,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
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; ".set(" ctx.get_sym(sym) "," span ")");
"],new Map(["
joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
","
"]),"
ellipsis
")"
);
@@ -480,7 +453,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
impl<Ctx: CodegenContext> Compile<Ctx> for Call {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"Nix.call("
"$c("
ctx.get_ir(self.func)
","
ctx.get_ir(self.arg)
@@ -501,7 +474,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for [(ExprId, ExprId)] {
let inner_ir = ctx.get_ir(inner);
code!(
buf, ctx;
"let expr" slot.0 "=Nix.createThunk(()=>(" inner_ir "),"
"let expr" slot.0 "=$t(()=>(" inner_ir "),"
"\"expr" slot.0 "\");"
);
}
@@ -525,9 +498,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for WithExpr {
let body = ctx.get_ir(self.body);
let has_thunks = !self.thunks.is_empty();
if has_thunks {
code!(buf, ctx; "((__with)=>{" self.thunks "return " body "})({env:" namespace ",last:__with})");
code!(buf, ctx; "((_w)=>{" self.thunks "return " body "})({env:" namespace ",last:_w})");
} else {
code!(buf, ctx; "((__with)=>(" body "))({env:" namespace ",last:__with})");
code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})");
}
}
}
@@ -536,7 +509,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if let Some(default) = self.default {
code!(buf, ctx;
"Nix.selectWithDefault("
"$sd("
ctx.get_ir(self.expr)
",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
@@ -553,7 +526,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
);
} else {
code!(buf, ctx;
"Nix.select("
"$s("
ctx.get_ir(self.expr)
",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
@@ -574,31 +547,28 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if !self.dyns.is_empty() {
code!(buf, ctx;
"Nix.mkAttrsWithPos(new Map()"
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
"$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr);
code!(
buf, ctx;
".set(" key ",Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val ")))"
"[" key "," val "]"
);
})
",new Map()"
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")");
"]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
",{dynKeys:["
"]),{dynKeys:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; ctx.get_ir(*key));
})
"],dynVals:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
let val = ctx.get_ir(*val);
code!(
buf, ctx;
"Nix.withContext(\"while evaluating a dynamic attribute\"," val.span() ",()=>(" val "))"
);
code!(buf, ctx; val);
})
"],dynSpans:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
@@ -608,21 +578,21 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
);
} else if !self.stcs.is_empty() {
code!(buf, ctx;
"Nix.mkAttrsWithPos(new Map()"
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
"$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr);
code!(
buf, ctx;
".set(" key ",Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val ")))"
"[" key "," val "]"
);
})
",new Map()"
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")");
"]),new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
")"
"]))"
);
} else {
code!(buf, ctx; "new Map()");
@@ -634,12 +604,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"["
joined(self.items.iter().enumerate(), ",", |ctx: &Ctx, buf, (idx, item)| {
joined(self.items.iter(), ",", |ctx: &Ctx, buf, item| {
let item = ctx.get_ir(*item);
code!(
buf, ctx;
"Nix.withContext(\"while evaluating list element " idx "\"," item.span() ",()=>(" item "))"
);
code!(buf, ctx; item);
})
"]"
);
@@ -649,13 +616,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"Nix.concatStringsWithContext(["
"$cs(["
joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| {
let part = ctx.get_ir(*part);
code!(
buf, ctx;
"Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))"
);
code!(buf, ctx; part);
})
"]," self.force_string ")"
);
@@ -665,7 +629,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx;
"Nix.hasAttr("
"$h("
ctx.get_ir(self.lhs)
",["
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| {

View File

@@ -1,3 +1,4 @@
use std::cell::UnsafeCell;
use std::path::Path;
use std::ptr::NonNull;
@@ -123,9 +124,14 @@ impl Context {
let code = self.ctx.compile(source, None)?;
self.runtime.eval(
format!(
"Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}')",
"Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}');{}0n",
code,
self.get_store_dir()
self.get_store_dir(),
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
"Nix.DEBUG_THUNKS.enabled=true;"
} else {
""
}
),
&mut self.ctx,
)?;
@@ -182,6 +188,7 @@ pub(crate) struct Ctx {
global: NonNull<HashMap<SymId, ExprId>>,
sources: Vec<Source>,
store: DaemonStore,
spans: UnsafeCell<Vec<(usize, TextRange)>>,
}
impl Ctx {
@@ -278,6 +285,7 @@ impl Ctx {
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
sources: Vec::new(),
store,
spans: UnsafeCell::new(Vec::new()),
})
}
@@ -366,6 +374,12 @@ impl CodegenContext for Ctx {
fn get_store_dir(&self) -> &str {
self.store.get_store_dir()
}
fn register_span(&self, range: rnix::TextRange) -> usize {
let spans = unsafe { &mut *self.spans.get() };
let id = spans.len();
spans.push((self.get_current_source_id(), range));
id
}
}
impl RuntimeContext for Ctx {
@@ -387,6 +401,10 @@ impl RuntimeContext for Ctx {
fn get_store(&self) -> &DaemonStore {
&self.store
}
fn get_span(&self, id: usize) -> (usize, rnix::TextRange) {
let spans = unsafe { &*self.spans.get() };
spans[id]
}
}
enum Scope<'ctx> {

View File

@@ -292,43 +292,32 @@ fn parse_frames(stack: &str, ctx: &impl RuntimeContext) -> Vec<NixStackFrame> {
let mut frames = Vec::new();
for line in stack.lines() {
// Format: NIX_STACK_FRAME:source_id:start:end[:extra_data]
// Format: NIX_STACK_FRAME:span_id:message
let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else {
continue;
};
let parts: Vec<&str> = rest.splitn(4, ':').collect();
let parts: Vec<&str> = rest.splitn(2, ':').collect();
if parts.len() < 3 {
if parts.is_empty() {
continue;
}
let src = match parts[0].parse() {
Ok(id) => ctx.get_source(id),
Err(_) => continue,
};
let start: u32 = match parts[1].parse() {
Ok(v) => v,
Err(_) => continue,
};
let end: u32 = match parts[2].parse() {
Ok(v) => v,
let span_id: usize = match parts[0].parse() {
Ok(id) => id,
Err(_) => continue,
};
let (source_id, span) = ctx.get_span(span_id);
let src = ctx.get_source(source_id);
let span = rnix::TextRange::new(rnix::TextSize::from(start), rnix::TextSize::from(end));
let message = {
if parts.len() == 4 {
parts[3].to_string()
} else {
String::new()
}
let message = if parts.len() == 2 {
parts[1].to_string()
} else {
String::new()
};
frames.push(NixStackFrame { span, message, src });
}
// Deduplicate consecutive identical frames
frames.dedup_by(|a, b| a.span == b.span && a.message == b.message);
frames

View File

@@ -26,6 +26,7 @@ pub(crate) trait RuntimeContext: 'static {
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>;
fn get_source(&self, id: usize) -> Source;
fn get_store(&self) -> &DaemonStore;
fn get_span(&self, id: usize) -> (usize, rnix::TextRange);
}
pub(crate) trait OpStateExt<Ctx: RuntimeContext> {

View File

@@ -21,10 +21,10 @@ use deno_core::url::Url;
use fastwebsockets::Frame;
use fastwebsockets::OpCode;
use fastwebsockets::WebSocket;
use hashbrown::HashMap;
use hyper::body::Bytes;
use hyper_util::rt::TokioIo;
use std::cell::RefCell;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::pin::pin;
use std::process;

View File

@@ -259,25 +259,14 @@ pub(super) fn op_make_placeholder(#[string] output: String) -> String {
#[serde]
pub(super) fn op_decode_span<Ctx: RuntimeContext>(
state: &mut OpState,
#[string] span_str: String,
#[smi] span_id: u32,
) -> Result<serde_json::Value> {
let parts: Vec<&str> = span_str.split(':').collect();
if parts.len() != 3 {
return Ok(serde_json::json!({
"file": serde_json::Value::Null,
"line": serde_json::Value::Null,
"column": serde_json::Value::Null
}));
}
let source_id: usize = parts[0].parse().map_err(|_| "Invalid source ID")?;
let start: u32 = parts[1].parse().map_err(|_| "Invalid start offset")?;
let ctx: &Ctx = state.get_ctx();
let (source_id, range) = ctx.get_span(span_id as usize);
let source = ctx.get_source(source_id);
let content = &source.src;
let start = u32::from(range.start());
let (line, column) = byte_offset_to_line_col(content, start as usize);
let (line, column) = byte_offset_to_line_col(&source.src, start as usize);
Ok(serde_json::json!({
"file": source.get_name(),