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"
|
||||
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]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@@ -2055,6 +2064,7 @@ dependencies = [
|
||||
"bumpalo",
|
||||
"bzip2",
|
||||
"clap",
|
||||
"colored",
|
||||
"criterion",
|
||||
"deno_core",
|
||||
"deno_error",
|
||||
|
||||
@@ -75,6 +75,7 @@ http-body-util = { version = "0.1", optional = true }
|
||||
http = { version = "1", optional = true }
|
||||
uuid = { version = "1", features = ["v4"], optional = true }
|
||||
ghost-cell = "0.2.6"
|
||||
colored = "3.1.1"
|
||||
|
||||
[features]
|
||||
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 { baseNameOf } from "./path";
|
||||
import { isAttrs, isPath, isString } from "./type-check";
|
||||
import { execBytecode, execBytecodeScoped } from "../vm";
|
||||
|
||||
const importCache = new Map<string, NixValue>();
|
||||
|
||||
@@ -49,7 +50,8 @@ export const importFunc = (path: NixValue): NixValue => {
|
||||
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);
|
||||
return result;
|
||||
@@ -63,10 +65,8 @@ export const scopedImport =
|
||||
|
||||
const pathStr = realisePath(path);
|
||||
|
||||
const code = Deno.core.ops.op_scoped_import(pathStr, scopeKeys);
|
||||
|
||||
const scopedFunc = Function(`return (${code})`)();
|
||||
return scopedFunc(scopeAttrs);
|
||||
const [code, currentDir] = Deno.core.ops.op_scoped_import(pathStr, scopeKeys);
|
||||
return execBytecodeScoped(code, currentDir, scopeAttrs);
|
||||
};
|
||||
|
||||
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 { forceBool } from "./type-assert";
|
||||
import { IS_PATH, mkAttrs, mkFunction, type NixValue } from "./types";
|
||||
import { execBytecode, execBytecodeScoped, vmStrings, vmConstants } from "./vm";
|
||||
|
||||
export type NixRuntime = typeof Nix;
|
||||
|
||||
@@ -55,6 +56,11 @@ export const Nix = {
|
||||
op,
|
||||
builtins,
|
||||
|
||||
strings: vmStrings,
|
||||
constants: vmConstants,
|
||||
execBytecode,
|
||||
execBytecodeScoped,
|
||||
|
||||
replBindings,
|
||||
setReplBinding: (name: string, value: NixValue) => {
|
||||
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 core {
|
||||
namespace ops {
|
||||
function op_import(path: string): NixValue;
|
||||
function op_scoped_import(path: string, scopeKeys: string[]): string;
|
||||
function op_import(path: string): [Uint8Array, string];
|
||||
function op_scoped_import(path: string, scopeKeys: string[]): [Uint8Array, 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 string_interner::DefaultStringInterner;
|
||||
|
||||
use crate::bytecode::{self, BytecodeContext, Bytecode, Constant};
|
||||
use crate::codegen::{CodegenContext, compile};
|
||||
use crate::disassembler::{Disassembler, DisassemblerContext};
|
||||
use crate::downgrade::*;
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::ir::{ArgId, Ir, IrKey, IrRef, RawIrRef, SymId, ThunkId, ir_content_eq};
|
||||
#[cfg(feature = "inspector")]
|
||||
use crate::runtime::inspector::InspectorServer;
|
||||
use crate::runtime::{Runtime, RuntimeContext};
|
||||
use crate::runtime::{ForceMode, Runtime, RuntimeContext};
|
||||
use crate::store::{DaemonStore, Store, StoreConfig};
|
||||
use crate::value::{Symbol, Value};
|
||||
|
||||
@@ -53,16 +55,16 @@ pub struct Context {
|
||||
_inspector_server: Option<InspectorServer>,
|
||||
}
|
||||
|
||||
macro_rules! eval {
|
||||
($name:ident, $wrapper:literal) => {
|
||||
macro_rules! eval_bc {
|
||||
($name:ident, $mode:expr) => {
|
||||
pub fn $name(&mut self, source: Source) -> Result<Value> {
|
||||
tracing::info!("Starting evaluation");
|
||||
|
||||
tracing::debug!("Compiling code");
|
||||
let code = self.compile(source)?;
|
||||
tracing::debug!("Compiling bytecode");
|
||||
let bytecode = self.ctx.compile_bytecode(source)?;
|
||||
|
||||
tracing::debug!("Executing JavaScript");
|
||||
self.runtime.eval(format!($wrapper, code), &mut self.ctx)
|
||||
tracing::debug!("Executing bytecode");
|
||||
self.runtime.eval_bytecode(bytecode, &mut self.ctx, $mode)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -137,10 +139,9 @@ impl Context {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
eval!(eval, "Nix.force({})");
|
||||
eval!(eval_shallow, "Nix.forceShallow({})");
|
||||
eval!(eval_deep, "Nix.forceDeep({})");
|
||||
|
||||
eval_bc!(eval, ForceMode::Force);
|
||||
eval_bc!(eval_shallow, ForceMode::ForceShallow);
|
||||
eval_bc!(eval_deep, ForceMode::ForceDeep);
|
||||
pub fn eval_repl<'a>(&'a mut self, source: Source, scope: &'a HashSet<SymId>) -> Result<Value> {
|
||||
tracing::info!("Starting evaluation");
|
||||
|
||||
@@ -156,6 +157,18 @@ impl Context {
|
||||
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 {
|
||||
self.ctx.get_store_dir()
|
||||
}
|
||||
@@ -188,6 +201,12 @@ struct Ctx {
|
||||
store: DaemonStore,
|
||||
spans: UnsafeCell<Vec<(usize, TextRange)>>,
|
||||
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.
|
||||
@@ -261,6 +280,12 @@ impl Ctx {
|
||||
store,
|
||||
spans: UnsafeCell::new(Vec::new()),
|
||||
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);
|
||||
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 {
|
||||
@@ -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 {
|
||||
fn get_current_dir(&self) -> &Path {
|
||||
self.get_current_dir()
|
||||
@@ -391,6 +474,16 @@ impl RuntimeContext for Ctx {
|
||||
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String> {
|
||||
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 {
|
||||
self.sources.get(id).expect("source not found").clone()
|
||||
}
|
||||
@@ -401,6 +494,24 @@ impl RuntimeContext for Ctx {
|
||||
let spans = unsafe { &*self.spans.get() };
|
||||
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> {
|
||||
|
||||
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 value;
|
||||
|
||||
mod bytecode;
|
||||
mod codegen;
|
||||
mod derivation;
|
||||
mod disassembler;
|
||||
mod downgrade;
|
||||
mod fetcher;
|
||||
mod ir;
|
||||
|
||||
@@ -77,10 +77,10 @@ fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<(
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
match context.compile(src) {
|
||||
match context.compile_bytecode(src) {
|
||||
Ok(compiled) => {
|
||||
if !silent {
|
||||
println!("{compiled}");
|
||||
println!("{}", context.disassemble_colored(&compiled));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::path::Path;
|
||||
use deno_core::PollEventLoopOptions;
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8};
|
||||
|
||||
use crate::bytecode::{Bytecode, Constant};
|
||||
use crate::error::{Error, Result, Source};
|
||||
use crate::store::DaemonStore;
|
||||
use crate::value::{AttrSet, List, Symbol, Value};
|
||||
@@ -24,9 +25,16 @@ pub(crate) trait RuntimeContext: 'static {
|
||||
fn add_source(&mut self, path: Source);
|
||||
fn compile(&mut self, source: Source) -> 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_store(&self) -> &DaemonStore;
|
||||
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> {
|
||||
@@ -121,6 +129,7 @@ pub(crate) struct Runtime<Ctx: RuntimeContext> {
|
||||
#[cfg(feature = "inspector")]
|
||||
wait_for_inspector: bool,
|
||||
symbols: GlobalSymbols,
|
||||
cached_fns: CachedFunctions,
|
||||
_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(DrvHashCache::new());
|
||||
|
||||
let symbols = {
|
||||
let (symbols, cached_fns) = {
|
||||
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 {
|
||||
@@ -177,6 +188,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
#[cfg(feature = "inspector")]
|
||||
wait_for_inspector: inspector_options.wait,
|
||||
symbols,
|
||||
cached_fns,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
}
|
||||
@@ -227,6 +239,87 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
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> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let nix_key = v8::String::new(scope, "Nix")
|
||||
@@ -267,6 +360,61 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
|
||||
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 {
|
||||
@@ -297,6 +445,51 @@ struct LocalSymbols<'a> {
|
||||
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>(
|
||||
val: LocalValue<'a>,
|
||||
scope: &ScopeRef<'a, '_>,
|
||||
|
||||
@@ -10,6 +10,7 @@ use regex::Regex;
|
||||
use rust_embed::Embed;
|
||||
|
||||
use super::{NixRuntimeError, OpStateExt, RuntimeContext};
|
||||
use crate::bytecode::{Bytecode, Constant};
|
||||
use crate::error::Source;
|
||||
use crate::store::Store as _;
|
||||
|
||||
@@ -79,35 +80,67 @@ fn new_simple_jserror(msg: String) -> Box<JsError> {
|
||||
.into()
|
||||
}
|
||||
|
||||
struct Compiled(String);
|
||||
impl<'a> ToV8<'a> for Compiled {
|
||||
struct BytecodeRet {
|
||||
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>;
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn to_v8<'i>(
|
||||
self,
|
||||
scope: &mut v8::PinScope<'a, 'i>,
|
||||
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
|
||||
let Ok(script) = self.0.to_v8(scope);
|
||||
let Some(source) = script.to_string(scope) else {
|
||||
unsafe { std::hint::unreachable_unchecked() }
|
||||
};
|
||||
let tc = std::pin::pin!(v8::TryCatch::new(scope));
|
||||
let mut scope = tc.init();
|
||||
let Some(compiled) = v8::Script::compile(&scope, source, None) else {
|
||||
let msg = scope
|
||||
.exception()
|
||||
.map(|e| e.to_rust_string_lossy(&scope))
|
||||
.unwrap_or_else(|| "failed to compile code".into());
|
||||
return Err(new_simple_jserror(msg));
|
||||
};
|
||||
match compiled.run(&scope) {
|
||||
Some(val) => Ok(val),
|
||||
None => Err(scope
|
||||
.exception()
|
||||
.map(|e| JsError::from_v8_exception(&mut scope, e))
|
||||
.unwrap_or_else(|| {
|
||||
new_simple_jserror("script execution failed unexpectedly".into())
|
||||
})),
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let nix_key = v8::String::new(scope, "Nix")
|
||||
.ok_or_else(|| new_simple_jserror("failed to create v8 string".into()))?;
|
||||
let nix_obj = global
|
||||
.get(scope, nix_key.into())
|
||||
.ok_or_else(|| new_simple_jserror("failed to get Nix global".into()))?
|
||||
.to_object(scope)
|
||||
.ok_or_else(|| new_simple_jserror("Nix is not an object".into()))?;
|
||||
|
||||
let s_key = v8::String::new(scope, "strings").unwrap();
|
||||
let s_array: v8::Local<v8::Array> = nix_obj
|
||||
.get(scope, s_key.into())
|
||||
.unwrap()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
for (i, s) in unsafe { &*self.new_strings }.iter().enumerate() {
|
||||
let idx = (self.strings_base + i) as u32;
|
||||
let val = v8::String::new(scope, s).unwrap();
|
||||
s_array.set_index(scope, idx, val.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>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
) -> Result<Compiled> {
|
||||
) -> Result<BytecodeRet> {
|
||||
let _span = tracing::info_span!("op_import", path = %path).entered();
|
||||
let ctx: &mut Ctx = state.get_ctx_mut();
|
||||
|
||||
@@ -131,8 +164,17 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
|
||||
.into(),
|
||||
);
|
||||
ctx.add_source(source.clone());
|
||||
let code = ctx.compile(source).map_err(|err| err.to_string())?;
|
||||
return Ok(Compiled(code));
|
||||
let bytecode = ctx
|
||||
.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 {
|
||||
return Err(format!("Corepkg not found: {}", corepkg_name).into());
|
||||
}
|
||||
@@ -156,17 +198,25 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
|
||||
tracing::debug!("Compiling file");
|
||||
ctx.add_source(source.clone());
|
||||
|
||||
let code = ctx.compile(source).map_err(|err| err.to_string())?;
|
||||
Ok(Compiled(code))
|
||||
let bytecode = ctx
|
||||
.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]
|
||||
#[string]
|
||||
#[deno_core::op2(reentrant)]
|
||||
pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
#[string] path: String,
|
||||
#[scoped] scope: Vec<String>,
|
||||
) -> Result<String> {
|
||||
#[serde] scope: Vec<String>,
|
||||
) -> Result<BytecodeRet> {
|
||||
let _span = tracing::info_span!("op_scoped_import", path = %path).entered();
|
||||
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");
|
||||
ctx.add_source(source.clone());
|
||||
|
||||
Ok(ctx
|
||||
.compile_scoped(source, scope)
|
||||
.map_err(|err| err.to_string())?)
|
||||
let bytecode = ctx
|
||||
.compile_bytecode_scoped(source, scope)
|
||||
.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_read_file(#[string] path: String) -> Result<String> {
|
||||
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 {
|
||||
let must_be_dir = path.ends_with('/') || path.ends_with("/.");
|
||||
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]
|
||||
pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
|
||||
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())
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static str>> {
|
||||
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))
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_resolve_path(
|
||||
#[string] current_dir: String,
|
||||
@@ -312,7 +370,7 @@ pub(super) fn op_resolve_path(
|
||||
Ok(normalized.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_make_placeholder(#[string] output: String) -> String {
|
||||
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>(
|
||||
state: &mut OpState,
|
||||
#[smi] span_id: u32,
|
||||
@@ -390,7 +448,7 @@ mod private {
|
||||
}
|
||||
use private::*;
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
pub(super) fn op_parse_hash(
|
||||
#[string] hash_str: String,
|
||||
#[string] algo: Option<String>,
|
||||
@@ -416,7 +474,7 @@ pub(super) fn op_parse_hash(
|
||||
})
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
@@ -425,11 +483,12 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
||||
recursive: bool,
|
||||
#[string] sha256: Option<String>,
|
||||
) -> Result<String> {
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
let path_obj = Path::new(&path);
|
||||
|
||||
if !path_obj.exists() {
|
||||
@@ -495,7 +554,7 @@ pub(super) fn op_add_path<Ctx: RuntimeContext>(
|
||||
Ok(store_path)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_store_path<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
@@ -516,7 +575,7 @@ pub(super) fn op_store_path<Ctx: RuntimeContext>(
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
@@ -533,7 +592,7 @@ pub(super) fn op_to_file<Ctx: RuntimeContext>(
|
||||
Ok(store_path)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
@@ -565,7 +624,7 @@ pub(super) fn op_copy_path_to_store<Ctx: RuntimeContext>(
|
||||
Ok(store_path)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
|
||||
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)>> {
|
||||
fn walk_recursive(
|
||||
base: &Path,
|
||||
@@ -629,7 +688,7 @@ pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
||||
state: &mut OpState,
|
||||
@@ -639,9 +698,10 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
||||
#[string] sha256: Option<String>,
|
||||
#[scoped] include_paths: Vec<String>,
|
||||
) -> Result<String> {
|
||||
use std::fs;
|
||||
|
||||
use nix_compat::nixhash::{HashAlgo, NixHash};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::fs;
|
||||
|
||||
let src = Path::new(&src_path);
|
||||
if !src.exists() {
|
||||
@@ -735,7 +795,7 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
|
||||
Ok(store_path)
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
pub(super) fn op_match(
|
||||
state: &mut OpState,
|
||||
#[string] regex: String,
|
||||
@@ -759,7 +819,7 @@ pub(super) fn op_match(
|
||||
}
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
pub(super) fn op_split(
|
||||
state: &mut OpState,
|
||||
#[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> {
|
||||
let parsed: serde_json::Value = serde_json::from_str(&json_str)
|
||||
.map_err(|e| NixRuntimeError::from(format!("builtins.fromJSON: {e}")))?;
|
||||
Ok(json_to_nix(parsed))
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> {
|
||||
let parsed: toml::Value = toml::from_str(&toml_str)
|
||||
.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>(
|
||||
state: &mut OpState,
|
||||
#[string] name: String,
|
||||
@@ -1180,7 +1240,7 @@ fn op_make_fixed_output_path_impl(
|
||||
}
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_hash_string(#[string] algo: String, #[string] data: String) -> Result<String> {
|
||||
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))
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Result<String> {
|
||||
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))
|
||||
}
|
||||
|
||||
#[deno_core::op2]
|
||||
#[deno_core::op2(reentrant)]
|
||||
#[string]
|
||||
pub(super) fn op_convert_hash(
|
||||
#[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>) {
|
||||
(value.xml, value.context)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user