From a256e535b5e40c6ab4154054ca7a0420b2bb2974 Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Thu, 10 Oct 2024 16:17:49 +0300 Subject: [PATCH 1/3] Update oldest supported compilers version --- src/constants.ts | 2 +- src/core/compiler/CircomCompilerDownloader.ts | 11 +++++++++- src/core/compiler/CircomCompilerFactory.ts | 20 +++++++++---------- .../circom-compiler-downloader.test.ts | 12 +++++++++++ .../compile/circom-compiler-factory.test.ts | 19 +++++++++++++----- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index f404b83..2a0a634 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -28,8 +28,8 @@ export const COMPILER_ARM_REPOSITORY_URL = "https://github.com/distributed-lab/c export const COMPILER_WASM_REPOSITORY_URL = "https://github.com/distributed-lab/circom-wasm/releases/download"; export const LATEST_SUPPORTED_CIRCOM_VERSION = "2.1.9"; +export const OLDEST_SUPPORTED_CIRCOM_VERSION = "2.0.5"; -export const OLDEST_SUPPORTED_ARM_CIRCOM_VERSION = "2.0.5"; export const WASM_COMPILER_VERSIONING: { [key: string]: string } = { "2.1.8": "0.2.18-rc.3", "2.1.9": "0.2.19-rc.0", diff --git a/src/core/compiler/CircomCompilerDownloader.ts b/src/core/compiler/CircomCompilerDownloader.ts index bf818b8..0443680 100644 --- a/src/core/compiler/CircomCompilerDownloader.ts +++ b/src/core/compiler/CircomCompilerDownloader.ts @@ -252,7 +252,7 @@ export class CircomCompilerDownloader { url = `${COMPILER_ARM_REPOSITORY_URL}/v${version}/${this._platform}`; break; default: - url = `${COMPILER_WASM_REPOSITORY_URL}/v${WASM_COMPILER_VERSIONING[version]}/circom.wasm`; + url = this._getWasmDownloadURL(version); } if ( @@ -279,6 +279,15 @@ export class CircomCompilerDownloader { return path.join(this._compilersDir, version, "circom.wasm"); } + private _getWasmDownloadURL(version: string): string { + const wasmVersion = WASM_COMPILER_VERSIONING[version]; + if (!wasmVersion) { + throw new HardhatZKitError(`Unsupported WASM version - ${version}`); + } + + return `${COMPILER_WASM_REPOSITORY_URL}/v${wasmVersion}/circom.wasm`; + } + private async _postProcessCompilerDownload(downloadPath: string): Promise { if ( this._platform !== CompilerPlatformBinary.WINDOWS_AMD && diff --git a/src/core/compiler/CircomCompilerFactory.ts b/src/core/compiler/CircomCompilerFactory.ts index 43a4293..0280527 100644 --- a/src/core/compiler/CircomCompilerFactory.ts +++ b/src/core/compiler/CircomCompilerFactory.ts @@ -7,7 +7,7 @@ import { exec } from "child_process"; import { Reporter } from "../../reporter"; import { HardhatZKitError } from "../../errors"; -import { LATEST_SUPPORTED_CIRCOM_VERSION, OLDEST_SUPPORTED_ARM_CIRCOM_VERSION } from "../../constants"; +import { LATEST_SUPPORTED_CIRCOM_VERSION, OLDEST_SUPPORTED_CIRCOM_VERSION } from "../../constants"; import { BinaryCircomCompiler, WASMCircomCompiler } from "./CircomCompiler"; import { CircomCompilerDownloader } from "./CircomCompilerDownloader"; @@ -59,8 +59,14 @@ export class BaseCircomCompilerFactory { isVersionStrict: boolean, verifyCompiler: boolean = true, ): Promise { - if (!semver.gte(LATEST_SUPPORTED_CIRCOM_VERSION, version)) { - throw new HardhatZKitError(`Unsupported Circom compiler version - ${version}. Please provide another version.`); + const supportedVersionsRange = semver.validRange( + `${OLDEST_SUPPORTED_CIRCOM_VERSION} - ${LATEST_SUPPORTED_CIRCOM_VERSION}`, + ); + + if (isVersionStrict && !semver.satisfies(version, supportedVersionsRange!)) { + throw new HardhatZKitError( + `Unsupported Circom compiler version - ${version}. Please provide another version from the range ${supportedVersionsRange}.`, + ); } let compiler = await this._tryCreateNativeCompiler(version, isVersionStrict); @@ -69,13 +75,7 @@ export class BaseCircomCompilerFactory { return compiler; } - let compilerPlatformBinary = CircomCompilerDownloader.getCompilerPlatformBinary(); - - // Utilize binary translators like Rosetta (macOS) or Prism (Windows) - // to run x64 binaries on arm64 systems when no arm64 versions are available. - if (isVersionStrict && os.arch() === "arm64" && !semver.gte(version, OLDEST_SUPPORTED_ARM_CIRCOM_VERSION)) { - compilerPlatformBinary = CircomCompilerDownloader.getCompilerPlatformBinary("x64"); - } + const compilerPlatformBinary = CircomCompilerDownloader.getCompilerPlatformBinary(); if (compilerPlatformBinary !== CompilerPlatformBinary.WASM) { compiler = await this._tryCreateBinaryCompiler(compilerPlatformBinary, version, isVersionStrict, verifyCompiler); diff --git a/test/unit/core/compile/circom-compiler-downloader.test.ts b/test/unit/core/compile/circom-compiler-downloader.test.ts index 66227ea..298b950 100644 --- a/test/unit/core/compile/circom-compiler-downloader.test.ts +++ b/test/unit/core/compile/circom-compiler-downloader.test.ts @@ -165,6 +165,18 @@ describe("CircomCompilerDownloader", () => { expect(reporterSpy.called).to.be.false; }); + it("should throw an error if pass unsupported WASM compiler version", async function () { + const compilersDir = getNormalizedFullPath(this.hre.config.paths.root, "compilers"); + await fsExtra.ensureDir(compilersDir); + + const platform = CompilerPlatformBinary.WASM; + const circomCompilerDownloader = CircomCompilerDownloader.getCircomCompilerDownloader(platform, compilersDir); + + await expect(circomCompilerDownloader.downloadCompiler("2.0.5", true, true)).to.be.rejectedWith( + "Unsupported WASM version - 2.0.5", + ); + }); + it("should throw an error if the downloaded compiler is not working", async function () { const compilersDir = getNormalizedFullPath(this.hre.config.paths.root, "compilers"); await fsExtra.ensureDir(compilersDir); diff --git a/test/unit/core/compile/circom-compiler-factory.test.ts b/test/unit/core/compile/circom-compiler-factory.test.ts index 76e03fb..60a7bab 100644 --- a/test/unit/core/compile/circom-compiler-factory.test.ts +++ b/test/unit/core/compile/circom-compiler-factory.test.ts @@ -1,5 +1,6 @@ import os from "os"; import path from "path"; +import semver from "semver"; import fsExtra from "fs-extra"; import { expect } from "chai"; @@ -20,7 +21,7 @@ import { import { CircomCompilerDownloader } from "@src/core/compiler/CircomCompilerDownloader"; import { defaultCompileFlags } from "../../../constants"; -import { LATEST_SUPPORTED_CIRCOM_VERSION } from "@src/constants"; +import { LATEST_SUPPORTED_CIRCOM_VERSION, OLDEST_SUPPORTED_CIRCOM_VERSION } from "@src/constants"; describe("CircomCompilerFactory", () => { let nativeCompilerStub: SinonStub; @@ -66,7 +67,7 @@ describe("CircomCompilerFactory", () => { it("should correctly create circom compiler instance", async function () { createCircomCompilerFactory(); - const compiler: ICircomCompiler = await CircomCompilerFactory!.createCircomCompiler("0.2.18", false); + const compiler: ICircomCompiler = await CircomCompilerFactory!.createCircomCompiler("2.1.7", false); const circuitFullPath: string = getNormalizedFullPath(this.hre.config.paths.root, "circuits/main/mul2.circom"); const artifactsFullPath: string = getNormalizedFullPath( @@ -96,12 +97,20 @@ describe("CircomCompilerFactory", () => { }); it("should correctly throw error if pass invalid version", async function () { - const invalidVersion = "2.1.10"; + const supportedVersionsRange = semver.validRange( + `${OLDEST_SUPPORTED_CIRCOM_VERSION} - ${LATEST_SUPPORTED_CIRCOM_VERSION}`, + ); - const reason = `Unsupported Circom compiler version - ${invalidVersion}. Please provide another version.`; + let invalidVersion = "2.1.10"; + let reason = `Unsupported Circom compiler version - ${invalidVersion}. Please provide another version from the range ${supportedVersionsRange}.`; createCircomCompilerFactory(); - await expect(CircomCompilerFactory!.createCircomCompiler(invalidVersion, false)).to.be.rejectedWith(reason); + await expect(CircomCompilerFactory!.createCircomCompiler(invalidVersion, true)).to.be.rejectedWith(reason); + + invalidVersion = "2.0.4"; + reason = `Unsupported Circom compiler version - ${invalidVersion}. Please provide another version from the range ${supportedVersionsRange}.`; + + await expect(CircomCompilerFactory!.createCircomCompiler(invalidVersion, true)).to.be.rejectedWith(reason); }); it("should create compiler for each platform properly", async function () { From 90db590f09b6360409e2246d153ec80131eddd3a Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Thu, 10 Oct 2024 19:43:14 +0300 Subject: [PATCH 2/3] Move WASM runner and update dependencies --- .eslintignore | 3 +- package-lock.json | 104 +- package.json | 13 +- src/core/compiler/CircomCompiler.ts | 4 +- src/core/compiler/vendor/index.js | 128 ++ src/core/compiler/vendor/wasi.js | 1229 +++++++++++++++++ .../parser/CircomTemplateInputsVisitor.ts | 2 +- tsconfig.json | 1 + 8 files changed, 1452 insertions(+), 32 deletions(-) create mode 100644 src/core/compiler/vendor/index.js create mode 100644 src/core/compiler/vendor/wasi.js diff --git a/.eslintignore b/.eslintignore index ddb212c..51bfcf9 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ dist/*.js *.md -test/fixture-projects/**/*.* \ No newline at end of file +test/fixture-projects/**/*.* +src/core/compiler/vendor \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 19a8f4d..841f57d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,27 +1,29 @@ { "name": "@solarity/hardhat-zkit", - "version": "0.4.5", + "version": "0.4.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@solarity/hardhat-zkit", - "version": "0.4.5", + "version": "0.4.6", "license": "MIT", "workspaces": [ "test/fixture-projects/*" ], "dependencies": { - "@distributedlab/circom-parser": "0.1.2", - "@distributedlab/circom2": "0.2.18-rc.4", + "@distributedlab/circom-parser": "0.1.3", "@solarity/zkit": "0.2.6", "@solarity/zktype": "0.3.1", + "@wasmer/wasi": "0.12.0", "chalk": "4.1.2", "cli-progress": "3.12.0", "cli-table3": "0.6.5", "debug": "4.3.5", + "is-typed-array": "1.1.13", "lodash": "4.17.21", "ora": "5.4.1", + "path-browserify": "1.0.1", "resolve": "1.22.8", "semver": "7.6.3", "snarkjs": "0.7.3", @@ -66,6 +68,9 @@ "typechain": "^8.3.2", "typescript": "5.3.3" }, + "engines": { + "node": ">=15" + }, "peerDependencies": { "hardhat": "^2.16.0" } @@ -483,9 +488,9 @@ } }, "node_modules/@distributedlab/circom-parser": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@distributedlab/circom-parser/-/circom-parser-0.1.2.tgz", - "integrity": "sha512-q5IaKs60LPxQhLCdMH8fcpvUP2IKItukrDdLFurdHVfrIypEZRSTW3FeFwqSC/ruFcWwdDhHIZd0tG2x5iFryA==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@distributedlab/circom-parser/-/circom-parser-0.1.3.tgz", + "integrity": "sha512-nU2/+1hVnorZ5DQeR0La78QJPYFAaSPjxShIuc8YBi+co5SSHONli04DbYEnCs44Bbqj2VH+4dTqIt9Tjof/Bg==", "dependencies": { "antlr4": "4.13.1-patch-1", "ejs": "3.1.10" @@ -495,6 +500,8 @@ "version": "0.2.18-rc.4", "resolved": "https://registry.npmjs.org/@distributedlab/circom2/-/circom2-0.2.18-rc.4.tgz", "integrity": "sha512-5ALgnpk+mdzZDeKwR3ZIjJXjVeWt+Qz4g/lWAR4cxTrnEhiQptDacg0wtd7WKBUghDpEruLZ4wXyIdRoXuJSaQ==", + "dev": true, + "peer": true, "dependencies": { "@wasmer/wasi": "^0.12.0", "is-typed-array": "^1.1.8", @@ -2177,6 +2184,17 @@ "hardhat": "^2.16.0" } }, + "node_modules/@solarity/hardhat-zkit/node_modules/@distributedlab/circom-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@distributedlab/circom-parser/-/circom-parser-0.1.2.tgz", + "integrity": "sha512-q5IaKs60LPxQhLCdMH8fcpvUP2IKItukrDdLFurdHVfrIypEZRSTW3FeFwqSC/ruFcWwdDhHIZd0tG2x5iFryA==", + "dev": true, + "peer": true, + "dependencies": { + "antlr4": "4.13.1-patch-1", + "ejs": "3.1.10" + } + }, "node_modules/@solarity/zkit": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/@solarity/zkit/-/zkit-0.2.6.tgz", @@ -3041,7 +3059,9 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "dev": true, "optional": true, + "peer": true, "dependencies": { "follow-redirects": "1.5.10" } @@ -3124,7 +3144,9 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-0.0.1.tgz", "integrity": "sha512-axr6lqB4ec/pkEOb/JMnZpfcroWv1zT48pVz1oQHG7XmGkS77vmdxmP1btuH79lWQdy9e2MVw/uW0D8siopkRg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "axios": "^0.19.0", "env-paths": "^2.2.0", @@ -3520,7 +3542,9 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/ci-info": { "version": "2.0.0", @@ -4150,7 +4174,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -5166,7 +5190,9 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "debug": "=3.1.0" }, @@ -5178,7 +5204,9 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -5187,7 +5215,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/for-each": { "version": "0.3.3", @@ -5255,7 +5285,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -5267,7 +5299,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true + "dev": true }, "node_modules/fsevents": { "version": "2.3.3", @@ -5406,7 +5438,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "devOptional": true, + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5438,7 +5470,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5448,7 +5480,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6004,7 +6036,9 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/hasurl/-/hasurl-1.0.0.tgz", "integrity": "sha512-43ypUd3DbwyCT01UYpA99AEZxZ4aKtRxWGBHEIbjcOsUghd9YUON0C+JF6isNjaiwC/UF5neaUudy6JS9jZPZQ==", + "dev": true, "optional": true, + "peer": true, "engines": { "node": ">= 4" } @@ -6172,7 +6206,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "devOptional": true, + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -6943,7 +6977,9 @@ "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -7097,7 +7133,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "devOptional": true, + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7106,7 +7142,9 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7118,7 +7156,9 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -7131,7 +7171,9 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -7596,7 +7638,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, + "dev": true, "dependencies": { "wrappy": "1" } @@ -7762,7 +7804,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7965,7 +8007,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -8192,7 +8234,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "devOptional": true, + "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -8267,8 +8309,10 @@ "version": "0.1.29", "resolved": "https://registry.npmjs.org/rustwasmc/-/rustwasmc-0.1.29.tgz", "integrity": "sha512-yYqYQ61W1P/DUIcUF/djhltHqLQwETuiVorJ52ZqDSYeU2D6+4FrVPbICe7vhz7Y9FeMPz0K1SkBoJjnLjFS6g==", + "dev": true, "hasInstallScript": true, "optional": true, + "peer": true, "dependencies": { "binary-install": "0.0.1" }, @@ -8922,7 +8966,9 @@ "version": "5.0.11", "resolved": "https://registry.npmjs.org/tar/-/tar-5.0.11.tgz", "integrity": "sha512-E6q48d5y4XSCD+Xmwc0yc8lXuyDK38E0FB8N4S/drQRtXOMUhfhDxbB0xr2KKDhNfO51CFmoa6Oz00nAkWsjnA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "chownr": "^1.1.4", "fs-minipass": "^2.1.0", @@ -9023,7 +9069,9 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "punycode": "^2.1.0" } @@ -9473,7 +9521,9 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universal-url/-/universal-url-2.0.0.tgz", "integrity": "sha512-3DLtXdm/G1LQMCnPj+Aw7uDoleQttNHp2g5FnNQKR6cP6taNWS1b/Ehjjx4PVyvejKi3TJyu8iBraKM4q3JQPg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "hasurl": "^1.0.0", "whatwg-url": "^7.0.0" @@ -9592,13 +9642,17 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, "optional": true, + "peer": true, "dependencies": { "lodash.sortby": "^4.7.0", "tr46": "^1.0.1", @@ -9729,7 +9783,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true + "dev": true }, "node_modules/write-file-atomic": { "version": "3.0.3", @@ -9777,7 +9831,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true + "dev": true, + "optional": true, + "peer": true }, "node_modules/yargs": { "version": "16.2.0", diff --git a/package.json b/package.json index 3374b02..c1b6c40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@solarity/hardhat-zkit", - "version": "0.4.5", + "version": "0.4.6", "description": "The ultimate TypeScript environment for Circom development", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -49,11 +49,14 @@ "text" ] }, + "engines": { + "node": ">=15" + }, "dependencies": { - "@distributedlab/circom-parser": "0.1.2", - "@distributedlab/circom2": "0.2.18-rc.4", + "@distributedlab/circom-parser": "0.1.3", "@solarity/zkit": "0.2.6", "@solarity/zktype": "0.3.1", + "@wasmer/wasi": "0.12.0", "chalk": "4.1.2", "cli-progress": "3.12.0", "cli-table3": "0.6.5", @@ -64,7 +67,9 @@ "semver": "7.6.3", "snarkjs": "0.7.3", "uuid": "9.0.1", - "zod": "3.23.8" + "zod": "3.23.8", + "is-typed-array": "1.1.13", + "path-browserify": "1.0.1" }, "peerDependencies": { "hardhat": "^2.16.0" diff --git a/src/core/compiler/CircomCompiler.ts b/src/core/compiler/CircomCompiler.ts index 0b6abfa..894a911 100644 --- a/src/core/compiler/CircomCompiler.ts +++ b/src/core/compiler/CircomCompiler.ts @@ -7,7 +7,7 @@ import { ICircomCompiler, CompileConfig, BaseCompileConfig } from "../../types/c import { MAGIC_DESCRIPTOR } from "../../constants"; // eslint-disable-next-line -const { Context, CircomRunner, bindings } = require("@distributedlab/circom2"); +const { Context, CircomRunner, bindings, preopens } = require("./vendor"); /** * An abstract class that serves as the base for all `circom` compiler implementations. @@ -167,7 +167,7 @@ export class WASMCircomCompiler extends BaseCircomCompiler { private _getCircomRunner(callArgs: string[], quiet: boolean, errDescriptor: number): typeof CircomRunner { return new CircomRunner({ args: callArgs, - preopens: { "/": "/" }, + preopens, bindings: { ...bindings, exit(code: number) { diff --git a/src/core/compiler/vendor/index.js b/src/core/compiler/vendor/index.js new file mode 100644 index 0000000..4b23f0d --- /dev/null +++ b/src/core/compiler/vendor/index.js @@ -0,0 +1,128 @@ +const isTypedArray = require('is-typed-array') +const path = require('path-browserify') + +const { WASI, WASIExitError, WASIKillError } = require('./wasi') + +const baseNow = Math.floor((Date.now() - performance.now()) * 1e-3) + +function hrtime() { + let clocktime = performance.now() * 1e-3 + let seconds = Math.floor(clocktime) + baseNow + let nanoseconds = Math.floor((clocktime % 1) * 1e9) + // return BigInt(seconds) * BigInt(1e9) + BigInt(nanoseconds) + return seconds * 1e9 + nanoseconds +} + +function randomFillSync(buf, offset, size) { + if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { + // Similar to the implementation of `randomfill` on npm + let uint = new Uint8Array(buf.buffer, offset, size) + crypto.getRandomValues(uint) + return buf + } else { + try { + // Try to load webcrypto in node + let crypto = require('crypto') + // TODO: Update to webcrypto in nodejs + return crypto.randomFillSync(buf, offset, size) + } catch { + // If an error occurs, fall back to the least secure version + // TODO: Should we throw instead since this would be a crazy old browser + // or nodejs built without crypto APIs + if (buf instanceof Uint8Array) { + for (let i = offset; i < offset + size; i++) { + buf[i] = Math.floor(Math.random() * 256) + } + } + return buf + } + } +} + +const defaultBindings = { + hrtime: hrtime, + exit(code) { + throw new WASIExitError(code) + }, + kill(signal) { + throw new WASIKillError(signal) + }, + randomFillSync: randomFillSync, + isTTY: () => true, + path: path, + fs: null, +} + +const defaultPreopens = { + '.': '.', + "/": "/", +} + +class CircomRunner { + constructor({ + args, + env, + preopens = defaultPreopens, + bindings = defaultBindings, + descriptors = undefined, + } = {}) { + if (!bindings.fs) { + throw new Error('You must specify an `fs`-compatible API as part of bindings') + } + this.wasi = new WASI({ + args: ['circom2', ...args], + env, + preopens, + bindings, + descriptors, + }) + } + + async compile(bufOrResponse) { + // TODO: Handle ArrayBuffer + if (isTypedArray(bufOrResponse)) { + return WebAssembly.compile(bufOrResponse) + } + + // Require Response object if not a TypedArray + const response = await bufOrResponse + if (!(response instanceof Response)) { + throw new Error('Expected TypedArray or Response object') + } + + const contentType = response.headers.get('Content-Type') || '' + + if ('instantiateStreaming' in WebAssembly && contentType.startsWith('application/wasm')) { + return WebAssembly.compileStreaming(response) + } + + const buffer = await response.arrayBuffer() + return WebAssembly.compile(buffer) + } + + async execute(bufOrResponse) { + const mod = await this.compile(bufOrResponse) + const instance = await WebAssembly.instantiate(mod, { + ...this.wasi.getImports(mod), + }) + + try { + this.wasi.start(instance) + } catch (err) { + // The circom devs decided to start forcing an exit call instead of exiting gracefully + // so we look for WASIExitError with success code so we can actually be graceful + if (err instanceof WASIExitError && err.code === 0) { + return instance + } + + throw err + } + + // Return the instance in case someone wants to access exports or something + return instance + } +} + +module.exports.CircomRunner = CircomRunner +module.exports.preopens = defaultPreopens +module.exports.bindings = defaultBindings diff --git a/src/core/compiler/vendor/wasi.js b/src/core/compiler/vendor/wasi.js new file mode 100644 index 0000000..ac03a52 --- /dev/null +++ b/src/core/compiler/vendor/wasi.js @@ -0,0 +1,1229 @@ +'use strict' +/* eslint-disable no-unused-vars */ +Object.defineProperty(exports, '__esModule', { value: true }) +const bigint_1 = require('@wasmer/wasi/lib/polyfills/bigint') +const dataview_1 = require('@wasmer/wasi/lib/polyfills/dataview') +const buffer_1 = require('@wasmer/wasi/lib/polyfills/buffer') +// Import our default bindings depending on the environment +let defaultBindings +/*ROLLUP_REPLACE_NODE +import nodeBindings from "./bindings/node"; +defaultBindings = nodeBindings; +ROLLUP_REPLACE_NODE*/ +/*ROLLUP_REPLACE_BROWSER +import browserBindings from "./bindings/browser"; +defaultBindings = browserBindings; +ROLLUP_REPLACE_BROWSER*/ +/* + +This project is based from the Node implementation made by Gus Caplan +https://github.com/devsnek/node-wasi +However, JavaScript WASI is focused on: + * Bringing WASI to the Browsers + * Make easy to plug different filesystems + * Provide a type-safe api using Typescript + * Providing multiple output targets to support both browsers and node + * The API is adapted to the Node-WASI API: https://github.com/nodejs/wasi/blob/wasi/lib/wasi.js + +Copyright 2019 Gus Caplan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. + + */ +const constants_1 = require('@wasmer/wasi/lib/constants') +const STDIN_DEFAULT_RIGHTS = + constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_READ | + constants_1.WASI_RIGHT_FD_SYNC | + constants_1.WASI_RIGHT_FD_ADVISE | + constants_1.WASI_RIGHT_FD_FILESTAT_GET | + constants_1.WASI_RIGHT_POLL_FD_READWRITE +const STDOUT_DEFAULT_RIGHTS = + constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_WRITE | + constants_1.WASI_RIGHT_FD_SYNC | + constants_1.WASI_RIGHT_FD_ADVISE | + constants_1.WASI_RIGHT_FD_FILESTAT_GET | + constants_1.WASI_RIGHT_POLL_FD_READWRITE +const STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS +const msToNs = (ms) => { + const msInt = Math.trunc(ms) + const decimal = bigint_1.BigIntPolyfill(Math.round((ms - msInt) * 1000000)) + const ns = bigint_1.BigIntPolyfill(msInt) * bigint_1.BigIntPolyfill(1000000) + return ns + decimal +} +const nsToMs = (ns) => { + if (typeof ns === 'number') { + ns = Math.trunc(ns) + } + const nsInt = bigint_1.BigIntPolyfill(ns) + return Number(nsInt / bigint_1.BigIntPolyfill(1000000)) +} +const wrap = + (f) => + (...args) => { + try { + return f(...args) + } catch (e) { + // If it's an error from the fs + if (e && e.code && typeof e.code === 'string') { + return constants_1.ERROR_MAP[e.code] || constants_1.WASI_EINVAL + } + // If it's a WASI error, we return it directly + if (e instanceof WASIError) { + return e.errno + } + // Otherwise we let the error bubble up + throw e + } + } +const stat = (wasi, fd) => { + const entry = wasi.FD_MAP.get(fd) + if (!entry) { + throw new WASIError(constants_1.WASI_EBADF) + } + if (entry.filetype === undefined) { + const stats = wasi.bindings.fs.fstatSync(entry.real) + const { filetype, rightsBase, rightsInheriting } = translateFileAttributes(wasi, fd, stats) + entry.filetype = filetype + if (!entry.rights) { + entry.rights = { + base: rightsBase, + inheriting: rightsInheriting, + } + } + } + return entry +} +const translateFileAttributes = (wasi, fd, stats) => { + switch (true) { + case stats.isBlockDevice(): + return { + filetype: constants_1.WASI_FILETYPE_BLOCK_DEVICE, + rightsBase: constants_1.RIGHTS_BLOCK_DEVICE_BASE, + rightsInheriting: constants_1.RIGHTS_BLOCK_DEVICE_INHERITING, + } + case stats.isCharacterDevice(): { + const filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE + if (fd !== undefined && wasi.bindings.isTTY(fd)) { + return { + filetype, + rightsBase: constants_1.RIGHTS_TTY_BASE, + rightsInheriting: constants_1.RIGHTS_TTY_INHERITING, + } + } + return { + filetype, + rightsBase: constants_1.RIGHTS_CHARACTER_DEVICE_BASE, + rightsInheriting: constants_1.RIGHTS_CHARACTER_DEVICE_INHERITING, + } + } + case stats.isDirectory(): + return { + filetype: constants_1.WASI_FILETYPE_DIRECTORY, + rightsBase: constants_1.RIGHTS_DIRECTORY_BASE, + rightsInheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, + } + case stats.isFIFO(): + return { + filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, + rightsBase: constants_1.RIGHTS_SOCKET_BASE, + rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, + } + case stats.isFile(): + return { + filetype: constants_1.WASI_FILETYPE_REGULAR_FILE, + rightsBase: constants_1.RIGHTS_REGULAR_FILE_BASE, + rightsInheriting: constants_1.RIGHTS_REGULAR_FILE_INHERITING, + } + case stats.isSocket(): + return { + filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, + rightsBase: constants_1.RIGHTS_SOCKET_BASE, + rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, + } + case stats.isSymbolicLink(): + return { + filetype: constants_1.WASI_FILETYPE_SYMBOLIC_LINK, + rightsBase: bigint_1.BigIntPolyfill(0), + rightsInheriting: bigint_1.BigIntPolyfill(0), + } + default: + return { + filetype: constants_1.WASI_FILETYPE_UNKNOWN, + rightsBase: bigint_1.BigIntPolyfill(0), + rightsInheriting: bigint_1.BigIntPolyfill(0), + } + } +} +class WASIError extends Error { + constructor(errno) { + super() + this.errno = errno + Object.setPrototypeOf(this, WASIError.prototype) + } +} +exports.WASIError = WASIError +class WASIExitError extends Error { + constructor(code) { + super(`WASI Exit error: ${code}`) + this.code = code + Object.setPrototypeOf(this, WASIExitError.prototype) + } +} +exports.WASIExitError = WASIExitError +class WASIKillError extends Error { + constructor(signal) { + super(`WASI Kill signal: ${signal}`) + this.signal = signal + Object.setPrototypeOf(this, WASIKillError.prototype) + } +} +exports.WASIKillError = WASIKillError +class WASIDefault { + constructor(wasiConfig) { + // Destructure our wasiConfig + let preopens = {} + if (wasiConfig && wasiConfig.preopens) { + preopens = wasiConfig.preopens + } else if (wasiConfig && wasiConfig.preopenDirectories) { + preopens = wasiConfig.preopenDirectories + } + let env = {} + if (wasiConfig && wasiConfig.env) { + env = wasiConfig.env + } + let args = [] + if (wasiConfig && wasiConfig.args) { + args = wasiConfig.args + } + let bindings = defaultBindings + if (wasiConfig && wasiConfig.bindings) { + bindings = wasiConfig.bindings + } + // @ts-ignore + this.memory = undefined + // @ts-ignore + this.view = undefined + this.bindings = bindings + this.FD_MAP = new Map([ + [ + constants_1.WASI_STDIN_FILENO, + { + real: 0, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDIN_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + [ + constants_1.WASI_STDOUT_FILENO, + { + real: 1, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDOUT_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + [ + constants_1.WASI_STDERR_FILENO, + { + real: 2, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDERR_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + ]) + + if (wasiConfig && wasiConfig.descriptors) { + if (wasiConfig.descriptors.stdout) { + this.FD_MAP.get(constants_1.WASI_STDOUT_FILENO).real = wasiConfig.descriptors.stdout + } + + if (wasiConfig.descriptors.stderr) { + this.FD_MAP.get(constants_1.WASI_STDERR_FILENO).real = wasiConfig.descriptors.stderr + } + } + + let fs = this.bindings.fs + let path = this.bindings.path + for (const [k, v] of Object.entries(preopens)) { + const real = fs.openSync(v, fs.constants.O_RDONLY) + const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1 + this.FD_MAP.set(newfd, { + real, + filetype: constants_1.WASI_FILETYPE_DIRECTORY, + // offset: BigInt(0), + rights: { + base: constants_1.RIGHTS_DIRECTORY_BASE, + inheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, + }, + fakePath: k, + path: v, + }) + } + const getiovs = (iovs, iovsLen) => { + // iovs* -> [iov, iov, ...] + // __wasi_ciovec_t { + // void* buf, + // size_t buf_len, + // } + this.refreshMemory() + const buffers = Array.from({ length: iovsLen }, (_, i) => { + const ptr = iovs + i * 8 + const buf = this.view.getUint32(ptr, true) + const bufLen = this.view.getUint32(ptr + 4, true) + return new Uint8Array(this.memory.buffer, buf, bufLen) + }) + return buffers + } + const CHECK_FD = (fd, rights) => { + const stats = stat(this, fd) + // console.log(`CHECK_FD: stats.real: ${stats.real}, stats.path:`, stats.path); + // console.log('fd_check', fd, rights, stats) + if ( + rights !== bigint_1.BigIntPolyfill(0) && + (stats.rights.base & rights) === bigint_1.BigIntPolyfill(0) + ) { + throw new WASIError(constants_1.WASI_EPERM) + } + return stats + } + const CPUTIME_START = bindings.hrtime() + const now = (clockId) => { + switch (clockId) { + case constants_1.WASI_CLOCK_MONOTONIC: + return bindings.hrtime() + case constants_1.WASI_CLOCK_REALTIME: + return msToNs(Date.now()) + case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: + case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: + // return bindings.hrtime(CPUTIME_START) + return bindings.hrtime() - CPUTIME_START + default: + return null + } + } + this.wasiImport = { + args_get: (argv, argvBuf) => { + this.refreshMemory() + let coffset = argv + let offset = argvBuf + args.forEach((a) => { + this.view.setUint32(coffset, offset, true) + coffset += 4 + offset += buffer_1.default.from(this.memory.buffer).write(`${a}\0`, offset) + }) + return constants_1.WASI_ESUCCESS + }, + args_sizes_get: (argc, argvBufSize) => { + this.refreshMemory() + this.view.setUint32(argc, args.length, true) + const size = args.reduce((acc, a) => acc + buffer_1.default.byteLength(a) + 1, 0) + this.view.setUint32(argvBufSize, size, true) + return constants_1.WASI_ESUCCESS + }, + environ_get: (environ, environBuf) => { + this.refreshMemory() + let coffset = environ + let offset = environBuf + Object.entries(env).forEach(([key, value]) => { + this.view.setUint32(coffset, offset, true) + coffset += 4 + offset += buffer_1.default + .from(this.memory.buffer) + .write(`${key}=${value}\0`, offset) + }) + return constants_1.WASI_ESUCCESS + }, + environ_sizes_get: (environCount, environBufSize) => { + this.refreshMemory() + const envProcessed = Object.entries(env).map(([key, value]) => `${key}=${value}\0`) + const size = envProcessed.reduce( + (acc, e) => acc + buffer_1.default.byteLength(e), + 0, + ) + this.view.setUint32(environCount, envProcessed.length, true) + this.view.setUint32(environBufSize, size, true) + return constants_1.WASI_ESUCCESS + }, + clock_res_get: (clockId, resolution) => { + let res + switch (clockId) { + case constants_1.WASI_CLOCK_MONOTONIC: + case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: + case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: { + res = bigint_1.BigIntPolyfill(1) + break + } + case constants_1.WASI_CLOCK_REALTIME: { + res = bigint_1.BigIntPolyfill(1000) + break + } + } + this.view.setBigUint64(resolution, res) + return constants_1.WASI_ESUCCESS + }, + clock_time_get: (clockId, precision, time) => { + this.refreshMemory() + const n = now(clockId) + if (n === null) { + return constants_1.WASI_EINVAL + } + this.view.setBigUint64(time, bigint_1.BigIntPolyfill(n), true) + return constants_1.WASI_ESUCCESS + }, + fd_advise: wrap((fd, offset, len, advice) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ADVISE) + return constants_1.WASI_ENOSYS + }), + fd_allocate: wrap((fd, offset, len) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ALLOCATE) + return constants_1.WASI_ENOSYS + }), + fd_close: wrap((fd) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) + fs.closeSync(stats.real) + this.FD_MAP.delete(fd) + return constants_1.WASI_ESUCCESS + }), + fd_datasync: wrap((fd) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_DATASYNC) + fs.fdatasyncSync(stats.real) + return constants_1.WASI_ESUCCESS + }), + fd_fdstat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) + this.refreshMemory() + this.view.setUint8(bufPtr, stats.filetype) // FILETYPE u8 + this.view.setUint16(bufPtr + 2, 0, true) // FDFLAG u16 + this.view.setUint16(bufPtr + 4, 0, true) // FDFLAG u16 + this.view.setBigUint64(bufPtr + 8, bigint_1.BigIntPolyfill(stats.rights.base), true) // u64 + this.view.setBigUint64( + bufPtr + 8 + 8, + bigint_1.BigIntPolyfill(stats.rights.inheriting), + true, + ) // u64 + return constants_1.WASI_ESUCCESS + }), + fd_fdstat_set_flags: wrap((fd, flags) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FDSTAT_SET_FLAGS) + return constants_1.WASI_ENOSYS + }), + fd_fdstat_set_rights: wrap((fd, fsRightsBase, fsRightsInheriting) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) + const nrb = stats.rights.base | fsRightsBase + if (nrb > stats.rights.base) { + return constants_1.WASI_EPERM + } + const nri = stats.rights.inheriting | fsRightsInheriting + if (nri > stats.rights.inheriting) { + return constants_1.WASI_EPERM + } + stats.rights.base = fsRightsBase + stats.rights.inheriting = fsRightsInheriting + return constants_1.WASI_ESUCCESS + }), + fd_filestat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_GET) + const rstats = fs.fstatSync(stats.real) + this.refreshMemory() + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.dev), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true) + bufPtr += 8 + this.view.setUint8(bufPtr, stats.filetype) + bufPtr += 8 + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.nlink), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.size), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true) + return constants_1.WASI_ESUCCESS + }), + fd_filestat_set_size: wrap((fd, stSize) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE) + fs.ftruncateSync(stats.real, Number(stSize)) + return constants_1.WASI_ESUCCESS + }), + fd_filestat_set_times: wrap((fd, stAtim, stMtim, fstflags) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_TIMES) + const rstats = fs.fstatSync(stats.real) + let atim = rstats.atime + let mtim = rstats.mtime + const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)) + const atimflags = + constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW + if ((fstflags & atimflags) === atimflags) { + return constants_1.WASI_EINVAL + } + const mtimflags = + constants_1.WASI_FILESTAT_SET_MTIM | constants_1.WASI_FILESTAT_SET_MTIM_NOW + if ((fstflags & mtimflags) === mtimflags) { + return constants_1.WASI_EINVAL + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === + constants_1.WASI_FILESTAT_SET_ATIM + ) { + atim = nsToMs(stAtim) + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === + constants_1.WASI_FILESTAT_SET_ATIM_NOW + ) { + atim = n + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === + constants_1.WASI_FILESTAT_SET_MTIM + ) { + mtim = nsToMs(stMtim) + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === + constants_1.WASI_FILESTAT_SET_MTIM_NOW + ) { + mtim = n + } + fs.futimesSync(stats.real, new Date(atim), new Date(mtim)) + return constants_1.WASI_ESUCCESS + }), + fd_prestat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + this.view.setUint8(bufPtr, constants_1.WASI_PREOPENTYPE_DIR) + this.view.setUint32(bufPtr + 4, buffer_1.default.byteLength(stats.fakePath), true) + return constants_1.WASI_ESUCCESS + }), + fd_prestat_dir_name: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + buffer_1.default + .from(this.memory.buffer) + .write(stats.fakePath, pathPtr, pathLen, 'utf8') + return constants_1.WASI_ESUCCESS + }), + fd_pwrite: wrap((fd, iovs, iovsLen, offset, nwritten) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_WRITE | constants_1.WASI_RIGHT_FD_SEEK, + ) + let written = 0 + getiovs(iovs, iovsLen).forEach((iov) => { + let w = 0 + while (w < iov.byteLength) { + w += fs.writeSync( + stats.real, + iov, + w, + iov.byteLength - w, + Number(offset) + written + w, + ) + } + written += w + }) + this.view.setUint32(nwritten, written, true) + return constants_1.WASI_ESUCCESS + }), + fd_write: wrap((fd, iovs, iovsLen, nwritten) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE) + let written = 0 + getiovs(iovs, iovsLen).forEach((iov) => { + let w = 0 + while (w < iov.byteLength) { + const i = fs.writeSync( + stats.real, + iov, + w, + iov.byteLength - w, + stats.offset !== undefined ? Number(stats.offset) : null, + ) + if (stats.offset !== undefined) stats.offset += bigint_1.BigIntPolyfill(i) + w += i + } + written += w + }) + this.view.setUint32(nwritten, written, true) + return constants_1.WASI_ESUCCESS + }), + fd_pread: wrap((fd, iovs, iovsLen, offset, nread) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_READ | constants_1.WASI_RIGHT_FD_SEEK, + ) + let read = 0 + outer: for (const iov of getiovs(iovs, iovsLen)) { + let r = 0 + while (r < iov.byteLength) { + const length = iov.byteLength - r + const rr = fs.readSync( + stats.real, + iov, + r, + iov.byteLength - r, + Number(offset) + read + r, + ) + r += rr + read += rr + // If we don't read anything, or we receive less than requested + if (rr === 0 || rr < length) { + break outer + } + } + read += r + } + this.view.setUint32(nread, read, true) + return constants_1.WASI_ESUCCESS + }), + fd_read: wrap((fd, iovs, iovsLen, nread) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ) + const IS_STDIN = stats.real === 0 + let read = 0 + outer: for (const iov of getiovs(iovs, iovsLen)) { + let r = 0 + while (r < iov.byteLength) { + let length = iov.byteLength - r + let position = + IS_STDIN || stats.offset === undefined ? null : Number(stats.offset) + let rr = fs.readSync( + stats.real, // fd + iov, // buffer + r, // offset + length, // length + position, // position + ) + if (!IS_STDIN) { + stats.offset = + (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + + bigint_1.BigIntPolyfill(rr) + } + r += rr + read += rr + // If we don't read anything, or we receive less than requested + if (rr === 0 || rr < length) { + break outer + } + } + } + // We should not modify the offset of stdin + this.view.setUint32(nread, read, true) + return constants_1.WASI_ESUCCESS + }), + fd_readdir: wrap((fd, bufPtr, bufLen, cookie, bufusedPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READDIR) + this.refreshMemory() + const entries = fs.readdirSync(stats.path, { withFileTypes: true }) + const startPtr = bufPtr + for (let i = Number(cookie); i < entries.length; i += 1) { + const entry = entries[i] + let nameLength = buffer_1.default.byteLength(entry.name) + if (bufPtr - startPtr > bufLen) { + break + } + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(i + 1), true) + bufPtr += 8 + if (bufPtr - startPtr > bufLen) { + break + } + const rstats = fs.statSync(path.resolve(stats.path, entry.name)) + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true) + bufPtr += 8 + if (bufPtr - startPtr > bufLen) { + break + } + this.view.setUint32(bufPtr, nameLength, true) + bufPtr += 4 + if (bufPtr - startPtr > bufLen) { + break + } + let filetype + switch (true) { + case rstats.isBlockDevice(): + filetype = constants_1.WASI_FILETYPE_BLOCK_DEVICE + break + case rstats.isCharacterDevice(): + filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE + break + case rstats.isDirectory(): + filetype = constants_1.WASI_FILETYPE_DIRECTORY + break + case rstats.isFIFO(): + filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM + break + case rstats.isFile(): + filetype = constants_1.WASI_FILETYPE_REGULAR_FILE + break + case rstats.isSocket(): + filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM + break + case rstats.isSymbolicLink(): + filetype = constants_1.WASI_FILETYPE_SYMBOLIC_LINK + break + default: + filetype = constants_1.WASI_FILETYPE_UNKNOWN + break + } + this.view.setUint8(bufPtr, filetype) + bufPtr += 1 + bufPtr += 3 // padding + if (bufPtr + nameLength >= startPtr + bufLen) { + // It doesn't fit in the buffer + break + } + let memory_buffer = buffer_1.default.from(this.memory.buffer) + memory_buffer.write(entry.name, bufPtr) + bufPtr += nameLength + } + const bufused = bufPtr - startPtr + this.view.setUint32(bufusedPtr, Math.min(bufused, bufLen), true) + return constants_1.WASI_ESUCCESS + }), + fd_renumber: wrap((from, to) => { + CHECK_FD(from, bigint_1.BigIntPolyfill(0)) + CHECK_FD(to, bigint_1.BigIntPolyfill(0)) + fs.closeSync(this.FD_MAP.get(from).real) + this.FD_MAP.set(from, this.FD_MAP.get(to)) + this.FD_MAP.delete(to) + return constants_1.WASI_ESUCCESS + }), + fd_seek: wrap((fd, offset, whence, newOffsetPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SEEK) + // console.log('fd_seek', fd, offset, whence, newOffsetPtr, '=', stats.offset); + this.refreshMemory() + switch (whence) { + case constants_1.WASI_WHENCE_CUR: + stats.offset = + (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + + bigint_1.BigIntPolyfill(offset) + break + case constants_1.WASI_WHENCE_END: + const { size } = fs.fstatSync(stats.real) + stats.offset = + bigint_1.BigIntPolyfill(size) + bigint_1.BigIntPolyfill(offset) + break + case constants_1.WASI_WHENCE_SET: + stats.offset = bigint_1.BigIntPolyfill(offset) + break + } + this.view.setBigUint64(newOffsetPtr, stats.offset, true) + return constants_1.WASI_ESUCCESS + }), + fd_tell: wrap((fd, offsetPtr) => { + // console.log('fd_tell') + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_TELL) + this.refreshMemory() + if (!stats.offset) { + stats.offset = bigint_1.BigIntPolyfill(0) + } + this.view.setBigUint64(offsetPtr, stats.offset, true) + return constants_1.WASI_ESUCCESS + }), + fd_sync: wrap((fd) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SYNC) + fs.fsyncSync(stats.real) + return constants_1.WASI_ESUCCESS + }), + path_create_directory: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_CREATE_DIRECTORY) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() + fs.mkdirSync(path.resolve(stats.path, p)) + return constants_1.WASI_ESUCCESS + }), + path_filestat_get: wrap((fd, flags, pathPtr, pathLen, bufPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_GET) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() + const rstats = fs.statSync(path.resolve(stats.path, p)) + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.dev), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true) + bufPtr += 8 + this.view.setUint8( + bufPtr, + translateFileAttributes(this, undefined, rstats).filetype, + ) + bufPtr += 8 + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.nlink), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.size), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true) + bufPtr += 8 + this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true) + return constants_1.WASI_ESUCCESS + }), + path_filestat_set_times: wrap( + (fd, dirflags, pathPtr, pathLen, stAtim, stMtim, fstflags) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_SET_TIMES) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const rstats = fs.fstatSync(stats.real) + let atim = rstats.atime + let mtim = rstats.mtime + const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)) + const atimflags = + constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW + if ((fstflags & atimflags) === atimflags) { + return constants_1.WASI_EINVAL + } + const mtimflags = + constants_1.WASI_FILESTAT_SET_MTIM | constants_1.WASI_FILESTAT_SET_MTIM_NOW + if ((fstflags & mtimflags) === mtimflags) { + return constants_1.WASI_EINVAL + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === + constants_1.WASI_FILESTAT_SET_ATIM + ) { + atim = nsToMs(stAtim) + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === + constants_1.WASI_FILESTAT_SET_ATIM_NOW + ) { + atim = n + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === + constants_1.WASI_FILESTAT_SET_MTIM + ) { + mtim = nsToMs(stMtim) + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === + constants_1.WASI_FILESTAT_SET_MTIM_NOW + ) { + mtim = n + } + const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() + fs.utimesSync(path.resolve(stats.path, p), new Date(atim), new Date(mtim)) + return constants_1.WASI_ESUCCESS + }, + ), + path_link: wrap((oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen) => { + const ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_LINK_SOURCE) + const nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_LINK_TARGET) + if (!ostats.path || !nstats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString() + const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString() + fs.linkSync(path.resolve(ostats.path, op), path.resolve(nstats.path, np)) + return constants_1.WASI_ESUCCESS + }), + path_open: wrap( + ( + dirfd, + dirflags, + pathPtr, + pathLen, + oflags, + fsRightsBase, + fsRightsInheriting, + fsFlags, + fd, + ) => { + const stats = CHECK_FD(dirfd, constants_1.WASI_RIGHT_PATH_OPEN) + fsRightsBase = bigint_1.BigIntPolyfill(fsRightsBase) + fsRightsInheriting = bigint_1.BigIntPolyfill(fsRightsInheriting) + const read = + (fsRightsBase & + (constants_1.WASI_RIGHT_FD_READ | + constants_1.WASI_RIGHT_FD_READDIR)) !== + bigint_1.BigIntPolyfill(0) + const write = + (fsRightsBase & + (constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_WRITE | + constants_1.WASI_RIGHT_FD_ALLOCATE | + constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !== + bigint_1.BigIntPolyfill(0) + let noflags + if (write && read) { + noflags = fs.constants.O_RDWR + } else if (read) { + noflags = fs.constants.O_RDONLY + } else if (write) { + noflags = fs.constants.O_WRONLY + } + // fsRightsBase is needed here but perhaps we should do it in neededInheriting + let neededBase = fsRightsBase | constants_1.WASI_RIGHT_PATH_OPEN + let neededInheriting = fsRightsBase | fsRightsInheriting + if ((oflags & constants_1.WASI_O_CREAT) !== 0) { + noflags |= fs.constants.O_CREAT + neededBase |= constants_1.WASI_RIGHT_PATH_CREATE_FILE + } + if ((oflags & constants_1.WASI_O_DIRECTORY) !== 0) { + noflags |= fs.constants.O_DIRECTORY + } + if ((oflags & constants_1.WASI_O_EXCL) !== 0) { + noflags |= fs.constants.O_EXCL + } + if ((oflags & constants_1.WASI_O_TRUNC) !== 0) { + noflags |= fs.constants.O_TRUNC + neededBase |= constants_1.WASI_RIGHT_PATH_FILESTAT_SET_SIZE + } + // Convert file descriptor flags. + if ((fsFlags & constants_1.WASI_FDFLAG_APPEND) !== 0) { + noflags |= fs.constants.O_APPEND + } + if ((fsFlags & constants_1.WASI_FDFLAG_DSYNC) !== 0) { + if (fs.constants.O_DSYNC) { + noflags |= fs.constants.O_DSYNC + } else { + noflags |= fs.constants.O_SYNC + } + neededInheriting |= constants_1.WASI_RIGHT_FD_DATASYNC + } + if ((fsFlags & constants_1.WASI_FDFLAG_NONBLOCK) !== 0) { + noflags |= fs.constants.O_NONBLOCK + } + if ((fsFlags & constants_1.WASI_FDFLAG_RSYNC) !== 0) { + if (fs.constants.O_RSYNC) { + noflags |= fs.constants.O_RSYNC + } else { + noflags |= fs.constants.O_SYNC + } + neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC + } + if ((fsFlags & constants_1.WASI_FDFLAG_SYNC) !== 0) { + noflags |= fs.constants.O_SYNC + neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC + } + if (write && (noflags & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0) { + neededInheriting |= constants_1.WASI_RIGHT_FD_SEEK + } + this.refreshMemory() + const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() + const fullUnresolved = path.resolve(stats.path, p) + if (path.relative(stats.path, fullUnresolved).startsWith('..')) { + return constants_1.WASI_ENOTCAPABLE + } + let full + try { + full = fs.realpathSync(fullUnresolved) + if (path.relative(stats.path, full).startsWith('..')) { + return constants_1.WASI_ENOTCAPABLE + } + } catch (e) { + if (e.code === 'ENOENT') { + full = fullUnresolved + } else { + throw e + } + } + /* check if the file is a directory (unless opening for write, + * in which case the file may not exist and should be created) */ + let isDirectory + try { + isDirectory = fs.statSync(full).isDirectory() + } catch (e) {} + let realfd + if (!write && isDirectory) { + realfd = fs.openSync(full, fs.constants.O_RDONLY) + } else { + realfd = fs.openSync(full, noflags) + } + const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1 + this.FD_MAP.set(newfd, { + real: realfd, + filetype: undefined, + offset: BigInt(0), + rights: { + base: neededBase, + inheriting: neededInheriting, + }, + path: full, + }) + stat(this, newfd) + this.view.setUint32(fd, newfd, true) + return constants_1.WASI_ESUCCESS + }, + ), + path_readlink: wrap((fd, pathPtr, pathLen, buf, bufLen, bufused) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_READLINK) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() + const full = path.resolve(stats.path, p) + const r = fs.readlinkSync(full) + const used = buffer_1.default.from(this.memory.buffer).write(r, buf, bufLen) + this.view.setUint32(bufused, used, true) + return constants_1.WASI_ESUCCESS + }), + path_remove_directory: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_REMOVE_DIRECTORY) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() + fs.rmdirSync(path.resolve(stats.path, p)) + return constants_1.WASI_ESUCCESS + }), + path_rename: wrap((oldFd, oldPath, oldPathLen, newFd, newPath, newPathLen) => { + const ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_RENAME_SOURCE) + const nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_RENAME_TARGET) + if (!ostats.path || !nstats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString() + const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString() + fs.renameSync(path.resolve(ostats.path, op), path.resolve(nstats.path, np)) + return constants_1.WASI_ESUCCESS + }), + path_symlink: wrap((oldPath, oldPathLen, fd, newPath, newPathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_SYMLINK) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString() + const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString() + fs.symlinkSync(op, path.resolve(stats.path, np)) + return constants_1.WASI_ESUCCESS + }), + path_unlink_file: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_UNLINK_FILE) + if (!stats.path) { + return constants_1.WASI_EINVAL + } + this.refreshMemory() + const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() + fs.unlinkSync(path.resolve(stats.path, p)) + return constants_1.WASI_ESUCCESS + }), + poll_oneoff: (sin, sout, nsubscriptions, nevents) => { + let eventc = 0 + let waitEnd = 0 + this.refreshMemory() + for (let i = 0; i < nsubscriptions; i += 1) { + const userdata = this.view.getBigUint64(sin, true) + sin += 8 + const type = this.view.getUint8(sin) + sin += 1 + switch (type) { + case constants_1.WASI_EVENTTYPE_CLOCK: { + sin += 7 // padding + const identifier = this.view.getBigUint64(sin, true) + sin += 8 + const clockid = this.view.getUint32(sin, true) + sin += 4 + sin += 4 // padding + const timestamp = this.view.getBigUint64(sin, true) + sin += 8 + const precision = this.view.getBigUint64(sin, true) + sin += 8 + const subclockflags = this.view.getUint16(sin, true) + sin += 2 + sin += 6 // padding + const absolute = subclockflags === 1 + let e = constants_1.WASI_ESUCCESS + const n = bigint_1.BigIntPolyfill(now(clockid)) + if (n === null) { + e = constants_1.WASI_EINVAL + } else { + const end = absolute ? timestamp : n + timestamp + waitEnd = end > waitEnd ? end : waitEnd + } + this.view.setBigUint64(sout, userdata, true) + sout += 8 + this.view.setUint16(sout, e, true) // error + sout += 2 // pad offset 2 + this.view.setUint8(sout, constants_1.WASI_EVENTTYPE_CLOCK) + sout += 1 // pad offset 3 + sout += 5 // padding to 8 + eventc += 1 + break + } + case constants_1.WASI_EVENTTYPE_FD_READ: + case constants_1.WASI_EVENTTYPE_FD_WRITE: { + sin += 3 // padding + const fd = this.view.getUint32(sin, true) + sin += 4 + this.view.setBigUint64(sout, userdata, true) + sout += 8 + this.view.setUint16(sout, constants_1.WASI_ENOSYS, true) // error + sout += 2 // pad offset 2 + this.view.setUint8(sout, type) + sout += 1 // pad offset 3 + sout += 5 // padding to 8 + eventc += 1 + break + } + default: + return constants_1.WASI_EINVAL + } + } + this.view.setUint32(nevents, eventc, true) + while (bindings.hrtime() < waitEnd) { + // nothing + } + return constants_1.WASI_ESUCCESS + }, + proc_exit: (rval) => { + bindings.exit(rval) + return constants_1.WASI_ESUCCESS + }, + proc_raise: (sig) => { + if (!(sig in constants_1.SIGNAL_MAP)) { + return constants_1.WASI_EINVAL + } + bindings.kill(constants_1.SIGNAL_MAP[sig]) + return constants_1.WASI_ESUCCESS + }, + random_get: (bufPtr, bufLen) => { + this.refreshMemory() + bindings.randomFillSync(new Uint8Array(this.memory.buffer), bufPtr, bufLen) + return constants_1.WASI_ESUCCESS + }, + sched_yield() { + // Single threaded environment + // This is a no-op in JS + return constants_1.WASI_ESUCCESS + }, + sock_recv() { + return constants_1.WASI_ENOSYS + }, + sock_send() { + return constants_1.WASI_ENOSYS + }, + sock_shutdown() { + return constants_1.WASI_ENOSYS + }, + } + // Wrap each of the imports to show the calls in the console + if (wasiConfig.traceSyscalls) { + Object.keys(this.wasiImport).forEach((key) => { + const prevImport = this.wasiImport[key] + this.wasiImport[key] = function (...args) { + console.log(`WASI: wasiImport called: ${key} (${args})`) + try { + let result = prevImport(...args) + console.log(`WASI: => ${result}`) + return result + } catch (e) { + console.log(`Catched error: ${e}`) + throw e + } + } + }) + } + } + refreshMemory() { + // @ts-ignore + if (!this.view || this.view.buffer.byteLength === 0) { + this.view = new dataview_1.DataViewPolyfill(this.memory.buffer) + } + } + setMemory(memory) { + this.memory = memory + } + start(instance) { + const exports = instance.exports + if (exports === null || typeof exports !== 'object') { + throw new Error(`instance.exports must be an Object. Received ${exports}.`) + } + const { memory } = exports + if (!(memory instanceof WebAssembly.Memory)) { + throw new Error( + `instance.exports.memory must be a WebAssembly.Memory. Recceived ${memory}.`, + ) + } + this.setMemory(memory) + if (exports._start) { + exports._start() + } + } + getImportNamespace(module) { + let namespace = null + for (let imp of WebAssembly.Module.imports(module)) { + // We only check for the functions + if (imp.kind !== 'function') { + continue + } + // We allow functions in other namespaces other than wasi + if (!imp.module.startsWith('wasi_')) { + continue + } + if (!namespace) { + namespace = imp.module + } else { + if (namespace !== imp.module) { + throw new Error('Multiple namespaces detected.') + } + } + } + return namespace + } + getImports(module) { + let namespace = this.getImportNamespace(module) + switch (namespace) { + case 'wasi_unstable': + return { + wasi_unstable: this.wasiImport, + } + case 'wasi_snapshot_preview1': + return { + wasi_snapshot_preview1: this.wasiImport, + } + default: + throw new Error("Can't detect a WASI namespace for the WebAssembly Module") + } + } +} +exports.default = WASIDefault +WASIDefault.defaultBindings = defaultBindings +// Also export it as a field in the export object +exports.WASI = WASIDefault diff --git a/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts b/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts index d71a72f..b43c213 100644 --- a/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts +++ b/src/core/dependencies/parser/CircomTemplateInputsVisitor.ts @@ -99,7 +99,7 @@ export class CircomTemplateInputsVisitor extends CircomVisitor { }); return { - name: identifier.ID().getText(), + name: identifier.ID(0).getText(), dimension: inputDimension, }; } diff --git a/tsconfig.json b/tsconfig.json index cbc6415..a9a7b51 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "compilerOptions": { "target": "ES2022", "module": "commonjs", + "allowJs": true, "declaration": true, "declarationMap": true, "sourceMap": true, From c2bc50f78836383bf2adc7b9b36b5fb525c91015 Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Fri, 11 Oct 2024 11:46:11 +0300 Subject: [PATCH 3/3] Remove old comments and format files --- src/core/compiler/vendor/index.js | 214 +-- src/core/compiler/vendor/wasi.js | 2468 +++++++++++++++-------------- 2 files changed, 1394 insertions(+), 1288 deletions(-) diff --git a/src/core/compiler/vendor/index.js b/src/core/compiler/vendor/index.js index 4b23f0d..cf6ae05 100644 --- a/src/core/compiler/vendor/index.js +++ b/src/core/compiler/vendor/index.js @@ -1,128 +1,136 @@ -const isTypedArray = require('is-typed-array') -const path = require('path-browserify') +const isTypedArray = require("is-typed-array"); +const path = require("path-browserify"); -const { WASI, WASIExitError, WASIKillError } = require('./wasi') +const { WASI, WASIExitError, WASIKillError } = require("./wasi"); -const baseNow = Math.floor((Date.now() - performance.now()) * 1e-3) +const baseNow = Math.floor((Date.now() - performance.now()) * 1e-3); function hrtime() { - let clocktime = performance.now() * 1e-3 - let seconds = Math.floor(clocktime) + baseNow - let nanoseconds = Math.floor((clocktime % 1) * 1e9) - // return BigInt(seconds) * BigInt(1e9) + BigInt(nanoseconds) - return seconds * 1e9 + nanoseconds + let clocktime = performance.now() * 1e-3; + let seconds = Math.floor(clocktime) + baseNow; + let nanoseconds = Math.floor((clocktime % 1) * 1e9); + // return BigInt(seconds) * BigInt(1e9) + BigInt(nanoseconds) + return seconds * 1e9 + nanoseconds; } function randomFillSync(buf, offset, size) { - if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { - // Similar to the implementation of `randomfill` on npm - let uint = new Uint8Array(buf.buffer, offset, size) - crypto.getRandomValues(uint) - return buf - } else { - try { - // Try to load webcrypto in node - let crypto = require('crypto') - // TODO: Update to webcrypto in nodejs - return crypto.randomFillSync(buf, offset, size) - } catch { - // If an error occurs, fall back to the least secure version - // TODO: Should we throw instead since this would be a crazy old browser - // or nodejs built without crypto APIs - if (buf instanceof Uint8Array) { - for (let i = offset; i < offset + size; i++) { - buf[i] = Math.floor(Math.random() * 256) - } - } - return buf + if ( + typeof crypto !== "undefined" && + typeof crypto.getRandomValues === "function" + ) { + // Similar to the implementation of `randomfill` on npm + let uint = new Uint8Array(buf.buffer, offset, size); + crypto.getRandomValues(uint); + return buf; + } else { + try { + // Try to load webcrypto in node + let crypto = require("crypto"); + // TODO: Update to webcrypto in nodejs + return crypto.randomFillSync(buf, offset, size); + } catch { + // If an error occurs, fall back to the least secure version + // TODO: Should we throw instead since this would be a crazy old browser + // or nodejs built without crypto APIs + if (buf instanceof Uint8Array) { + for (let i = offset; i < offset + size; i++) { + buf[i] = Math.floor(Math.random() * 256); } + } + return buf; } + } } const defaultBindings = { - hrtime: hrtime, - exit(code) { - throw new WASIExitError(code) - }, - kill(signal) { - throw new WASIKillError(signal) - }, - randomFillSync: randomFillSync, - isTTY: () => true, - path: path, - fs: null, -} + hrtime: hrtime, + exit(code) { + throw new WASIExitError(code); + }, + kill(signal) { + throw new WASIKillError(signal); + }, + randomFillSync: randomFillSync, + isTTY: () => true, + path: path, + fs: null, +}; const defaultPreopens = { - '.': '.', - "/": "/", -} + ".": ".", + "/": "/", +}; class CircomRunner { - constructor({ - args, - env, - preopens = defaultPreopens, - bindings = defaultBindings, - descriptors = undefined, - } = {}) { - if (!bindings.fs) { - throw new Error('You must specify an `fs`-compatible API as part of bindings') - } - this.wasi = new WASI({ - args: ['circom2', ...args], - env, - preopens, - bindings, - descriptors, - }) + constructor({ + args, + env, + preopens = defaultPreopens, + bindings = defaultBindings, + descriptors = undefined, + } = {}) { + if (!bindings.fs) { + throw new Error( + "You must specify an `fs`-compatible API as part of bindings", + ); + } + this.wasi = new WASI({ + args: ["circom2", ...args], + env, + preopens, + bindings, + descriptors, + }); + } + + async compile(bufOrResponse) { + // TODO: Handle ArrayBuffer + if (isTypedArray(bufOrResponse)) { + return WebAssembly.compile(bufOrResponse); } - async compile(bufOrResponse) { - // TODO: Handle ArrayBuffer - if (isTypedArray(bufOrResponse)) { - return WebAssembly.compile(bufOrResponse) - } - - // Require Response object if not a TypedArray - const response = await bufOrResponse - if (!(response instanceof Response)) { - throw new Error('Expected TypedArray or Response object') - } - - const contentType = response.headers.get('Content-Type') || '' + // Require Response object if not a TypedArray + const response = await bufOrResponse; + if (!(response instanceof Response)) { + throw new Error("Expected TypedArray or Response object"); + } - if ('instantiateStreaming' in WebAssembly && contentType.startsWith('application/wasm')) { - return WebAssembly.compileStreaming(response) - } + const contentType = response.headers.get("Content-Type") || ""; - const buffer = await response.arrayBuffer() - return WebAssembly.compile(buffer) + if ( + "instantiateStreaming" in WebAssembly && + contentType.startsWith("application/wasm") + ) { + return WebAssembly.compileStreaming(response); } - async execute(bufOrResponse) { - const mod = await this.compile(bufOrResponse) - const instance = await WebAssembly.instantiate(mod, { - ...this.wasi.getImports(mod), - }) - - try { - this.wasi.start(instance) - } catch (err) { - // The circom devs decided to start forcing an exit call instead of exiting gracefully - // so we look for WASIExitError with success code so we can actually be graceful - if (err instanceof WASIExitError && err.code === 0) { - return instance - } - - throw err - } - - // Return the instance in case someone wants to access exports or something - return instance + const buffer = await response.arrayBuffer(); + return WebAssembly.compile(buffer); + } + + async execute(bufOrResponse) { + const mod = await this.compile(bufOrResponse); + const instance = await WebAssembly.instantiate(mod, { + ...this.wasi.getImports(mod), + }); + + try { + this.wasi.start(instance); + } catch (err) { + // The circom devs decided to start forcing an exit call instead of exiting gracefully + // so we look for WASIExitError with success code so we can actually be graceful + if (err instanceof WASIExitError && err.code === 0) { + return instance; + } + + throw err; } + + // Return the instance in case someone wants to access exports or something + return instance; + } } -module.exports.CircomRunner = CircomRunner -module.exports.preopens = defaultPreopens -module.exports.bindings = defaultBindings +module.exports.CircomRunner = CircomRunner; +module.exports.preopens = defaultPreopens; +module.exports.bindings = defaultBindings; diff --git a/src/core/compiler/vendor/wasi.js b/src/core/compiler/vendor/wasi.js index ac03a52..0af9323 100644 --- a/src/core/compiler/vendor/wasi.js +++ b/src/core/compiler/vendor/wasi.js @@ -1,1229 +1,1327 @@ -'use strict' +"use strict"; /* eslint-disable no-unused-vars */ -Object.defineProperty(exports, '__esModule', { value: true }) -const bigint_1 = require('@wasmer/wasi/lib/polyfills/bigint') -const dataview_1 = require('@wasmer/wasi/lib/polyfills/dataview') -const buffer_1 = require('@wasmer/wasi/lib/polyfills/buffer') -// Import our default bindings depending on the environment -let defaultBindings -/*ROLLUP_REPLACE_NODE -import nodeBindings from "./bindings/node"; -defaultBindings = nodeBindings; -ROLLUP_REPLACE_NODE*/ -/*ROLLUP_REPLACE_BROWSER -import browserBindings from "./bindings/browser"; -defaultBindings = browserBindings; -ROLLUP_REPLACE_BROWSER*/ -/* +Object.defineProperty(exports, "__esModule", { value: true }); +const bigint_1 = require("@wasmer/wasi/lib/polyfills/bigint"); +const dataview_1 = require("@wasmer/wasi/lib/polyfills/dataview"); +const buffer_1 = require("@wasmer/wasi/lib/polyfills/buffer"); -This project is based from the Node implementation made by Gus Caplan -https://github.com/devsnek/node-wasi -However, JavaScript WASI is focused on: - * Bringing WASI to the Browsers - * Make easy to plug different filesystems - * Provide a type-safe api using Typescript - * Providing multiple output targets to support both browsers and node - * The API is adapted to the Node-WASI API: https://github.com/nodejs/wasi/blob/wasi/lib/wasi.js - -Copyright 2019 Gus Caplan - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to -deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -sell copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -IN THE SOFTWARE. - - */ -const constants_1 = require('@wasmer/wasi/lib/constants') +const constants_1 = require("@wasmer/wasi/lib/constants"); const STDIN_DEFAULT_RIGHTS = - constants_1.WASI_RIGHT_FD_DATASYNC | - constants_1.WASI_RIGHT_FD_READ | - constants_1.WASI_RIGHT_FD_SYNC | - constants_1.WASI_RIGHT_FD_ADVISE | - constants_1.WASI_RIGHT_FD_FILESTAT_GET | - constants_1.WASI_RIGHT_POLL_FD_READWRITE + constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_READ | + constants_1.WASI_RIGHT_FD_SYNC | + constants_1.WASI_RIGHT_FD_ADVISE | + constants_1.WASI_RIGHT_FD_FILESTAT_GET | + constants_1.WASI_RIGHT_POLL_FD_READWRITE; const STDOUT_DEFAULT_RIGHTS = - constants_1.WASI_RIGHT_FD_DATASYNC | - constants_1.WASI_RIGHT_FD_WRITE | - constants_1.WASI_RIGHT_FD_SYNC | - constants_1.WASI_RIGHT_FD_ADVISE | - constants_1.WASI_RIGHT_FD_FILESTAT_GET | - constants_1.WASI_RIGHT_POLL_FD_READWRITE -const STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS + constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_WRITE | + constants_1.WASI_RIGHT_FD_SYNC | + constants_1.WASI_RIGHT_FD_ADVISE | + constants_1.WASI_RIGHT_FD_FILESTAT_GET | + constants_1.WASI_RIGHT_POLL_FD_READWRITE; +const STDERR_DEFAULT_RIGHTS = STDOUT_DEFAULT_RIGHTS; const msToNs = (ms) => { - const msInt = Math.trunc(ms) - const decimal = bigint_1.BigIntPolyfill(Math.round((ms - msInt) * 1000000)) - const ns = bigint_1.BigIntPolyfill(msInt) * bigint_1.BigIntPolyfill(1000000) - return ns + decimal -} + const msInt = Math.trunc(ms); + const decimal = bigint_1.BigIntPolyfill(Math.round((ms - msInt) * 1000000)); + const ns = bigint_1.BigIntPolyfill(msInt) * bigint_1.BigIntPolyfill(1000000); + return ns + decimal; +}; const nsToMs = (ns) => { - if (typeof ns === 'number') { - ns = Math.trunc(ns) - } - const nsInt = bigint_1.BigIntPolyfill(ns) - return Number(nsInt / bigint_1.BigIntPolyfill(1000000)) -} + if (typeof ns === "number") { + ns = Math.trunc(ns); + } + const nsInt = bigint_1.BigIntPolyfill(ns); + return Number(nsInt / bigint_1.BigIntPolyfill(1000000)); +}; const wrap = - (f) => - (...args) => { - try { - return f(...args) - } catch (e) { - // If it's an error from the fs - if (e && e.code && typeof e.code === 'string') { - return constants_1.ERROR_MAP[e.code] || constants_1.WASI_EINVAL - } - // If it's a WASI error, we return it directly - if (e instanceof WASIError) { - return e.errno - } - // Otherwise we let the error bubble up - throw e - } + (f) => + (...args) => { + try { + return f(...args); + } catch (e) { + // If it's an error from the fs + if (e && e.code && typeof e.code === "string") { + return constants_1.ERROR_MAP[e.code] || constants_1.WASI_EINVAL; + } + // If it's a WASI error, we return it directly + if (e instanceof WASIError) { + return e.errno; + } + // Otherwise we let the error bubble up + throw e; } + }; const stat = (wasi, fd) => { - const entry = wasi.FD_MAP.get(fd) - if (!entry) { - throw new WASIError(constants_1.WASI_EBADF) + const entry = wasi.FD_MAP.get(fd); + if (!entry) { + throw new WASIError(constants_1.WASI_EBADF); + } + if (entry.filetype === undefined) { + const stats = wasi.bindings.fs.fstatSync(entry.real); + const { filetype, rightsBase, rightsInheriting } = translateFileAttributes( + wasi, + fd, + stats, + ); + entry.filetype = filetype; + if (!entry.rights) { + entry.rights = { + base: rightsBase, + inheriting: rightsInheriting, + }; } - if (entry.filetype === undefined) { - const stats = wasi.bindings.fs.fstatSync(entry.real) - const { filetype, rightsBase, rightsInheriting } = translateFileAttributes(wasi, fd, stats) - entry.filetype = filetype - if (!entry.rights) { - entry.rights = { - base: rightsBase, - inheriting: rightsInheriting, - } - } - } - return entry -} + } + return entry; +}; const translateFileAttributes = (wasi, fd, stats) => { - switch (true) { - case stats.isBlockDevice(): - return { - filetype: constants_1.WASI_FILETYPE_BLOCK_DEVICE, - rightsBase: constants_1.RIGHTS_BLOCK_DEVICE_BASE, - rightsInheriting: constants_1.RIGHTS_BLOCK_DEVICE_INHERITING, - } - case stats.isCharacterDevice(): { - const filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE - if (fd !== undefined && wasi.bindings.isTTY(fd)) { - return { - filetype, - rightsBase: constants_1.RIGHTS_TTY_BASE, - rightsInheriting: constants_1.RIGHTS_TTY_INHERITING, - } - } - return { - filetype, - rightsBase: constants_1.RIGHTS_CHARACTER_DEVICE_BASE, - rightsInheriting: constants_1.RIGHTS_CHARACTER_DEVICE_INHERITING, - } - } - case stats.isDirectory(): - return { - filetype: constants_1.WASI_FILETYPE_DIRECTORY, - rightsBase: constants_1.RIGHTS_DIRECTORY_BASE, - rightsInheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, - } - case stats.isFIFO(): - return { - filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, - rightsBase: constants_1.RIGHTS_SOCKET_BASE, - rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, - } - case stats.isFile(): - return { - filetype: constants_1.WASI_FILETYPE_REGULAR_FILE, - rightsBase: constants_1.RIGHTS_REGULAR_FILE_BASE, - rightsInheriting: constants_1.RIGHTS_REGULAR_FILE_INHERITING, - } - case stats.isSocket(): - return { - filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, - rightsBase: constants_1.RIGHTS_SOCKET_BASE, - rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, - } - case stats.isSymbolicLink(): - return { - filetype: constants_1.WASI_FILETYPE_SYMBOLIC_LINK, - rightsBase: bigint_1.BigIntPolyfill(0), - rightsInheriting: bigint_1.BigIntPolyfill(0), - } - default: - return { - filetype: constants_1.WASI_FILETYPE_UNKNOWN, - rightsBase: bigint_1.BigIntPolyfill(0), - rightsInheriting: bigint_1.BigIntPolyfill(0), - } + switch (true) { + case stats.isBlockDevice(): + return { + filetype: constants_1.WASI_FILETYPE_BLOCK_DEVICE, + rightsBase: constants_1.RIGHTS_BLOCK_DEVICE_BASE, + rightsInheriting: constants_1.RIGHTS_BLOCK_DEVICE_INHERITING, + }; + case stats.isCharacterDevice(): { + const filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE; + if (fd !== undefined && wasi.bindings.isTTY(fd)) { + return { + filetype, + rightsBase: constants_1.RIGHTS_TTY_BASE, + rightsInheriting: constants_1.RIGHTS_TTY_INHERITING, + }; + } + return { + filetype, + rightsBase: constants_1.RIGHTS_CHARACTER_DEVICE_BASE, + rightsInheriting: constants_1.RIGHTS_CHARACTER_DEVICE_INHERITING, + }; } -} + case stats.isDirectory(): + return { + filetype: constants_1.WASI_FILETYPE_DIRECTORY, + rightsBase: constants_1.RIGHTS_DIRECTORY_BASE, + rightsInheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, + }; + case stats.isFIFO(): + return { + filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, + rightsBase: constants_1.RIGHTS_SOCKET_BASE, + rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, + }; + case stats.isFile(): + return { + filetype: constants_1.WASI_FILETYPE_REGULAR_FILE, + rightsBase: constants_1.RIGHTS_REGULAR_FILE_BASE, + rightsInheriting: constants_1.RIGHTS_REGULAR_FILE_INHERITING, + }; + case stats.isSocket(): + return { + filetype: constants_1.WASI_FILETYPE_SOCKET_STREAM, + rightsBase: constants_1.RIGHTS_SOCKET_BASE, + rightsInheriting: constants_1.RIGHTS_SOCKET_INHERITING, + }; + case stats.isSymbolicLink(): + return { + filetype: constants_1.WASI_FILETYPE_SYMBOLIC_LINK, + rightsBase: bigint_1.BigIntPolyfill(0), + rightsInheriting: bigint_1.BigIntPolyfill(0), + }; + default: + return { + filetype: constants_1.WASI_FILETYPE_UNKNOWN, + rightsBase: bigint_1.BigIntPolyfill(0), + rightsInheriting: bigint_1.BigIntPolyfill(0), + }; + } +}; class WASIError extends Error { - constructor(errno) { - super() - this.errno = errno - Object.setPrototypeOf(this, WASIError.prototype) - } + constructor(errno) { + super(); + this.errno = errno; + Object.setPrototypeOf(this, WASIError.prototype); + } } -exports.WASIError = WASIError +exports.WASIError = WASIError; class WASIExitError extends Error { - constructor(code) { - super(`WASI Exit error: ${code}`) - this.code = code - Object.setPrototypeOf(this, WASIExitError.prototype) - } + constructor(code) { + super(`WASI Exit error: ${code}`); + this.code = code; + Object.setPrototypeOf(this, WASIExitError.prototype); + } } -exports.WASIExitError = WASIExitError +exports.WASIExitError = WASIExitError; class WASIKillError extends Error { - constructor(signal) { - super(`WASI Kill signal: ${signal}`) - this.signal = signal - Object.setPrototypeOf(this, WASIKillError.prototype) - } + constructor(signal) { + super(`WASI Kill signal: ${signal}`); + this.signal = signal; + Object.setPrototypeOf(this, WASIKillError.prototype); + } } -exports.WASIKillError = WASIKillError +exports.WASIKillError = WASIKillError; class WASIDefault { - constructor(wasiConfig) { - // Destructure our wasiConfig - let preopens = {} - if (wasiConfig && wasiConfig.preopens) { - preopens = wasiConfig.preopens - } else if (wasiConfig && wasiConfig.preopenDirectories) { - preopens = wasiConfig.preopenDirectories + constructor(wasiConfig) { + // Destructure our wasiConfig + let preopens = {}; + if (wasiConfig && wasiConfig.preopens) { + preopens = wasiConfig.preopens; + } else if (wasiConfig && wasiConfig.preopenDirectories) { + preopens = wasiConfig.preopenDirectories; + } + let env = {}; + if (wasiConfig && wasiConfig.env) { + env = wasiConfig.env; + } + let args = []; + if (wasiConfig && wasiConfig.args) { + args = wasiConfig.args; + } + let bindings; + if (wasiConfig && wasiConfig.bindings) { + bindings = wasiConfig.bindings; + } + // @ts-ignore + this.memory = undefined; + // @ts-ignore + this.view = undefined; + this.bindings = bindings; + this.FD_MAP = new Map([ + [ + constants_1.WASI_STDIN_FILENO, + { + real: 0, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDIN_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + [ + constants_1.WASI_STDOUT_FILENO, + { + real: 1, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDOUT_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + [ + constants_1.WASI_STDERR_FILENO, + { + real: 2, + filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, + // offset: BigInt(0), + rights: { + base: STDERR_DEFAULT_RIGHTS, + inheriting: bigint_1.BigIntPolyfill(0), + }, + path: undefined, + }, + ], + ]); + + if (wasiConfig && wasiConfig.descriptors) { + if (wasiConfig.descriptors.stdout) { + this.FD_MAP.get(constants_1.WASI_STDOUT_FILENO).real = + wasiConfig.descriptors.stdout; + } + + if (wasiConfig.descriptors.stderr) { + this.FD_MAP.get(constants_1.WASI_STDERR_FILENO).real = + wasiConfig.descriptors.stderr; + } + } + + let fs = this.bindings.fs; + let path = this.bindings.path; + for (const [k, v] of Object.entries(preopens)) { + const real = fs.openSync(v, fs.constants.O_RDONLY); + const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1; + this.FD_MAP.set(newfd, { + real, + filetype: constants_1.WASI_FILETYPE_DIRECTORY, + // offset: BigInt(0), + rights: { + base: constants_1.RIGHTS_DIRECTORY_BASE, + inheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, + }, + fakePath: k, + path: v, + }); + } + const getiovs = (iovs, iovsLen) => { + // iovs* -> [iov, iov, ...] + // __wasi_ciovec_t { + // void* buf, + // size_t buf_len, + // } + this.refreshMemory(); + const buffers = Array.from({ length: iovsLen }, (_, i) => { + const ptr = iovs + i * 8; + const buf = this.view.getUint32(ptr, true); + const bufLen = this.view.getUint32(ptr + 4, true); + return new Uint8Array(this.memory.buffer, buf, bufLen); + }); + return buffers; + }; + const CHECK_FD = (fd, rights) => { + const stats = stat(this, fd); + // console.log(`CHECK_FD: stats.real: ${stats.real}, stats.path:`, stats.path); + // console.log('fd_check', fd, rights, stats) + if ( + rights !== bigint_1.BigIntPolyfill(0) && + (stats.rights.base & rights) === bigint_1.BigIntPolyfill(0) + ) { + throw new WASIError(constants_1.WASI_EPERM); + } + return stats; + }; + const CPUTIME_START = bindings.hrtime(); + const now = (clockId) => { + switch (clockId) { + case constants_1.WASI_CLOCK_MONOTONIC: + return bindings.hrtime(); + case constants_1.WASI_CLOCK_REALTIME: + return msToNs(Date.now()); + case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: + case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: + // return bindings.hrtime(CPUTIME_START) + return bindings.hrtime() - CPUTIME_START; + default: + return null; + } + }; + this.wasiImport = { + args_get: (argv, argvBuf) => { + this.refreshMemory(); + let coffset = argv; + let offset = argvBuf; + args.forEach((a) => { + this.view.setUint32(coffset, offset, true); + coffset += 4; + offset += buffer_1.default + .from(this.memory.buffer) + .write(`${a}\0`, offset); + }); + return constants_1.WASI_ESUCCESS; + }, + args_sizes_get: (argc, argvBufSize) => { + this.refreshMemory(); + this.view.setUint32(argc, args.length, true); + const size = args.reduce( + (acc, a) => acc + buffer_1.default.byteLength(a) + 1, + 0, + ); + this.view.setUint32(argvBufSize, size, true); + return constants_1.WASI_ESUCCESS; + }, + environ_get: (environ, environBuf) => { + this.refreshMemory(); + let coffset = environ; + let offset = environBuf; + Object.entries(env).forEach(([key, value]) => { + this.view.setUint32(coffset, offset, true); + coffset += 4; + offset += buffer_1.default + .from(this.memory.buffer) + .write(`${key}=${value}\0`, offset); + }); + return constants_1.WASI_ESUCCESS; + }, + environ_sizes_get: (environCount, environBufSize) => { + this.refreshMemory(); + const envProcessed = Object.entries(env).map( + ([key, value]) => `${key}=${value}\0`, + ); + const size = envProcessed.reduce( + (acc, e) => acc + buffer_1.default.byteLength(e), + 0, + ); + this.view.setUint32(environCount, envProcessed.length, true); + this.view.setUint32(environBufSize, size, true); + return constants_1.WASI_ESUCCESS; + }, + clock_res_get: (clockId, resolution) => { + let res; + switch (clockId) { + case constants_1.WASI_CLOCK_MONOTONIC: + case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: + case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: { + res = bigint_1.BigIntPolyfill(1); + break; + } + case constants_1.WASI_CLOCK_REALTIME: { + res = bigint_1.BigIntPolyfill(1000); + break; + } } - let env = {} - if (wasiConfig && wasiConfig.env) { - env = wasiConfig.env + this.view.setBigUint64(resolution, res); + return constants_1.WASI_ESUCCESS; + }, + clock_time_get: (clockId, precision, time) => { + this.refreshMemory(); + const n = now(clockId); + if (n === null) { + return constants_1.WASI_EINVAL; } - let args = [] - if (wasiConfig && wasiConfig.args) { - args = wasiConfig.args + this.view.setBigUint64(time, bigint_1.BigIntPolyfill(n), true); + return constants_1.WASI_ESUCCESS; + }, + fd_advise: wrap((fd, offset, len, advice) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ADVISE); + return constants_1.WASI_ENOSYS; + }), + fd_allocate: wrap((fd, offset, len) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ALLOCATE); + return constants_1.WASI_ENOSYS; + }), + fd_close: wrap((fd) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + fs.closeSync(stats.real); + this.FD_MAP.delete(fd); + return constants_1.WASI_ESUCCESS; + }), + fd_datasync: wrap((fd) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_DATASYNC); + fs.fdatasyncSync(stats.real); + return constants_1.WASI_ESUCCESS; + }), + fd_fdstat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + this.refreshMemory(); + this.view.setUint8(bufPtr, stats.filetype); // FILETYPE u8 + this.view.setUint16(bufPtr + 2, 0, true); // FDFLAG u16 + this.view.setUint16(bufPtr + 4, 0, true); // FDFLAG u16 + this.view.setBigUint64( + bufPtr + 8, + bigint_1.BigIntPolyfill(stats.rights.base), + true, + ); // u64 + this.view.setBigUint64( + bufPtr + 8 + 8, + bigint_1.BigIntPolyfill(stats.rights.inheriting), + true, + ); // u64 + return constants_1.WASI_ESUCCESS; + }), + fd_fdstat_set_flags: wrap((fd, flags) => { + CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FDSTAT_SET_FLAGS); + return constants_1.WASI_ENOSYS; + }), + fd_fdstat_set_rights: wrap((fd, fsRightsBase, fsRightsInheriting) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + const nrb = stats.rights.base | fsRightsBase; + if (nrb > stats.rights.base) { + return constants_1.WASI_EPERM; } - let bindings = defaultBindings - if (wasiConfig && wasiConfig.bindings) { - bindings = wasiConfig.bindings + const nri = stats.rights.inheriting | fsRightsInheriting; + if (nri > stats.rights.inheriting) { + return constants_1.WASI_EPERM; } - // @ts-ignore - this.memory = undefined - // @ts-ignore - this.view = undefined - this.bindings = bindings - this.FD_MAP = new Map([ - [ - constants_1.WASI_STDIN_FILENO, - { - real: 0, - filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, - // offset: BigInt(0), - rights: { - base: STDIN_DEFAULT_RIGHTS, - inheriting: bigint_1.BigIntPolyfill(0), - }, - path: undefined, - }, - ], - [ - constants_1.WASI_STDOUT_FILENO, - { - real: 1, - filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, - // offset: BigInt(0), - rights: { - base: STDOUT_DEFAULT_RIGHTS, - inheriting: bigint_1.BigIntPolyfill(0), - }, - path: undefined, - }, - ], - [ - constants_1.WASI_STDERR_FILENO, - { - real: 2, - filetype: constants_1.WASI_FILETYPE_CHARACTER_DEVICE, - // offset: BigInt(0), - rights: { - base: STDERR_DEFAULT_RIGHTS, - inheriting: bigint_1.BigIntPolyfill(0), - }, - path: undefined, - }, - ], - ]) - - if (wasiConfig && wasiConfig.descriptors) { - if (wasiConfig.descriptors.stdout) { - this.FD_MAP.get(constants_1.WASI_STDOUT_FILENO).real = wasiConfig.descriptors.stdout - } - - if (wasiConfig.descriptors.stderr) { - this.FD_MAP.get(constants_1.WASI_STDERR_FILENO).real = wasiConfig.descriptors.stderr - } + stats.rights.base = fsRightsBase; + stats.rights.inheriting = fsRightsInheriting; + return constants_1.WASI_ESUCCESS; + }), + fd_filestat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_GET); + const rstats = fs.fstatSync(stats.real); + this.refreshMemory(); + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.dev), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.ino), + true, + ); + bufPtr += 8; + this.view.setUint8(bufPtr, stats.filetype); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.nlink), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.size), + true, + ); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true); + return constants_1.WASI_ESUCCESS; + }), + fd_filestat_set_size: wrap((fd, stSize) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE); + fs.ftruncateSync(stats.real, Number(stSize)); + return constants_1.WASI_ESUCCESS; + }), + fd_filestat_set_times: wrap((fd, stAtim, stMtim, fstflags) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_FILESTAT_SET_TIMES, + ); + const rstats = fs.fstatSync(stats.real); + let atim = rstats.atime; + let mtim = rstats.mtime; + const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)); + const atimflags = + constants_1.WASI_FILESTAT_SET_ATIM | + constants_1.WASI_FILESTAT_SET_ATIM_NOW; + if ((fstflags & atimflags) === atimflags) { + return constants_1.WASI_EINVAL; } - - let fs = this.bindings.fs - let path = this.bindings.path - for (const [k, v] of Object.entries(preopens)) { - const real = fs.openSync(v, fs.constants.O_RDONLY) - const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1 - this.FD_MAP.set(newfd, { - real, - filetype: constants_1.WASI_FILETYPE_DIRECTORY, - // offset: BigInt(0), - rights: { - base: constants_1.RIGHTS_DIRECTORY_BASE, - inheriting: constants_1.RIGHTS_DIRECTORY_INHERITING, - }, - fakePath: k, - path: v, - }) + const mtimflags = + constants_1.WASI_FILESTAT_SET_MTIM | + constants_1.WASI_FILESTAT_SET_MTIM_NOW; + if ((fstflags & mtimflags) === mtimflags) { + return constants_1.WASI_EINVAL; } - const getiovs = (iovs, iovsLen) => { - // iovs* -> [iov, iov, ...] - // __wasi_ciovec_t { - // void* buf, - // size_t buf_len, - // } - this.refreshMemory() - const buffers = Array.from({ length: iovsLen }, (_, i) => { - const ptr = iovs + i * 8 - const buf = this.view.getUint32(ptr, true) - const bufLen = this.view.getUint32(ptr + 4, true) - return new Uint8Array(this.memory.buffer, buf, bufLen) - }) - return buffers + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === + constants_1.WASI_FILESTAT_SET_ATIM + ) { + atim = nsToMs(stAtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === + constants_1.WASI_FILESTAT_SET_ATIM_NOW + ) { + atim = n; } - const CHECK_FD = (fd, rights) => { - const stats = stat(this, fd) - // console.log(`CHECK_FD: stats.real: ${stats.real}, stats.path:`, stats.path); - // console.log('fd_check', fd, rights, stats) - if ( - rights !== bigint_1.BigIntPolyfill(0) && - (stats.rights.base & rights) === bigint_1.BigIntPolyfill(0) - ) { - throw new WASIError(constants_1.WASI_EPERM) - } - return stats + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === + constants_1.WASI_FILESTAT_SET_MTIM + ) { + mtim = nsToMs(stMtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === + constants_1.WASI_FILESTAT_SET_MTIM_NOW + ) { + mtim = n; + } + fs.futimesSync(stats.real, new Date(atim), new Date(mtim)); + return constants_1.WASI_ESUCCESS; + }), + fd_prestat_get: wrap((fd, bufPtr) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + if (!stats.path) { + return constants_1.WASI_EINVAL; } - const CPUTIME_START = bindings.hrtime() - const now = (clockId) => { - switch (clockId) { - case constants_1.WASI_CLOCK_MONOTONIC: - return bindings.hrtime() - case constants_1.WASI_CLOCK_REALTIME: - return msToNs(Date.now()) - case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: - case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: - // return bindings.hrtime(CPUTIME_START) - return bindings.hrtime() - CPUTIME_START - default: - return null + this.refreshMemory(); + this.view.setUint8(bufPtr, constants_1.WASI_PREOPENTYPE_DIR); + this.view.setUint32( + bufPtr + 4, + buffer_1.default.byteLength(stats.fakePath), + true, + ); + return constants_1.WASI_ESUCCESS; + }), + fd_prestat_dir_name: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + buffer_1.default + .from(this.memory.buffer) + .write(stats.fakePath, pathPtr, pathLen, "utf8"); + return constants_1.WASI_ESUCCESS; + }), + fd_pwrite: wrap((fd, iovs, iovsLen, offset, nwritten) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_WRITE | constants_1.WASI_RIGHT_FD_SEEK, + ); + let written = 0; + getiovs(iovs, iovsLen).forEach((iov) => { + let w = 0; + while (w < iov.byteLength) { + w += fs.writeSync( + stats.real, + iov, + w, + iov.byteLength - w, + Number(offset) + written + w, + ); + } + written += w; + }); + this.view.setUint32(nwritten, written, true); + return constants_1.WASI_ESUCCESS; + }), + fd_write: wrap((fd, iovs, iovsLen, nwritten) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE); + let written = 0; + getiovs(iovs, iovsLen).forEach((iov) => { + let w = 0; + while (w < iov.byteLength) { + const i = fs.writeSync( + stats.real, + iov, + w, + iov.byteLength - w, + stats.offset !== undefined ? Number(stats.offset) : null, + ); + if (stats.offset !== undefined) + stats.offset += bigint_1.BigIntPolyfill(i); + w += i; + } + written += w; + }); + this.view.setUint32(nwritten, written, true); + return constants_1.WASI_ESUCCESS; + }), + fd_pread: wrap((fd, iovs, iovsLen, offset, nread) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_FD_READ | constants_1.WASI_RIGHT_FD_SEEK, + ); + let read = 0; + outer: for (const iov of getiovs(iovs, iovsLen)) { + let r = 0; + while (r < iov.byteLength) { + const length = iov.byteLength - r; + const rr = fs.readSync( + stats.real, + iov, + r, + iov.byteLength - r, + Number(offset) + read + r, + ); + r += rr; + read += rr; + // If we don't read anything, or we receive less than requested + if (rr === 0 || rr < length) { + break outer; } + } + read += r; } - this.wasiImport = { - args_get: (argv, argvBuf) => { - this.refreshMemory() - let coffset = argv - let offset = argvBuf - args.forEach((a) => { - this.view.setUint32(coffset, offset, true) - coffset += 4 - offset += buffer_1.default.from(this.memory.buffer).write(`${a}\0`, offset) - }) - return constants_1.WASI_ESUCCESS - }, - args_sizes_get: (argc, argvBufSize) => { - this.refreshMemory() - this.view.setUint32(argc, args.length, true) - const size = args.reduce((acc, a) => acc + buffer_1.default.byteLength(a) + 1, 0) - this.view.setUint32(argvBufSize, size, true) - return constants_1.WASI_ESUCCESS - }, - environ_get: (environ, environBuf) => { - this.refreshMemory() - let coffset = environ - let offset = environBuf - Object.entries(env).forEach(([key, value]) => { - this.view.setUint32(coffset, offset, true) - coffset += 4 - offset += buffer_1.default - .from(this.memory.buffer) - .write(`${key}=${value}\0`, offset) - }) - return constants_1.WASI_ESUCCESS - }, - environ_sizes_get: (environCount, environBufSize) => { - this.refreshMemory() - const envProcessed = Object.entries(env).map(([key, value]) => `${key}=${value}\0`) - const size = envProcessed.reduce( - (acc, e) => acc + buffer_1.default.byteLength(e), - 0, - ) - this.view.setUint32(environCount, envProcessed.length, true) - this.view.setUint32(environBufSize, size, true) - return constants_1.WASI_ESUCCESS - }, - clock_res_get: (clockId, resolution) => { - let res - switch (clockId) { - case constants_1.WASI_CLOCK_MONOTONIC: - case constants_1.WASI_CLOCK_PROCESS_CPUTIME_ID: - case constants_1.WASI_CLOCK_THREAD_CPUTIME_ID: { - res = bigint_1.BigIntPolyfill(1) - break - } - case constants_1.WASI_CLOCK_REALTIME: { - res = bigint_1.BigIntPolyfill(1000) - break - } - } - this.view.setBigUint64(resolution, res) - return constants_1.WASI_ESUCCESS - }, - clock_time_get: (clockId, precision, time) => { - this.refreshMemory() - const n = now(clockId) - if (n === null) { - return constants_1.WASI_EINVAL - } - this.view.setBigUint64(time, bigint_1.BigIntPolyfill(n), true) - return constants_1.WASI_ESUCCESS - }, - fd_advise: wrap((fd, offset, len, advice) => { - CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ADVISE) - return constants_1.WASI_ENOSYS - }), - fd_allocate: wrap((fd, offset, len) => { - CHECK_FD(fd, constants_1.WASI_RIGHT_FD_ALLOCATE) - return constants_1.WASI_ENOSYS - }), - fd_close: wrap((fd) => { - const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) - fs.closeSync(stats.real) - this.FD_MAP.delete(fd) - return constants_1.WASI_ESUCCESS - }), - fd_datasync: wrap((fd) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_DATASYNC) - fs.fdatasyncSync(stats.real) - return constants_1.WASI_ESUCCESS - }), - fd_fdstat_get: wrap((fd, bufPtr) => { - const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) - this.refreshMemory() - this.view.setUint8(bufPtr, stats.filetype) // FILETYPE u8 - this.view.setUint16(bufPtr + 2, 0, true) // FDFLAG u16 - this.view.setUint16(bufPtr + 4, 0, true) // FDFLAG u16 - this.view.setBigUint64(bufPtr + 8, bigint_1.BigIntPolyfill(stats.rights.base), true) // u64 - this.view.setBigUint64( - bufPtr + 8 + 8, - bigint_1.BigIntPolyfill(stats.rights.inheriting), - true, - ) // u64 - return constants_1.WASI_ESUCCESS - }), - fd_fdstat_set_flags: wrap((fd, flags) => { - CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FDSTAT_SET_FLAGS) - return constants_1.WASI_ENOSYS - }), - fd_fdstat_set_rights: wrap((fd, fsRightsBase, fsRightsInheriting) => { - const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) - const nrb = stats.rights.base | fsRightsBase - if (nrb > stats.rights.base) { - return constants_1.WASI_EPERM - } - const nri = stats.rights.inheriting | fsRightsInheriting - if (nri > stats.rights.inheriting) { - return constants_1.WASI_EPERM - } - stats.rights.base = fsRightsBase - stats.rights.inheriting = fsRightsInheriting - return constants_1.WASI_ESUCCESS - }), - fd_filestat_get: wrap((fd, bufPtr) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_GET) - const rstats = fs.fstatSync(stats.real) - this.refreshMemory() - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.dev), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true) - bufPtr += 8 - this.view.setUint8(bufPtr, stats.filetype) - bufPtr += 8 - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.nlink), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.size), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true) - return constants_1.WASI_ESUCCESS - }), - fd_filestat_set_size: wrap((fd, stSize) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE) - fs.ftruncateSync(stats.real, Number(stSize)) - return constants_1.WASI_ESUCCESS - }), - fd_filestat_set_times: wrap((fd, stAtim, stMtim, fstflags) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_FILESTAT_SET_TIMES) - const rstats = fs.fstatSync(stats.real) - let atim = rstats.atime - let mtim = rstats.mtime - const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)) - const atimflags = - constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW - if ((fstflags & atimflags) === atimflags) { - return constants_1.WASI_EINVAL - } - const mtimflags = - constants_1.WASI_FILESTAT_SET_MTIM | constants_1.WASI_FILESTAT_SET_MTIM_NOW - if ((fstflags & mtimflags) === mtimflags) { - return constants_1.WASI_EINVAL - } - if ( - (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === - constants_1.WASI_FILESTAT_SET_ATIM - ) { - atim = nsToMs(stAtim) - } else if ( - (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === - constants_1.WASI_FILESTAT_SET_ATIM_NOW - ) { - atim = n - } - if ( - (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === - constants_1.WASI_FILESTAT_SET_MTIM - ) { - mtim = nsToMs(stMtim) - } else if ( - (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === - constants_1.WASI_FILESTAT_SET_MTIM_NOW - ) { - mtim = n - } - fs.futimesSync(stats.real, new Date(atim), new Date(mtim)) - return constants_1.WASI_ESUCCESS - }), - fd_prestat_get: wrap((fd, bufPtr) => { - const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - this.view.setUint8(bufPtr, constants_1.WASI_PREOPENTYPE_DIR) - this.view.setUint32(bufPtr + 4, buffer_1.default.byteLength(stats.fakePath), true) - return constants_1.WASI_ESUCCESS - }), - fd_prestat_dir_name: wrap((fd, pathPtr, pathLen) => { - const stats = CHECK_FD(fd, bigint_1.BigIntPolyfill(0)) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - buffer_1.default - .from(this.memory.buffer) - .write(stats.fakePath, pathPtr, pathLen, 'utf8') - return constants_1.WASI_ESUCCESS - }), - fd_pwrite: wrap((fd, iovs, iovsLen, offset, nwritten) => { - const stats = CHECK_FD( - fd, - constants_1.WASI_RIGHT_FD_WRITE | constants_1.WASI_RIGHT_FD_SEEK, - ) - let written = 0 - getiovs(iovs, iovsLen).forEach((iov) => { - let w = 0 - while (w < iov.byteLength) { - w += fs.writeSync( - stats.real, - iov, - w, - iov.byteLength - w, - Number(offset) + written + w, - ) - } - written += w - }) - this.view.setUint32(nwritten, written, true) - return constants_1.WASI_ESUCCESS - }), - fd_write: wrap((fd, iovs, iovsLen, nwritten) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_WRITE) - let written = 0 - getiovs(iovs, iovsLen).forEach((iov) => { - let w = 0 - while (w < iov.byteLength) { - const i = fs.writeSync( - stats.real, - iov, - w, - iov.byteLength - w, - stats.offset !== undefined ? Number(stats.offset) : null, - ) - if (stats.offset !== undefined) stats.offset += bigint_1.BigIntPolyfill(i) - w += i - } - written += w - }) - this.view.setUint32(nwritten, written, true) - return constants_1.WASI_ESUCCESS - }), - fd_pread: wrap((fd, iovs, iovsLen, offset, nread) => { - const stats = CHECK_FD( - fd, - constants_1.WASI_RIGHT_FD_READ | constants_1.WASI_RIGHT_FD_SEEK, - ) - let read = 0 - outer: for (const iov of getiovs(iovs, iovsLen)) { - let r = 0 - while (r < iov.byteLength) { - const length = iov.byteLength - r - const rr = fs.readSync( - stats.real, - iov, - r, - iov.byteLength - r, - Number(offset) + read + r, - ) - r += rr - read += rr - // If we don't read anything, or we receive less than requested - if (rr === 0 || rr < length) { - break outer - } - } - read += r - } - this.view.setUint32(nread, read, true) - return constants_1.WASI_ESUCCESS - }), - fd_read: wrap((fd, iovs, iovsLen, nread) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ) - const IS_STDIN = stats.real === 0 - let read = 0 - outer: for (const iov of getiovs(iovs, iovsLen)) { - let r = 0 - while (r < iov.byteLength) { - let length = iov.byteLength - r - let position = - IS_STDIN || stats.offset === undefined ? null : Number(stats.offset) - let rr = fs.readSync( - stats.real, // fd - iov, // buffer - r, // offset - length, // length - position, // position - ) - if (!IS_STDIN) { - stats.offset = - (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + - bigint_1.BigIntPolyfill(rr) - } - r += rr - read += rr - // If we don't read anything, or we receive less than requested - if (rr === 0 || rr < length) { - break outer - } - } - } - // We should not modify the offset of stdin - this.view.setUint32(nread, read, true) - return constants_1.WASI_ESUCCESS - }), - fd_readdir: wrap((fd, bufPtr, bufLen, cookie, bufusedPtr) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READDIR) - this.refreshMemory() - const entries = fs.readdirSync(stats.path, { withFileTypes: true }) - const startPtr = bufPtr - for (let i = Number(cookie); i < entries.length; i += 1) { - const entry = entries[i] - let nameLength = buffer_1.default.byteLength(entry.name) - if (bufPtr - startPtr > bufLen) { - break - } - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(i + 1), true) - bufPtr += 8 - if (bufPtr - startPtr > bufLen) { - break - } - const rstats = fs.statSync(path.resolve(stats.path, entry.name)) - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true) - bufPtr += 8 - if (bufPtr - startPtr > bufLen) { - break - } - this.view.setUint32(bufPtr, nameLength, true) - bufPtr += 4 - if (bufPtr - startPtr > bufLen) { - break - } - let filetype - switch (true) { - case rstats.isBlockDevice(): - filetype = constants_1.WASI_FILETYPE_BLOCK_DEVICE - break - case rstats.isCharacterDevice(): - filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE - break - case rstats.isDirectory(): - filetype = constants_1.WASI_FILETYPE_DIRECTORY - break - case rstats.isFIFO(): - filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM - break - case rstats.isFile(): - filetype = constants_1.WASI_FILETYPE_REGULAR_FILE - break - case rstats.isSocket(): - filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM - break - case rstats.isSymbolicLink(): - filetype = constants_1.WASI_FILETYPE_SYMBOLIC_LINK - break - default: - filetype = constants_1.WASI_FILETYPE_UNKNOWN - break - } - this.view.setUint8(bufPtr, filetype) - bufPtr += 1 - bufPtr += 3 // padding - if (bufPtr + nameLength >= startPtr + bufLen) { - // It doesn't fit in the buffer - break - } - let memory_buffer = buffer_1.default.from(this.memory.buffer) - memory_buffer.write(entry.name, bufPtr) - bufPtr += nameLength - } - const bufused = bufPtr - startPtr - this.view.setUint32(bufusedPtr, Math.min(bufused, bufLen), true) - return constants_1.WASI_ESUCCESS - }), - fd_renumber: wrap((from, to) => { - CHECK_FD(from, bigint_1.BigIntPolyfill(0)) - CHECK_FD(to, bigint_1.BigIntPolyfill(0)) - fs.closeSync(this.FD_MAP.get(from).real) - this.FD_MAP.set(from, this.FD_MAP.get(to)) - this.FD_MAP.delete(to) - return constants_1.WASI_ESUCCESS - }), - fd_seek: wrap((fd, offset, whence, newOffsetPtr) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SEEK) - // console.log('fd_seek', fd, offset, whence, newOffsetPtr, '=', stats.offset); - this.refreshMemory() - switch (whence) { - case constants_1.WASI_WHENCE_CUR: - stats.offset = - (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + - bigint_1.BigIntPolyfill(offset) - break - case constants_1.WASI_WHENCE_END: - const { size } = fs.fstatSync(stats.real) - stats.offset = - bigint_1.BigIntPolyfill(size) + bigint_1.BigIntPolyfill(offset) - break - case constants_1.WASI_WHENCE_SET: - stats.offset = bigint_1.BigIntPolyfill(offset) - break - } - this.view.setBigUint64(newOffsetPtr, stats.offset, true) - return constants_1.WASI_ESUCCESS - }), - fd_tell: wrap((fd, offsetPtr) => { - // console.log('fd_tell') - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_TELL) - this.refreshMemory() - if (!stats.offset) { - stats.offset = bigint_1.BigIntPolyfill(0) - } - this.view.setBigUint64(offsetPtr, stats.offset, true) - return constants_1.WASI_ESUCCESS - }), - fd_sync: wrap((fd) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SYNC) - fs.fsyncSync(stats.real) - return constants_1.WASI_ESUCCESS - }), - path_create_directory: wrap((fd, pathPtr, pathLen) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_CREATE_DIRECTORY) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() - fs.mkdirSync(path.resolve(stats.path, p)) - return constants_1.WASI_ESUCCESS - }), - path_filestat_get: wrap((fd, flags, pathPtr, pathLen, bufPtr) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_GET) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() - const rstats = fs.statSync(path.resolve(stats.path, p)) - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.dev), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.ino), true) - bufPtr += 8 - this.view.setUint8( - bufPtr, - translateFileAttributes(this, undefined, rstats).filetype, - ) - bufPtr += 8 - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.nlink), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(rstats.size), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true) - bufPtr += 8 - this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true) - return constants_1.WASI_ESUCCESS - }), - path_filestat_set_times: wrap( - (fd, dirflags, pathPtr, pathLen, stAtim, stMtim, fstflags) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_SET_TIMES) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const rstats = fs.fstatSync(stats.real) - let atim = rstats.atime - let mtim = rstats.mtime - const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)) - const atimflags = - constants_1.WASI_FILESTAT_SET_ATIM | constants_1.WASI_FILESTAT_SET_ATIM_NOW - if ((fstflags & atimflags) === atimflags) { - return constants_1.WASI_EINVAL - } - const mtimflags = - constants_1.WASI_FILESTAT_SET_MTIM | constants_1.WASI_FILESTAT_SET_MTIM_NOW - if ((fstflags & mtimflags) === mtimflags) { - return constants_1.WASI_EINVAL - } - if ( - (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === - constants_1.WASI_FILESTAT_SET_ATIM - ) { - atim = nsToMs(stAtim) - } else if ( - (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === - constants_1.WASI_FILESTAT_SET_ATIM_NOW - ) { - atim = n - } - if ( - (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === - constants_1.WASI_FILESTAT_SET_MTIM - ) { - mtim = nsToMs(stMtim) - } else if ( - (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === - constants_1.WASI_FILESTAT_SET_MTIM_NOW - ) { - mtim = n - } - const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() - fs.utimesSync(path.resolve(stats.path, p), new Date(atim), new Date(mtim)) - return constants_1.WASI_ESUCCESS - }, - ), - path_link: wrap((oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen) => { - const ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_LINK_SOURCE) - const nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_LINK_TARGET) - if (!ostats.path || !nstats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString() - const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString() - fs.linkSync(path.resolve(ostats.path, op), path.resolve(nstats.path, np)) - return constants_1.WASI_ESUCCESS - }), - path_open: wrap( - ( - dirfd, - dirflags, - pathPtr, - pathLen, - oflags, - fsRightsBase, - fsRightsInheriting, - fsFlags, - fd, - ) => { - const stats = CHECK_FD(dirfd, constants_1.WASI_RIGHT_PATH_OPEN) - fsRightsBase = bigint_1.BigIntPolyfill(fsRightsBase) - fsRightsInheriting = bigint_1.BigIntPolyfill(fsRightsInheriting) - const read = - (fsRightsBase & - (constants_1.WASI_RIGHT_FD_READ | - constants_1.WASI_RIGHT_FD_READDIR)) !== - bigint_1.BigIntPolyfill(0) - const write = - (fsRightsBase & - (constants_1.WASI_RIGHT_FD_DATASYNC | - constants_1.WASI_RIGHT_FD_WRITE | - constants_1.WASI_RIGHT_FD_ALLOCATE | - constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !== - bigint_1.BigIntPolyfill(0) - let noflags - if (write && read) { - noflags = fs.constants.O_RDWR - } else if (read) { - noflags = fs.constants.O_RDONLY - } else if (write) { - noflags = fs.constants.O_WRONLY - } - // fsRightsBase is needed here but perhaps we should do it in neededInheriting - let neededBase = fsRightsBase | constants_1.WASI_RIGHT_PATH_OPEN - let neededInheriting = fsRightsBase | fsRightsInheriting - if ((oflags & constants_1.WASI_O_CREAT) !== 0) { - noflags |= fs.constants.O_CREAT - neededBase |= constants_1.WASI_RIGHT_PATH_CREATE_FILE - } - if ((oflags & constants_1.WASI_O_DIRECTORY) !== 0) { - noflags |= fs.constants.O_DIRECTORY - } - if ((oflags & constants_1.WASI_O_EXCL) !== 0) { - noflags |= fs.constants.O_EXCL - } - if ((oflags & constants_1.WASI_O_TRUNC) !== 0) { - noflags |= fs.constants.O_TRUNC - neededBase |= constants_1.WASI_RIGHT_PATH_FILESTAT_SET_SIZE - } - // Convert file descriptor flags. - if ((fsFlags & constants_1.WASI_FDFLAG_APPEND) !== 0) { - noflags |= fs.constants.O_APPEND - } - if ((fsFlags & constants_1.WASI_FDFLAG_DSYNC) !== 0) { - if (fs.constants.O_DSYNC) { - noflags |= fs.constants.O_DSYNC - } else { - noflags |= fs.constants.O_SYNC - } - neededInheriting |= constants_1.WASI_RIGHT_FD_DATASYNC - } - if ((fsFlags & constants_1.WASI_FDFLAG_NONBLOCK) !== 0) { - noflags |= fs.constants.O_NONBLOCK - } - if ((fsFlags & constants_1.WASI_FDFLAG_RSYNC) !== 0) { - if (fs.constants.O_RSYNC) { - noflags |= fs.constants.O_RSYNC - } else { - noflags |= fs.constants.O_SYNC - } - neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC - } - if ((fsFlags & constants_1.WASI_FDFLAG_SYNC) !== 0) { - noflags |= fs.constants.O_SYNC - neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC - } - if (write && (noflags & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0) { - neededInheriting |= constants_1.WASI_RIGHT_FD_SEEK - } - this.refreshMemory() - const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() - const fullUnresolved = path.resolve(stats.path, p) - if (path.relative(stats.path, fullUnresolved).startsWith('..')) { - return constants_1.WASI_ENOTCAPABLE - } - let full - try { - full = fs.realpathSync(fullUnresolved) - if (path.relative(stats.path, full).startsWith('..')) { - return constants_1.WASI_ENOTCAPABLE - } - } catch (e) { - if (e.code === 'ENOENT') { - full = fullUnresolved - } else { - throw e - } - } - /* check if the file is a directory (unless opening for write, - * in which case the file may not exist and should be created) */ - let isDirectory - try { - isDirectory = fs.statSync(full).isDirectory() - } catch (e) {} - let realfd - if (!write && isDirectory) { - realfd = fs.openSync(full, fs.constants.O_RDONLY) - } else { - realfd = fs.openSync(full, noflags) - } - const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1 - this.FD_MAP.set(newfd, { - real: realfd, - filetype: undefined, - offset: BigInt(0), - rights: { - base: neededBase, - inheriting: neededInheriting, - }, - path: full, - }) - stat(this, newfd) - this.view.setUint32(fd, newfd, true) - return constants_1.WASI_ESUCCESS - }, - ), - path_readlink: wrap((fd, pathPtr, pathLen, buf, bufLen, bufused) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_READLINK) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() - const full = path.resolve(stats.path, p) - const r = fs.readlinkSync(full) - const used = buffer_1.default.from(this.memory.buffer).write(r, buf, bufLen) - this.view.setUint32(bufused, used, true) - return constants_1.WASI_ESUCCESS - }), - path_remove_directory: wrap((fd, pathPtr, pathLen) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_REMOVE_DIRECTORY) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() - fs.rmdirSync(path.resolve(stats.path, p)) - return constants_1.WASI_ESUCCESS - }), - path_rename: wrap((oldFd, oldPath, oldPathLen, newFd, newPath, newPathLen) => { - const ostats = CHECK_FD(oldFd, constants_1.WASI_RIGHT_PATH_RENAME_SOURCE) - const nstats = CHECK_FD(newFd, constants_1.WASI_RIGHT_PATH_RENAME_TARGET) - if (!ostats.path || !nstats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString() - const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString() - fs.renameSync(path.resolve(ostats.path, op), path.resolve(nstats.path, np)) - return constants_1.WASI_ESUCCESS - }), - path_symlink: wrap((oldPath, oldPathLen, fd, newPath, newPathLen) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_SYMLINK) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const op = buffer_1.default.from(this.memory.buffer, oldPath, oldPathLen).toString() - const np = buffer_1.default.from(this.memory.buffer, newPath, newPathLen).toString() - fs.symlinkSync(op, path.resolve(stats.path, np)) - return constants_1.WASI_ESUCCESS - }), - path_unlink_file: wrap((fd, pathPtr, pathLen) => { - const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_UNLINK_FILE) - if (!stats.path) { - return constants_1.WASI_EINVAL - } - this.refreshMemory() - const p = buffer_1.default.from(this.memory.buffer, pathPtr, pathLen).toString() - fs.unlinkSync(path.resolve(stats.path, p)) - return constants_1.WASI_ESUCCESS - }), - poll_oneoff: (sin, sout, nsubscriptions, nevents) => { - let eventc = 0 - let waitEnd = 0 - this.refreshMemory() - for (let i = 0; i < nsubscriptions; i += 1) { - const userdata = this.view.getBigUint64(sin, true) - sin += 8 - const type = this.view.getUint8(sin) - sin += 1 - switch (type) { - case constants_1.WASI_EVENTTYPE_CLOCK: { - sin += 7 // padding - const identifier = this.view.getBigUint64(sin, true) - sin += 8 - const clockid = this.view.getUint32(sin, true) - sin += 4 - sin += 4 // padding - const timestamp = this.view.getBigUint64(sin, true) - sin += 8 - const precision = this.view.getBigUint64(sin, true) - sin += 8 - const subclockflags = this.view.getUint16(sin, true) - sin += 2 - sin += 6 // padding - const absolute = subclockflags === 1 - let e = constants_1.WASI_ESUCCESS - const n = bigint_1.BigIntPolyfill(now(clockid)) - if (n === null) { - e = constants_1.WASI_EINVAL - } else { - const end = absolute ? timestamp : n + timestamp - waitEnd = end > waitEnd ? end : waitEnd - } - this.view.setBigUint64(sout, userdata, true) - sout += 8 - this.view.setUint16(sout, e, true) // error - sout += 2 // pad offset 2 - this.view.setUint8(sout, constants_1.WASI_EVENTTYPE_CLOCK) - sout += 1 // pad offset 3 - sout += 5 // padding to 8 - eventc += 1 - break - } - case constants_1.WASI_EVENTTYPE_FD_READ: - case constants_1.WASI_EVENTTYPE_FD_WRITE: { - sin += 3 // padding - const fd = this.view.getUint32(sin, true) - sin += 4 - this.view.setBigUint64(sout, userdata, true) - sout += 8 - this.view.setUint16(sout, constants_1.WASI_ENOSYS, true) // error - sout += 2 // pad offset 2 - this.view.setUint8(sout, type) - sout += 1 // pad offset 3 - sout += 5 // padding to 8 - eventc += 1 - break - } - default: - return constants_1.WASI_EINVAL - } - } - this.view.setUint32(nevents, eventc, true) - while (bindings.hrtime() < waitEnd) { - // nothing - } - return constants_1.WASI_ESUCCESS - }, - proc_exit: (rval) => { - bindings.exit(rval) - return constants_1.WASI_ESUCCESS - }, - proc_raise: (sig) => { - if (!(sig in constants_1.SIGNAL_MAP)) { - return constants_1.WASI_EINVAL - } - bindings.kill(constants_1.SIGNAL_MAP[sig]) - return constants_1.WASI_ESUCCESS - }, - random_get: (bufPtr, bufLen) => { - this.refreshMemory() - bindings.randomFillSync(new Uint8Array(this.memory.buffer), bufPtr, bufLen) - return constants_1.WASI_ESUCCESS - }, - sched_yield() { - // Single threaded environment - // This is a no-op in JS - return constants_1.WASI_ESUCCESS - }, - sock_recv() { - return constants_1.WASI_ENOSYS - }, - sock_send() { - return constants_1.WASI_ENOSYS - }, - sock_shutdown() { - return constants_1.WASI_ENOSYS - }, + this.view.setUint32(nread, read, true); + return constants_1.WASI_ESUCCESS; + }), + fd_read: wrap((fd, iovs, iovsLen, nread) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READ); + const IS_STDIN = stats.real === 0; + let read = 0; + outer: for (const iov of getiovs(iovs, iovsLen)) { + let r = 0; + while (r < iov.byteLength) { + let length = iov.byteLength - r; + let position = + IS_STDIN || stats.offset === undefined + ? null + : Number(stats.offset); + let rr = fs.readSync( + stats.real, // fd + iov, // buffer + r, // offset + length, // length + position, // position + ); + if (!IS_STDIN) { + stats.offset = + (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + + bigint_1.BigIntPolyfill(rr); + } + r += rr; + read += rr; + // If we don't read anything, or we receive less than requested + if (rr === 0 || rr < length) { + break outer; + } + } } - // Wrap each of the imports to show the calls in the console - if (wasiConfig.traceSyscalls) { - Object.keys(this.wasiImport).forEach((key) => { - const prevImport = this.wasiImport[key] - this.wasiImport[key] = function (...args) { - console.log(`WASI: wasiImport called: ${key} (${args})`) - try { - let result = prevImport(...args) - console.log(`WASI: => ${result}`) - return result - } catch (e) { - console.log(`Catched error: ${e}`) - throw e - } - } - }) + // We should not modify the offset of stdin + this.view.setUint32(nread, read, true); + return constants_1.WASI_ESUCCESS; + }), + fd_readdir: wrap((fd, bufPtr, bufLen, cookie, bufusedPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_READDIR); + this.refreshMemory(); + const entries = fs.readdirSync(stats.path, { withFileTypes: true }); + const startPtr = bufPtr; + for (let i = Number(cookie); i < entries.length; i += 1) { + const entry = entries[i]; + let nameLength = buffer_1.default.byteLength(entry.name); + if (bufPtr - startPtr > bufLen) { + break; + } + this.view.setBigUint64(bufPtr, bigint_1.BigIntPolyfill(i + 1), true); + bufPtr += 8; + if (bufPtr - startPtr > bufLen) { + break; + } + const rstats = fs.statSync(path.resolve(stats.path, entry.name)); + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.ino), + true, + ); + bufPtr += 8; + if (bufPtr - startPtr > bufLen) { + break; + } + this.view.setUint32(bufPtr, nameLength, true); + bufPtr += 4; + if (bufPtr - startPtr > bufLen) { + break; + } + let filetype; + switch (true) { + case rstats.isBlockDevice(): + filetype = constants_1.WASI_FILETYPE_BLOCK_DEVICE; + break; + case rstats.isCharacterDevice(): + filetype = constants_1.WASI_FILETYPE_CHARACTER_DEVICE; + break; + case rstats.isDirectory(): + filetype = constants_1.WASI_FILETYPE_DIRECTORY; + break; + case rstats.isFIFO(): + filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM; + break; + case rstats.isFile(): + filetype = constants_1.WASI_FILETYPE_REGULAR_FILE; + break; + case rstats.isSocket(): + filetype = constants_1.WASI_FILETYPE_SOCKET_STREAM; + break; + case rstats.isSymbolicLink(): + filetype = constants_1.WASI_FILETYPE_SYMBOLIC_LINK; + break; + default: + filetype = constants_1.WASI_FILETYPE_UNKNOWN; + break; + } + this.view.setUint8(bufPtr, filetype); + bufPtr += 1; + bufPtr += 3; // padding + if (bufPtr + nameLength >= startPtr + bufLen) { + // It doesn't fit in the buffer + break; + } + let memory_buffer = buffer_1.default.from(this.memory.buffer); + memory_buffer.write(entry.name, bufPtr); + bufPtr += nameLength; } - } - refreshMemory() { - // @ts-ignore - if (!this.view || this.view.buffer.byteLength === 0) { - this.view = new dataview_1.DataViewPolyfill(this.memory.buffer) + const bufused = bufPtr - startPtr; + this.view.setUint32(bufusedPtr, Math.min(bufused, bufLen), true); + return constants_1.WASI_ESUCCESS; + }), + fd_renumber: wrap((from, to) => { + CHECK_FD(from, bigint_1.BigIntPolyfill(0)); + CHECK_FD(to, bigint_1.BigIntPolyfill(0)); + fs.closeSync(this.FD_MAP.get(from).real); + this.FD_MAP.set(from, this.FD_MAP.get(to)); + this.FD_MAP.delete(to); + return constants_1.WASI_ESUCCESS; + }), + fd_seek: wrap((fd, offset, whence, newOffsetPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SEEK); + // console.log('fd_seek', fd, offset, whence, newOffsetPtr, '=', stats.offset); + this.refreshMemory(); + switch (whence) { + case constants_1.WASI_WHENCE_CUR: + stats.offset = + (stats.offset ? stats.offset : bigint_1.BigIntPolyfill(0)) + + bigint_1.BigIntPolyfill(offset); + break; + case constants_1.WASI_WHENCE_END: + const { size } = fs.fstatSync(stats.real); + stats.offset = + bigint_1.BigIntPolyfill(size) + bigint_1.BigIntPolyfill(offset); + break; + case constants_1.WASI_WHENCE_SET: + stats.offset = bigint_1.BigIntPolyfill(offset); + break; } - } - setMemory(memory) { - this.memory = memory - } - start(instance) { - const exports = instance.exports - if (exports === null || typeof exports !== 'object') { - throw new Error(`instance.exports must be an Object. Received ${exports}.`) + this.view.setBigUint64(newOffsetPtr, stats.offset, true); + return constants_1.WASI_ESUCCESS; + }), + fd_tell: wrap((fd, offsetPtr) => { + // console.log('fd_tell') + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_TELL); + this.refreshMemory(); + if (!stats.offset) { + stats.offset = bigint_1.BigIntPolyfill(0); } - const { memory } = exports - if (!(memory instanceof WebAssembly.Memory)) { - throw new Error( - `instance.exports.memory must be a WebAssembly.Memory. Recceived ${memory}.`, - ) + this.view.setBigUint64(offsetPtr, stats.offset, true); + return constants_1.WASI_ESUCCESS; + }), + fd_sync: wrap((fd) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_FD_SYNC); + fs.fsyncSync(stats.real); + return constants_1.WASI_ESUCCESS; + }), + path_create_directory: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_PATH_CREATE_DIRECTORY, + ); + if (!stats.path) { + return constants_1.WASI_EINVAL; } - this.setMemory(memory) - if (exports._start) { - exports._start() + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.mkdirSync(path.resolve(stats.path, p)); + return constants_1.WASI_ESUCCESS; + }), + path_filestat_get: wrap((fd, flags, pathPtr, pathLen, bufPtr) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_FILESTAT_GET); + if (!stats.path) { + return constants_1.WASI_EINVAL; } - } - getImportNamespace(module) { - let namespace = null - for (let imp of WebAssembly.Module.imports(module)) { - // We only check for the functions - if (imp.kind !== 'function') { - continue + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + const rstats = fs.statSync(path.resolve(stats.path, p)); + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.dev), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.ino), + true, + ); + bufPtr += 8; + this.view.setUint8( + bufPtr, + translateFileAttributes(this, undefined, rstats).filetype, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.nlink), + true, + ); + bufPtr += 8; + this.view.setBigUint64( + bufPtr, + bigint_1.BigIntPolyfill(rstats.size), + true, + ); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.atimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.mtimeMs), true); + bufPtr += 8; + this.view.setBigUint64(bufPtr, msToNs(rstats.ctimeMs), true); + return constants_1.WASI_ESUCCESS; + }), + path_filestat_set_times: wrap( + (fd, dirflags, pathPtr, pathLen, stAtim, stMtim, fstflags) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_PATH_FILESTAT_SET_TIMES, + ); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const rstats = fs.fstatSync(stats.real); + let atim = rstats.atime; + let mtim = rstats.mtime; + const n = nsToMs(now(constants_1.WASI_CLOCK_REALTIME)); + const atimflags = + constants_1.WASI_FILESTAT_SET_ATIM | + constants_1.WASI_FILESTAT_SET_ATIM_NOW; + if ((fstflags & atimflags) === atimflags) { + return constants_1.WASI_EINVAL; + } + const mtimflags = + constants_1.WASI_FILESTAT_SET_MTIM | + constants_1.WASI_FILESTAT_SET_MTIM_NOW; + if ((fstflags & mtimflags) === mtimflags) { + return constants_1.WASI_EINVAL; + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM) === + constants_1.WASI_FILESTAT_SET_ATIM + ) { + atim = nsToMs(stAtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_ATIM_NOW) === + constants_1.WASI_FILESTAT_SET_ATIM_NOW + ) { + atim = n; + } + if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM) === + constants_1.WASI_FILESTAT_SET_MTIM + ) { + mtim = nsToMs(stMtim); + } else if ( + (fstflags & constants_1.WASI_FILESTAT_SET_MTIM_NOW) === + constants_1.WASI_FILESTAT_SET_MTIM_NOW + ) { + mtim = n; + } + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.utimesSync( + path.resolve(stats.path, p), + new Date(atim), + new Date(mtim), + ); + return constants_1.WASI_ESUCCESS; + }, + ), + path_link: wrap( + (oldFd, oldFlags, oldPath, oldPathLen, newFd, newPath, newPathLen) => { + const ostats = CHECK_FD( + oldFd, + constants_1.WASI_RIGHT_PATH_LINK_SOURCE, + ); + const nstats = CHECK_FD( + newFd, + constants_1.WASI_RIGHT_PATH_LINK_TARGET, + ); + if (!ostats.path || !nstats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const op = buffer_1.default + .from(this.memory.buffer, oldPath, oldPathLen) + .toString(); + const np = buffer_1.default + .from(this.memory.buffer, newPath, newPathLen) + .toString(); + fs.linkSync( + path.resolve(ostats.path, op), + path.resolve(nstats.path, np), + ); + return constants_1.WASI_ESUCCESS; + }, + ), + path_open: wrap( + ( + dirfd, + dirflags, + pathPtr, + pathLen, + oflags, + fsRightsBase, + fsRightsInheriting, + fsFlags, + fd, + ) => { + const stats = CHECK_FD(dirfd, constants_1.WASI_RIGHT_PATH_OPEN); + fsRightsBase = bigint_1.BigIntPolyfill(fsRightsBase); + fsRightsInheriting = bigint_1.BigIntPolyfill(fsRightsInheriting); + const read = + (fsRightsBase & + (constants_1.WASI_RIGHT_FD_READ | + constants_1.WASI_RIGHT_FD_READDIR)) !== + bigint_1.BigIntPolyfill(0); + const write = + (fsRightsBase & + (constants_1.WASI_RIGHT_FD_DATASYNC | + constants_1.WASI_RIGHT_FD_WRITE | + constants_1.WASI_RIGHT_FD_ALLOCATE | + constants_1.WASI_RIGHT_FD_FILESTAT_SET_SIZE)) !== + bigint_1.BigIntPolyfill(0); + let noflags; + if (write && read) { + noflags = fs.constants.O_RDWR; + } else if (read) { + noflags = fs.constants.O_RDONLY; + } else if (write) { + noflags = fs.constants.O_WRONLY; + } + // fsRightsBase is needed here but perhaps we should do it in neededInheriting + let neededBase = fsRightsBase | constants_1.WASI_RIGHT_PATH_OPEN; + let neededInheriting = fsRightsBase | fsRightsInheriting; + if ((oflags & constants_1.WASI_O_CREAT) !== 0) { + noflags |= fs.constants.O_CREAT; + neededBase |= constants_1.WASI_RIGHT_PATH_CREATE_FILE; + } + if ((oflags & constants_1.WASI_O_DIRECTORY) !== 0) { + noflags |= fs.constants.O_DIRECTORY; + } + if ((oflags & constants_1.WASI_O_EXCL) !== 0) { + noflags |= fs.constants.O_EXCL; + } + if ((oflags & constants_1.WASI_O_TRUNC) !== 0) { + noflags |= fs.constants.O_TRUNC; + neededBase |= constants_1.WASI_RIGHT_PATH_FILESTAT_SET_SIZE; + } + // Convert file descriptor flags. + if ((fsFlags & constants_1.WASI_FDFLAG_APPEND) !== 0) { + noflags |= fs.constants.O_APPEND; + } + if ((fsFlags & constants_1.WASI_FDFLAG_DSYNC) !== 0) { + if (fs.constants.O_DSYNC) { + noflags |= fs.constants.O_DSYNC; + } else { + noflags |= fs.constants.O_SYNC; + } + neededInheriting |= constants_1.WASI_RIGHT_FD_DATASYNC; + } + if ((fsFlags & constants_1.WASI_FDFLAG_NONBLOCK) !== 0) { + noflags |= fs.constants.O_NONBLOCK; + } + if ((fsFlags & constants_1.WASI_FDFLAG_RSYNC) !== 0) { + if (fs.constants.O_RSYNC) { + noflags |= fs.constants.O_RSYNC; + } else { + noflags |= fs.constants.O_SYNC; } - // We allow functions in other namespaces other than wasi - if (!imp.module.startsWith('wasi_')) { - continue + neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC; + } + if ((fsFlags & constants_1.WASI_FDFLAG_SYNC) !== 0) { + noflags |= fs.constants.O_SYNC; + neededInheriting |= constants_1.WASI_RIGHT_FD_SYNC; + } + if ( + write && + (noflags & (fs.constants.O_APPEND | fs.constants.O_TRUNC)) === 0 + ) { + neededInheriting |= constants_1.WASI_RIGHT_FD_SEEK; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + const fullUnresolved = path.resolve(stats.path, p); + if (path.relative(stats.path, fullUnresolved).startsWith("..")) { + return constants_1.WASI_ENOTCAPABLE; + } + let full; + try { + full = fs.realpathSync(fullUnresolved); + if (path.relative(stats.path, full).startsWith("..")) { + return constants_1.WASI_ENOTCAPABLE; } - if (!namespace) { - namespace = imp.module + } catch (e) { + if (e.code === "ENOENT") { + full = fullUnresolved; } else { - if (namespace !== imp.module) { - throw new Error('Multiple namespaces detected.') - } + throw e; } + } + /* check if the file is a directory (unless opening for write, + * in which case the file may not exist and should be created) */ + let isDirectory; + try { + isDirectory = fs.statSync(full).isDirectory(); + } catch (e) {} + let realfd; + if (!write && isDirectory) { + realfd = fs.openSync(full, fs.constants.O_RDONLY); + } else { + realfd = fs.openSync(full, noflags); + } + const newfd = [...this.FD_MAP.keys()].reverse()[0] + 1; + this.FD_MAP.set(newfd, { + real: realfd, + filetype: undefined, + offset: BigInt(0), + rights: { + base: neededBase, + inheriting: neededInheriting, + }, + path: full, + }); + stat(this, newfd); + this.view.setUint32(fd, newfd, true); + return constants_1.WASI_ESUCCESS; + }, + ), + path_readlink: wrap((fd, pathPtr, pathLen, buf, bufLen, bufused) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_READLINK); + if (!stats.path) { + return constants_1.WASI_EINVAL; } - return namespace - } - getImports(module) { - let namespace = this.getImportNamespace(module) - switch (namespace) { - case 'wasi_unstable': - return { - wasi_unstable: this.wasiImport, - } - case 'wasi_snapshot_preview1': - return { - wasi_snapshot_preview1: this.wasiImport, - } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + const full = path.resolve(stats.path, p); + const r = fs.readlinkSync(full); + const used = buffer_1.default + .from(this.memory.buffer) + .write(r, buf, bufLen); + this.view.setUint32(bufused, used, true); + return constants_1.WASI_ESUCCESS; + }), + path_remove_directory: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD( + fd, + constants_1.WASI_RIGHT_PATH_REMOVE_DIRECTORY, + ); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.rmdirSync(path.resolve(stats.path, p)); + return constants_1.WASI_ESUCCESS; + }), + path_rename: wrap( + (oldFd, oldPath, oldPathLen, newFd, newPath, newPathLen) => { + const ostats = CHECK_FD( + oldFd, + constants_1.WASI_RIGHT_PATH_RENAME_SOURCE, + ); + const nstats = CHECK_FD( + newFd, + constants_1.WASI_RIGHT_PATH_RENAME_TARGET, + ); + if (!ostats.path || !nstats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const op = buffer_1.default + .from(this.memory.buffer, oldPath, oldPathLen) + .toString(); + const np = buffer_1.default + .from(this.memory.buffer, newPath, newPathLen) + .toString(); + fs.renameSync( + path.resolve(ostats.path, op), + path.resolve(nstats.path, np), + ); + return constants_1.WASI_ESUCCESS; + }, + ), + path_symlink: wrap((oldPath, oldPathLen, fd, newPath, newPathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_SYMLINK); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const op = buffer_1.default + .from(this.memory.buffer, oldPath, oldPathLen) + .toString(); + const np = buffer_1.default + .from(this.memory.buffer, newPath, newPathLen) + .toString(); + fs.symlinkSync(op, path.resolve(stats.path, np)); + return constants_1.WASI_ESUCCESS; + }), + path_unlink_file: wrap((fd, pathPtr, pathLen) => { + const stats = CHECK_FD(fd, constants_1.WASI_RIGHT_PATH_UNLINK_FILE); + if (!stats.path) { + return constants_1.WASI_EINVAL; + } + this.refreshMemory(); + const p = buffer_1.default + .from(this.memory.buffer, pathPtr, pathLen) + .toString(); + fs.unlinkSync(path.resolve(stats.path, p)); + return constants_1.WASI_ESUCCESS; + }), + poll_oneoff: (sin, sout, nsubscriptions, nevents) => { + let eventc = 0; + let waitEnd = 0; + this.refreshMemory(); + for (let i = 0; i < nsubscriptions; i += 1) { + const userdata = this.view.getBigUint64(sin, true); + sin += 8; + const type = this.view.getUint8(sin); + sin += 1; + switch (type) { + case constants_1.WASI_EVENTTYPE_CLOCK: { + sin += 7; // padding + const identifier = this.view.getBigUint64(sin, true); + sin += 8; + const clockid = this.view.getUint32(sin, true); + sin += 4; + sin += 4; // padding + const timestamp = this.view.getBigUint64(sin, true); + sin += 8; + const precision = this.view.getBigUint64(sin, true); + sin += 8; + const subclockflags = this.view.getUint16(sin, true); + sin += 2; + sin += 6; // padding + const absolute = subclockflags === 1; + let e = constants_1.WASI_ESUCCESS; + const n = bigint_1.BigIntPolyfill(now(clockid)); + if (n === null) { + e = constants_1.WASI_EINVAL; + } else { + const end = absolute ? timestamp : n + timestamp; + waitEnd = end > waitEnd ? end : waitEnd; + } + this.view.setBigUint64(sout, userdata, true); + sout += 8; + this.view.setUint16(sout, e, true); // error + sout += 2; // pad offset 2 + this.view.setUint8(sout, constants_1.WASI_EVENTTYPE_CLOCK); + sout += 1; // pad offset 3 + sout += 5; // padding to 8 + eventc += 1; + break; + } + case constants_1.WASI_EVENTTYPE_FD_READ: + case constants_1.WASI_EVENTTYPE_FD_WRITE: { + sin += 3; // padding + const fd = this.view.getUint32(sin, true); + sin += 4; + this.view.setBigUint64(sout, userdata, true); + sout += 8; + this.view.setUint16(sout, constants_1.WASI_ENOSYS, true); // error + sout += 2; // pad offset 2 + this.view.setUint8(sout, type); + sout += 1; // pad offset 3 + sout += 5; // padding to 8 + eventc += 1; + break; + } default: - throw new Error("Can't detect a WASI namespace for the WebAssembly Module") + return constants_1.WASI_EINVAL; + } + } + this.view.setUint32(nevents, eventc, true); + while (bindings.hrtime() < waitEnd) { + // nothing } + return constants_1.WASI_ESUCCESS; + }, + proc_exit: (rval) => { + bindings.exit(rval); + return constants_1.WASI_ESUCCESS; + }, + proc_raise: (sig) => { + if (!(sig in constants_1.SIGNAL_MAP)) { + return constants_1.WASI_EINVAL; + } + bindings.kill(constants_1.SIGNAL_MAP[sig]); + return constants_1.WASI_ESUCCESS; + }, + random_get: (bufPtr, bufLen) => { + this.refreshMemory(); + bindings.randomFillSync( + new Uint8Array(this.memory.buffer), + bufPtr, + bufLen, + ); + return constants_1.WASI_ESUCCESS; + }, + sched_yield() { + // Single threaded environment + // This is a no-op in JS + return constants_1.WASI_ESUCCESS; + }, + sock_recv() { + return constants_1.WASI_ENOSYS; + }, + sock_send() { + return constants_1.WASI_ENOSYS; + }, + sock_shutdown() { + return constants_1.WASI_ENOSYS; + }, + }; + // Wrap each of the imports to show the calls in the console + if (wasiConfig.traceSyscalls) { + Object.keys(this.wasiImport).forEach((key) => { + const prevImport = this.wasiImport[key]; + this.wasiImport[key] = function (...args) { + console.log(`WASI: wasiImport called: ${key} (${args})`); + try { + let result = prevImport(...args); + console.log(`WASI: => ${result}`); + return result; + } catch (e) { + console.log(`Catched error: ${e}`); + throw e; + } + }; + }); + } + } + refreshMemory() { + // @ts-ignore + if (!this.view || this.view.buffer.byteLength === 0) { + this.view = new dataview_1.DataViewPolyfill(this.memory.buffer); + } + } + setMemory(memory) { + this.memory = memory; + } + start(instance) { + const exports = instance.exports; + if (exports === null || typeof exports !== "object") { + throw new Error( + `instance.exports must be an Object. Received ${exports}.`, + ); + } + const { memory } = exports; + if (!(memory instanceof WebAssembly.Memory)) { + throw new Error( + `instance.exports.memory must be a WebAssembly.Memory. Recceived ${memory}.`, + ); + } + this.setMemory(memory); + if (exports._start) { + exports._start(); + } + } + getImportNamespace(module) { + let namespace = null; + for (let imp of WebAssembly.Module.imports(module)) { + // We only check for the functions + if (imp.kind !== "function") { + continue; + } + // We allow functions in other namespaces other than wasi + if (!imp.module.startsWith("wasi_")) { + continue; + } + if (!namespace) { + namespace = imp.module; + } else { + if (namespace !== imp.module) { + throw new Error("Multiple namespaces detected."); + } + } + } + return namespace; + } + getImports(module) { + let namespace = this.getImportNamespace(module); + switch (namespace) { + case "wasi_unstable": + return { + wasi_unstable: this.wasiImport, + }; + case "wasi_snapshot_preview1": + return { + wasi_snapshot_preview1: this.wasiImport, + }; + default: + throw new Error( + "Can't detect a WASI namespace for the WebAssembly Module", + ); } + } } -exports.default = WASIDefault -WASIDefault.defaultBindings = defaultBindings -// Also export it as a field in the export object -exports.WASI = WASIDefault +exports.default = WASIDefault; +exports.WASI = WASIDefault;