feat: bytecode
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -494,6 +494,15 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "3.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "combine"
|
name = "combine"
|
||||||
version = "4.6.7"
|
version = "4.6.7"
|
||||||
@@ -2055,6 +2064,7 @@ dependencies = [
|
|||||||
"bumpalo",
|
"bumpalo",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"clap",
|
"clap",
|
||||||
|
"colored",
|
||||||
"criterion",
|
"criterion",
|
||||||
"deno_core",
|
"deno_core",
|
||||||
"deno_error",
|
"deno_error",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ http-body-util = { version = "0.1", optional = true }
|
|||||||
http = { version = "1", optional = true }
|
http = { version = "1", optional = true }
|
||||||
uuid = { version = "1", features = ["v4"], optional = true }
|
uuid = { version = "1", features = ["v4"], optional = true }
|
||||||
ghost-cell = "0.2.6"
|
ghost-cell = "0.2.6"
|
||||||
|
colored = "3.1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]
|
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { CatchableError, isNixPath, NixPath } from "../types";
|
|||||||
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
||||||
import { baseNameOf } from "./path";
|
import { baseNameOf } from "./path";
|
||||||
import { isAttrs, isPath, isString } from "./type-check";
|
import { isAttrs, isPath, isString } from "./type-check";
|
||||||
|
import { execBytecode, execBytecodeScoped } from "../vm";
|
||||||
|
|
||||||
const importCache = new Map<string, NixValue>();
|
const importCache = new Map<string, NixValue>();
|
||||||
|
|
||||||
@@ -49,7 +50,8 @@ export const importFunc = (path: NixValue): NixValue => {
|
|||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = Deno.core.ops.op_import(pathStr);
|
const [code, currentDir] = Deno.core.ops.op_import(pathStr);
|
||||||
|
const result = execBytecode(code, currentDir);
|
||||||
|
|
||||||
importCache.set(pathStr, result);
|
importCache.set(pathStr, result);
|
||||||
return result;
|
return result;
|
||||||
@@ -63,10 +65,8 @@ export const scopedImport =
|
|||||||
|
|
||||||
const pathStr = realisePath(path);
|
const pathStr = realisePath(path);
|
||||||
|
|
||||||
const code = Deno.core.ops.op_scoped_import(pathStr, scopeKeys);
|
const [code, currentDir] = Deno.core.ops.op_scoped_import(pathStr, scopeKeys);
|
||||||
|
return execBytecodeScoped(code, currentDir, scopeAttrs);
|
||||||
const scopedFunc = Function(`return (${code})`)();
|
|
||||||
return scopedFunc(scopeAttrs);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const storePath = (pathArg: NixValue): StringWithContext => {
|
export const storePath = (pathArg: NixValue): StringWithContext => {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { HAS_CONTEXT } from "./string-context";
|
|||||||
import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk";
|
import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk";
|
||||||
import { forceBool } from "./type-assert";
|
import { forceBool } from "./type-assert";
|
||||||
import { IS_PATH, mkAttrs, mkFunction, type NixValue } from "./types";
|
import { IS_PATH, mkAttrs, mkFunction, type NixValue } from "./types";
|
||||||
|
import { execBytecode, execBytecodeScoped, vmStrings, vmConstants } from "./vm";
|
||||||
|
|
||||||
export type NixRuntime = typeof Nix;
|
export type NixRuntime = typeof Nix;
|
||||||
|
|
||||||
@@ -55,6 +56,11 @@ export const Nix = {
|
|||||||
op,
|
op,
|
||||||
builtins,
|
builtins,
|
||||||
|
|
||||||
|
strings: vmStrings,
|
||||||
|
constants: vmConstants,
|
||||||
|
execBytecode,
|
||||||
|
execBytecodeScoped,
|
||||||
|
|
||||||
replBindings,
|
replBindings,
|
||||||
setReplBinding: (name: string, value: NixValue) => {
|
setReplBinding: (name: string, value: NixValue) => {
|
||||||
replBindings.set(name, value);
|
replBindings.set(name, value);
|
||||||
|
|||||||
4
nix-js/runtime-ts/src/types/global.d.ts
vendored
4
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -49,8 +49,8 @@ declare global {
|
|||||||
namespace Deno {
|
namespace Deno {
|
||||||
namespace core {
|
namespace core {
|
||||||
namespace ops {
|
namespace ops {
|
||||||
function op_import(path: string): NixValue;
|
function op_import(path: string): [Uint8Array, string];
|
||||||
function op_scoped_import(path: string, scopeKeys: string[]): string;
|
function op_scoped_import(path: string, scopeKeys: string[]): [Uint8Array, string];
|
||||||
|
|
||||||
function op_resolve_path(currentDir: string, path: string): string;
|
function op_resolve_path(currentDir: string, path: string): string;
|
||||||
|
|
||||||
|
|||||||
617
nix-js/runtime-ts/src/vm.ts
Normal file
617
nix-js/runtime-ts/src/vm.ts
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
import {
|
||||||
|
assert,
|
||||||
|
call,
|
||||||
|
concatStringsWithContext,
|
||||||
|
hasAttr,
|
||||||
|
lookupWith,
|
||||||
|
mkPos,
|
||||||
|
resolvePath,
|
||||||
|
select,
|
||||||
|
selectWithDefault,
|
||||||
|
} from "./helpers";
|
||||||
|
import { op } from "./operators";
|
||||||
|
import { NixThunk } from "./thunk";
|
||||||
|
import { forceBool } from "./type-assert";
|
||||||
|
import { mkAttrs, NixArgs, type NixAttrs, type NixFunction, type NixValue } from "./types";
|
||||||
|
import { builtins } from "./builtins";
|
||||||
|
|
||||||
|
enum Op {
|
||||||
|
PushConst = 0x01,
|
||||||
|
PushString = 0x02,
|
||||||
|
PushNull = 0x03,
|
||||||
|
PushTrue = 0x04,
|
||||||
|
PushFalse = 0x05,
|
||||||
|
|
||||||
|
LoadLocal = 0x06,
|
||||||
|
LoadOuter = 0x07,
|
||||||
|
StoreLocal = 0x08,
|
||||||
|
AllocLocals = 0x09,
|
||||||
|
|
||||||
|
MakeThunk = 0x0A,
|
||||||
|
MakeClosure = 0x0B,
|
||||||
|
MakePatternClosure = 0x0C,
|
||||||
|
|
||||||
|
Call = 0x0D,
|
||||||
|
CallNoSpan = 0x0E,
|
||||||
|
|
||||||
|
MakeAttrs = 0x0F,
|
||||||
|
MakeAttrsDyn = 0x10,
|
||||||
|
MakeEmptyAttrs = 0x11,
|
||||||
|
Select = 0x12,
|
||||||
|
SelectDefault = 0x13,
|
||||||
|
HasAttr = 0x14,
|
||||||
|
|
||||||
|
MakeList = 0x15,
|
||||||
|
|
||||||
|
OpAdd = 0x16,
|
||||||
|
OpSub = 0x17,
|
||||||
|
OpMul = 0x18,
|
||||||
|
OpDiv = 0x19,
|
||||||
|
OpEq = 0x20,
|
||||||
|
OpNeq = 0x21,
|
||||||
|
OpLt = 0x22,
|
||||||
|
OpGt = 0x23,
|
||||||
|
OpLeq = 0x24,
|
||||||
|
OpGeq = 0x25,
|
||||||
|
OpConcat = 0x26,
|
||||||
|
OpUpdate = 0x27,
|
||||||
|
|
||||||
|
OpNeg = 0x28,
|
||||||
|
OpNot = 0x29,
|
||||||
|
|
||||||
|
ForceBool = 0x30,
|
||||||
|
JumpIfFalse = 0x31,
|
||||||
|
JumpIfTrue = 0x32,
|
||||||
|
Jump = 0x33,
|
||||||
|
|
||||||
|
ConcatStrings = 0x34,
|
||||||
|
ResolvePath = 0x35,
|
||||||
|
|
||||||
|
Assert = 0x36,
|
||||||
|
|
||||||
|
PushWith = 0x37,
|
||||||
|
PopWith = 0x38,
|
||||||
|
WithLookup = 0x39,
|
||||||
|
|
||||||
|
LoadBuiltins = 0x40,
|
||||||
|
LoadBuiltin = 0x41,
|
||||||
|
|
||||||
|
MkPos = 0x43,
|
||||||
|
|
||||||
|
LoadReplBinding = 0x44,
|
||||||
|
LoadScopedBinding = 0x45,
|
||||||
|
|
||||||
|
Return = 0x46,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScopeChain {
|
||||||
|
locals: NixValue[];
|
||||||
|
parent: ScopeChain | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WithScope {
|
||||||
|
env: NixValue;
|
||||||
|
last: WithScope | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const strings: string[] = [];
|
||||||
|
const constants: NixValue[] = [];
|
||||||
|
|
||||||
|
const $e: NixAttrs = new Map();
|
||||||
|
|
||||||
|
function readU16(code: Uint8Array, offset: number): number {
|
||||||
|
return code[offset] | (code[offset + 1] << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readU32(code: Uint8Array, offset: number): number {
|
||||||
|
return (
|
||||||
|
code[offset] |
|
||||||
|
(code[offset + 1] << 8) |
|
||||||
|
(code[offset + 2] << 16) |
|
||||||
|
(code[offset + 3] << 24)
|
||||||
|
) >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readI32(code: Uint8Array, offset: number): number {
|
||||||
|
return code[offset] | (code[offset + 1] << 8) | (code[offset + 2] << 16) | (code[offset + 3] << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function execBytecode(code: Uint8Array, currentDir: string): NixValue {
|
||||||
|
const chain: ScopeChain = { locals: [], parent: null };
|
||||||
|
return execFrame(code, 0, chain, currentDir, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function execBytecodeScoped(
|
||||||
|
code: Uint8Array,
|
||||||
|
currentDir: string,
|
||||||
|
scopeMap: NixAttrs,
|
||||||
|
): NixValue {
|
||||||
|
const chain: ScopeChain = { locals: [], parent: null };
|
||||||
|
return execFrame(code, 0, chain, currentDir, null, scopeMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
function execFrame(
|
||||||
|
code: Uint8Array,
|
||||||
|
startPc: number,
|
||||||
|
chain: ScopeChain,
|
||||||
|
currentDir: string,
|
||||||
|
withScope: WithScope | null,
|
||||||
|
scopeMap: NixAttrs | null,
|
||||||
|
): NixValue {
|
||||||
|
const locals = chain.locals;
|
||||||
|
const stack: NixValue[] = [];
|
||||||
|
let pc = startPc;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
const opcode = code[pc++];
|
||||||
|
switch (opcode) {
|
||||||
|
case Op.PushConst: {
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(constants[idx]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.PushString: {
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(strings[idx]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.PushNull:
|
||||||
|
stack.push(null);
|
||||||
|
break;
|
||||||
|
case Op.PushTrue:
|
||||||
|
stack.push(true);
|
||||||
|
break;
|
||||||
|
case Op.PushFalse:
|
||||||
|
stack.push(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op.LoadLocal: {
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(locals[idx]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.LoadOuter: {
|
||||||
|
const layer = code[pc++];
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
let c: ScopeChain = chain;
|
||||||
|
for (let i = 0; i < layer; i++) c = c.parent!;
|
||||||
|
stack.push(c.locals[idx]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.StoreLocal: {
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
locals[idx] = stack.pop()!;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.AllocLocals: {
|
||||||
|
const n = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
for (let i = 0; i < n; i++) locals.push(null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.MakeThunk: {
|
||||||
|
const bodyPc = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const labelIdx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const label = strings[labelIdx];
|
||||||
|
const scopeChain = chain;
|
||||||
|
const scopeCode = code;
|
||||||
|
const scopeDir = currentDir;
|
||||||
|
const scopeWith = withScope;
|
||||||
|
stack.push(
|
||||||
|
new NixThunk(
|
||||||
|
() => execFrame(scopeCode, bodyPc, scopeChain, scopeDir, scopeWith, null),
|
||||||
|
label,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.MakeClosure: {
|
||||||
|
const bodyPc = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const nSlots = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const closureChain = chain;
|
||||||
|
const closureCode = code;
|
||||||
|
const closureDir = currentDir;
|
||||||
|
const closureWith = withScope;
|
||||||
|
const func: NixFunction = (arg: NixValue) => {
|
||||||
|
const innerLocals = new Array<NixValue>(1 + nSlots).fill(null);
|
||||||
|
innerLocals[0] = arg;
|
||||||
|
const innerChain: ScopeChain = { locals: innerLocals, parent: closureChain };
|
||||||
|
return execFrame(closureCode, bodyPc, innerChain, closureDir, closureWith, null);
|
||||||
|
};
|
||||||
|
stack.push(func);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.MakePatternClosure: {
|
||||||
|
const bodyPc = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const nSlots = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const nRequired = readU16(code, pc);
|
||||||
|
pc += 2;
|
||||||
|
const nOptional = readU16(code, pc);
|
||||||
|
pc += 2;
|
||||||
|
const hasEllipsis = code[pc++] !== 0;
|
||||||
|
|
||||||
|
const required: string[] = [];
|
||||||
|
for (let i = 0; i < nRequired; i++) {
|
||||||
|
required.push(strings[readU32(code, pc)]);
|
||||||
|
pc += 4;
|
||||||
|
}
|
||||||
|
const optional: string[] = [];
|
||||||
|
for (let i = 0; i < nOptional; i++) {
|
||||||
|
optional.push(strings[readU32(code, pc)]);
|
||||||
|
pc += 4;
|
||||||
|
}
|
||||||
|
const positions = new Map<string, number>();
|
||||||
|
const nTotal = nRequired + nOptional;
|
||||||
|
for (let i = 0; i < nTotal; i++) {
|
||||||
|
const nameIdx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const spanId = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
positions.set(strings[nameIdx], spanId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const closureChain = chain;
|
||||||
|
const closureCode = code;
|
||||||
|
const closureDir = currentDir;
|
||||||
|
const closureWith = withScope;
|
||||||
|
const func: NixFunction = (arg: NixValue) => {
|
||||||
|
const innerLocals = new Array<NixValue>(1 + nSlots).fill(null);
|
||||||
|
innerLocals[0] = arg;
|
||||||
|
const innerChain: ScopeChain = { locals: innerLocals, parent: closureChain };
|
||||||
|
return execFrame(closureCode, bodyPc, innerChain, closureDir, closureWith, null);
|
||||||
|
};
|
||||||
|
func.args = new NixArgs(required, optional, positions, hasEllipsis);
|
||||||
|
stack.push(func);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.Call: {
|
||||||
|
const spanId = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const arg = stack.pop()!;
|
||||||
|
const func = stack.pop()!;
|
||||||
|
stack.push(call(func, arg, spanId));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.CallNoSpan: {
|
||||||
|
const arg = stack.pop()!;
|
||||||
|
const func = stack.pop()!;
|
||||||
|
stack.push(call(func, arg));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.MakeAttrs: {
|
||||||
|
const n = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const spanValues: number[] = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
spanValues.push(stack.pop() as number);
|
||||||
|
}
|
||||||
|
spanValues.reverse();
|
||||||
|
const map: NixAttrs = new Map();
|
||||||
|
const posMap = new Map<string, number>();
|
||||||
|
const pairs: [string, NixValue][] = [];
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
const val = stack.pop()!;
|
||||||
|
const key = stack.pop() as string;
|
||||||
|
pairs.push([key, val]);
|
||||||
|
}
|
||||||
|
pairs.reverse();
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
map.set(pairs[i][0], pairs[i][1]);
|
||||||
|
posMap.set(pairs[i][0], spanValues[i]);
|
||||||
|
}
|
||||||
|
stack.push(mkAttrs(map, posMap));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.MakeAttrsDyn: {
|
||||||
|
const nStatic = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const nDyn = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
|
||||||
|
const dynTriples: [NixValue, NixValue, number][] = [];
|
||||||
|
for (let i = 0; i < nDyn; i++) {
|
||||||
|
const dynSpan = stack.pop() as number;
|
||||||
|
const dynVal = stack.pop()!;
|
||||||
|
const dynKey = stack.pop()!;
|
||||||
|
dynTriples.push([dynKey, dynVal, dynSpan]);
|
||||||
|
}
|
||||||
|
dynTriples.reverse();
|
||||||
|
|
||||||
|
const spanValues: number[] = [];
|
||||||
|
for (let i = 0; i < nStatic; i++) {
|
||||||
|
spanValues.push(stack.pop() as number);
|
||||||
|
}
|
||||||
|
spanValues.reverse();
|
||||||
|
|
||||||
|
const map: NixAttrs = new Map();
|
||||||
|
const posMap = new Map<string, number>();
|
||||||
|
const pairs: [string, NixValue][] = [];
|
||||||
|
for (let i = 0; i < nStatic; i++) {
|
||||||
|
const val = stack.pop()!;
|
||||||
|
const key = stack.pop() as string;
|
||||||
|
pairs.push([key, val]);
|
||||||
|
}
|
||||||
|
pairs.reverse();
|
||||||
|
for (let i = 0; i < nStatic; i++) {
|
||||||
|
map.set(pairs[i][0], pairs[i][1]);
|
||||||
|
posMap.set(pairs[i][0], spanValues[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynKeys: NixValue[] = [];
|
||||||
|
const dynVals: NixValue[] = [];
|
||||||
|
const dynSpans: number[] = [];
|
||||||
|
for (const [k, v, s] of dynTriples) {
|
||||||
|
dynKeys.push(k);
|
||||||
|
dynVals.push(v);
|
||||||
|
dynSpans.push(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
stack.push(mkAttrs(map, posMap, { dynKeys, dynVals, dynSpans }));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.MakeEmptyAttrs:
|
||||||
|
stack.push($e);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Op.Select: {
|
||||||
|
const nKeys = readU16(code, pc);
|
||||||
|
pc += 2;
|
||||||
|
const spanId = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const keys: NixValue[] = [];
|
||||||
|
for (let i = 0; i < nKeys; i++) keys.push(stack.pop()!);
|
||||||
|
keys.reverse();
|
||||||
|
const obj = stack.pop()!;
|
||||||
|
stack.push(select(obj, keys, spanId));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.SelectDefault: {
|
||||||
|
const nKeys = readU16(code, pc);
|
||||||
|
pc += 2;
|
||||||
|
const spanId = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const defaultVal = stack.pop()!;
|
||||||
|
const keys: NixValue[] = [];
|
||||||
|
for (let i = 0; i < nKeys; i++) keys.push(stack.pop()!);
|
||||||
|
keys.reverse();
|
||||||
|
const obj = stack.pop()!;
|
||||||
|
stack.push(selectWithDefault(obj, keys, defaultVal, spanId));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.HasAttr: {
|
||||||
|
const nKeys = readU16(code, pc);
|
||||||
|
pc += 2;
|
||||||
|
const keys: NixValue[] = [];
|
||||||
|
for (let i = 0; i < nKeys; i++) keys.push(stack.pop()!);
|
||||||
|
keys.reverse();
|
||||||
|
const obj = stack.pop()!;
|
||||||
|
stack.push(hasAttr(obj, keys));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.MakeList: {
|
||||||
|
const count = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const items: NixValue[] = new Array(count);
|
||||||
|
for (let i = count - 1; i >= 0; i--) {
|
||||||
|
items[i] = stack.pop()!;
|
||||||
|
}
|
||||||
|
stack.push(items);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.OpAdd: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.add(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpSub: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.sub(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpMul: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.mul(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpDiv: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.div(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpEq: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.eq(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpNeq: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(!op.eq(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpLt: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.lt(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpGt: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.gt(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpLeq: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(!op.gt(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpGeq: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(!op.lt(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpConcat: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.concat(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpUpdate: {
|
||||||
|
const b = stack.pop()!;
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.update(a, b));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.OpNeg: {
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(op.sub(0n, a));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.OpNot: {
|
||||||
|
const a = stack.pop()!;
|
||||||
|
stack.push(!forceBool(a));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.ForceBool: {
|
||||||
|
const val = stack.pop()!;
|
||||||
|
stack.push(forceBool(val));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.JumpIfFalse: {
|
||||||
|
const offset = readI32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const val = stack.pop()!;
|
||||||
|
if (val === false) {
|
||||||
|
pc += offset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.JumpIfTrue: {
|
||||||
|
const offset = readI32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const val = stack.pop()!;
|
||||||
|
if (val === true) {
|
||||||
|
pc += offset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.Jump: {
|
||||||
|
const offset = readI32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
pc += offset;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.ConcatStrings: {
|
||||||
|
const nParts = readU16(code, pc);
|
||||||
|
pc += 2;
|
||||||
|
const forceString = code[pc++] !== 0;
|
||||||
|
const parts: NixValue[] = new Array(nParts);
|
||||||
|
for (let i = nParts - 1; i >= 0; i--) {
|
||||||
|
parts[i] = stack.pop()!;
|
||||||
|
}
|
||||||
|
stack.push(concatStringsWithContext(parts, forceString));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.ResolvePath: {
|
||||||
|
const pathExpr = stack.pop()!;
|
||||||
|
stack.push(resolvePath(currentDir, pathExpr));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.Assert: {
|
||||||
|
const rawIdx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const spanId = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
const expr = stack.pop()!;
|
||||||
|
const assertion = stack.pop()!;
|
||||||
|
stack.push(assert(assertion, expr, strings[rawIdx], spanId));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.PushWith: {
|
||||||
|
const namespace = stack.pop()!;
|
||||||
|
withScope = { env: namespace, last: withScope };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.PopWith:
|
||||||
|
withScope = withScope!.last;
|
||||||
|
break;
|
||||||
|
case Op.WithLookup: {
|
||||||
|
const nameIdx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(lookupWith(strings[nameIdx], withScope!));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.LoadBuiltins:
|
||||||
|
stack.push(builtins);
|
||||||
|
break;
|
||||||
|
case Op.LoadBuiltin: {
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(builtins.get(strings[idx])!);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.MkPos: {
|
||||||
|
const spanId = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(mkPos(spanId));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.LoadReplBinding: {
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(Nix.getReplBinding(strings[idx]));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Op.LoadScopedBinding: {
|
||||||
|
const idx = readU32(code, pc);
|
||||||
|
pc += 4;
|
||||||
|
stack.push(scopeMap!.get(strings[idx])!);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.Return:
|
||||||
|
return stack.pop()!;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown bytecode opcode: ${opcode ? `0x${opcode.toString(16)}` : "undefined"} at pc=${pc - 1}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const Nix: {
|
||||||
|
getReplBinding: (name: string) => NixValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { strings as vmStrings, constants as vmConstants };
|
||||||
906
nix-js/src/bytecode.rs
Normal file
906
nix-js/src/bytecode.rs
Normal file
@@ -0,0 +1,906 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
use rnix::TextRange;
|
||||||
|
|
||||||
|
use crate::ir::{ArgId, Attr, BinOpKind, Ir, Param, RawIrRef, SymId, ThunkId, UnOpKind};
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
|
pub(crate) enum Constant {
|
||||||
|
Int(i64),
|
||||||
|
Float(u64),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Bytecode {
|
||||||
|
pub code: Box<[u8]>,
|
||||||
|
pub current_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait BytecodeContext {
|
||||||
|
fn intern_string(&mut self, s: &str) -> u32;
|
||||||
|
fn intern_constant(&mut self, c: Constant) -> u32;
|
||||||
|
fn register_span(&self, range: TextRange) -> u32;
|
||||||
|
fn get_sym(&self, id: SymId) -> &str;
|
||||||
|
fn get_current_dir(&self) -> &Path;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Clone, Copy, TryFromPrimitive)]
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
pub enum Op {
|
||||||
|
PushConst = 0x01,
|
||||||
|
PushString = 0x02,
|
||||||
|
PushNull = 0x03,
|
||||||
|
PushTrue = 0x04,
|
||||||
|
PushFalse = 0x05,
|
||||||
|
|
||||||
|
LoadLocal = 0x06,
|
||||||
|
LoadOuter = 0x07,
|
||||||
|
StoreLocal = 0x08,
|
||||||
|
AllocLocals = 0x09,
|
||||||
|
|
||||||
|
MakeThunk = 0x0A,
|
||||||
|
MakeClosure = 0x0B,
|
||||||
|
MakePatternClosure = 0x0C,
|
||||||
|
|
||||||
|
Call = 0x0D,
|
||||||
|
CallNoSpan = 0x0E,
|
||||||
|
|
||||||
|
MakeAttrs = 0x0F,
|
||||||
|
MakeAttrsDyn = 0x10,
|
||||||
|
MakeEmptyAttrs = 0x11,
|
||||||
|
Select = 0x12,
|
||||||
|
SelectDefault = 0x13,
|
||||||
|
HasAttr = 0x14,
|
||||||
|
|
||||||
|
MakeList = 0x15,
|
||||||
|
|
||||||
|
OpAdd = 0x16,
|
||||||
|
OpSub = 0x17,
|
||||||
|
OpMul = 0x18,
|
||||||
|
OpDiv = 0x19,
|
||||||
|
OpEq = 0x20,
|
||||||
|
OpNeq = 0x21,
|
||||||
|
OpLt = 0x22,
|
||||||
|
OpGt = 0x23,
|
||||||
|
OpLeq = 0x24,
|
||||||
|
OpGeq = 0x25,
|
||||||
|
OpConcat = 0x26,
|
||||||
|
OpUpdate = 0x27,
|
||||||
|
|
||||||
|
OpNeg = 0x28,
|
||||||
|
OpNot = 0x29,
|
||||||
|
|
||||||
|
ForceBool = 0x30,
|
||||||
|
JumpIfFalse = 0x31,
|
||||||
|
JumpIfTrue = 0x32,
|
||||||
|
Jump = 0x33,
|
||||||
|
|
||||||
|
ConcatStrings = 0x34,
|
||||||
|
ResolvePath = 0x35,
|
||||||
|
|
||||||
|
Assert = 0x36,
|
||||||
|
|
||||||
|
PushWith = 0x37,
|
||||||
|
PopWith = 0x38,
|
||||||
|
WithLookup = 0x39,
|
||||||
|
|
||||||
|
LoadBuiltins = 0x40,
|
||||||
|
LoadBuiltin = 0x41,
|
||||||
|
|
||||||
|
MkPos = 0x43,
|
||||||
|
|
||||||
|
LoadReplBinding = 0x44,
|
||||||
|
LoadScopedBinding = 0x45,
|
||||||
|
|
||||||
|
Return = 0x46,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ScopeInfo {
|
||||||
|
depth: u16,
|
||||||
|
arg_id: Option<ArgId>,
|
||||||
|
thunk_map: HashMap<ThunkId, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BytecodeEmitter<'a, Ctx: BytecodeContext> {
|
||||||
|
ctx: &'a mut Ctx,
|
||||||
|
code: Vec<u8>,
|
||||||
|
scope_stack: Vec<ScopeInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compile_bytecode(ir: RawIrRef<'_>, ctx: &mut impl BytecodeContext) -> Bytecode {
|
||||||
|
let current_dir = ctx.get_current_dir().to_string_lossy().to_string();
|
||||||
|
let mut emitter = BytecodeEmitter::new(ctx);
|
||||||
|
emitter.emit_toplevel(ir);
|
||||||
|
Bytecode {
|
||||||
|
code: emitter.code.into_boxed_slice(),
|
||||||
|
current_dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compile_bytecode_scoped(
|
||||||
|
ir: RawIrRef<'_>,
|
||||||
|
ctx: &mut impl BytecodeContext,
|
||||||
|
) -> Bytecode {
|
||||||
|
let current_dir = ctx.get_current_dir().to_string_lossy().to_string();
|
||||||
|
let mut emitter = BytecodeEmitter::new(ctx);
|
||||||
|
emitter.emit_toplevel_scoped(ir);
|
||||||
|
Bytecode {
|
||||||
|
code: emitter.code.into_boxed_slice(),
|
||||||
|
current_dir,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Ctx: BytecodeContext> BytecodeEmitter<'a, Ctx> {
|
||||||
|
fn new(ctx: &'a mut Ctx) -> Self {
|
||||||
|
Self {
|
||||||
|
ctx,
|
||||||
|
code: Vec::with_capacity(4096),
|
||||||
|
scope_stack: Vec::with_capacity(32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_op(&mut self, op: Op) {
|
||||||
|
self.code.push(op as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_u8(&mut self, val: u8) {
|
||||||
|
self.code.push(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_u16(&mut self, val: u16) {
|
||||||
|
self.code.extend_from_slice(&val.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_u32(&mut self, val: u32) {
|
||||||
|
self.code.extend_from_slice(&val.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_i32_placeholder(&mut self) -> usize {
|
||||||
|
let offset = self.code.len();
|
||||||
|
self.code.extend_from_slice(&[0u8; 4]);
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn patch_i32(&mut self, offset: usize, val: i32) {
|
||||||
|
self.code[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn emit_jump_placeholder(&mut self) -> usize {
|
||||||
|
self.emit_op(Op::Jump);
|
||||||
|
self.emit_i32_placeholder()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn patch_jump_target(&mut self, placeholder_offset: usize) {
|
||||||
|
let current_pos = self.code.len();
|
||||||
|
let relative_offset = (current_pos as i32) - (placeholder_offset as i32) - 4;
|
||||||
|
self.patch_i32(placeholder_offset, relative_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_depth(&self) -> u16 {
|
||||||
|
self.scope_stack.last().map_or(0, |s| s.depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_thunk(&self, id: ThunkId) -> (u16, u32) {
|
||||||
|
for scope in self.scope_stack.iter().rev() {
|
||||||
|
if let Some(&local_idx) = scope.thunk_map.get(&id) {
|
||||||
|
let layer = self.current_depth() - scope.depth;
|
||||||
|
return (layer, local_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("ThunkId {:?} not found in any scope", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_arg(&self, id: ArgId) -> (u16, u32) {
|
||||||
|
for scope in self.scope_stack.iter().rev() {
|
||||||
|
if scope.arg_id == Some(id) {
|
||||||
|
let layer = self.current_depth() - scope.depth;
|
||||||
|
return (layer, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("ArgId {:?} not found in any scope", id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_load(&mut self, layer: u16, local: u32) {
|
||||||
|
if layer == 0 {
|
||||||
|
self.emit_op(Op::LoadLocal);
|
||||||
|
self.emit_u32(local);
|
||||||
|
} else {
|
||||||
|
self.emit_op(Op::LoadOuter);
|
||||||
|
self.emit_u8(layer as u8);
|
||||||
|
self.emit_u32(local);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn count_with_thunks(&self, ir: RawIrRef<'_>) -> usize {
|
||||||
|
match ir.deref() {
|
||||||
|
Ir::With { thunks, body, .. } => thunks.len() + self.count_with_thunks(*body),
|
||||||
|
Ir::TopLevel { thunks, body } => thunks.len() + self.count_with_thunks(*body),
|
||||||
|
Ir::If { cond, consq, alter } => {
|
||||||
|
self.count_with_thunks(*cond)
|
||||||
|
+ self.count_with_thunks(*consq)
|
||||||
|
+ self.count_with_thunks(*alter)
|
||||||
|
}
|
||||||
|
Ir::BinOp { lhs, rhs, .. } => {
|
||||||
|
self.count_with_thunks(*lhs) + self.count_with_thunks(*rhs)
|
||||||
|
}
|
||||||
|
Ir::UnOp { rhs, .. } => self.count_with_thunks(*rhs),
|
||||||
|
Ir::Call { func, arg, .. } => {
|
||||||
|
self.count_with_thunks(*func) + self.count_with_thunks(*arg)
|
||||||
|
}
|
||||||
|
Ir::Assert {
|
||||||
|
assertion, expr, ..
|
||||||
|
} => self.count_with_thunks(*assertion) + self.count_with_thunks(*expr),
|
||||||
|
Ir::Select { expr, default, .. } => {
|
||||||
|
self.count_with_thunks(*expr) + default.map_or(0, |d| self.count_with_thunks(d))
|
||||||
|
}
|
||||||
|
Ir::HasAttr { lhs, .. } => self.count_with_thunks(*lhs),
|
||||||
|
Ir::ConcatStrings { parts, .. } => {
|
||||||
|
parts.iter().map(|p| self.count_with_thunks(*p)).sum()
|
||||||
|
}
|
||||||
|
Ir::Path(p) => self.count_with_thunks(*p),
|
||||||
|
Ir::List { items } => items.iter().map(|item| self.count_with_thunks(*item)).sum(),
|
||||||
|
Ir::AttrSet { stcs, dyns } => {
|
||||||
|
stcs.iter()
|
||||||
|
.map(|(_, &(val, _))| self.count_with_thunks(val))
|
||||||
|
.sum::<usize>()
|
||||||
|
+ dyns
|
||||||
|
.iter()
|
||||||
|
.map(|&(k, v, _)| self.count_with_thunks(k) + self.count_with_thunks(v))
|
||||||
|
.sum::<usize>()
|
||||||
|
}
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_all_thunks<'ir>(
|
||||||
|
&self,
|
||||||
|
own_thunks: &[(ThunkId, RawIrRef<'ir>)],
|
||||||
|
body: RawIrRef<'ir>,
|
||||||
|
) -> Vec<(ThunkId, RawIrRef<'ir>)> {
|
||||||
|
let mut all = Vec::from(own_thunks);
|
||||||
|
self.collect_with_thunks_recursive(body, &mut all);
|
||||||
|
let mut i = 0;
|
||||||
|
while i < all.len() {
|
||||||
|
let thunk_body = all[i].1;
|
||||||
|
self.collect_with_thunks_recursive(thunk_body, &mut all);
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
all
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_with_thunks_recursive<'ir>(
|
||||||
|
&self,
|
||||||
|
ir: RawIrRef<'ir>,
|
||||||
|
out: &mut Vec<(ThunkId, RawIrRef<'ir>)>,
|
||||||
|
) {
|
||||||
|
match ir.deref() {
|
||||||
|
Ir::With { thunks, body, .. } => {
|
||||||
|
for &(id, inner) in thunks.iter() {
|
||||||
|
out.push((id, inner));
|
||||||
|
}
|
||||||
|
self.collect_with_thunks_recursive(*body, out);
|
||||||
|
}
|
||||||
|
Ir::TopLevel { thunks, body } => {
|
||||||
|
for &(id, inner) in thunks.iter() {
|
||||||
|
out.push((id, inner));
|
||||||
|
}
|
||||||
|
self.collect_with_thunks_recursive(*body, out);
|
||||||
|
}
|
||||||
|
Ir::If { cond, consq, alter } => {
|
||||||
|
self.collect_with_thunks_recursive(*cond, out);
|
||||||
|
self.collect_with_thunks_recursive(*consq, out);
|
||||||
|
self.collect_with_thunks_recursive(*alter, out);
|
||||||
|
}
|
||||||
|
Ir::BinOp { lhs, rhs, .. } => {
|
||||||
|
self.collect_with_thunks_recursive(*lhs, out);
|
||||||
|
self.collect_with_thunks_recursive(*rhs, out);
|
||||||
|
}
|
||||||
|
Ir::UnOp { rhs, .. } => self.collect_with_thunks_recursive(*rhs, out),
|
||||||
|
Ir::Call { func, arg, .. } => {
|
||||||
|
self.collect_with_thunks_recursive(*func, out);
|
||||||
|
self.collect_with_thunks_recursive(*arg, out);
|
||||||
|
}
|
||||||
|
Ir::Assert {
|
||||||
|
assertion, expr, ..
|
||||||
|
} => {
|
||||||
|
self.collect_with_thunks_recursive(*assertion, out);
|
||||||
|
self.collect_with_thunks_recursive(*expr, out);
|
||||||
|
}
|
||||||
|
Ir::Select { expr, default, .. } => {
|
||||||
|
self.collect_with_thunks_recursive(*expr, out);
|
||||||
|
if let Some(d) = default {
|
||||||
|
self.collect_with_thunks_recursive(*d, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ir::HasAttr { lhs, .. } => self.collect_with_thunks_recursive(*lhs, out),
|
||||||
|
Ir::ConcatStrings { parts, .. } => {
|
||||||
|
for p in parts.iter() {
|
||||||
|
self.collect_with_thunks_recursive(*p, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ir::Path(p) => self.collect_with_thunks_recursive(*p, out),
|
||||||
|
Ir::List { items } => {
|
||||||
|
for item in items.iter() {
|
||||||
|
self.collect_with_thunks_recursive(*item, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ir::AttrSet { stcs, dyns } => {
|
||||||
|
for (_, &(val, _)) in stcs.iter() {
|
||||||
|
self.collect_with_thunks_recursive(val, out);
|
||||||
|
}
|
||||||
|
for &(key, val, _) in dyns.iter() {
|
||||||
|
self.collect_with_thunks_recursive(key, out);
|
||||||
|
self.collect_with_thunks_recursive(val, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_scope(&mut self, has_arg: bool, arg_id: Option<ArgId>, thunk_ids: &[ThunkId]) {
|
||||||
|
let depth = self.scope_stack.len() as u16;
|
||||||
|
let thunk_base = if has_arg { 1u32 } else { 0u32 };
|
||||||
|
let thunk_map = thunk_ids
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, &id)| (id, thunk_base + i as u32))
|
||||||
|
.collect();
|
||||||
|
self.scope_stack.push(ScopeInfo {
|
||||||
|
depth,
|
||||||
|
arg_id,
|
||||||
|
thunk_map,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_scope(&mut self) {
|
||||||
|
self.scope_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_toplevel(&mut self, ir: RawIrRef<'_>) {
|
||||||
|
match ir.deref() {
|
||||||
|
Ir::TopLevel { body, thunks } => {
|
||||||
|
let with_thunk_count = self.count_with_thunks(*body);
|
||||||
|
let total_slots = thunks.len() + with_thunk_count;
|
||||||
|
|
||||||
|
let all_thunks = self.collect_all_thunks(thunks, *body);
|
||||||
|
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||||
|
|
||||||
|
self.push_scope(false, None, &thunk_ids);
|
||||||
|
|
||||||
|
if total_slots > 0 {
|
||||||
|
self.emit_op(Op::AllocLocals);
|
||||||
|
self.emit_u32(total_slots as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit_scope_thunks(thunks);
|
||||||
|
self.emit_expr(*body);
|
||||||
|
self.emit_op(Op::Return);
|
||||||
|
|
||||||
|
self.pop_scope();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.push_scope(false, None, &[]);
|
||||||
|
self.emit_expr(ir);
|
||||||
|
self.emit_op(Op::Return);
|
||||||
|
self.pop_scope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_toplevel_scoped(&mut self, ir: RawIrRef<'_>) {
|
||||||
|
match ir.deref() {
|
||||||
|
Ir::TopLevel { body, thunks } => {
|
||||||
|
let with_thunk_count = self.count_with_thunks(*body);
|
||||||
|
let total_slots = thunks.len() + with_thunk_count;
|
||||||
|
|
||||||
|
let all_thunks = self.collect_all_thunks(thunks, *body);
|
||||||
|
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||||
|
|
||||||
|
self.push_scope(false, None, &thunk_ids);
|
||||||
|
|
||||||
|
if total_slots > 0 {
|
||||||
|
self.emit_op(Op::AllocLocals);
|
||||||
|
self.emit_u32(total_slots as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emit_scope_thunks(thunks);
|
||||||
|
self.emit_expr(*body);
|
||||||
|
self.emit_op(Op::Return);
|
||||||
|
|
||||||
|
self.pop_scope();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.push_scope(false, None, &[]);
|
||||||
|
self.emit_expr(ir);
|
||||||
|
self.emit_op(Op::Return);
|
||||||
|
self.pop_scope();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_scope_thunks(&mut self, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||||
|
for &(id, inner) in thunks {
|
||||||
|
let label = format!("e{}", id.0);
|
||||||
|
let label_idx = self.ctx.intern_string(&label);
|
||||||
|
|
||||||
|
let skip_patch = self.emit_jump_placeholder();
|
||||||
|
let entry_point = self.code.len() as u32;
|
||||||
|
self.emit_expr(inner);
|
||||||
|
self.emit_op(Op::Return);
|
||||||
|
self.patch_jump_target(skip_patch);
|
||||||
|
self.emit_op(Op::MakeThunk);
|
||||||
|
self.emit_u32(entry_point);
|
||||||
|
self.emit_u32(label_idx);
|
||||||
|
let (_, local_idx) = self.resolve_thunk(id);
|
||||||
|
self.emit_op(Op::StoreLocal);
|
||||||
|
self.emit_u32(local_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_expr(&mut self, ir: RawIrRef<'_>) {
|
||||||
|
match ir.deref() {
|
||||||
|
&Ir::Int(x) => {
|
||||||
|
let idx = self.ctx.intern_constant(Constant::Int(x));
|
||||||
|
self.emit_op(Op::PushConst);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
&Ir::Float(x) => {
|
||||||
|
let idx = self.ctx.intern_constant(Constant::Float(x.to_bits()));
|
||||||
|
self.emit_op(Op::PushConst);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
&Ir::Bool(true) => self.emit_op(Op::PushTrue),
|
||||||
|
&Ir::Bool(false) => self.emit_op(Op::PushFalse),
|
||||||
|
Ir::Null => self.emit_op(Op::PushNull),
|
||||||
|
Ir::Str(s) => {
|
||||||
|
let idx = self.ctx.intern_string(s.deref());
|
||||||
|
self.emit_op(Op::PushString);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
&Ir::Path(p) => {
|
||||||
|
self.emit_expr(p);
|
||||||
|
self.emit_op(Op::ResolvePath);
|
||||||
|
}
|
||||||
|
&Ir::If { cond, consq, alter } => {
|
||||||
|
self.emit_expr(cond);
|
||||||
|
self.emit_op(Op::ForceBool);
|
||||||
|
|
||||||
|
self.emit_op(Op::JumpIfFalse);
|
||||||
|
let else_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jif = self.code.len();
|
||||||
|
|
||||||
|
self.emit_expr(consq);
|
||||||
|
|
||||||
|
self.emit_op(Op::Jump);
|
||||||
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jump = self.code.len();
|
||||||
|
|
||||||
|
let else_offset = (after_jump as i32) - (after_jif as i32);
|
||||||
|
self.patch_i32(else_placeholder, else_offset);
|
||||||
|
|
||||||
|
self.emit_expr(alter);
|
||||||
|
|
||||||
|
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
||||||
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
|
}
|
||||||
|
&Ir::BinOp { lhs, rhs, kind } => {
|
||||||
|
self.emit_binop(lhs, rhs, kind);
|
||||||
|
}
|
||||||
|
&Ir::UnOp { rhs, kind } => match kind {
|
||||||
|
UnOpKind::Neg => {
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_op(Op::OpNeg);
|
||||||
|
}
|
||||||
|
UnOpKind::Not => {
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_op(Op::OpNot);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
&Ir::Func {
|
||||||
|
body,
|
||||||
|
ref param,
|
||||||
|
arg,
|
||||||
|
ref thunks,
|
||||||
|
} => {
|
||||||
|
self.emit_func(arg, thunks, param, body);
|
||||||
|
}
|
||||||
|
Ir::AttrSet { stcs, dyns } => {
|
||||||
|
self.emit_attrset(stcs, dyns);
|
||||||
|
}
|
||||||
|
Ir::List { items } => {
|
||||||
|
for &item in items.iter() {
|
||||||
|
self.emit_expr(item);
|
||||||
|
}
|
||||||
|
self.emit_op(Op::MakeList);
|
||||||
|
self.emit_u32(items.len() as u32);
|
||||||
|
}
|
||||||
|
&Ir::Call { func, arg, span } => {
|
||||||
|
self.emit_expr(func);
|
||||||
|
self.emit_expr(arg);
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
self.emit_op(Op::Call);
|
||||||
|
self.emit_u32(span_id);
|
||||||
|
}
|
||||||
|
&Ir::Arg(id) => {
|
||||||
|
let (layer, local) = self.resolve_arg(id);
|
||||||
|
self.emit_load(layer, local);
|
||||||
|
}
|
||||||
|
&Ir::TopLevel { body, ref thunks } => {
|
||||||
|
self.emit_toplevel_inner(body, thunks);
|
||||||
|
}
|
||||||
|
&Ir::Select {
|
||||||
|
expr,
|
||||||
|
ref attrpath,
|
||||||
|
default,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
self.emit_select(expr, attrpath, default, span);
|
||||||
|
}
|
||||||
|
&Ir::Thunk(id) => {
|
||||||
|
let (layer, local) = self.resolve_thunk(id);
|
||||||
|
self.emit_load(layer, local);
|
||||||
|
}
|
||||||
|
Ir::Builtins => {
|
||||||
|
self.emit_op(Op::LoadBuiltins);
|
||||||
|
}
|
||||||
|
&Ir::Builtin(name) => {
|
||||||
|
let sym = self.ctx.get_sym(name).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&sym);
|
||||||
|
self.emit_op(Op::LoadBuiltin);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
&Ir::ConcatStrings {
|
||||||
|
ref parts,
|
||||||
|
force_string,
|
||||||
|
} => {
|
||||||
|
for &part in parts.iter() {
|
||||||
|
self.emit_expr(part);
|
||||||
|
}
|
||||||
|
self.emit_op(Op::ConcatStrings);
|
||||||
|
self.emit_u16(parts.len() as u16);
|
||||||
|
self.emit_u8(if force_string { 1 } else { 0 });
|
||||||
|
}
|
||||||
|
&Ir::HasAttr { lhs, ref rhs } => {
|
||||||
|
self.emit_has_attr(lhs, rhs);
|
||||||
|
}
|
||||||
|
Ir::Assert {
|
||||||
|
assertion,
|
||||||
|
expr,
|
||||||
|
assertion_raw,
|
||||||
|
span,
|
||||||
|
} => {
|
||||||
|
let raw_idx = self.ctx.intern_string(assertion_raw);
|
||||||
|
let span_id = self.ctx.register_span(*span);
|
||||||
|
self.emit_expr(*assertion);
|
||||||
|
self.emit_expr(*expr);
|
||||||
|
self.emit_op(Op::Assert);
|
||||||
|
self.emit_u32(raw_idx);
|
||||||
|
self.emit_u32(span_id);
|
||||||
|
}
|
||||||
|
&Ir::CurPos(span) => {
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
self.emit_op(Op::MkPos);
|
||||||
|
self.emit_u32(span_id);
|
||||||
|
}
|
||||||
|
&Ir::ReplBinding(name) => {
|
||||||
|
let sym = self.ctx.get_sym(name).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&sym);
|
||||||
|
self.emit_op(Op::LoadReplBinding);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
&Ir::ScopedImportBinding(name) => {
|
||||||
|
let sym = self.ctx.get_sym(name).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&sym);
|
||||||
|
self.emit_op(Op::LoadScopedBinding);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
&Ir::With {
|
||||||
|
namespace,
|
||||||
|
body,
|
||||||
|
ref thunks,
|
||||||
|
} => {
|
||||||
|
self.emit_with(namespace, body, thunks);
|
||||||
|
}
|
||||||
|
&Ir::WithLookup(name) => {
|
||||||
|
let sym = self.ctx.get_sym(name).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&sym);
|
||||||
|
self.emit_op(Op::WithLookup);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_binop(&mut self, lhs: RawIrRef<'_>, rhs: RawIrRef<'_>, kind: BinOpKind) {
|
||||||
|
use BinOpKind::*;
|
||||||
|
match kind {
|
||||||
|
And => {
|
||||||
|
self.emit_expr(lhs);
|
||||||
|
self.emit_op(Op::ForceBool);
|
||||||
|
self.emit_op(Op::JumpIfFalse);
|
||||||
|
let skip_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jif = self.code.len();
|
||||||
|
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_op(Op::ForceBool);
|
||||||
|
self.emit_op(Op::Jump);
|
||||||
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jump = self.code.len();
|
||||||
|
|
||||||
|
let false_offset = (after_jump as i32) - (after_jif as i32);
|
||||||
|
self.patch_i32(skip_placeholder, false_offset);
|
||||||
|
|
||||||
|
self.emit_op(Op::PushFalse);
|
||||||
|
|
||||||
|
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
||||||
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
|
}
|
||||||
|
Or => {
|
||||||
|
self.emit_expr(lhs);
|
||||||
|
self.emit_op(Op::ForceBool);
|
||||||
|
self.emit_op(Op::JumpIfTrue);
|
||||||
|
let skip_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jit = self.code.len();
|
||||||
|
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_op(Op::ForceBool);
|
||||||
|
self.emit_op(Op::Jump);
|
||||||
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jump = self.code.len();
|
||||||
|
|
||||||
|
let true_offset = (after_jump as i32) - (after_jit as i32);
|
||||||
|
self.patch_i32(skip_placeholder, true_offset);
|
||||||
|
|
||||||
|
self.emit_op(Op::PushTrue);
|
||||||
|
|
||||||
|
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
||||||
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
|
}
|
||||||
|
Impl => {
|
||||||
|
self.emit_expr(lhs);
|
||||||
|
self.emit_op(Op::ForceBool);
|
||||||
|
self.emit_op(Op::JumpIfFalse);
|
||||||
|
let skip_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jif = self.code.len();
|
||||||
|
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_op(Op::ForceBool);
|
||||||
|
self.emit_op(Op::Jump);
|
||||||
|
let end_placeholder = self.emit_i32_placeholder();
|
||||||
|
let after_jump = self.code.len();
|
||||||
|
|
||||||
|
let true_offset = (after_jump as i32) - (after_jif as i32);
|
||||||
|
self.patch_i32(skip_placeholder, true_offset);
|
||||||
|
|
||||||
|
self.emit_op(Op::PushTrue);
|
||||||
|
|
||||||
|
let end_offset = (self.code.len() as i32) - (after_jump as i32);
|
||||||
|
self.patch_i32(end_placeholder, end_offset);
|
||||||
|
}
|
||||||
|
PipeL => {
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_expr(lhs);
|
||||||
|
self.emit_op(Op::CallNoSpan);
|
||||||
|
}
|
||||||
|
PipeR => {
|
||||||
|
self.emit_expr(lhs);
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_op(Op::CallNoSpan);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.emit_expr(lhs);
|
||||||
|
self.emit_expr(rhs);
|
||||||
|
self.emit_op(match kind {
|
||||||
|
Add => Op::OpAdd,
|
||||||
|
Sub => Op::OpSub,
|
||||||
|
Mul => Op::OpMul,
|
||||||
|
Div => Op::OpDiv,
|
||||||
|
Eq => Op::OpEq,
|
||||||
|
Neq => Op::OpNeq,
|
||||||
|
Lt => Op::OpLt,
|
||||||
|
Gt => Op::OpGt,
|
||||||
|
Leq => Op::OpLeq,
|
||||||
|
Geq => Op::OpGeq,
|
||||||
|
Con => Op::OpConcat,
|
||||||
|
Upd => Op::OpUpdate,
|
||||||
|
_ => unreachable!(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_func(
|
||||||
|
&mut self,
|
||||||
|
arg: ArgId,
|
||||||
|
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||||
|
param: &Option<Param<'_>>,
|
||||||
|
body: RawIrRef<'_>,
|
||||||
|
) {
|
||||||
|
let with_thunk_count = self.count_with_thunks(body);
|
||||||
|
let total_slots = thunks.len() + with_thunk_count;
|
||||||
|
|
||||||
|
let all_thunks = self.collect_all_thunks(thunks, body);
|
||||||
|
let thunk_ids: Vec<ThunkId> = all_thunks.iter().map(|&(id, _)| id).collect();
|
||||||
|
|
||||||
|
let skip_patch = self.emit_jump_placeholder();
|
||||||
|
let entry_point = self.code.len() as u32;
|
||||||
|
self.push_scope(true, Some(arg), &thunk_ids);
|
||||||
|
self.emit_scope_thunks(thunks);
|
||||||
|
self.emit_expr(body);
|
||||||
|
self.emit_op(Op::Return);
|
||||||
|
self.pop_scope();
|
||||||
|
self.patch_jump_target(skip_patch);
|
||||||
|
|
||||||
|
if let Some(Param {
|
||||||
|
required,
|
||||||
|
optional,
|
||||||
|
ellipsis,
|
||||||
|
}) = param
|
||||||
|
{
|
||||||
|
self.emit_op(Op::MakePatternClosure);
|
||||||
|
self.emit_u32(entry_point);
|
||||||
|
self.emit_u32(total_slots as u32);
|
||||||
|
self.emit_u16(required.len() as u16);
|
||||||
|
self.emit_u16(optional.len() as u16);
|
||||||
|
self.emit_u8(if *ellipsis { 1 } else { 0 });
|
||||||
|
|
||||||
|
for &(sym, _) in required.iter() {
|
||||||
|
let name = self.ctx.get_sym(sym).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&name);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
for &(sym, _) in optional.iter() {
|
||||||
|
let name = self.ctx.get_sym(sym).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&name);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
for &(sym, span) in required.iter().chain(optional.iter()) {
|
||||||
|
let name = self.ctx.get_sym(sym).to_string();
|
||||||
|
let name_idx = self.ctx.intern_string(&name);
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
self.emit_u32(name_idx);
|
||||||
|
self.emit_u32(span_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.emit_op(Op::MakeClosure);
|
||||||
|
self.emit_u32(entry_point);
|
||||||
|
self.emit_u32(total_slots as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_attrset(
|
||||||
|
&mut self,
|
||||||
|
stcs: &crate::ir::HashMap<'_, SymId, (RawIrRef<'_>, TextRange)>,
|
||||||
|
dyns: &[(RawIrRef<'_>, RawIrRef<'_>, TextRange)],
|
||||||
|
) {
|
||||||
|
if stcs.is_empty() && dyns.is_empty() {
|
||||||
|
self.emit_op(Op::MakeEmptyAttrs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dyns.is_empty() {
|
||||||
|
for (&sym, &(val, _)) in stcs.iter() {
|
||||||
|
let key = self.ctx.get_sym(sym).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&key);
|
||||||
|
self.emit_op(Op::PushString);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
self.emit_expr(val);
|
||||||
|
}
|
||||||
|
for (_, &(_, span)) in stcs.iter() {
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
||||||
|
self.emit_op(Op::PushConst);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
for &(key, val, span) in dyns.iter() {
|
||||||
|
self.emit_expr(key);
|
||||||
|
self.emit_expr(val);
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
||||||
|
self.emit_op(Op::PushConst);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
self.emit_op(Op::MakeAttrsDyn);
|
||||||
|
self.emit_u32(stcs.len() as u32);
|
||||||
|
self.emit_u32(dyns.len() as u32);
|
||||||
|
} else {
|
||||||
|
for (&sym, &(val, _)) in stcs.iter() {
|
||||||
|
let key = self.ctx.get_sym(sym).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&key);
|
||||||
|
self.emit_op(Op::PushString);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
self.emit_expr(val);
|
||||||
|
}
|
||||||
|
for (_, &(_, span)) in stcs.iter() {
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
let idx = self.ctx.intern_constant(Constant::Int(span_id as i64));
|
||||||
|
self.emit_op(Op::PushConst);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
self.emit_op(Op::MakeAttrs);
|
||||||
|
self.emit_u32(stcs.len() as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_select(
|
||||||
|
&mut self,
|
||||||
|
expr: RawIrRef<'_>,
|
||||||
|
attrpath: &[Attr<RawIrRef<'_>>],
|
||||||
|
default: Option<RawIrRef<'_>>,
|
||||||
|
span: TextRange,
|
||||||
|
) {
|
||||||
|
self.emit_expr(expr);
|
||||||
|
for attr in attrpath.iter() {
|
||||||
|
match attr {
|
||||||
|
Attr::Str(sym, _) => {
|
||||||
|
let key = self.ctx.get_sym(*sym).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&key);
|
||||||
|
self.emit_op(Op::PushString);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
Attr::Dynamic(expr, _) => {
|
||||||
|
self.emit_expr(*expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(default) = default {
|
||||||
|
self.emit_expr(default);
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
self.emit_op(Op::SelectDefault);
|
||||||
|
self.emit_u16(attrpath.len() as u16);
|
||||||
|
self.emit_u32(span_id);
|
||||||
|
} else {
|
||||||
|
let span_id = self.ctx.register_span(span);
|
||||||
|
self.emit_op(Op::Select);
|
||||||
|
self.emit_u16(attrpath.len() as u16);
|
||||||
|
self.emit_u32(span_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_has_attr(&mut self, lhs: RawIrRef<'_>, rhs: &[Attr<RawIrRef<'_>>]) {
|
||||||
|
self.emit_expr(lhs);
|
||||||
|
for attr in rhs.iter() {
|
||||||
|
match attr {
|
||||||
|
Attr::Str(sym, _) => {
|
||||||
|
let key = self.ctx.get_sym(*sym).to_string();
|
||||||
|
let idx = self.ctx.intern_string(&key);
|
||||||
|
self.emit_op(Op::PushString);
|
||||||
|
self.emit_u32(idx);
|
||||||
|
}
|
||||||
|
Attr::Dynamic(expr, _) => {
|
||||||
|
self.emit_expr(*expr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.emit_op(Op::HasAttr);
|
||||||
|
self.emit_u16(rhs.len() as u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_with(
|
||||||
|
&mut self,
|
||||||
|
namespace: RawIrRef<'_>,
|
||||||
|
body: RawIrRef<'_>,
|
||||||
|
thunks: &[(ThunkId, RawIrRef<'_>)],
|
||||||
|
) {
|
||||||
|
self.emit_expr(namespace);
|
||||||
|
self.emit_op(Op::PushWith);
|
||||||
|
self.emit_scope_thunks(thunks);
|
||||||
|
self.emit_expr(body);
|
||||||
|
self.emit_op(Op::PopWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_toplevel_inner(&mut self, body: RawIrRef<'_>, thunks: &[(ThunkId, RawIrRef<'_>)]) {
|
||||||
|
self.emit_scope_thunks(thunks);
|
||||||
|
self.emit_expr(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,13 +8,15 @@ use hashbrown::{DefaultHashBuilder, HashMap, HashSet, HashTable};
|
|||||||
use rnix::TextRange;
|
use rnix::TextRange;
|
||||||
use string_interner::DefaultStringInterner;
|
use string_interner::DefaultStringInterner;
|
||||||
|
|
||||||
|
use crate::bytecode::{self, BytecodeContext, Bytecode, Constant};
|
||||||
use crate::codegen::{CodegenContext, compile};
|
use crate::codegen::{CodegenContext, compile};
|
||||||
|
use crate::disassembler::{Disassembler, DisassemblerContext};
|
||||||
use crate::downgrade::*;
|
use crate::downgrade::*;
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq};
|
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq};
|
||||||
#[cfg(feature = "inspector")]
|
#[cfg(feature = "inspector")]
|
||||||
use crate::runtime::inspector::InspectorServer;
|
use crate::runtime::inspector::InspectorServer;
|
||||||
use crate::runtime::{Runtime, RuntimeContext};
|
use crate::runtime::{ForceMode, Runtime, RuntimeContext};
|
||||||
use crate::store::{DaemonStore, Store, StoreConfig};
|
use crate::store::{DaemonStore, Store, StoreConfig};
|
||||||
use crate::value::{Symbol, Value};
|
use crate::value::{Symbol, Value};
|
||||||
|
|
||||||
@@ -53,16 +55,16 @@ pub struct Context {
|
|||||||
_inspector_server: Option<InspectorServer>,
|
_inspector_server: Option<InspectorServer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! eval {
|
macro_rules! eval_bc {
|
||||||
($name:ident, $wrapper:literal) => {
|
($name:ident, $mode:expr) => {
|
||||||
pub fn $name(&mut self, source: Source) -> Result<Value> {
|
pub fn $name(&mut self, source: Source) -> Result<Value> {
|
||||||
tracing::info!("Starting evaluation");
|
tracing::info!("Starting evaluation");
|
||||||
|
|
||||||
tracing::debug!("Compiling code");
|
tracing::debug!("Compiling bytecode");
|
||||||
let code = self.compile(source)?;
|
let bytecode = self.ctx.compile_bytecode(source)?;
|
||||||
|
|
||||||
tracing::debug!("Executing JavaScript");
|
tracing::debug!("Executing bytecode");
|
||||||
self.runtime.eval(format!($wrapper, code), &mut self.ctx)
|
self.runtime.eval_bytecode(bytecode, &mut self.ctx, $mode)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -137,10 +139,9 @@ impl Context {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
eval!(eval, "Nix.force({})");
|
eval_bc!(eval, ForceMode::Force);
|
||||||
eval!(eval_shallow, "Nix.forceShallow({})");
|
eval_bc!(eval_shallow, ForceMode::ForceShallow);
|
||||||
eval!(eval_deep, "Nix.forceDeep({})");
|
eval_bc!(eval_deep, ForceMode::ForceDeep);
|
||||||
|
|
||||||
pub fn eval_repl<'a>(&'a mut self, source: Source, scope: &'a HashSet<SymId>) -> Result<Value> {
|
pub fn eval_repl<'a>(&'a mut self, source: Source, scope: &'a HashSet<SymId>) -> Result<Value> {
|
||||||
tracing::info!("Starting evaluation");
|
tracing::info!("Starting evaluation");
|
||||||
|
|
||||||
@@ -156,6 +157,18 @@ impl Context {
|
|||||||
self.ctx.compile(source, None)
|
self.ctx.compile(source, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode> {
|
||||||
|
self.ctx.compile_bytecode(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disassemble(&self, bytecode: &Bytecode) -> String {
|
||||||
|
Disassembler::new(bytecode, &self.ctx).disassemble()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disassemble_colored(&self, bytecode: &Bytecode) -> String {
|
||||||
|
Disassembler::new(bytecode, &self.ctx).disassemble_colored()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_store_dir(&self) -> &str {
|
pub fn get_store_dir(&self) -> &str {
|
||||||
self.ctx.get_store_dir()
|
self.ctx.get_store_dir()
|
||||||
}
|
}
|
||||||
@@ -188,6 +201,12 @@ struct Ctx {
|
|||||||
store: DaemonStore,
|
store: DaemonStore,
|
||||||
spans: UnsafeCell<Vec<(usize, TextRange)>>,
|
spans: UnsafeCell<Vec<(usize, TextRange)>>,
|
||||||
thunk_count: usize,
|
thunk_count: usize,
|
||||||
|
global_strings: Vec<String>,
|
||||||
|
global_string_map: HashMap<String, u32>,
|
||||||
|
global_constants: Vec<Constant>,
|
||||||
|
global_constant_map: HashMap<Constant, u32>,
|
||||||
|
synced_strings: usize,
|
||||||
|
synced_constants: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Owns the bump allocator and a read-only reference into it.
|
/// Owns the bump allocator and a read-only reference into it.
|
||||||
@@ -261,6 +280,12 @@ impl Ctx {
|
|||||||
store,
|
store,
|
||||||
spans: UnsafeCell::new(Vec::new()),
|
spans: UnsafeCell::new(Vec::new()),
|
||||||
thunk_count: 0,
|
thunk_count: 0,
|
||||||
|
global_strings: Vec::new(),
|
||||||
|
global_string_map: HashMap::new(),
|
||||||
|
global_constants: Vec::new(),
|
||||||
|
global_constant_map: HashMap::new(),
|
||||||
|
synced_strings: 0,
|
||||||
|
synced_constants: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,6 +374,30 @@ impl Ctx {
|
|||||||
tracing::debug!("Generated scoped code: {}", &code);
|
tracing::debug!("Generated scoped code: {}", &code);
|
||||||
Ok(code)
|
Ok(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode> {
|
||||||
|
let root = self.downgrade(source, None)?;
|
||||||
|
tracing::debug!("Generating bytecode");
|
||||||
|
let bytecode = bytecode::compile_bytecode(root.as_ref(), self);
|
||||||
|
tracing::debug!("Compiled bytecode: {:#04X?}", bytecode.code);
|
||||||
|
Ok(bytecode)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_bytecode_scoped(
|
||||||
|
&mut self,
|
||||||
|
source: Source,
|
||||||
|
scope: Vec<String>,
|
||||||
|
) -> Result<Bytecode> {
|
||||||
|
let scope = Scope::ScopedImport(
|
||||||
|
scope
|
||||||
|
.into_iter()
|
||||||
|
.map(|k| self.symbols.get_or_intern(k))
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
let root = self.downgrade(source, Some(scope))?;
|
||||||
|
tracing::debug!("Generating bytecode for scoped import");
|
||||||
|
Ok(bytecode::compile_bytecode_scoped(root.as_ref(), self))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodegenContext for Ctx {
|
impl CodegenContext for Ctx {
|
||||||
@@ -378,6 +427,40 @@ impl CodegenContext for Ctx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BytecodeContext for Ctx {
|
||||||
|
fn intern_string(&mut self, s: &str) -> u32 {
|
||||||
|
if let Some(&idx) = self.global_string_map.get(s) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
let idx = self.global_strings.len() as u32;
|
||||||
|
self.global_strings.push(s.to_string());
|
||||||
|
self.global_string_map.insert(s.to_string(), idx);
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intern_constant(&mut self, c: Constant) -> u32 {
|
||||||
|
if let Some(&idx) = self.global_constant_map.get(&c) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
let idx = self.global_constants.len() as u32;
|
||||||
|
self.global_constants.push(c.clone());
|
||||||
|
self.global_constant_map.insert(c, idx);
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
|
||||||
|
fn register_span(&self, range: TextRange) -> u32 {
|
||||||
|
CodegenContext::register_span(self, range) as u32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_sym(&self, id: SymId) -> &str {
|
||||||
|
self.symbols.resolve(id).expect("SymId out of bounds")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_dir(&self) -> &Path {
|
||||||
|
Ctx::get_current_dir(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RuntimeContext for Ctx {
|
impl RuntimeContext for Ctx {
|
||||||
fn get_current_dir(&self) -> &Path {
|
fn get_current_dir(&self) -> &Path {
|
||||||
self.get_current_dir()
|
self.get_current_dir()
|
||||||
@@ -391,6 +474,16 @@ impl RuntimeContext for Ctx {
|
|||||||
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
|
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
|
||||||
self.compile_scoped(source, scope)
|
self.compile_scoped(source, scope)
|
||||||
}
|
}
|
||||||
|
fn compile_bytecode(&mut self, source: Source) -> Result<Bytecode> {
|
||||||
|
self.compile_bytecode(source)
|
||||||
|
}
|
||||||
|
fn compile_bytecode_scoped(
|
||||||
|
&mut self,
|
||||||
|
source: Source,
|
||||||
|
scope: Vec<String>,
|
||||||
|
) -> Result<Bytecode> {
|
||||||
|
self.compile_bytecode_scoped(source, scope)
|
||||||
|
}
|
||||||
fn get_source(&self, id: usize) -> Source {
|
fn get_source(&self, id: usize) -> Source {
|
||||||
self.sources.get(id).expect("source not found").clone()
|
self.sources.get(id).expect("source not found").clone()
|
||||||
}
|
}
|
||||||
@@ -401,6 +494,24 @@ impl RuntimeContext for Ctx {
|
|||||||
let spans = unsafe { &*self.spans.get() };
|
let spans = unsafe { &*self.spans.get() };
|
||||||
spans[id]
|
spans[id]
|
||||||
}
|
}
|
||||||
|
fn take_unsynced(&mut self) -> (Vec<String>, Vec<Constant>, usize, usize) {
|
||||||
|
let strings_base = self.synced_strings;
|
||||||
|
let constants_base = self.synced_constants;
|
||||||
|
let new_strings = self.global_strings[strings_base..].to_vec();
|
||||||
|
let new_constants = self.global_constants[constants_base..].to_vec();
|
||||||
|
self.synced_strings = self.global_strings.len();
|
||||||
|
self.synced_constants = self.global_constants.len();
|
||||||
|
(new_strings, new_constants, strings_base, constants_base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisassemblerContext for Ctx {
|
||||||
|
fn lookup_string(&self, id: u32) -> &str {
|
||||||
|
self.global_strings.get(id as usize).expect("string not found")
|
||||||
|
}
|
||||||
|
fn lookup_constant(&self, id: u32) -> &Constant {
|
||||||
|
self.global_constants.get(id as usize).expect("constant not found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Scope<'ctx> {
|
enum Scope<'ctx> {
|
||||||
|
|||||||
354
nix-js/src/disassembler.rs
Normal file
354
nix-js/src/disassembler.rs
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use colored::Colorize;
|
||||||
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
|
use crate::bytecode::{Bytecode, Constant, Op};
|
||||||
|
|
||||||
|
pub(crate) trait DisassemblerContext {
|
||||||
|
fn lookup_string(&self, id: u32) -> &str;
|
||||||
|
fn lookup_constant(&self, id: u32) -> &Constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Disassembler<'a, Ctx> {
|
||||||
|
code: &'a [u8],
|
||||||
|
ctx: &'a Ctx,
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Ctx: DisassemblerContext> Disassembler<'a, Ctx> {
|
||||||
|
pub fn new(bytecode: &'a Bytecode, ctx: &'a Ctx) -> Self {
|
||||||
|
Self {
|
||||||
|
code: &bytecode.code,
|
||||||
|
ctx,
|
||||||
|
pos: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u8(&mut self) -> u8 {
|
||||||
|
let b = self.code[self.pos];
|
||||||
|
self.pos += 1;
|
||||||
|
b
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u16(&mut self) -> u16 {
|
||||||
|
let bytes = self.code[self.pos..self.pos + 2]
|
||||||
|
.try_into()
|
||||||
|
.expect("no enough bytes");
|
||||||
|
self.pos += 2;
|
||||||
|
u16::from_le_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_u32(&mut self) -> u32 {
|
||||||
|
let bytes = self.code[self.pos..self.pos + 4]
|
||||||
|
.try_into()
|
||||||
|
.expect("no enough bytes");
|
||||||
|
self.pos += 4;
|
||||||
|
u32::from_le_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_i32(&mut self) -> i32 {
|
||||||
|
let bytes = self.code[self.pos..self.pos + 4]
|
||||||
|
.try_into()
|
||||||
|
.expect("no enough bytes");
|
||||||
|
self.pos += 4;
|
||||||
|
i32::from_le_bytes(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disassemble(&mut self) -> String {
|
||||||
|
self.disassemble_impl(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disassemble_colored(&mut self) -> String {
|
||||||
|
self.disassemble_impl(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disassemble_impl(&mut self, color: bool) -> String {
|
||||||
|
let mut out = String::new();
|
||||||
|
if color {
|
||||||
|
let _ = writeln!(out, "{}", "=== Bytecode Disassembly ===".bold().white());
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
"{} {}",
|
||||||
|
"Length:".white(),
|
||||||
|
format!("{} bytes", self.code.len()).cyan()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let _ = writeln!(out, "=== Bytecode Disassembly ===");
|
||||||
|
let _ = writeln!(out, "Length: {} bytes", self.code.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
while self.pos < self.code.len() {
|
||||||
|
let start_pos = self.pos;
|
||||||
|
let op_byte = self.read_u8();
|
||||||
|
let (mnemonic, args) = self.decode_instruction(op_byte, start_pos);
|
||||||
|
|
||||||
|
let bytes_slice = &self.code[start_pos + 1..self.pos];
|
||||||
|
|
||||||
|
for (i, chunk) in bytes_slice.chunks(4).enumerate() {
|
||||||
|
let bytes_str = {
|
||||||
|
let mut temp = String::new();
|
||||||
|
if i == 0 {
|
||||||
|
let _ = write!(&mut temp, "{:02x}", self.code[start_pos]);
|
||||||
|
} else {
|
||||||
|
let _ = write!(&mut temp, " ");
|
||||||
|
}
|
||||||
|
for b in chunk.iter() {
|
||||||
|
let _ = write!(&mut temp, " {:02x}", b);
|
||||||
|
}
|
||||||
|
temp
|
||||||
|
};
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
if color {
|
||||||
|
let sep = if args.is_empty() { "" } else { " " };
|
||||||
|
let _ = writeln!(
|
||||||
|
out,
|
||||||
|
"{} {:<14} | {}{}{}",
|
||||||
|
format!("{:04x}", start_pos).dimmed(),
|
||||||
|
bytes_str.green(),
|
||||||
|
mnemonic.yellow().bold(),
|
||||||
|
sep,
|
||||||
|
args.cyan()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let op_str = if args.is_empty() {
|
||||||
|
mnemonic.to_string()
|
||||||
|
} else {
|
||||||
|
format!("{} {}", mnemonic, args)
|
||||||
|
};
|
||||||
|
let _ = writeln!(out, "{:04x} {:<14} | {}", start_pos, bytes_str, op_str);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let extra_width = start_pos.ilog2() >> 4;
|
||||||
|
if color {
|
||||||
|
let _ = write!(out, " ");
|
||||||
|
for _ in 0..extra_width {
|
||||||
|
let _ = write!(out, " ");
|
||||||
|
}
|
||||||
|
let _ = writeln!(out, " {:<14} |", bytes_str.green());
|
||||||
|
} else {
|
||||||
|
let _ = write!(out, " ");
|
||||||
|
for _ in 0..extra_width {
|
||||||
|
let _ = write!(out, " ");
|
||||||
|
}
|
||||||
|
let _ = writeln!(out, " {:<14} |", bytes_str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_instruction(&mut self, op_byte: u8, current_pc: usize) -> (&'static str, String) {
|
||||||
|
let op = Op::try_from_primitive(op_byte).expect("invalid op code");
|
||||||
|
|
||||||
|
match op {
|
||||||
|
Op::PushConst => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
let val = self.ctx.lookup_constant(idx);
|
||||||
|
let val_str = match val {
|
||||||
|
Constant::Int(i) => format!("Int({})", i),
|
||||||
|
Constant::Float(f) => format!("Float(bits: {})", f),
|
||||||
|
};
|
||||||
|
("PushConst", format!("@{} ({})", idx, val_str))
|
||||||
|
}
|
||||||
|
Op::PushString => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
let s = self.ctx.lookup_string(idx);
|
||||||
|
let len = s.len();
|
||||||
|
let mut s_fmt = format!("{:?}", s);
|
||||||
|
if s_fmt.len() > 60 {
|
||||||
|
s_fmt.truncate(57);
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
write!(s_fmt, "...\" (total {len} bytes)").unwrap();
|
||||||
|
}
|
||||||
|
("PushString", format!("@{} {}", idx, s_fmt))
|
||||||
|
}
|
||||||
|
Op::PushNull => ("PushNull", String::new()),
|
||||||
|
Op::PushTrue => ("PushTrue", String::new()),
|
||||||
|
Op::PushFalse => ("PushFalse", String::new()),
|
||||||
|
|
||||||
|
Op::LoadLocal => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
("LoadLocal", format!("[{}]", idx))
|
||||||
|
}
|
||||||
|
Op::LoadOuter => {
|
||||||
|
let depth = self.read_u8();
|
||||||
|
let idx = self.read_u32();
|
||||||
|
("LoadOuter", format!("depth={} [{}]", depth, idx))
|
||||||
|
}
|
||||||
|
Op::StoreLocal => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
("StoreLocal", format!("[{}]", idx))
|
||||||
|
}
|
||||||
|
Op::AllocLocals => {
|
||||||
|
let count = self.read_u32();
|
||||||
|
("AllocLocals", format!("count={}", count))
|
||||||
|
}
|
||||||
|
|
||||||
|
Op::MakeThunk => {
|
||||||
|
let offset = self.read_u32();
|
||||||
|
let label_idx = self.read_u32();
|
||||||
|
let label = self.ctx.lookup_string(label_idx);
|
||||||
|
("MakeThunk", format!("-> {:04x} label={}", offset, label))
|
||||||
|
}
|
||||||
|
Op::MakeClosure => {
|
||||||
|
let offset = self.read_u32();
|
||||||
|
let slots = self.read_u32();
|
||||||
|
("MakeClosure", format!("-> {:04x} slots={}", offset, slots))
|
||||||
|
}
|
||||||
|
Op::MakePatternClosure => {
|
||||||
|
let offset = self.read_u32();
|
||||||
|
let slots = self.read_u32();
|
||||||
|
let req_count = self.read_u16();
|
||||||
|
let opt_count = self.read_u16();
|
||||||
|
let ellipsis = self.read_u8() != 0;
|
||||||
|
|
||||||
|
let mut arg_str = format!(
|
||||||
|
"-> {:04x} slots={} req={} opt={} ...={})",
|
||||||
|
offset, slots, req_count, opt_count, ellipsis
|
||||||
|
);
|
||||||
|
|
||||||
|
arg_str.push_str(" Args=[");
|
||||||
|
for _ in 0..req_count {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
arg_str.push_str(&format!("Req({}) ", self.ctx.lookup_string(idx)));
|
||||||
|
}
|
||||||
|
for _ in 0..opt_count {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
arg_str.push_str(&format!("Opt({}) ", self.ctx.lookup_string(idx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_args = req_count + opt_count;
|
||||||
|
for _ in 0..total_args {
|
||||||
|
let _name_idx = self.read_u32();
|
||||||
|
let _span_id = self.read_u32();
|
||||||
|
}
|
||||||
|
arg_str.push(']');
|
||||||
|
|
||||||
|
("MakePatternClosure", arg_str)
|
||||||
|
}
|
||||||
|
|
||||||
|
Op::Call => {
|
||||||
|
let span_id = self.read_u32();
|
||||||
|
("Call", format!("span={}", span_id))
|
||||||
|
}
|
||||||
|
Op::CallNoSpan => ("CallNoSpan", String::new()),
|
||||||
|
|
||||||
|
Op::MakeAttrs => {
|
||||||
|
let count = self.read_u32();
|
||||||
|
("MakeAttrs", format!("size={}", count))
|
||||||
|
}
|
||||||
|
Op::MakeAttrsDyn => {
|
||||||
|
let static_count = self.read_u32();
|
||||||
|
let dyn_count = self.read_u32();
|
||||||
|
(
|
||||||
|
"MakeAttrsDyn",
|
||||||
|
format!("static={} dyn={}", static_count, dyn_count),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Op::MakeEmptyAttrs => ("MakeEmptyAttrs", String::new()),
|
||||||
|
|
||||||
|
Op::Select => {
|
||||||
|
let path_len = self.read_u16();
|
||||||
|
let span_id = self.read_u32();
|
||||||
|
("Select", format!("path_len={} span={}", path_len, span_id))
|
||||||
|
}
|
||||||
|
Op::SelectDefault => {
|
||||||
|
let path_len = self.read_u16();
|
||||||
|
let span_id = self.read_u32();
|
||||||
|
(
|
||||||
|
"SelectDefault",
|
||||||
|
format!("path_len={} span={}", path_len, span_id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Op::HasAttr => {
|
||||||
|
let path_len = self.read_u16();
|
||||||
|
("HasAttr", format!("path_len={}", path_len))
|
||||||
|
}
|
||||||
|
|
||||||
|
Op::MakeList => {
|
||||||
|
let count = self.read_u32();
|
||||||
|
("MakeList", format!("size={}", count))
|
||||||
|
}
|
||||||
|
|
||||||
|
Op::OpAdd => ("OpAdd", String::new()),
|
||||||
|
Op::OpSub => ("OpSub", String::new()),
|
||||||
|
Op::OpMul => ("OpMul", String::new()),
|
||||||
|
Op::OpDiv => ("OpDiv", String::new()),
|
||||||
|
Op::OpEq => ("OpEq", String::new()),
|
||||||
|
Op::OpNeq => ("OpNeq", String::new()),
|
||||||
|
Op::OpLt => ("OpLt", String::new()),
|
||||||
|
Op::OpGt => ("OpGt", String::new()),
|
||||||
|
Op::OpLeq => ("OpLeq", String::new()),
|
||||||
|
Op::OpGeq => ("OpGeq", String::new()),
|
||||||
|
Op::OpConcat => ("OpConcat", String::new()),
|
||||||
|
Op::OpUpdate => ("OpUpdate", String::new()),
|
||||||
|
Op::OpNeg => ("OpNeg", String::new()),
|
||||||
|
Op::OpNot => ("OpNot", String::new()),
|
||||||
|
|
||||||
|
Op::ForceBool => ("ForceBool", String::new()),
|
||||||
|
|
||||||
|
Op::JumpIfFalse => {
|
||||||
|
let offset = self.read_i32();
|
||||||
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
|
(
|
||||||
|
"JumpIfFalse",
|
||||||
|
format!("-> {:04x} offset={}", target, offset),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Op::JumpIfTrue => {
|
||||||
|
let offset = self.read_i32();
|
||||||
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
|
("JumpIfTrue", format!("-> {:04x} offset={}", target, offset))
|
||||||
|
}
|
||||||
|
Op::Jump => {
|
||||||
|
let offset = self.read_i32();
|
||||||
|
let target = (current_pc as isize + 1 + 4 + offset as isize) as usize;
|
||||||
|
("Jump", format!("-> {:04x} offset={}", target, offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
Op::ConcatStrings => {
|
||||||
|
let count = self.read_u16();
|
||||||
|
let force = self.read_u8();
|
||||||
|
("ConcatStrings", format!("count={} force={}", count, force))
|
||||||
|
}
|
||||||
|
Op::ResolvePath => ("ResolvePath", String::new()),
|
||||||
|
Op::Assert => {
|
||||||
|
let raw_idx = self.read_u32();
|
||||||
|
let span_id = self.read_u32();
|
||||||
|
("Assert", format!("text_id={} span={}", raw_idx, span_id))
|
||||||
|
}
|
||||||
|
Op::PushWith => ("PushWith", String::new()),
|
||||||
|
Op::PopWith => ("PopWith", String::new()),
|
||||||
|
Op::WithLookup => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
let name = self.ctx.lookup_string(idx);
|
||||||
|
("WithLookup", format!("{:?}", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
Op::LoadBuiltins => ("LoadBuiltins", String::new()),
|
||||||
|
Op::LoadBuiltin => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
let name = self.ctx.lookup_string(idx);
|
||||||
|
("LoadBuiltin", format!("{:?}", name))
|
||||||
|
}
|
||||||
|
Op::MkPos => {
|
||||||
|
let span_id = self.read_u32();
|
||||||
|
("MkPos", format!("id={}", span_id))
|
||||||
|
}
|
||||||
|
Op::LoadReplBinding => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
let name = self.ctx.lookup_string(idx);
|
||||||
|
("LoadReplBinding", format!("{:?}", name))
|
||||||
|
}
|
||||||
|
Op::LoadScopedBinding => {
|
||||||
|
let idx = self.read_u32();
|
||||||
|
let name = self.ctx.lookup_string(idx);
|
||||||
|
("LoadScopedBinding", format!("{:?}", name))
|
||||||
|
}
|
||||||
|
Op::Return => ("Return", String::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,8 +5,10 @@ pub mod error;
|
|||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
|
||||||
|
mod bytecode;
|
||||||
mod codegen;
|
mod codegen;
|
||||||
mod derivation;
|
mod derivation;
|
||||||
|
mod disassembler;
|
||||||
mod downgrade;
|
mod downgrade;
|
||||||
mod fetcher;
|
mod fetcher;
|
||||||
mod ir;
|
mod ir;
|
||||||
|
|||||||
@@ -77,10 +77,10 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
|
|||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
match context.compile(src) {
|
match context.compile_bytecode(src) {
|
||||||
Ok(compiled) => {
|
Ok(compiled) => {
|
||||||
if !silent {
|
if !silent {
|
||||||
println!("{compiled}");
|
println!("{}", context.disassemble_colored(&compiled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::path::Path;
|
|||||||
use deno_core::PollEventLoopOptions;
|
use deno_core::PollEventLoopOptions;
|
||||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||||
|
|
||||||
|
use crate::bytecode::{Bytecode, Constant};
|
||||||
use crate::error::{Error, Result, Source};
|
use crate::error::{Error, Result, Source};
|
||||||
use crate::store::DaemonStore;
|
use crate::store::DaemonStore;
|
||||||
use crate::value::{AttrSet, List, Symbol, Value};
|
use crate::value::{AttrSet, List, Symbol, Value};
|
||||||
@@ -24,9 +25,16 @@ pub(crate) trait RuntimeContext: 'static {
|
|||||||
fn add_source(&mut self, path: Source);
|
fn add_source(&mut self, path: Source);
|
||||||
fn compile(&mut self, source: Source) -> Result<String>;
|
fn compile(&mut self, source: Source) -> Result<String>;
|
||||||
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 compile_bytecode(&mut self, source: Source) -> Result<Bytecode>;
|
||||||
|
fn compile_bytecode_scoped(
|
||||||
|
&mut self,
|
||||||
|
source: Source,
|
||||||
|
scope: Vec<String>,
|
||||||
|
) -> Result<Bytecode>;
|
||||||
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);
|
fn get_span(&self, id: usize) -> (usize, rnix::TextRange);
|
||||||
|
fn take_unsynced(&mut self) -> (Vec<String>, Vec<Constant>, usize, usize);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait OpStateExt<Ctx: RuntimeContext> {
|
pub(crate) trait OpStateExt<Ctx: RuntimeContext> {
|
||||||
@@ -121,6 +129,7 @@ pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
|||||||
#[cfg(feature = "inspector")]
|
#[cfg(feature = "inspector")]
|
||||||
wait_for_inspector: bool,
|
wait_for_inspector: bool,
|
||||||
symbols: GlobalSymbols,
|
symbols: GlobalSymbols,
|
||||||
|
cached_fns: CachedFunctions,
|
||||||
_marker: PhantomData<Ctx>,
|
_marker: PhantomData<Ctx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,9 +171,11 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
js_runtime.op_state().borrow_mut().put(RegexCache::new());
|
js_runtime.op_state().borrow_mut().put(RegexCache::new());
|
||||||
js_runtime.op_state().borrow_mut().put(DrvHashCache::new());
|
js_runtime.op_state().borrow_mut().put(DrvHashCache::new());
|
||||||
|
|
||||||
let symbols = {
|
let (symbols, cached_fns) = {
|
||||||
deno_core::scope!(scope, &mut js_runtime);
|
deno_core::scope!(scope, &mut js_runtime);
|
||||||
Self::get_symbols(scope)?
|
let symbols = Self::get_symbols(scope)?;
|
||||||
|
let cached_fns = Self::get_cached_functions(scope)?;
|
||||||
|
(symbols, cached_fns)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -177,6 +188,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
#[cfg(feature = "inspector")]
|
#[cfg(feature = "inspector")]
|
||||||
wait_for_inspector: inspector_options.wait,
|
wait_for_inspector: inspector_options.wait,
|
||||||
symbols,
|
symbols,
|
||||||
|
cached_fns,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -227,6 +239,87 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
Ok(to_value(local_value, scope, symbols))
|
Ok(to_value(local_value, scope, symbols))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn eval_bytecode(
|
||||||
|
&mut self,
|
||||||
|
result: Bytecode,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
force_mode: ForceMode,
|
||||||
|
) -> Result<Value> {
|
||||||
|
let ctx: &'static mut Ctx = unsafe { &mut *(ctx as *mut Ctx) };
|
||||||
|
{
|
||||||
|
deno_core::scope!(scope, self.js_runtime);
|
||||||
|
sync_global_tables(scope, &self.cached_fns, ctx);
|
||||||
|
}
|
||||||
|
let op_state = self.js_runtime.op_state();
|
||||||
|
op_state.borrow_mut().put(ctx);
|
||||||
|
|
||||||
|
#[cfg(feature = "inspector")]
|
||||||
|
if self.wait_for_inspector {
|
||||||
|
self.js_runtime
|
||||||
|
.inspector()
|
||||||
|
.wait_for_session_and_break_on_next_statement();
|
||||||
|
} else {
|
||||||
|
self.js_runtime.inspector().wait_for_session();
|
||||||
|
}
|
||||||
|
|
||||||
|
deno_core::scope!(scope, self.js_runtime);
|
||||||
|
|
||||||
|
let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(result.code);
|
||||||
|
let ab = v8::ArrayBuffer::with_backing_store(scope, &store.make_shared());
|
||||||
|
let u8a = v8::Uint8Array::new(scope, ab, 0, ab.byte_length())
|
||||||
|
.ok_or_else(|| Error::internal("failed to create Uint8Array".into()))?;
|
||||||
|
|
||||||
|
let dir = v8::String::new(scope, &result.current_dir)
|
||||||
|
.ok_or_else(|| Error::internal("failed to create dir string".into()))?;
|
||||||
|
|
||||||
|
let undef = v8::undefined(scope);
|
||||||
|
let tc = std::pin::pin!(v8::TryCatch::new(scope));
|
||||||
|
let scope = &mut tc.init();
|
||||||
|
|
||||||
|
let exec_bytecode = v8::Local::new(scope, &self.cached_fns.exec_bytecode);
|
||||||
|
let raw_result = exec_bytecode
|
||||||
|
.call(scope, undef.into(), &[u8a.into(), dir.into()])
|
||||||
|
.ok_or_else(|| {
|
||||||
|
scope
|
||||||
|
.exception()
|
||||||
|
.map(|e| {
|
||||||
|
let op_state_borrow = op_state.borrow();
|
||||||
|
let ctx: &Ctx = op_state_borrow.get_ctx();
|
||||||
|
Box::new(crate::error::parse_js_error(
|
||||||
|
deno_core::error::JsError::from_v8_exception(scope, e),
|
||||||
|
ctx,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Error::internal("bytecode execution failed".into()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let force_fn = match force_mode {
|
||||||
|
ForceMode::Force => &self.cached_fns.force_fn,
|
||||||
|
ForceMode::ForceShallow => &self.cached_fns.force_shallow_fn,
|
||||||
|
ForceMode::ForceDeep => &self.cached_fns.force_deep_fn,
|
||||||
|
};
|
||||||
|
let force_fn = v8::Local::new(scope, force_fn);
|
||||||
|
|
||||||
|
let forced = force_fn
|
||||||
|
.call(scope, undef.into(), &[raw_result])
|
||||||
|
.ok_or_else(|| {
|
||||||
|
scope
|
||||||
|
.exception()
|
||||||
|
.map(|e| {
|
||||||
|
let op_state_borrow = op_state.borrow();
|
||||||
|
let ctx: &Ctx = op_state_borrow.get_ctx();
|
||||||
|
Box::new(crate::error::parse_js_error(
|
||||||
|
deno_core::error::JsError::from_v8_exception(scope, e),
|
||||||
|
ctx,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| Error::internal("force failed".into()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let symbols = &self.symbols.local(scope);
|
||||||
|
Ok(to_value(forced, scope, symbols))
|
||||||
|
}
|
||||||
|
|
||||||
fn get_symbols(scope: &ScopeRef) -> Result<GlobalSymbols> {
|
fn get_symbols(scope: &ScopeRef) -> Result<GlobalSymbols> {
|
||||||
let global = scope.get_current_context().global(scope);
|
let global = scope.get_current_context().global(scope);
|
||||||
let nix_key = v8::String::new(scope, "Nix")
|
let nix_key = v8::String::new(scope, "Nix")
|
||||||
@@ -267,6 +360,61 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
|||||||
is_cycle,
|
is_cycle,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_cached_functions(scope: &ScopeRef) -> Result<CachedFunctions> {
|
||||||
|
let global = scope.get_current_context().global(scope);
|
||||||
|
let nix_key = v8::String::new(scope, "Nix")
|
||||||
|
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
|
||||||
|
let nix_obj = global
|
||||||
|
.get(scope, nix_key.into())
|
||||||
|
.ok_or_else(|| Error::internal("failed to get global Nix object".into()))?
|
||||||
|
.to_object(scope)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::internal("failed to convert global Nix Value to object".into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let get_fn = |name: &str| -> Result<v8::Global<v8::Function>> {
|
||||||
|
let key = v8::String::new(scope, name)
|
||||||
|
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
|
||||||
|
let val = nix_obj
|
||||||
|
.get(scope, key.into())
|
||||||
|
.ok_or_else(|| Error::internal(format!("failed to get Nix.{name}")))?;
|
||||||
|
let func = val
|
||||||
|
.try_cast::<v8::Function>()
|
||||||
|
.map_err(|err| Error::internal(format!("Nix.{name} is not a function ({err})")))?;
|
||||||
|
Ok(v8::Global::new(scope, func))
|
||||||
|
};
|
||||||
|
|
||||||
|
let exec_bytecode = get_fn("execBytecode")?;
|
||||||
|
let force_fn = get_fn("force")?;
|
||||||
|
let force_shallow_fn = get_fn("forceShallow")?;
|
||||||
|
let force_deep_fn = get_fn("forceDeep")?;
|
||||||
|
|
||||||
|
let strings_key = v8::String::new(scope, "strings")
|
||||||
|
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
|
||||||
|
let strings_array = nix_obj
|
||||||
|
.get(scope, strings_key.into())
|
||||||
|
.ok_or_else(|| Error::internal("failed to get Nix.strings".into()))?
|
||||||
|
.try_cast::<v8::Array>()
|
||||||
|
.map_err(|err| Error::internal(format!("Nix.strings is not an array ({err})")))?;
|
||||||
|
|
||||||
|
let constants_key = v8::String::new(scope, "constants")
|
||||||
|
.ok_or_else(|| Error::internal("failed to create V8 String".into()))?;
|
||||||
|
let constants_array = nix_obj
|
||||||
|
.get(scope, constants_key.into())
|
||||||
|
.ok_or_else(|| Error::internal("failed to get Nix.constants".into()))?
|
||||||
|
.try_cast::<v8::Array>()
|
||||||
|
.map_err(|err| Error::internal(format!("Nix.constants is not an array ({err})")))?;
|
||||||
|
|
||||||
|
Ok(CachedFunctions {
|
||||||
|
exec_bytecode,
|
||||||
|
force_fn,
|
||||||
|
force_shallow_fn,
|
||||||
|
force_deep_fn,
|
||||||
|
strings_array: v8::Global::new(scope, strings_array),
|
||||||
|
constants_array: v8::Global::new(scope, constants_array),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GlobalSymbols {
|
struct GlobalSymbols {
|
||||||
@@ -297,6 +445,51 @@ struct LocalSymbols<'a> {
|
|||||||
is_cycle: v8::Local<'a, v8::Symbol>,
|
is_cycle: v8::Local<'a, v8::Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CachedFunctions {
|
||||||
|
exec_bytecode: v8::Global<v8::Function>,
|
||||||
|
force_fn: v8::Global<v8::Function>,
|
||||||
|
force_shallow_fn: v8::Global<v8::Function>,
|
||||||
|
force_deep_fn: v8::Global<v8::Function>,
|
||||||
|
strings_array: v8::Global<v8::Array>,
|
||||||
|
constants_array: v8::Global<v8::Array>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum ForceMode {
|
||||||
|
Force,
|
||||||
|
ForceShallow,
|
||||||
|
ForceDeep,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_global_tables<Ctx: RuntimeContext>(
|
||||||
|
scope: &ScopeRef,
|
||||||
|
cached: &CachedFunctions,
|
||||||
|
ctx: &mut Ctx,
|
||||||
|
) {
|
||||||
|
let (new_strings, new_constants, strings_base, constants_base) = ctx.take_unsynced();
|
||||||
|
|
||||||
|
if !new_strings.is_empty() {
|
||||||
|
let s_array = v8::Local::new(scope, &cached.strings_array);
|
||||||
|
for (i, s) in new_strings.iter().enumerate() {
|
||||||
|
let idx = (strings_base + i) as u32;
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let val = v8::String::new(scope, s).unwrap();
|
||||||
|
s_array.set_index(scope, idx, val.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !new_constants.is_empty() {
|
||||||
|
let k_array = v8::Local::new(scope, &cached.constants_array);
|
||||||
|
for (i, c) in new_constants.iter().enumerate() {
|
||||||
|
let idx = (constants_base + i) as u32;
|
||||||
|
let val: v8::Local<v8::Value> = match c {
|
||||||
|
Constant::Int(n) => v8::BigInt::new_from_i64(scope, *n).into(),
|
||||||
|
Constant::Float(bits) => v8::Number::new(scope, f64::from_bits(*bits)).into(),
|
||||||
|
};
|
||||||
|
k_array.set_index(scope, idx, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn to_value<'a>(
|
fn to_value<'a>(
|
||||||
val: LocalValue<'a>,
|
val: LocalValue<'a>,
|
||||||
scope: &ScopeRef<'a, '_>,
|
scope: &ScopeRef<'a, '_>,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use regex::Regex;
|
|||||||
use rust_embed::Embed;
|
use rust_embed::Embed;
|
||||||
|
|
||||||
use super::{NixRuntimeError, OpStateExt, RuntimeContext};
|
use super::{NixRuntimeError, OpStateExt, RuntimeContext};
|
||||||
|
use crate::bytecode::{Bytecode, Constant};
|
||||||
use crate::error::Source;
|
use crate::error::Source;
|
||||||
use crate::store::Store as _;
|
use crate::store::Store as _;
|
||||||
|
|
||||||
@@ -79,35 +80,67 @@ fn new_simple_jserror(msg: String) -> Box<JsError> {
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Compiled(String);
|
struct BytecodeRet {
|
||||||
impl<'a> ToV8<'a> for Compiled {
|
bytecode: Bytecode,
|
||||||
|
new_strings: *const [String],
|
||||||
|
new_constants: *const [Constant],
|
||||||
|
strings_base: usize,
|
||||||
|
constants_base: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ToV8<'a> for BytecodeRet {
|
||||||
type Error = Box<JsError>;
|
type Error = Box<JsError>;
|
||||||
|
#[allow(clippy::unwrap_used)]
|
||||||
fn to_v8<'i>(
|
fn to_v8<'i>(
|
||||||
self,
|
self,
|
||||||
scope: &mut v8::PinScope<'a, 'i>,
|
scope: &mut v8::PinScope<'a, 'i>,
|
||||||
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
|
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||||
let Ok(script) = self.0.to_v8(scope);
|
let global = scope.get_current_context().global(scope);
|
||||||
let Some(source) = script.to_string(scope) else {
|
let nix_key = v8::String::new(scope, "Nix")
|
||||||
unsafe { std::hint::unreachable_unchecked() }
|
.ok_or_else(|| new_simple_jserror("failed to create v8 string".into()))?;
|
||||||
};
|
let nix_obj = global
|
||||||
let tc = std::pin::pin!(v8::TryCatch::new(scope));
|
.get(scope, nix_key.into())
|
||||||
let mut scope = tc.init();
|
.ok_or_else(|| new_simple_jserror("failed to get Nix global".into()))?
|
||||||
let Some(compiled) = v8::Script::compile(&scope, source, None) else {
|
.to_object(scope)
|
||||||
let msg = scope
|
.ok_or_else(|| new_simple_jserror("Nix is not an object".into()))?;
|
||||||
.exception()
|
|
||||||
.map(|e| e.to_rust_string_lossy(&scope))
|
let s_key = v8::String::new(scope, "strings").unwrap();
|
||||||
.unwrap_or_else(|| "failed to compile code".into());
|
let s_array: v8::Local<v8::Array> = nix_obj
|
||||||
return Err(new_simple_jserror(msg));
|
.get(scope, s_key.into())
|
||||||
};
|
.unwrap()
|
||||||
match compiled.run(&scope) {
|
.try_into()
|
||||||
Some(val) => Ok(val),
|
.unwrap();
|
||||||
None => Err(scope
|
for (i, s) in unsafe { &*self.new_strings }.iter().enumerate() {
|
||||||
.exception()
|
let idx = (self.strings_base + i) as u32;
|
||||||
.map(|e| JsError::from_v8_exception(&mut scope, e))
|
let val = v8::String::new(scope, s).unwrap();
|
||||||
.unwrap_or_else(|| {
|
s_array.set_index(scope, idx, val.into());
|
||||||
new_simple_jserror("script execution failed unexpectedly".into())
|
|
||||||
})),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let k_key = v8::String::new(scope, "constants").unwrap();
|
||||||
|
let k_array: v8::Local<v8::Array> = nix_obj
|
||||||
|
.get(scope, k_key.into())
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
for (i, c) in unsafe { &*self.new_constants }.iter().enumerate() {
|
||||||
|
let idx = (self.constants_base + i) as u32;
|
||||||
|
let val: v8::Local<v8::Value> = match c {
|
||||||
|
Constant::Int(n) => v8::BigInt::new_from_i64(scope, *n).into(),
|
||||||
|
Constant::Float(bits) => v8::Number::new(scope, f64::from_bits(*bits)).into(),
|
||||||
|
};
|
||||||
|
k_array.set_index(scope, idx, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
let store = v8::ArrayBuffer::new_backing_store_from_boxed_slice(self.bytecode.code);
|
||||||
|
let ab = v8::ArrayBuffer::with_backing_store(scope, &store.make_shared());
|
||||||
|
let u8a = v8::Uint8Array::new(scope, ab, 0, ab.byte_length())
|
||||||
|
.ok_or_else(|| new_simple_jserror("failed to create Uint8Array".into()))?;
|
||||||
|
|
||||||
|
let dir = v8::String::new(scope, &self.bytecode.current_dir)
|
||||||
|
.ok_or_else(|| new_simple_jserror("failed to create dir string".into()))?;
|
||||||
|
|
||||||
|
let arr = v8::Array::new_with_elements(scope, &[u8a.into(), dir.into()]);
|
||||||
|
Ok(arr.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +148,7 @@ impl<'a> ToV8<'a> for Compiled {
|
|||||||
pub(super) fn op_import<Ctx: RuntimeContext>(
|
pub(super) fn op_import<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[string] path: String,
|
#[string] path: String,
|
||||||
) -> Result<Compiled> {
|
) -> Result<BytecodeRet> {
|
||||||
let _span = tracing::info_span!("op_import", path = %path).entered();
|
let _span = tracing::info_span!("op_import", path = %path).entered();
|
||||||
let ctx: &mut Ctx = state.get_ctx_mut();
|
let ctx: &mut Ctx = state.get_ctx_mut();
|
||||||
|
|
||||||
@@ -131,8 +164,17 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
|
|||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
ctx.add_source(source.clone());
|
ctx.add_source(source.clone());
|
||||||
let code = ctx.compile(source).map_err(|err| err.to_string())?;
|
let bytecode = ctx
|
||||||
return Ok(Compiled(code));
|
.compile_bytecode(source)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let (new_strings, new_constants, strings_base, constants_base) = ctx.get_unsynced();
|
||||||
|
return Ok(BytecodeRet {
|
||||||
|
bytecode,
|
||||||
|
new_strings,
|
||||||
|
new_constants,
|
||||||
|
strings_base,
|
||||||
|
constants_base,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Corepkg not found: {}", corepkg_name).into());
|
return Err(format!("Corepkg not found: {}", corepkg_name).into());
|
||||||
}
|
}
|
||||||
@@ -156,17 +198,25 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
|
|||||||
tracing::debug!("Compiling file");
|
tracing::debug!("Compiling file");
|
||||||
ctx.add_source(source.clone());
|
ctx.add_source(source.clone());
|
||||||
|
|
||||||
let code = ctx.compile(source).map_err(|err| err.to_string())?;
|
let bytecode = ctx
|
||||||
Ok(Compiled(code))
|
.compile_bytecode(source)
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
let (new_strings, new_constants, strings_base, constants_base) = ctx.get_unsynced();
|
||||||
|
Ok(BytecodeRet {
|
||||||
|
bytecode,
|
||||||
|
new_strings,
|
||||||
|
new_constants,
|
||||||
|
strings_base,
|
||||||
|
constants_base,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
|
||||||
pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
|
pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[string] path: String,
|
#[string] path: String,
|
||||||
#[scoped] scope: Vec<String>,
|
#[serde] scope: Vec<String>,
|
||||||
) -> Result<String> {
|
) -> Result<BytecodeRet> {
|
||||||
let _span = tracing::info_span!("op_scoped_import", path = %path).entered();
|
let _span = tracing::info_span!("op_scoped_import", path = %path).entered();
|
||||||
let ctx: &mut Ctx = state.get_ctx_mut();
|
let ctx: &mut Ctx = state.get_ctx_mut();
|
||||||
|
|
||||||
@@ -185,18 +235,26 @@ pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
|
|||||||
tracing::debug!("Compiling file for scoped import");
|
tracing::debug!("Compiling file for scoped import");
|
||||||
ctx.add_source(source.clone());
|
ctx.add_source(source.clone());
|
||||||
|
|
||||||
Ok(ctx
|
let bytecode = ctx
|
||||||
.compile_scoped(source, scope)
|
.compile_bytecode_scoped(source, scope)
|
||||||
.map_err(|err| err.to_string())?)
|
.map_err(|err| err.to_string())?;
|
||||||
|
let (new_strings, new_constants, strings_base, constants_base) = ctx.get_unsynced();
|
||||||
|
Ok(BytecodeRet {
|
||||||
|
bytecode,
|
||||||
|
new_strings,
|
||||||
|
new_constants,
|
||||||
|
strings_base,
|
||||||
|
constants_base,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_read_file(#[string] path: String) -> Result<String> {
|
pub(super) fn op_read_file(#[string] path: String) -> Result<String> {
|
||||||
Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?)
|
Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2(fast)]
|
#[deno_core::op2(fast, reentrant)]
|
||||||
pub(super) fn op_path_exists(#[string] path: String) -> bool {
|
pub(super) fn op_path_exists(#[string] path: String) -> bool {
|
||||||
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
||||||
let p = Path::new(&path);
|
let p = Path::new(&path);
|
||||||
@@ -211,7 +269,7 @@ pub(super) fn op_path_exists(#[string] path: String) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
|
pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
|
||||||
let path = Path::new(&path);
|
let path = Path::new(&path);
|
||||||
@@ -232,7 +290,7 @@ pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
|
|||||||
Ok(type_str.to_string())
|
Ok(type_str.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static str>> {
|
pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static str>> {
|
||||||
let path = Path::new(&path);
|
let path = Path::new(&path);
|
||||||
|
|
||||||
@@ -273,7 +331,7 @@ pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static
|
|||||||
Ok(Map(result))
|
Ok(Map(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_resolve_path(
|
pub(super) fn op_resolve_path(
|
||||||
#[string] current_dir: String,
|
#[string] current_dir: String,
|
||||||
@@ -312,7 +370,7 @@ pub(super) fn op_resolve_path(
|
|||||||
Ok(normalized.to_string_lossy().to_string())
|
Ok(normalized.to_string_lossy().to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_make_placeholder(#[string] output: String) -> String {
|
pub(super) fn op_make_placeholder(#[string] output: String) -> String {
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
@@ -341,7 +399,7 @@ impl<'a> ToV8<'a> for StringOrU32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_decode_span<Ctx: RuntimeContext>(
|
pub(super) fn op_decode_span<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[smi] span_id: u32,
|
#[smi] span_id: u32,
|
||||||
@@ -390,7 +448,7 @@ mod private {
|
|||||||
}
|
}
|
||||||
use private::*;
|
use private::*;
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_parse_hash(
|
pub(super) fn op_parse_hash(
|
||||||
#[string] hash_str: String,
|
#[string] hash_str: String,
|
||||||
#[string] algo: Option<String>,
|
#[string] algo: Option<String>,
|
||||||
@@ -416,7 +474,7 @@ pub(super) fn op_parse_hash(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
@@ -425,11 +483,12 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
|||||||
recursive: bool,
|
recursive: bool,
|
||||||
#[string] sha256: Option<String>,
|
#[string] sha256: Option<String>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
let path_obj = Path::new(&path);
|
let path_obj = Path::new(&path);
|
||||||
|
|
||||||
if !path_obj.exists() {
|
if !path_obj.exists() {
|
||||||
@@ -495,7 +554,7 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
|||||||
Ok(store_path)
|
Ok(store_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_store_path<Ctx: RuntimeContext>(
|
pub(super) fn op_store_path<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
@@ -516,7 +575,7 @@ pub(super) fn op_store_path<Ctx: RuntimeContext>(
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
@@ -533,7 +592,7 @@ pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
|||||||
Ok(store_path)
|
Ok(store_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
@@ -565,7 +624,7 @@ pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
|||||||
Ok(store_path)
|
Ok(store_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
|
pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
|
||||||
match std::env::var(key) {
|
match std::env::var(key) {
|
||||||
@@ -575,7 +634,7 @@ pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> {
|
pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> {
|
||||||
fn walk_recursive(
|
fn walk_recursive(
|
||||||
base: &Path,
|
base: &Path,
|
||||||
@@ -629,7 +688,7 @@ pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)
|
|||||||
Ok(results)
|
Ok(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
@@ -639,9 +698,10 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
|||||||
#[string] sha256: Option<String>,
|
#[string] sha256: Option<String>,
|
||||||
#[scoped] include_paths: Vec<String>,
|
#[scoped] include_paths: Vec<String>,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
let src = Path::new(&src_path);
|
let src = Path::new(&src_path);
|
||||||
if !src.exists() {
|
if !src.exists() {
|
||||||
@@ -735,7 +795,7 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
|||||||
Ok(store_path)
|
Ok(store_path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_match(
|
pub(super) fn op_match(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[string] regex: String,
|
#[string] regex: String,
|
||||||
@@ -759,7 +819,7 @@ pub(super) fn op_match(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_split(
|
pub(super) fn op_split(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[string] regex: String,
|
#[string] regex: String,
|
||||||
@@ -925,14 +985,14 @@ fn toml_to_nix(value: toml::Value) -> Result<NixJsonValue> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_from_json(#[string] json_str: String) -> Result<NixJsonValue> {
|
pub(super) fn op_from_json(#[string] json_str: String) -> Result<NixJsonValue> {
|
||||||
let parsed: serde_json::Value = serde_json::from_str(&json_str)
|
let parsed: serde_json::Value = serde_json::from_str(&json_str)
|
||||||
.map_err(|e| NixRuntimeError::from(format!("builtins.fromJSON: {e}")))?;
|
.map_err(|e| NixRuntimeError::from(format!("builtins.fromJSON: {e}")))?;
|
||||||
Ok(json_to_nix(parsed))
|
Ok(json_to_nix(parsed))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> {
|
pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> {
|
||||||
let parsed: toml::Value = toml::from_str(&toml_str)
|
let parsed: toml::Value = toml::from_str(&toml_str)
|
||||||
.map_err(|e| NixRuntimeError::from(format!("while parsing TOML: {e}")))?;
|
.map_err(|e| NixRuntimeError::from(format!("while parsing TOML: {e}")))?;
|
||||||
@@ -966,7 +1026,7 @@ fn output_path_name(drv_name: &str, output: &str) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
|
pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
|
||||||
state: &mut OpState,
|
state: &mut OpState,
|
||||||
#[string] name: String,
|
#[string] name: String,
|
||||||
@@ -1180,7 +1240,7 @@ fn op_make_fixed_output_path_impl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) -> Result<String> {
|
pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) -> Result<String> {
|
||||||
use sha2::{Digest, Sha256, Sha512};
|
use sha2::{Digest, Sha256, Sha512};
|
||||||
@@ -1217,7 +1277,7 @@ pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) ->
|
|||||||
Ok(hex::encode(hash_bytes))
|
Ok(hex::encode(hash_bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Result<String> {
|
pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Result<String> {
|
||||||
let data = std::fs::read(&path)
|
let data = std::fs::read(&path)
|
||||||
@@ -1257,7 +1317,7 @@ pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Re
|
|||||||
Ok(hex::encode(hash_bytes))
|
Ok(hex::encode(hash_bytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
#[string]
|
#[string]
|
||||||
pub(super) fn op_convert_hash(
|
pub(super) fn op_convert_hash(
|
||||||
#[string] hash: &str,
|
#[string] hash: &str,
|
||||||
@@ -1830,7 +1890,7 @@ impl<'a> FromV8<'a> for ToXmlResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deno_core::op2]
|
#[deno_core::op2(reentrant)]
|
||||||
pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) {
|
pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) {
|
||||||
(value.xml, value.context)
|
(value.xml, value.context)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user