diff options
Diffstat (limited to 'cli/js')
-rw-r--r-- | cli/js/compiler.ts | 520 | ||||
-rw-r--r-- | cli/js/compiler_bootstrap.ts | 31 | ||||
-rw-r--r-- | cli/js/compiler_bundler.ts | 4 | ||||
-rw-r--r-- | cli/js/compiler_util.ts | 15 | ||||
-rw-r--r-- | cli/js/globals.ts | 97 | ||||
-rw-r--r-- | cli/js/lib.deno_main.d.ts | 4 | ||||
-rw-r--r-- | cli/js/main.ts | 59 | ||||
-rw-r--r-- | cli/js/os.ts | 68 | ||||
-rw-r--r-- | cli/js/runtime.ts | 90 | ||||
-rw-r--r-- | cli/js/runtime_main.ts | 85 | ||||
-rw-r--r-- | cli/js/runtime_worker.ts | 126 | ||||
-rw-r--r-- | cli/js/worker_main.ts | 89 |
12 files changed, 653 insertions, 535 deletions
diff --git a/cli/js/compiler.ts b/cli/js/compiler.ts index 2807422e2..394c6cf52 100644 --- a/cli/js/compiler.ts +++ b/cli/js/compiler.ts @@ -1,12 +1,20 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. // TODO(ry) Combine this implementation with //deno_typescript/compiler_main.js -// these are imported for their side effects -import "./globals.ts"; +// This module is the entry point for "compiler" isolate, ie. the one +// that is created when Deno needs to compile TS/WASM to JS. +// +// It provides a two functions that should be called by Rust: +// - `bootstrapTsCompilerRuntime` +// - `bootstrapWasmCompilerRuntime` +// Either of these functions must be called when creating isolate +// to properly setup runtime. + +// NOTE: this import has side effects! import "./ts_global.d.ts"; import { TranspileOnlyResult } from "./compiler_api.ts"; -import { oldProgram } from "./compiler_bootstrap.ts"; +import { TS_SNAPSHOT_PROGRAM } from "./compiler_bootstrap.ts"; import { setRootExports } from "./compiler_bundler.ts"; import { CompilerHostTarget, @@ -30,16 +38,12 @@ import { } from "./compiler_util.ts"; import { Diagnostic } from "./diagnostics.ts"; import { fromTypeScriptDiagnostic } from "./diagnostics_util.ts"; -import * as os from "./os.ts"; import { assert } from "./util.ts"; import * as util from "./util.ts"; import { - postMessage, - workerClose, - bootstrapWorkerRuntime -} from "./worker_main.ts"; - -const self = globalThis; + bootstrapWorkerRuntime, + runWorkerMessageLoop +} from "./runtime_worker.ts"; interface CompilerRequestCompile { type: CompilerRequestType.Compile; @@ -80,274 +84,280 @@ interface CompileResult { diagnostics?: Diagnostic; } -// bootstrap the runtime environment, this gets called as the isolate is setup -self.bootstrapCompilerRuntime = function bootstrapCompilerRuntime( - compilerType: string -): void { - os.start(true, compilerType); -}; - -// bootstrap the worker environment, this gets called as the isolate is setup -self.bootstrapWorkerRuntime = bootstrapWorkerRuntime; - -// provide the "main" function that will be called by the privileged side when -// lazy instantiating the compiler web worker -self.bootstrapTsCompiler = function tsCompilerMain(): void { - // bootstrapWorkerRuntime should have already been called since a compiler is a worker. - self.onmessage = async ({ - data: request - }: { - data: CompilerRequest; - }): Promise<void> => { - switch (request.type) { - // `Compile` are requests from the internals to Deno, generated by both - // the `run` and `bundle` sub command. - case CompilerRequestType.Compile: { - const { - bundle, - config, - configPath, - outFile, - rootNames, - target - } = request; - util.log(">>> compile start", { +// TODO(bartlomieju): refactor this function into multiple functions +// per CompilerRequestType +async function tsCompilerOnMessage({ + data: request +}: { + data: CompilerRequest; +}): Promise<void> { + switch (request.type) { + // `Compile` are requests from the internals to Deno, generated by both + // the `run` and `bundle` sub command. + case CompilerRequestType.Compile: { + const { + bundle, + config, + configPath, + outFile, + rootNames, + target + } = request; + util.log(">>> compile start", { + rootNames, + type: CompilerRequestType[request.type] + }); + + // This will recursively analyse all the code for other imports, + // requesting those from the privileged side, populating the in memory + // cache which will be used by the host, before resolving. + const resolvedRootModules = await processImports( + rootNames.map(rootName => [rootName, rootName]) + ); + + // When a programme is emitted, TypeScript will call `writeFile` with + // each file that needs to be emitted. The Deno compiler host delegates + // this, to make it easier to perform the right actions, which vary + // based a lot on the request. For a `Compile` request, we need to + // cache all the files in the privileged side if we aren't bundling, + // and if we are bundling we need to enrich the bundle and either write + // out the bundle or log it to the console. + const state: WriteFileState = { + type: request.type, + bundle, + host: undefined, + outFile, + rootNames + }; + const writeFile = createWriteFile(state); + + const host = (state.host = new Host({ + bundle, + target, + writeFile + })); + let diagnostics: readonly ts.Diagnostic[] | undefined; + + // if there is a configuration supplied, we need to parse that + if (config && config.length && configPath) { + const configResult = host.configure(configPath, config); + diagnostics = processConfigureResponse(configResult, configPath); + } + + let emitSkipped = true; + // if there was a configuration and no diagnostics with it, we will continue + // to generate the program and possibly emit it. + if (!diagnostics || (diagnostics && diagnostics.length === 0)) { + const options = host.getCompilationSettings(); + const program = ts.createProgram({ rootNames, - type: CompilerRequestType[request.type] + options, + host, + oldProgram: TS_SNAPSHOT_PROGRAM }); - // This will recursively analyse all the code for other imports, - // requesting those from the privileged side, populating the in memory - // cache which will be used by the host, before resolving. - const resolvedRootModules = await processImports( - rootNames.map(rootName => [rootName, rootName]) - ); - - // When a programme is emitted, TypeScript will call `writeFile` with - // each file that needs to be emitted. The Deno compiler host delegates - // this, to make it easier to perform the right actions, which vary - // based a lot on the request. For a `Compile` request, we need to - // cache all the files in the privileged side if we aren't bundling, - // and if we are bundling we need to enrich the bundle and either write - // out the bundle or log it to the console. - const state: WriteFileState = { - type: request.type, - bundle, - host: undefined, - outFile, - rootNames - }; - const writeFile = createWriteFile(state); - - const host = (state.host = new Host({ - bundle, - target, - writeFile - })); - let diagnostics: readonly ts.Diagnostic[] | undefined; - - // if there is a configuration supplied, we need to parse that - if (config && config.length && configPath) { - const configResult = host.configure(configPath, config); - diagnostics = processConfigureResponse(configResult, configPath); - } + diagnostics = ts + .getPreEmitDiagnostics(program) + .filter(({ code }) => !ignoredDiagnostics.includes(code)); - let emitSkipped = true; - // if there was a configuration and no diagnostics with it, we will continue - // to generate the program and possibly emit it. - if (!diagnostics || (diagnostics && diagnostics.length === 0)) { - const options = host.getCompilationSettings(); - const program = ts.createProgram({ - rootNames, - options, - host, - oldProgram - }); - - diagnostics = ts - .getPreEmitDiagnostics(program) - .filter(({ code }) => !ignoredDiagnostics.includes(code)); - - // We will only proceed with the emit if there are no diagnostics. - if (diagnostics && diagnostics.length === 0) { - if (bundle) { - // we only support a single root module when bundling - assert(resolvedRootModules.length === 1); - // warning so it goes to stderr instead of stdout - console.warn(`Bundling "${resolvedRootModules[0]}"`); - setRootExports(program, resolvedRootModules[0]); - } - const emitResult = program.emit(); - emitSkipped = emitResult.emitSkipped; - // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned - // without casting. - diagnostics = emitResult.diagnostics; + // We will only proceed with the emit if there are no diagnostics. + if (diagnostics && diagnostics.length === 0) { + if (bundle) { + // we only support a single root module when bundling + assert(resolvedRootModules.length === 1); + // warning so it goes to stderr instead of stdout + console.warn(`Bundling "${resolvedRootModules[0]}"`); + setRootExports(program, resolvedRootModules[0]); } + const emitResult = program.emit(); + emitSkipped = emitResult.emitSkipped; + // emitResult.diagnostics is `readonly` in TS3.5+ and can't be assigned + // without casting. + diagnostics = emitResult.diagnostics; } + } - const result: CompileResult = { - emitSkipped, - diagnostics: diagnostics.length - ? fromTypeScriptDiagnostic(diagnostics) - : undefined - }; - postMessage(result); - - util.log("<<< compile end", { - rootNames, - type: CompilerRequestType[request.type] - }); - break; + const result: CompileResult = { + emitSkipped, + diagnostics: diagnostics.length + ? fromTypeScriptDiagnostic(diagnostics) + : undefined + }; + globalThis.postMessage(result); + + util.log("<<< compile end", { + rootNames, + type: CompilerRequestType[request.type] + }); + break; + } + case CompilerRequestType.RuntimeCompile: { + // `RuntimeCompile` are requests from a runtime user, both compiles and + // bundles. The process is similar to a request from the privileged + // side, but also returns the output to the on message. + const { rootName, sources, options, bundle, target } = request; + + util.log(">>> runtime compile start", { + rootName, + bundle, + sources: sources ? Object.keys(sources) : undefined + }); + + const resolvedRootName = sources + ? rootName + : resolveModules([rootName])[0]; + + const rootNames = sources + ? processLocalImports(sources, [[resolvedRootName, resolvedRootName]]) + : await processImports([[resolvedRootName, resolvedRootName]]); + + const state: WriteFileState = { + type: request.type, + bundle, + host: undefined, + rootNames, + sources, + emitMap: {}, + emitBundle: undefined + }; + const writeFile = createWriteFile(state); + + const host = (state.host = new Host({ + bundle, + target, + writeFile + })); + const compilerOptions = [defaultRuntimeCompileOptions]; + if (options) { + compilerOptions.push(convertCompilerOptions(options)); } - case CompilerRequestType.RuntimeCompile: { - // `RuntimeCompile` are requests from a runtime user, both compiles and - // bundles. The process is similar to a request from the privileged - // side, but also returns the output to the on message. - const { rootName, sources, options, bundle, target } = request; - - util.log(">>> runtime compile start", { - rootName, - bundle, - sources: sources ? Object.keys(sources) : undefined - }); + if (bundle) { + compilerOptions.push(defaultBundlerOptions); + } + host.mergeOptions(...compilerOptions); - const resolvedRootName = sources - ? rootName - : resolveModules([rootName])[0]; + const program = ts.createProgram({ + rootNames, + options: host.getCompilationSettings(), + host, + oldProgram: TS_SNAPSHOT_PROGRAM + }); - const rootNames = sources - ? processLocalImports(sources, [[resolvedRootName, resolvedRootName]]) - : await processImports([[resolvedRootName, resolvedRootName]]); + if (bundle) { + setRootExports(program, rootNames[0]); + } - const state: WriteFileState = { - type: request.type, - bundle, - host: undefined, - rootNames, - sources, - emitMap: {}, - emitBundle: undefined - }; - const writeFile = createWriteFile(state); - - const host = (state.host = new Host({ - bundle, - target, - writeFile - })); - const compilerOptions = [defaultRuntimeCompileOptions]; - if (options) { - compilerOptions.push(convertCompilerOptions(options)); - } - if (bundle) { - compilerOptions.push(defaultBundlerOptions); - } - host.mergeOptions(...compilerOptions); + const diagnostics = ts + .getPreEmitDiagnostics(program) + .filter(({ code }) => !ignoredDiagnostics.includes(code)); - const program = ts.createProgram({ - rootNames, - options: host.getCompilationSettings(), - host, - oldProgram - }); + const emitResult = program.emit(); - if (bundle) { - setRootExports(program, rootNames[0]); - } + assert(emitResult.emitSkipped === false, "Unexpected skip of the emit."); + const result = [ + diagnostics.length ? fromTypeScriptDiagnostic(diagnostics) : undefined, + bundle ? state.emitBundle : state.emitMap + ]; + globalThis.postMessage(result); - const diagnostics = ts - .getPreEmitDiagnostics(program) - .filter(({ code }) => !ignoredDiagnostics.includes(code)); + assert(state.emitMap); + util.log("<<< runtime compile finish", { + rootName, + sources: sources ? Object.keys(sources) : undefined, + bundle, + emitMap: Object.keys(state.emitMap) + }); - const emitResult = program.emit(); - - assert( - emitResult.emitSkipped === false, - "Unexpected skip of the emit." + break; + } + case CompilerRequestType.RuntimeTranspile: { + const result: Record<string, TranspileOnlyResult> = {}; + const { sources, options } = request; + const compilerOptions = options + ? Object.assign( + {}, + defaultTranspileOptions, + convertCompilerOptions(options) + ) + : defaultTranspileOptions; + + for (const [fileName, inputText] of Object.entries(sources)) { + const { outputText: source, sourceMapText: map } = ts.transpileModule( + inputText, + { + fileName, + compilerOptions + } ); - const result = [ - diagnostics.length - ? fromTypeScriptDiagnostic(diagnostics) - : undefined, - bundle ? state.emitBundle : state.emitMap - ]; - postMessage(result); - - assert(state.emitMap); - util.log("<<< runtime compile finish", { - rootName, - sources: sources ? Object.keys(sources) : undefined, - bundle, - emitMap: Object.keys(state.emitMap) - }); - - break; + result[fileName] = { source, map }; } - case CompilerRequestType.RuntimeTranspile: { - const result: Record<string, TranspileOnlyResult> = {}; - const { sources, options } = request; - const compilerOptions = options - ? Object.assign( - {}, - defaultTranspileOptions, - convertCompilerOptions(options) - ) - : defaultTranspileOptions; - - for (const [fileName, inputText] of Object.entries(sources)) { - const { outputText: source, sourceMapText: map } = ts.transpileModule( - inputText, - { - fileName, - compilerOptions - } - ); - result[fileName] = { source, map }; - } - postMessage(result); + globalThis.postMessage(result); - break; - } - default: - util.log( - `!!! unhandled CompilerRequestType: ${ - (request as CompilerRequest).type - } (${CompilerRequestType[(request as CompilerRequest).type]})` - ); + break; } + default: + util.log( + `!!! unhandled CompilerRequestType: ${ + (request as CompilerRequest).type + } (${CompilerRequestType[(request as CompilerRequest).type]})` + ); + } + + // The compiler isolate exits after a single message. + globalThis.workerClose(); +} + +async function wasmCompilerOnMessage({ + data: binary +}: { + data: string; +}): Promise<void> { + const buffer = util.base64ToUint8Array(binary); + // @ts-ignore + const compiled = await WebAssembly.compile(buffer); + + util.log(">>> WASM compile start"); - // The compiler isolate exits after a single message. - workerClose(); - }; -}; - -self.bootstrapWasmCompiler = function wasmCompilerMain(): void { - // bootstrapWorkerRuntime should have already been called since a compiler is a worker. - self.onmessage = async ({ - data: binary - }: { - data: string; - }): Promise<void> => { - const buffer = util.base64ToUint8Array(binary); + const importList = Array.from( // @ts-ignore - const compiled = await WebAssembly.compile(buffer); + new Set(WebAssembly.Module.imports(compiled).map(({ module }) => module)) + ); + const exportList = Array.from( + // @ts-ignore + new Set(WebAssembly.Module.exports(compiled).map(({ name }) => name)) + ); - util.log(">>> WASM compile start"); + globalThis.postMessage({ importList, exportList }); - const importList = Array.from( - // @ts-ignore - new Set(WebAssembly.Module.imports(compiled).map(({ module }) => module)) - ); - const exportList = Array.from( - // @ts-ignore - new Set(WebAssembly.Module.exports(compiled).map(({ name }) => name)) - ); + util.log("<<< WASM compile end"); - postMessage({ importList, exportList }); + // The compiler isolate exits after a single message. + globalThis.workerClose(); +} - util.log("<<< WASM compile end"); +function bootstrapTsCompilerRuntime(): void { + bootstrapWorkerRuntime("TS"); + globalThis.onmessage = tsCompilerOnMessage; + runWorkerMessageLoop(); +} + +function bootstrapWasmCompilerRuntime(): void { + bootstrapWorkerRuntime("WASM"); + globalThis.onmessage = wasmCompilerOnMessage; + runWorkerMessageLoop(); +} - // The compiler isolate exits after a single message. - workerClose(); - }; -}; +Object.defineProperties(globalThis, { + bootstrapWasmCompilerRuntime: { + value: bootstrapWasmCompilerRuntime, + enumerable: false, + writable: false, + configurable: false + }, + bootstrapTsCompilerRuntime: { + value: bootstrapTsCompilerRuntime, + enumerable: false, + writable: false, + configurable: false + } +}); diff --git a/cli/js/compiler_bootstrap.ts b/cli/js/compiler_bootstrap.ts index a5c7eb029..afb3d2be5 100644 --- a/cli/js/compiler_bootstrap.ts +++ b/cli/js/compiler_bootstrap.ts @@ -1,20 +1,11 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { ASSETS, CompilerHostTarget, Host } from "./compiler_host.ts"; -import { core } from "./core.ts"; -import * as dispatch from "./dispatch.ts"; import { getAsset } from "./compiler_util.ts"; -// This registers ops that are available during the snapshotting process. -const ops = core.ops(); -for (const [name, opId] of Object.entries(ops)) { - const opName = `OP_${name.toUpperCase()}`; - // TODO This type casting is dangerous, and should be improved when the same - // code in `os.ts` is done. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (dispatch as any)[opName] = opId; -} - +// NOTE: target doesn't really matter here, +// this is in fact a mock host created just to +// load all type definitions and snapshot them. const host = new Host({ target: CompilerHostTarget.Main, writeFile(): void {} @@ -35,11 +26,15 @@ const options = host.getCompilationSettings(); host.getSourceFile(`${ASSETS}/lib.deno_main.d.ts`, ts.ScriptTarget.ESNext); host.getSourceFile(`${ASSETS}/lib.deno_worker.d.ts`, ts.ScriptTarget.ESNext); host.getSourceFile(`${ASSETS}/lib.deno.d.ts`, ts.ScriptTarget.ESNext); -host.getSourceFile(`${ASSETS}/lib.webworker.d.ts`, ts.ScriptTarget.ESNext); -/** Used to generate the foundational AST for all other compilations, so it can - * be cached as part of the snapshot and available to speed up startup */ -export const oldProgram = ts.createProgram({ +/** + * This function spins up TS compiler and loads all available libraries + * into memory (including ones specified above). + * + * Used to generate the foundational AST for all other compilations, so it can + * be cached as part of the snapshot and available to speed up startup. + */ +export const TS_SNAPSHOT_PROGRAM = ts.createProgram({ rootNames: [`${ASSETS}/bootstrap.ts`], options, host @@ -49,5 +44,5 @@ export const oldProgram = ts.createProgram({ * * We read all static assets during the snapshotting process, which is * why this is located in compiler_bootstrap. - **/ -export const bundleLoader = getAsset("bundle_loader.js"); + */ +export const BUNDLE_LOADER = getAsset("bundle_loader.js"); diff --git a/cli/js/compiler_bundler.ts b/cli/js/compiler_bundler.ts index 8fb68cc7a..d334b0fc3 100644 --- a/cli/js/compiler_bundler.ts +++ b/cli/js/compiler_bundler.ts @@ -1,6 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { bundleLoader } from "./compiler_bootstrap.ts"; +import { BUNDLE_LOADER } from "./compiler_bootstrap.ts"; import { assert, commonPath, @@ -55,7 +55,7 @@ export function buildBundle( } else { instantiate = `instantiate("${rootName}");\n`; } - return `${bundleLoader}\n${data}\n${instantiate}`; + return `${BUNDLE_LOADER}\n${data}\n${instantiate}`; } /** Set the rootExports which will by the `emitBundle()` */ diff --git a/cli/js/compiler_util.ts b/cli/js/compiler_util.ts index fbb30d67d..f541ea46f 100644 --- a/cli/js/compiler_util.ts +++ b/cli/js/compiler_util.ts @@ -90,6 +90,9 @@ function cache( assert(false, `Trying to cache unhandled file type "${emittedFileName}"`); } } + +let OP_FETCH_ASSET: number; + /** * This op is called only during snapshotting. * @@ -98,12 +101,16 @@ function cache( * as raw byte arrays. */ export function getAsset(name: string): string { + if (!OP_FETCH_ASSET) { + const ops = core.ops(); + const opFetchAsset = ops["fetch_asset"]; + assert(opFetchAsset, "OP_FETCH_ASSET is not registered"); + OP_FETCH_ASSET = opFetchAsset; + } + const encoder = new TextEncoder(); const decoder = new TextDecoder(); - const sourceCodeBytes = core.dispatch( - dispatch.OP_FETCH_ASSET, - encoder.encode(name) - ); + const sourceCodeBytes = core.dispatch(OP_FETCH_ASSET, encoder.encode(name)); return decoder.decode(sourceCodeBytes!); } diff --git a/cli/js/globals.ts b/cli/js/globals.ts index cb1b17678..e6c1e855c 100644 --- a/cli/js/globals.ts +++ b/cli/js/globals.ts @@ -1,15 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -// This is a "special" module, in that it define the global runtime scope of -// Deno, and therefore it defines a lot of the runtime environment that code -// is evaluated in. - -// By convention we import those items that are globally exposed as namespaces import * as blob from "./blob.ts"; import * as consoleTypes from "./console.ts"; -import * as csprng from "./get_random_values.ts"; import * as customEvent from "./custom_event.ts"; -import * as Deno from "./deno.ts"; import * as domTypes from "./dom_types.ts"; import * as domFile from "./dom_file.ts"; import * as event from "./event.ts"; @@ -21,7 +14,6 @@ import * as textEncoding from "./text_encoding.ts"; import * as timers from "./timers.ts"; import * as url from "./url.ts"; import * as urlSearchParams from "./url_search_params.ts"; -import * as workerRuntime from "./worker_main.ts"; import * as workers from "./workers.ts"; import * as performanceUtil from "./performance.ts"; import * as request from "./request.ts"; @@ -29,7 +21,6 @@ import * as request from "./request.ts"; // These imports are not exposed and therefore are fine to just import the // symbols required. import { core } from "./core.ts"; -import { internalObject } from "./internals.ts"; // This global augmentation is just enough types to be able to build Deno, // the runtime types are fully defined in `lib.deno_runtime.d.ts`. @@ -111,14 +102,23 @@ declare global { callback: (event: domTypes.Event) => void | null, options?: boolean | domTypes.AddEventListenerOptions | undefined ) => void; - var bootstrapTsCompiler: (() => void) | undefined; + var queueMicrotask: (callback: () => void) => void; var console: consoleTypes.Console; + var location: domTypes.Location; + + // Assigned to `window` global - main runtime var Deno: { core: DenoCore; }; - var bootstrapCompilerRuntime: ((compilerType: string) => void) | undefined; + var onload: ((e: domTypes.Event) => void) | undefined; + var onunload: ((e: domTypes.Event) => void) | undefined; var bootstrapMainRuntime: (() => void) | undefined; - var location: domTypes.Location; + + // Assigned to `self` global - worker runtime and compiler + var bootstrapWorkerRuntime: + | ((name: string) => Promise<void> | void) + | undefined; + var runWorkerMessageLoop: (() => Promise<void> | void) | undefined; var onerror: | (( msg: string, @@ -128,22 +128,20 @@ declare global { e: domTypes.Event ) => boolean | void) | undefined; - var onload: ((e: domTypes.Event) => void) | undefined; + // eslint-disable-next-line @typescript-eslint/no-explicit-any var onmessage: ((e: { data: any }) => Promise<void> | void) | undefined; - var onunload: ((e: domTypes.Event) => void) | undefined; - var queueMicrotask: (callback: () => void) => void; - var bootstrapWasmCompiler: (() => void) | undefined; - var bootstrapWorkerRuntime: (() => Promise<void> | void) | undefined; + // Called in compiler + var workerClose: () => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + var postMessage: (msg: any) => void; + // Assigned to `self` global - compiler + var bootstrapTsCompilerRuntime: (() => void) | undefined; + var bootstrapWasmCompilerRuntime: (() => void) | undefined; /* eslint-enable */ } -// Add internal object to Deno object. -// This is not exposed as part of the Deno types. -// @ts-ignore -Deno[Deno.symbols.internal] = internalObject; - -function writable(value: unknown): PropertyDescriptor { +export function writable(value: unknown): PropertyDescriptor { return { value, writable: true, @@ -152,7 +150,7 @@ function writable(value: unknown): PropertyDescriptor { }; } -function nonEnumerable(value: unknown): PropertyDescriptor { +export function nonEnumerable(value: unknown): PropertyDescriptor { return { value, writable: true, @@ -160,27 +158,28 @@ function nonEnumerable(value: unknown): PropertyDescriptor { }; } -function readOnly(value: unknown): PropertyDescriptor { +export function readOnly(value: unknown): PropertyDescriptor { return { value, enumerable: true }; } -const globalProperties = { - window: readOnly(globalThis), - Deno: readOnly(Deno), +// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope +export const windowOrWorkerGlobalScopeMethods = { atob: writable(textEncoding.atob), btoa: writable(textEncoding.btoa), - fetch: writable(fetchTypes.fetch), - clearTimeout: writable(timers.clearTimeout), clearInterval: writable(timers.clearInterval), - console: writable(new consoleTypes.Console(core.print)), - setTimeout: writable(timers.setTimeout), + clearTimeout: writable(timers.clearTimeout), + fetch: writable(fetchTypes.fetch), + // queueMicrotask is bound in Rust setInterval: writable(timers.setInterval), - onload: writable(undefined), - onunload: writable(undefined), - crypto: readOnly(csprng), + setTimeout: writable(timers.setTimeout) +}; + +// Other properties shared between WindowScope and WorkerGlobalScope +export const windowOrWorkerGlobalScopeProperties = { + console: writable(new consoleTypes.Console(core.print)), Blob: nonEnumerable(blob.DenoBlob), File: nonEnumerable(domFile.DomFileImpl), CustomEvent: nonEnumerable(customEvent.CustomEvent), @@ -195,15 +194,10 @@ const globalProperties = { Request: nonEnumerable(request.Request), Response: nonEnumerable(fetchTypes.Response), performance: writable(new performanceUtil.Performance()), + Worker: nonEnumerable(workers.WorkerImpl) +}; - onmessage: writable(workerRuntime.onmessage), - onerror: writable(workerRuntime.onerror), - - bootstrapWorkerRuntime: nonEnumerable(workerRuntime.bootstrapWorkerRuntime), - workerClose: nonEnumerable(workerRuntime.workerClose), - postMessage: writable(workerRuntime.postMessage), - Worker: nonEnumerable(workers.WorkerImpl), - +export const eventTargetProperties = { [domTypes.eventTargetHost]: nonEnumerable(null), [domTypes.eventTargetListeners]: nonEnumerable({}), [domTypes.eventTargetMode]: nonEnumerable(""), @@ -219,20 +213,3 @@ const globalProperties = { eventTarget.EventTarget.prototype.removeEventListener ) }; - -Object.defineProperties(globalThis, globalProperties); - -// Registers the handler for window.onload function. -globalThis.addEventListener("load", (e: domTypes.Event): void => { - const { onload } = globalThis; - if (typeof onload === "function") { - onload(e); - } -}); -// Registers the handler for window.onunload function. -globalThis.addEventListener("unload", (e: domTypes.Event): void => { - const { onunload } = globalThis; - if (typeof onunload === "function") { - onunload(e); - } -}); diff --git a/cli/js/lib.deno_main.d.ts b/cli/js/lib.deno_main.d.ts index ec050fc1e..52b6fb7f5 100644 --- a/cli/js/lib.deno_main.d.ts +++ b/cli/js/lib.deno_main.d.ts @@ -36,7 +36,6 @@ declare interface Window { performance: __performanceUtil.Performance; onmessage: (e: { data: any }) => void; onerror: undefined | typeof onerror; - bootstrapWorkerRuntime: typeof __workerMain.bootstrapWorkerRuntime; workerClose: typeof __workerMain.workerClose; postMessage: typeof __workerMain.postMessage; Worker: typeof __workers.WorkerImpl; @@ -95,7 +94,6 @@ declare let onerror: e: Event ) => boolean | void) | undefined; -declare const bootstrapWorkerRuntime: typeof __workerMain.bootstrapWorkerRuntime; declare const workerClose: typeof __workerMain.workerClose; declare const postMessage: typeof __workerMain.postMessage; declare const Worker: typeof __workers.WorkerImpl; @@ -1525,9 +1523,7 @@ declare namespace __workerMain { export let onmessage: (e: { data: any }) => void; export function postMessage(data: any): void; export function getMessage(): Promise<any>; - export let isClosing: boolean; export function workerClose(): void; - export function bootstrapWorkerRuntime(): Promise<void>; } declare namespace __workers { diff --git a/cli/js/main.ts b/cli/js/main.ts index 1da56eaa5..b48277960 100644 --- a/cli/js/main.ts +++ b/cli/js/main.ts @@ -1,38 +1,27 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import "./globals.ts"; +import { bootstrapMainRuntime } from "./runtime_main.ts"; +import { + bootstrapWorkerRuntime, + runWorkerMessageLoop +} from "./runtime_worker.ts"; -import { assert, log } from "./util.ts"; -import * as os from "./os.ts"; -import { args } from "./deno.ts"; -import { setPrepareStackTrace } from "./error_stack.ts"; -import { replLoop } from "./repl.ts"; -import { setVersions } from "./version.ts"; -import { setLocation } from "./location.ts"; -import { setBuildInfo } from "./build.ts"; -import { setSignals } from "./process.ts"; - -function bootstrapMainRuntime(): void { - const s = os.start(true); - - setBuildInfo(s.os, s.arch); - setSignals(); - setVersions(s.denoVersion, s.v8Version, s.tsVersion); - - setPrepareStackTrace(Error); - - if (s.mainModule) { - assert(s.mainModule.length > 0); - setLocation(s.mainModule); - } - log("cwd", s.cwd); - for (let i = 0; i < s.argv.length; i++) { - args.push(s.argv[i]); - } - log("args", args); - Object.freeze(args); - - if (!s.mainModule) { - replLoop(); +Object.defineProperties(globalThis, { + bootstrapMainRuntime: { + value: bootstrapMainRuntime, + enumerable: false, + writable: false, + configurable: false + }, + bootstrapWorkerRuntime: { + value: bootstrapWorkerRuntime, + enumerable: false, + writable: false, + configurable: false + }, + runWorkerMessageLoop: { + value: runWorkerMessageLoop, + enumerable: false, + writable: false, + configurable: false } -} -globalThis["bootstrapMainRuntime"] = bootstrapMainRuntime; +}); diff --git a/cli/js/os.ts b/cli/js/os.ts index cf522f857..554d4f78d 100644 --- a/cli/js/os.ts +++ b/cli/js/os.ts @@ -1,11 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import { core } from "./core.ts"; import * as dispatch from "./dispatch.ts"; import { sendSync } from "./dispatch_json.ts"; import { ErrorKind } from "./errors.ts"; -import { assert } from "./util.ts"; import * as util from "./util.ts"; -import { OperatingSystem, Arch } from "./build.ts"; /** Check if running in terminal. * @@ -67,71 +64,6 @@ export function env( }); } -interface Start { - cwd: string; - pid: number; - argv: string[]; - mainModule: string; // Absolute URL. - debugFlag: boolean; - depsFlag: boolean; - typesFlag: boolean; - versionFlag: boolean; - denoVersion: string; - v8Version: string; - tsVersion: string; - noColor: boolean; - os: OperatingSystem; - arch: Arch; -} - -// TODO(bartlomieju): temporary solution, must be fixed when moving -// dispatches to separate crates -export function initOps(): void { - const ops = core.ops(); - for (const [name, opId] of Object.entries(ops)) { - const opName = `OP_${name.toUpperCase()}`; - // Assign op ids to actual variables - // TODO(ry) This type casting is gross and should be fixed. - ((dispatch as unknown) as { [key: string]: number })[opName] = opId; - core.setAsyncHandler(opId, dispatch.getAsyncHandler(opName)); - } -} - -// This function bootstraps an environment within Deno, it is shared both by -// the runtime and the compiler environments. -// @internal -export function start(preserveDenoNamespace = true, source?: string): Start { - initOps(); - // First we send an empty `Start` message to let the privileged side know we - // are ready. The response should be a `StartRes` message containing the CLI - // args and other info. - const startResponse = sendSync(dispatch.OP_START); - const { pid, noColor, debugFlag } = startResponse; - - util.setLogDebug(debugFlag, source); - - // pid and noColor need to be set in the Deno module before it's set to be - // frozen. - util.immutableDefine(globalThis.Deno, "pid", pid); - util.immutableDefine(globalThis.Deno, "noColor", noColor); - Object.freeze(globalThis.Deno); - - if (preserveDenoNamespace) { - util.immutableDefine(globalThis, "Deno", globalThis.Deno); - // Deno.core could ONLY be safely frozen here (not in globals.ts) - // since shared_queue.js will modify core properties. - Object.freeze(globalThis.Deno.core); - // core.sharedQueue is an object so we should also freeze it. - Object.freeze(globalThis.Deno.core.sharedQueue); - } else { - // Remove globalThis.Deno - delete globalThis.Deno; - assert(globalThis.Deno === undefined); - } - - return startResponse; -} - type DirKind = | "home" | "cache" diff --git a/cli/js/runtime.ts b/cli/js/runtime.ts new file mode 100644 index 000000000..8cebf5dca --- /dev/null +++ b/cli/js/runtime.ts @@ -0,0 +1,90 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { core } from "./core.ts"; +import * as dispatch from "./dispatch.ts"; +import { sendSync } from "./dispatch_json.ts"; +import { assert } from "./util.ts"; +import * as util from "./util.ts"; +import { OperatingSystem, Arch } from "./build.ts"; +import { setBuildInfo } from "./build.ts"; +import { setVersions } from "./version.ts"; +import { setLocation } from "./location.ts"; +import { setPrepareStackTrace } from "./error_stack.ts"; + +interface Start { + cwd: string; + pid: number; + argv: string[]; + mainModule: string; // Absolute URL. + debugFlag: boolean; + depsFlag: boolean; + typesFlag: boolean; + versionFlag: boolean; + denoVersion: string; + v8Version: string; + tsVersion: string; + noColor: boolean; + os: OperatingSystem; + arch: Arch; +} + +// TODO(bartlomieju): temporary solution, must be fixed when moving +// dispatches to separate crates +export function initOps(): void { + const ops = core.ops(); + for (const [name, opId] of Object.entries(ops)) { + const opName = `OP_${name.toUpperCase()}`; + // Assign op ids to actual variables + // TODO(ry) This type casting is gross and should be fixed. + ((dispatch as unknown) as { [key: string]: number })[opName] = opId; + core.setAsyncHandler(opId, dispatch.getAsyncHandler(opName)); + } +} + +/** + * This function bootstraps JS runtime, unfortunately some of runtime + * code depends on information like "os" and thus getting this information + * is required at startup. + */ +export function start(preserveDenoNamespace = true, source?: string): Start { + initOps(); + // First we send an empty `Start` message to let the privileged side know we + // are ready. The response should be a `StartRes` message containing the CLI + // args and other info. + const s = sendSync(dispatch.OP_START); + + setVersions(s.denoVersion, s.v8Version, s.tsVersion); + setBuildInfo(s.os, s.arch); + util.setLogDebug(s.debugFlag, source); + + // TODO(bartlomieju): this field should always be set + if (s.mainModule) { + assert(s.mainModule.length > 0); + setLocation(s.mainModule); + } + setPrepareStackTrace(Error); + + // TODO(bartlomieju): I don't like that it's mixed in here, when + // compiler and worker runtimes call this funtion and they don't use + // Deno namespace (sans shared queue - Deno.core) + + // pid and noColor need to be set in the Deno module before it's set to be + // frozen. + util.immutableDefine(globalThis.Deno, "pid", s.pid); + util.immutableDefine(globalThis.Deno, "noColor", s.noColor); + Object.freeze(globalThis.Deno); + + if (preserveDenoNamespace) { + util.immutableDefine(globalThis, "Deno", globalThis.Deno); + // Deno.core could ONLY be safely frozen here (not in globals.ts) + // since shared_queue.js will modify core properties. + Object.freeze(globalThis.Deno.core); + // core.sharedQueue is an object so we should also freeze it. + Object.freeze(globalThis.Deno.core.sharedQueue); + } else { + // Remove globalThis.Deno + delete globalThis.Deno; + assert(globalThis.Deno === undefined); + } + + return s; +} diff --git a/cli/js/runtime_main.ts b/cli/js/runtime_main.ts new file mode 100644 index 000000000..2ede2e20f --- /dev/null +++ b/cli/js/runtime_main.ts @@ -0,0 +1,85 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// This module is the entry point for "main" isolate, ie. the one +// that is created when you run "deno" executable. +// +// It provides a single function that should be called by Rust: +// - `bootstrapMainRuntime` - must be called once, when Isolate is created. +// It sets up runtime by providing globals for `WindowScope` and adds `Deno` global. + +import { + readOnly, + writable, + windowOrWorkerGlobalScopeMethods, + windowOrWorkerGlobalScopeProperties, + eventTargetProperties +} from "./globals.ts"; +import * as domTypes from "./dom_types.ts"; +import { log } from "./util.ts"; +import * as runtime from "./runtime.ts"; +import { args } from "./deno.ts"; +import * as csprng from "./get_random_values.ts"; +import { replLoop } from "./repl.ts"; +import { setSignals } from "./process.ts"; +import * as Deno from "./deno.ts"; +import { internalObject } from "./internals.ts"; + +// TODO: factor out `Deno` global assignment to separate function +// Add internal object to Deno object. +// This is not exposed as part of the Deno types. +// @ts-ignore +Deno[Deno.symbols.internal] = internalObject; + +export const mainRuntimeGlobalProperties = { + window: readOnly(globalThis), + Deno: readOnly(Deno), + + crypto: readOnly(csprng), + // TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope) + // it seems those two properties should be availble to workers as well + onload: writable(undefined), + onunload: writable(undefined) +}; + +let hasBootstrapped = false; + +export function bootstrapMainRuntime(): void { + if (hasBootstrapped) { + throw new Error("Worker runtime already bootstrapped"); + } + log("bootstrapMainRuntime"); + hasBootstrapped = true; + Object.defineProperties(globalThis, windowOrWorkerGlobalScopeMethods); + Object.defineProperties(globalThis, windowOrWorkerGlobalScopeProperties); + Object.defineProperties(globalThis, eventTargetProperties); + Object.defineProperties(globalThis, mainRuntimeGlobalProperties); + // Registers the handler for window.onload function. + globalThis.addEventListener("load", (e: domTypes.Event): void => { + const { onload } = globalThis; + if (typeof onload === "function") { + onload(e); + } + }); + // Registers the handler for window.onunload function. + globalThis.addEventListener("unload", (e: domTypes.Event): void => { + const { onunload } = globalThis; + if (typeof onunload === "function") { + onunload(e); + } + }); + + const s = runtime.start(true); + setSignals(); + + log("cwd", s.cwd); + for (let i = 0; i < s.argv.length; i++) { + args.push(s.argv[i]); + } + log("args", args); + Object.freeze(args); + + // TODO(bartlomieju): rename to s.repl + if (!s.mainModule) { + replLoop(); + } +} diff --git a/cli/js/runtime_worker.ts b/cli/js/runtime_worker.ts new file mode 100644 index 000000000..7f1d1b69c --- /dev/null +++ b/cli/js/runtime_worker.ts @@ -0,0 +1,126 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// This module is the entry point for "worker" isolate, ie. the one +// that is created using `new Worker()` JS API. +// +// It provides two functions that should be called by Rust: +// - `bootstrapWorkerRuntime` - must be called once, when Isolate is created. +// It sets up runtime by providing globals for `DedicatedWorkerScope`. +// - `runWorkerMessageLoop` - starts receiving messages from parent worker, +// can be called multiple times - eg. to restart worker execution after +// exception occurred and was handled by parent worker + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + readOnly, + writable, + nonEnumerable, + windowOrWorkerGlobalScopeMethods, + windowOrWorkerGlobalScopeProperties, + eventTargetProperties +} from "./globals.ts"; +import * as dispatch from "./dispatch.ts"; +import { sendAsync, sendSync } from "./dispatch_json.ts"; +import { log } from "./util.ts"; +import { TextDecoder, TextEncoder } from "./text_encoding.ts"; +import * as runtime from "./runtime.ts"; + +const encoder = new TextEncoder(); +const decoder = new TextDecoder(); + +// TODO(bartlomieju): remove these funtions +// Stuff for workers +export const onmessage: (e: { data: any }) => void = (): void => {}; +export const onerror: (e: { data: any }) => void = (): void => {}; + +export function postMessage(data: any): void { + const dataJson = JSON.stringify(data); + const dataIntArray = encoder.encode(dataJson); + sendSync(dispatch.OP_WORKER_POST_MESSAGE, {}, dataIntArray); +} + +export async function getMessage(): Promise<any> { + log("getMessage"); + const res = await sendAsync(dispatch.OP_WORKER_GET_MESSAGE); + if (res.data != null) { + const dataIntArray = new Uint8Array(res.data); + const dataJson = decoder.decode(dataIntArray); + return JSON.parse(dataJson); + } else { + return null; + } +} + +let isClosing = false; +let hasBootstrapped = false; + +export function workerClose(): void { + isClosing = true; +} + +export async function runWorkerMessageLoop(): Promise<void> { + while (!isClosing) { + const data = await getMessage(); + if (data == null) { + log("runWorkerMessageLoop got null message. quitting."); + break; + } + + let result: void | Promise<void>; + const event = { data }; + + try { + if (!globalThis["onmessage"]) { + break; + } + result = globalThis.onmessage!(event); + if (result && "then" in result) { + await result; + } + if (!globalThis["onmessage"]) { + break; + } + } catch (e) { + if (globalThis["onerror"]) { + const result = globalThis.onerror( + e.message, + e.fileName, + e.lineNumber, + e.columnNumber, + e + ); + if (result === true) { + continue; + } + } + throw e; + } + } +} + +export const workerRuntimeGlobalProperties = { + self: readOnly(globalThis), + onmessage: writable(onmessage), + onerror: writable(onerror), + workerClose: nonEnumerable(workerClose), + postMessage: writable(postMessage) +}; + +/** + * Main method to initialize worker runtime. + * + * It sets up global variables for DedicatedWorkerScope, + * and initializes ops. + */ +export function bootstrapWorkerRuntime(name: string): void { + if (hasBootstrapped) { + throw new Error("Worker runtime already bootstrapped"); + } + log("bootstrapWorkerRuntime"); + hasBootstrapped = true; + Object.defineProperties(globalThis, windowOrWorkerGlobalScopeMethods); + Object.defineProperties(globalThis, windowOrWorkerGlobalScopeProperties); + Object.defineProperties(globalThis, workerRuntimeGlobalProperties); + Object.defineProperties(globalThis, eventTargetProperties); + runtime.start(false, name); +} diff --git a/cli/js/worker_main.ts b/cli/js/worker_main.ts deleted file mode 100644 index 16d1ef24e..000000000 --- a/cli/js/worker_main.ts +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -/* eslint-disable @typescript-eslint/no-explicit-any */ -import * as dispatch from "./dispatch.ts"; -import { sendAsync, sendSync } from "./dispatch_json.ts"; -import { log } from "./util.ts"; -import { TextDecoder, TextEncoder } from "./text_encoding.ts"; -import { initOps } from "./os.ts"; - -const encoder = new TextEncoder(); -const decoder = new TextDecoder(); - -function encodeMessage(data: any): Uint8Array { - const dataJson = JSON.stringify(data); - return encoder.encode(dataJson); -} - -function decodeMessage(dataIntArray: Uint8Array): any { - const dataJson = decoder.decode(dataIntArray); - return JSON.parse(dataJson); -} - -// Stuff for workers -export const onmessage: (e: { data: any }) => void = (): void => {}; -export const onerror: (e: { data: any }) => void = (): void => {}; - -export function postMessage(data: any): void { - const dataIntArray = encodeMessage(data); - sendSync(dispatch.OP_WORKER_POST_MESSAGE, {}, dataIntArray); -} - -export async function getMessage(): Promise<any> { - log("getMessage"); - const res = await sendAsync(dispatch.OP_WORKER_GET_MESSAGE); - if (res.data != null) { - return decodeMessage(new Uint8Array(res.data)); - } else { - return null; - } -} - -export let isClosing = false; - -export function workerClose(): void { - isClosing = true; -} - -export async function bootstrapWorkerRuntime(): Promise<void> { - initOps(); - - log("bootstrapWorkerRuntime"); - - while (!isClosing) { - const data = await getMessage(); - if (data == null) { - log("bootstrapWorkerRuntime got null message. quitting."); - break; - } - - let result: void | Promise<void>; - const event = { data }; - - try { - if (!globalThis["onmessage"]) { - break; - } - result = globalThis.onmessage!(event); - if (result && "then" in result) { - await result; - } - if (!globalThis["onmessage"]) { - break; - } - } catch (e) { - if (globalThis["onerror"]) { - const result = globalThis.onerror( - e.message, - e.fileName, - e.lineNumber, - e.columnNumber, - e - ); - if (result === true) { - continue; - } - } - throw e; - } - } -} |