From 90db590f09b6360409e2246d153ec80131eddd3a Mon Sep 17 00:00:00 2001 From: Oleh Komendant Date: Thu, 10 Oct 2024 19:43:14 +0300 Subject: [PATCH] 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,