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 prof.json
*.cpuprofile *.cpuprofile
*.cpuprofile.gz *.cpuprofile.gz
*v8.log *v8.log*

8
Cargo.lock generated
View File

@@ -1986,7 +1986,7 @@ dependencies = [
[[package]] [[package]]
name = "nix-compat" name = "nix-compat"
version = "0.1.0" 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 = [ dependencies = [
"bitflags", "bitflags",
"bstr", "bstr",
@@ -2009,7 +2009,7 @@ dependencies = [
[[package]] [[package]]
name = "nix-compat-derive" name = "nix-compat-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3271,9 +3271,9 @@ dependencies = [
[[package]] [[package]]
name = "syn-match" name = "syn-match"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783c4140d7ed89f37116e865b49e5a9fdd28608b9071a9dd1e158b50fc0a31fc" checksum = "54b8f0a9004d6aafa6a588602a1119e6cdaacec9921aa1605383e6e7d6258fd6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

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

View File

@@ -8,7 +8,7 @@ import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
import { CatchableError, isNixPath } from "./types"; import { CatchableError, isNixPath } from "./types";
interface StackFrame { interface StackFrame {
span: string; span: number;
message: string; message: string;
} }
@@ -32,7 +32,7 @@ function enrichError(error: unknown): Error {
return err; return err;
} }
const pushContext = (message: string, span: string): void => { const pushContext = (message: string, span: number): void => {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
@@ -43,7 +43,7 @@ const popContext = (): void => {
callStack.pop(); 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); pushContext(message, span);
try { try {
return fn(); return fn();
@@ -142,8 +142,8 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
return mkPath(resolved); return mkPath(resolved);
}; };
export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => { export const select = (obj: NixValue, attrpath: NixValue[], span?: number): NixValue => {
if (span) { if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
@@ -190,9 +190,9 @@ export const selectWithDefault = (
obj: NixValue, obj: NixValue,
attrpath: NixValue[], attrpath: NixValue[],
defaultVal: NixValue, defaultVal: NixValue,
span?: string, span?: number,
): NixValue => { ): NixValue => {
if (span) { if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
@@ -263,8 +263,8 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
return attrs.has(forceStringValue(attrpath[attrpath.length - 1])); return attrs.has(forceStringValue(attrpath[attrpath.length - 1]));
}; };
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => { export const call = (func: NixValue, arg: NixValue, span?: number): NixValue => {
if (span) { if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
@@ -282,19 +282,19 @@ export const call = (func: NixValue, arg: NixValue, span?: string): NixValue =>
}; };
function callImpl(func: NixValue, arg: NixValue): NixValue { function callImpl(func: NixValue, arg: NixValue): NixValue {
const forcedFunc = force(func); const forced = force(func);
if (typeof forcedFunc === "function") { if (typeof forced === "function") {
forcedFunc.args?.check(arg); forced.args?.check(arg);
return forcedFunc(arg); return forced(arg);
} }
if (forcedFunc instanceof Map && forcedFunc.has("__functor")) { if (forced instanceof Map && forced.has("__functor")) {
const functor = forceFunction(forcedFunc.get("__functor") as NixValue); const functor = forceFunction(forced.get("__functor") as NixValue);
return call(functor(forcedFunc), arg); 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)) { if (forceBool(assertion)) {
return expr; return expr;
} }
@@ -304,14 +304,7 @@ export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string
throw "unreachable"; throw "unreachable";
}; };
export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => { export const mkPos = (span: number): NixAttrs => {
if (forceBool(cond)) {
return consq;
}
return alter;
};
export const mkPos = (span: string): NixAttrs => {
return new Map(Object.entries(Deno.core.ops.op_decode_span(span))); 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 { builtins, PRIMOP_METADATA } from "./builtins";
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
import { import {
assert, assert,
call, call,
@@ -16,40 +15,31 @@ import {
resolvePath, resolvePath,
select, select,
selectWithDefault, selectWithDefault,
withContext,
} from "./helpers"; } from "./helpers";
import { op } from "./operators"; import { op } from "./operators";
import { HAS_CONTEXT } from "./string-context"; import { HAS_CONTEXT } from "./string-context";
import { import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk";
createThunk,
DEBUG_THUNKS,
force,
forceDeep,
forceShallow,
IS_CYCLE,
IS_THUNK,
isThunk,
} from "./thunk";
import { forceBool } from "./type-assert"; 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; export type NixRuntime = typeof Nix;
const replBindings: Map<string, NixValue> = new Map(); const replBindings: Map<string, NixValue> = new Map();
export const Nix = { export const Nix = {
createThunk,
force,
forceShallow,
forceDeep,
forceBool,
isThunk,
IS_THUNK, IS_THUNK,
IS_CYCLE, IS_CYCLE,
HAS_CONTEXT, HAS_CONTEXT,
IS_PATH, IS_PATH,
PRIMOP_METADATA,
DEBUG_THUNKS, DEBUG_THUNKS,
createThunk,
force,
forceBool,
forceShallow,
forceDeep,
assert, assert,
call, call,
hasAttr, hasAttr,
@@ -57,20 +47,13 @@ export const Nix = {
selectWithDefault, selectWithDefault,
lookupWith, lookupWith,
resolvePath, resolvePath,
coerceToString,
concatStringsWithContext, concatStringsWithContext,
StringCoercionMode,
mkAttrs, mkAttrs,
mkAttrsWithPos,
mkFunction, mkFunction,
mkPos, mkPos,
ATTR_POSITIONS,
withContext,
op, op,
builtins, builtins,
PRIMOP_METADATA,
replBindings, replBindings,
setReplBinding: (name: string, value: NixValue) => { setReplBinding: (name: string, value: NixValue) => {
@@ -80,3 +63,29 @@ export const Nix = {
}; };
globalThis.Nix = 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 const ATTR_POSITIONS = Symbol("attrPositions");
export type NixList = NixValue[]; 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 type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs };
export class NixArgs { export class NixArgs {
required: string[]; required: string[];
optional: string[]; optional: string[];
allowed: Set<string>; allowed: Set<string>;
ellipsis: boolean; ellipsis: boolean;
positions: Map<string, string>; positions: Map<string, number>;
constructor(required: string[], optional: string[], positions: Map<string, string>, ellipsis: boolean) { constructor(required: string[], optional: string[], positions: Map<string, number>, ellipsis: boolean) {
this.required = required; this.required = required;
this.optional = optional; this.optional = optional;
this.positions = positions; this.positions = positions;
@@ -64,7 +64,7 @@ export const mkFunction = (
f: (arg: NixValue) => NixValue, f: (arg: NixValue) => NixValue,
required: string[], required: string[],
optional: string[], optional: string[],
positions: Map<string, string>, positions: Map<string, number>,
ellipsis: boolean, ellipsis: boolean,
): NixFunction => { ): NixFunction => {
const func: NixFunction = f; const func: NixFunction = f;
@@ -72,23 +72,10 @@ export const mkFunction = (
return func; return func;
}; };
export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]): NixAttrs => { export const mkAttrs = (
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 = (
attrs: NixAttrs, attrs: NixAttrs,
positions: Map<string, string>, positions: Map<string, number>,
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] }, dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: number[] },
): NixAttrs => { ): NixAttrs => {
if (dyns) { if (dyns) {
const len = dyns.dynKeys.length; const len = dyns.dynKeys.length;

View File

@@ -1,8 +1,50 @@
import type { NixRuntime } from ".."; import type { NixRuntime } from "..";
import type { builtins } from "../builtins";
import type { FetchGitResult, FetchTarballResult, FetchUrlResult } from "../builtins/io"; 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 { declare global {
var Nix: NixRuntime; 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 Deno {
namespace core { namespace core {
namespace ops { namespace ops {
@@ -20,11 +62,7 @@ declare global {
function op_make_placeholder(output: string): string; function op_make_placeholder(output: string): string;
function op_store_path(path: string): string; function op_store_path(path: string): string;
function op_convert_hash(input: { function op_convert_hash(hash: string, hashAlgo: string | null, toHashFormat: string): string;
hash: string;
hashAlgo: string | null;
toHashFormat: string;
}): string;
function op_hash_string(algo: string, data: string): string; function op_hash_string(algo: string, data: string): string;
function op_hash_file(algo: string, path: string): string; function op_hash_file(algo: string, path: string): string;
function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string }; function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string };
@@ -43,7 +81,7 @@ declare global {
includePaths: string[], includePaths: string[],
): string; ): string;
function op_decode_span(span: string): { function op_decode_span(span: number): {
file: string | null; file: string | null;
line: number | null; line: number | null;
column: 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; "(()=>{"); 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; code!(&mut buf, ctx;
"const __currentDir=" "const _d="
quoted(&ctx.get_current_dir().display().to_string()) quoted(&ctx.get_current_dir().display().to_string())
";const __with=null;return " ",_w=null;return "
expr 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 { pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String {
let mut buf = CodeBuffer::with_capacity(8192); let mut buf = CodeBuffer::with_capacity(8192);
code!(&mut buf, ctx; "((__scope)=>{"); code!(&mut buf, ctx; "((_s)=>{");
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
}
code!(&mut buf, ctx; code!(&mut buf, ctx;
"const __currentDir=" "const _d="
quoted(&ctx.get_current_dir().display().to_string()) quoted(&ctx.get_current_dir().display().to_string())
";return " ",_w=null;return "
expr expr
"})" "})"
); );
@@ -191,13 +183,7 @@ where
impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange { impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!( code!(buf, "{}", ctx.register_span(*self));
buf,
"\"{}:{}:{}\"",
ctx.get_current_source_id(),
usize::from(self.start()),
usize::from(self.end())
);
} }
} }
@@ -207,6 +193,7 @@ pub(crate) trait CodegenContext {
fn get_current_dir(&self) -> &Path; fn get_current_dir(&self) -> &Path;
fn get_store_dir(&self) -> &str; fn get_store_dir(&self) -> &str;
fn get_current_source_id(&self) -> usize; fn get_current_source_id(&self) -> usize;
fn register_span(&self, range: rnix::TextRange) -> usize;
} }
impl<Ctx: CodegenContext> Compile<Ctx> for ExprId { impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
@@ -240,7 +227,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
code!(buf, ctx; quoted(&s.val)); code!(buf, ctx; quoted(&s.val));
} }
Ir::Path(p) => { 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::If(x) => x.compile(ctx, buf),
Ir::BinOp(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); code!(buf, "expr{}", expr_id.0);
} }
Ir::Builtins(_) => { Ir::Builtins(_) => {
code!(buf, ctx; "Nix.builtins"); // Nix.builtins
code!(buf, ctx; "$b");
} }
&Ir::Builtin(Builtin { inner: name, .. }) => { &Ir::Builtin(Builtin { inner: name, .. }) => {
// Nix.builtins
code!(buf, ctx; code!(buf, ctx;
"Nix.builtins.get(" "$b.get("
ctx.get_sym(name) ctx.get_sym(name)
")" ")"
); );
@@ -275,50 +265,51 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
ref assertion_raw, ref assertion_raw,
span: assert_span, span: assert_span,
}) => { }) => {
let assertion_ir = ctx.get_ir(assertion); let assertion = ctx.get_ir(assertion);
let assertion_span = assertion_ir.span();
// Nix.assert
code!(buf, ctx; code!(buf, ctx;
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\"," "$a("
assertion_span assertion
",()=>(" ","
assertion_ir ctx.get_ir(expr)
"))," ","
ctx.get_ir(expr) quoted(assertion_raw)
"," ","
quoted(assertion_raw) assert_span
","
assert_span
")" ")"
); );
} }
Ir::CurPos(cur_pos) => { Ir::CurPos(cur_pos) => {
// Nix.mkPos
code!(buf, ctx; code!(buf, ctx;
"Nix.mkPos(" "$mp("
cur_pos.span cur_pos.span
")" ")"
); );
} }
&Ir::ReplBinding(ReplBinding { inner: name, .. }) => { &Ir::ReplBinding(ReplBinding { inner: name, .. }) => {
// Nix.getReplBinding
code!(buf, ctx; code!(buf, ctx;
"Nix.getReplBinding(" "$gb("
ctx.get_sym(name) ctx.get_sym(name)
")" ")"
); );
} }
&Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { &Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => {
code!(buf, ctx; code!(buf, ctx;
"__scope.get(" "_s.get("
ctx.get_sym(name) ctx.get_sym(name)
")" ")"
); );
} }
Ir::WithExpr(x) => x.compile(ctx, buf), Ir::WithExpr(x) => x.compile(ctx, buf),
&Ir::WithLookup(WithLookup { inner: name, .. }) => { &Ir::WithLookup(WithLookup { inner: name, .. }) => {
// Nix.lookupWith
code!(buf, ctx; code!(buf, ctx;
"Nix.lookupWith(" "$l("
ctx.get_sym(name) ctx.get_sym(name)
",__with)" ",_w)"
); );
} }
} }
@@ -333,13 +324,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for If {
alter, alter,
span: _, span: _,
} = self; } = self;
let cond_ir = ctx.get_ir(cond); let cond = ctx.get_ir(cond);
let cond_span = cond_ir.span();
code!(buf, ctx; // Nix.forceBool
"(Nix.withContext(\"while evaluating a branch condition\"," cond_span ",()=>Nix.forceBool(" cond_ir ")))" code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")");
"?(" consq "):(" alter ")"
);
} }
} }
@@ -352,65 +340,50 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
match self.kind { match self.kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => { 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 { let op_func = match self.kind {
Add => "Nix.op.add", Add => "$oa",
Sub => "Nix.op.sub", Sub => "$os",
Mul => "Nix.op.mul", Mul => "$om",
Div => "Nix.op.div", Div => "$od",
Eq => "Nix.op.eq", Eq => "$oe",
Neq => "Nix.op.neq", Neq => "!$oe",
Lt => "Nix.op.lt", Lt => "$ol",
Gt => "Nix.op.gt", Gt => "$og",
Leq => "Nix.op.lte", Leq => "!$og",
Geq => "Nix.op.gte", Geq => "!$ol",
Con => "Nix.op.concat", Con => "$oc",
Upd => "Nix.op.update", Upd => "$ou",
_ => unreachable!(), _ => unreachable!(),
}; };
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the " op_name " operator\"," self.span ",()=>(" op_func "(" lhs "," rhs ")))" op_func "(" lhs "," rhs ")"
); );
} }
And => { And => {
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the && operator\"," self.span ",()=>(Nix.forceBool(" lhs ")&&Nix.forceBool(" rhs ")))" "$fb(" lhs ")" "&&" "$fb(" rhs ")"
); );
} }
Or => { Or => {
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))" "$fb(" lhs ")" "||" "$fb(" rhs ")"
); );
} }
Impl => { Impl => {
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the -> operator\"," self.span ",()=>(!Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))" "!$fb(" lhs ")" "||" "$fb(" rhs ")"
); );
} }
PipeL => { PipeL => {
code!(buf, ctx; "Nix.call(" rhs "," lhs ")"); code!(buf, ctx; "$c(" rhs "," lhs ")");
} }
PipeR => { 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); let rhs = ctx.get_ir(self.rhs);
match self.kind { match self.kind {
Neg => { Neg => {
code!(buf, ctx; "Nix.op.sub(0n," rhs ")"); code!(buf, ctx; "$os(0n," rhs ")");
} }
Not => { 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, ellipsis,
}) = &self.param }) = &self.param
{ {
code!(buf, "Nix.mkFunction(arg{}=>", id); code!(buf, "$mf(arg{}=>", id);
if has_thunks { if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}"); code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else { } else {
@@ -458,11 +431,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym)); code!(buf, ctx; ctx.get_sym(sym));
}) })
"],new Map()" "],new Map(["
joined(required.iter().chain(optional.iter()), "", |ctx: &Ctx, buf, &(sym, span)| { joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")"); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
"," "]),"
ellipsis ellipsis
")" ")"
); );
@@ -480,7 +453,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
impl<Ctx: CodegenContext> Compile<Ctx> for Call { impl<Ctx: CodegenContext> Compile<Ctx> for Call {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"Nix.call(" "$c("
ctx.get_ir(self.func) ctx.get_ir(self.func)
"," ","
ctx.get_ir(self.arg) ctx.get_ir(self.arg)
@@ -501,7 +474,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for [(ExprId, ExprId)] {
let inner_ir = ctx.get_ir(inner); let inner_ir = ctx.get_ir(inner);
code!( code!(
buf, ctx; buf, ctx;
"let expr" slot.0 "=Nix.createThunk(()=>(" inner_ir ")," "let expr" slot.0 "=$t(()=>(" inner_ir "),"
"\"expr" slot.0 "\");" "\"expr" slot.0 "\");"
); );
} }
@@ -525,9 +498,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for WithExpr {
let body = ctx.get_ir(self.body); let body = ctx.get_ir(self.body);
let has_thunks = !self.thunks.is_empty(); let has_thunks = !self.thunks.is_empty();
if has_thunks { 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 { } 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) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if let Some(default) = self.default { if let Some(default) = self.default {
code!(buf, ctx; code!(buf, ctx;
"Nix.selectWithDefault(" "$sd("
ctx.get_ir(self.expr) ctx.get_ir(self.expr)
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
@@ -553,7 +526,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
); );
} else { } else {
code!(buf, ctx; code!(buf, ctx;
"Nix.select(" "$s("
ctx.get_ir(self.expr) ctx.get_ir(self.expr)
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { 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) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if !self.dyns.is_empty() { if !self.dyns.is_empty() {
code!(buf, ctx; code!(buf, ctx;
"Nix.mkAttrsWithPos(new Map()" "$ma(new Map(["
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(expr, _))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym); let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr); let val = ctx.get_ir(expr);
code!( code!(
buf, ctx; buf, ctx;
".set(" key ",Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val ")))" "[" key "," val "]"
); );
}) })
",new Map()" "]),new Map(["
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(_, span))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")"); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
",{dynKeys:[" "]),{dynKeys:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; ctx.get_ir(*key)); code!(buf, ctx; ctx.get_ir(*key));
}) })
"],dynVals:[" "],dynVals:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| { joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
let val = ctx.get_ir(*val); let val = ctx.get_ir(*val);
code!( code!(buf, ctx; val);
buf, ctx;
"Nix.withContext(\"while evaluating a dynamic attribute\"," val.span() ",()=>(" val "))"
);
}) })
"],dynSpans:[" "],dynSpans:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| { 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() { } else if !self.stcs.is_empty() {
code!(buf, ctx; code!(buf, ctx;
"Nix.mkAttrsWithPos(new Map()" "$ma(new Map(["
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(expr, _))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym); let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr); let val = ctx.get_ir(expr);
code!( code!(
buf, ctx; buf, ctx;
".set(" key ",Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val ")))" "[" key "," val "]"
); );
}) })
",new Map()" "]),new Map(["
joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(_, span))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")"); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
")" "]))"
); );
} else { } else {
code!(buf, ctx; "new Map()"); code!(buf, ctx; "new Map()");
@@ -634,12 +604,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; 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); let item = ctx.get_ir(*item);
code!( code!(buf, ctx; item);
buf, ctx;
"Nix.withContext(\"while evaluating list element " idx "\"," item.span() ",()=>(" item "))"
);
}) })
"]" "]"
); );
@@ -649,13 +616,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings { impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"Nix.concatStringsWithContext([" "$cs(["
joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| { joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| {
let part = ctx.get_ir(*part); let part = ctx.get_ir(*part);
code!( code!(buf, ctx; part);
buf, ctx;
"Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))"
);
}) })
"]," self.force_string ")" "]," self.force_string ")"
); );
@@ -665,7 +629,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr { impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"Nix.hasAttr(" "$h("
ctx.get_ir(self.lhs) ctx.get_ir(self.lhs)
",[" ",["
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| {

View File

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

View File

@@ -292,43 +292,32 @@ fn parse_frames(stack: &str, ctx: &impl RuntimeContext) -> Vec<NixStackFrame> {
let mut frames = Vec::new(); let mut frames = Vec::new();
for line in stack.lines() { 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 { let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else {
continue; 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; continue;
} }
let src = match parts[0].parse() { let span_id: usize = match parts[0].parse() {
Ok(id) => ctx.get_source(id), Ok(id) => 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,
Err(_) => continue, 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() == 2 {
parts[1].to_string()
let message = { } else {
if parts.len() == 4 { String::new()
parts[3].to_string()
} else {
String::new()
}
}; };
frames.push(NixStackFrame { span, message, src }); frames.push(NixStackFrame { span, message, src });
} }
// Deduplicate consecutive identical frames
frames.dedup_by(|a, b| a.span == b.span && a.message == b.message); frames.dedup_by(|a, b| a.span == b.span && a.message == b.message);
frames 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 compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>;
fn get_source(&self, id: usize) -> Source; fn get_source(&self, id: usize) -> Source;
fn get_store(&self) -> &DaemonStore; fn get_store(&self) -> &DaemonStore;
fn get_span(&self, id: usize) -> (usize, rnix::TextRange);
} }
pub(crate) trait OpStateExt<Ctx: RuntimeContext> { pub(crate) trait OpStateExt<Ctx: RuntimeContext> {

View File

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

View File

@@ -259,25 +259,14 @@ pub(super) fn op_make_placeholder(#[string] output: String) -> String {
#[serde] #[serde]
pub(super) fn op_decode_span<Ctx: RuntimeContext>( pub(super) fn op_decode_span<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] span_str: String, #[smi] span_id: u32,
) -> Result<serde_json::Value> { ) -> 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 ctx: &Ctx = state.get_ctx();
let (source_id, range) = ctx.get_span(span_id as usize);
let source = ctx.get_source(source_id); 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!({ Ok(serde_json::json!({
"file": source.get_name(), "file": source.get_name(),