mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-07 05:41:22 -06:00
rudimentary parallel parse
This commit is contained in:
parent
fd390e78fe
commit
7fa1fd0f36
@ -189,5 +189,77 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
|
||||
------------------------------------------------------
|
||||
|
||||
------------------ esfx ----------------------------
|
||||
Copyright 2019 Ron Buckton
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
------------------------------------------------------
|
||||
|
||||
----------------- .NET Core ----------------------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) .NET Foundation and Contributors
|
||||
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
------------------------------------------------------
|
||||
|
||||
------------------ xxHash Library ----------------------------
|
||||
Copyright (c) 2012-2021 Yann Collet
|
||||
All rights reserved.
|
||||
|
||||
BSD 2-Clause License (https://www.opensource.org/licenses/bsd-license.php)
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or
|
||||
other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
------------------------------------------------------
|
||||
|
||||
------------- End of ThirdPartyNotices ------------------------------------------- */
|
||||
|
||||
|
||||
@ -90,6 +90,7 @@
|
||||
"test:eslint-rules": "hereby run-eslint-rules-tests",
|
||||
"build": "npm run build:compiler && npm run build:tests",
|
||||
"build:compiler": "hereby local",
|
||||
"build:tsc": "hereby tsc",
|
||||
"build:tests": "hereby tests",
|
||||
"build:tests:notypecheck": "hereby tests --no-typecheck",
|
||||
"start": "node lib/tsc",
|
||||
|
||||
@ -4,7 +4,7 @@ import os from "os";
|
||||
const ci = ["1", "true"].includes(process.env.CI ?? "");
|
||||
|
||||
const parsed = minimist(process.argv.slice(2), {
|
||||
boolean: ["dirty", "light", "colors", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built", "ci", "bundle", "typecheck", "lint", "coverage"],
|
||||
boolean: ["dirty", "light", "colors", "lkg", "soft", "fix", "failed", "keepFailed", "force", "built", "ci", "bundle", "typecheck", "lint", "coverage", "structs"],
|
||||
string: ["browser", "tests", "break", "host", "reporter", "stackTraceLimit", "timeout", "shards", "shardId"],
|
||||
alias: {
|
||||
/* eslint-disable quote-props */
|
||||
@ -43,6 +43,7 @@ const parsed = minimist(process.argv.slice(2), {
|
||||
typecheck: true,
|
||||
lint: true,
|
||||
coverage: false,
|
||||
structs: false,
|
||||
}
|
||||
});
|
||||
|
||||
@ -57,6 +58,12 @@ if (!options.bundle && !options.typecheck) {
|
||||
throw new Error("--no-typecheck cannot be passed when bundling is disabled");
|
||||
}
|
||||
|
||||
// TODO(rbuckton): remove this when esbuild supports `using` and decorators.
|
||||
if (options.structs) {
|
||||
options.bundle = false;
|
||||
options.lkg = true;
|
||||
}
|
||||
|
||||
export default options;
|
||||
|
||||
|
||||
@ -90,5 +97,6 @@ export default options;
|
||||
* @property {boolean} typecheck
|
||||
* @property {boolean} lint
|
||||
* @property {boolean} coverage
|
||||
* @property {boolean} structs
|
||||
*/
|
||||
void 0;
|
||||
|
||||
@ -35,6 +35,7 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, opt
|
||||
const shards = +cmdLineOptions.shards || undefined;
|
||||
const shardId = +cmdLineOptions.shardId || undefined;
|
||||
const coverage = cmdLineOptions.coverage;
|
||||
const structs = cmdLineOptions.structs;
|
||||
if (!cmdLineOptions.dirty) {
|
||||
if (options.watching) {
|
||||
console.log(chalk.yellowBright(`[watch] cleaning test directories...`));
|
||||
@ -119,6 +120,12 @@ export async function runConsoleTests(runJs, defaultReporter, runInParallel, opt
|
||||
args.push(runJs);
|
||||
}
|
||||
|
||||
if (structs) {
|
||||
args.unshift("--harmony-struct");
|
||||
args.unshift("--shared-string-table");
|
||||
args.unshift("--enable-source-map");
|
||||
}
|
||||
|
||||
/** @type {number | undefined} */
|
||||
let errorStatus;
|
||||
|
||||
|
||||
@ -331,7 +331,7 @@ function verifyMatchingSymbols(decl) {
|
||||
* @param {ts.Symbol} moduleSymbol
|
||||
*/
|
||||
function emitAsNamespace(name, moduleSymbol) {
|
||||
assert(moduleSymbol.flags & ts.SymbolFlags.ValueModule, "moduleSymbol is not a module");
|
||||
assert(moduleSymbol.flags & ts.SymbolFlags.ValueModule, `moduleSymbol '${moduleSymbol.name}' is not a module, was a ${ts.Debug.formatSymbolFlags(moduleSymbol.flags)} instead`);
|
||||
|
||||
scopeStack.push(new Map());
|
||||
const currentScope = scopeStack[scopeStack.length - 1];
|
||||
|
||||
@ -9,6 +9,35 @@ export * from "../perfLogger";
|
||||
export * from "../tracing";
|
||||
export * from "../types";
|
||||
export * from "../sys";
|
||||
export * from "../workerThreads";
|
||||
export * from "../threading/condition";
|
||||
export * from "../threading/countdownEvent";
|
||||
export * from "../threading/lockable";
|
||||
export * from "../threading/manualResetEvent";
|
||||
export * from "../threading/mutex";
|
||||
export * from "../threading/scopedLock";
|
||||
export * from "../threading/sharedLockable";
|
||||
export * from "../threading/sharedMutex";
|
||||
export * from "../threading/threadPool";
|
||||
export * from "../threading/uniqueLock";
|
||||
export * from "../sharing/collections/hashData";
|
||||
export * from "../sharing/collections/sharedLinkedList";
|
||||
export * from "../sharing/collections/sharedMap";
|
||||
export * from "../sharing/collections/sharedResizableArray";
|
||||
export * from "../sharing/collections/sharedSet";
|
||||
export * from "../sharing/collections/xxhash32";
|
||||
export * from "../sharing/structs/fakeSharedStruct";
|
||||
export * from "../sharing/structs/identifiableStruct";
|
||||
export * from "../sharing/structs/shareable";
|
||||
export * from "../sharing/structs/sharedStruct";
|
||||
export * from "../sharing/structs/taggedStruct";
|
||||
export * from "../sharing/sharedDiagnostics";
|
||||
export * from "../sharing/sharedNode";
|
||||
export * from "../sharing/sharedNodeAdapter";
|
||||
export * from "../sharing/sharedNodeArray";
|
||||
export * from "../sharing/sharedObjectAllocator";
|
||||
export * from "../sharing/sharedParserState";
|
||||
export * from "../sharing/sharedSymbol";
|
||||
export * from "../path";
|
||||
export * from "../diagnosticInformationMap.generated";
|
||||
export * from "../scanner";
|
||||
@ -68,6 +97,7 @@ export * from "../builder";
|
||||
export * from "../builderPublic";
|
||||
export * from "../resolutionCache";
|
||||
export * from "../watch";
|
||||
export * from "../worker";
|
||||
export * from "../watchPublic";
|
||||
export * from "../tsbuild";
|
||||
export * from "../tsbuildPublic";
|
||||
|
||||
@ -736,6 +736,7 @@ import {
|
||||
isUMDExportSymbol,
|
||||
isValidBigIntString,
|
||||
isValidESSymbolDeclaration,
|
||||
isValidNumberString,
|
||||
isValidTypeOnlyAliasUseSite,
|
||||
isValueSignatureDeclaration,
|
||||
isVariableDeclaration,
|
||||
@ -24322,17 +24323,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
sourceEnd.slice(sourceEnd.length - endLen) !== targetEnd.slice(targetEnd.length - endLen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the provided string can be parsed as a number.
|
||||
* @param s The string to test.
|
||||
* @param roundTripOnly Indicates the resulting number matches the input when converted back to a string.
|
||||
*/
|
||||
function isValidNumberString(s: string, roundTripOnly: boolean): boolean {
|
||||
if (s === "") return false;
|
||||
const n = +s;
|
||||
return isFinite(n) && (!roundTripOnly || "" + n === s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text a valid bigint string excluding a trailing `n`, but including a possible prefix `-`. Use `isValidBigIntString(text, roundTripOnly)` before calling this function.
|
||||
*/
|
||||
|
||||
@ -506,6 +506,18 @@ export const commonOptionsWithBuild: CommandLineOption[] = [
|
||||
description: Diagnostics.Set_the_language_of_the_messaging_from_TypeScript_This_does_not_affect_emit,
|
||||
defaultValueDescription: Diagnostics.Platform_specific
|
||||
},
|
||||
{
|
||||
name: "maxCpuCount",
|
||||
type: "number",
|
||||
category: Diagnostics.Command_line_Options,
|
||||
isCommandLineOnly: false,
|
||||
description: Diagnostics.Sets_the_maximum_number_of_worker_threads_to_use_during_build,
|
||||
defaultValueDescription: Diagnostics.Platform_specific,
|
||||
defaultIfValueMissing: () => sys.cpuCount?.(),
|
||||
extraValidation: value => typeof value === "number" && value <= 0 ?
|
||||
[Diagnostics.Option_maxCpuCount_must_be_set_to_1_or_greater] :
|
||||
undefined
|
||||
}
|
||||
];
|
||||
|
||||
/** @internal */
|
||||
@ -1882,16 +1894,26 @@ function parseOptionValue(
|
||||
}
|
||||
else {
|
||||
// Check to see if no argument was provided (e.g. "--locale" is the last command-line argument).
|
||||
if (!args[i] && opt.type !== "boolean") {
|
||||
if (!args[i] && opt.type !== "boolean" && !(opt.type === "number" && opt.defaultIfValueMissing)) {
|
||||
errors.push(createCompilerDiagnostic(diagnostics.optionTypeMismatchDiagnostic, opt.name, getCompilerOptionValueTypeString(opt)));
|
||||
}
|
||||
|
||||
if (args[i] !== "null") {
|
||||
switch (opt.type) {
|
||||
case "number":
|
||||
options[opt.name] = validateJsonOptionValue(opt, parseInt(args[i]), errors);
|
||||
i++;
|
||||
case "number": {
|
||||
let value: number | undefined;
|
||||
if (args[i]) {
|
||||
value = parseInt(args[i]);
|
||||
}
|
||||
if ((isNullOrUndefined(value) || isNaN(value) || !isFinite(value)) && opt.defaultIfValueMissing) {
|
||||
value = opt.defaultIfValueMissing();
|
||||
}
|
||||
else {
|
||||
i++;
|
||||
}
|
||||
options[opt.name] = validateJsonOptionValue(opt, value, errors);
|
||||
break;
|
||||
}
|
||||
case "boolean":
|
||||
// boolean flag has optional value true, false, others
|
||||
const optValue = args[i];
|
||||
|
||||
@ -5,13 +5,18 @@ import {
|
||||
Comparison,
|
||||
Debug,
|
||||
EqualityComparer,
|
||||
isTaggedStruct,
|
||||
isWhiteSpaceLike,
|
||||
MapLike,
|
||||
Queue,
|
||||
SharedNodeArray,
|
||||
SortedArray,
|
||||
SortedReadonlyArray,
|
||||
TextSpan,
|
||||
} from "./_namespaces/ts";
|
||||
import { SharedNodeBase } from "./sharing/sharedNode";
|
||||
import { isShareableNonPrimitive, isSharedArray } from "./sharing/structs/shareable";
|
||||
import { Tag } from "./sharing/structs/taggedStruct";
|
||||
|
||||
|
||||
/** @internal */
|
||||
@ -660,21 +665,22 @@ export function mapEntries<K1, V1, K2, V2>(map: ReadonlyMap<K1, V1> | undefined,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function some<T>(array: readonly T[] | undefined): array is readonly T[];
|
||||
export function some<T>(array: readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>> | undefined): array is readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>>;
|
||||
/** @internal */
|
||||
export function some<T>(array: readonly T[] | undefined, predicate: (value: T) => boolean): boolean;
|
||||
export function some<T>(array: readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>> | undefined, predicate: (value: T) => boolean): boolean;
|
||||
/** @internal */
|
||||
export function some<T>(array: readonly T[] | undefined, predicate?: (value: T) => boolean): boolean {
|
||||
export function some<T>(array: readonly T[] | SharedNodeArray<Extract<T, SharedNodeBase>> | undefined, predicate?: (value: T) => boolean): boolean {
|
||||
if (array) {
|
||||
if (predicate) {
|
||||
for (const v of array) {
|
||||
const iterable = isTaggedStruct(array, Tag.NodeArray) ? SharedNodeArray.values(array) : array;
|
||||
for (const v of iterable) {
|
||||
if (predicate(v)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
return array.length > 0;
|
||||
return (array instanceof SharedNodeArray ? array.items.length : array.length) > 0;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -1841,12 +1847,12 @@ export function isArray(value: any): value is readonly unknown[] {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function toArray<T>(value: T | T[]): T[];
|
||||
export function toArray<T>(value: T | T[] | SharedArray<Extract<T, Shareable>>): T[];
|
||||
/** @internal */
|
||||
export function toArray<T>(value: T | readonly T[]): readonly T[];
|
||||
export function toArray<T>(value: T | readonly T[] | SharedArray<Extract<T, Shareable>>): readonly T[];
|
||||
/** @internal */
|
||||
export function toArray<T>(value: T | T[]): T[] {
|
||||
return isArray(value) ? value : [value];
|
||||
export function toArray<T>(value: T | T[] | SharedArray<Extract<T, Shareable>>): T[] {
|
||||
return isSharedArray(value) ? Array.from(value) : isArray(value) ? value : [value];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1857,11 +1863,17 @@ export function toArray<T>(value: T | T[]): T[] {
|
||||
export function isString(text: unknown): text is string {
|
||||
return typeof text === "string";
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isNumber(x: unknown): x is number {
|
||||
return typeof x === "number";
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isNull(x: unknown): x is null { // eslint-disable-line no-null/no-null
|
||||
return x === null; // eslint-disable-line no-null/no-null
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut | undefined {
|
||||
return value !== undefined && test(value) ? value : undefined;
|
||||
@ -1871,7 +1883,24 @@ export function tryCast<TOut extends TIn, TIn = any>(value: TIn | undefined, tes
|
||||
export function cast<TOut extends TIn, TIn = any>(value: TIn | undefined, test: (value: TIn) => value is TOut): TOut {
|
||||
if (value !== undefined && test(value)) return value;
|
||||
|
||||
return Debug.fail(`Invalid cast. The supplied value ${value} did not pass the test '${Debug.getFunctionName(test)}'.`);
|
||||
let valueArg: unknown = value;
|
||||
if (isShareableNonPrimitive(valueArg)) {
|
||||
if ("__tag__" in valueArg) {
|
||||
const tag = valueArg.__tag__ as Tag;
|
||||
if (tag === Tag.Node) {
|
||||
const kind = (valueArg as SharedNodeBase).kind;
|
||||
valueArg = `[object SharedNodeBase(${Debug.formatSyntaxKind(kind)})]`;
|
||||
}
|
||||
else {
|
||||
valueArg = `[object TaggedStruct(${Debug.formatTag(tag)})]`;
|
||||
}
|
||||
}
|
||||
else {
|
||||
valueArg = "[object SharedStruct]";
|
||||
}
|
||||
}
|
||||
|
||||
return Debug.fail(`Invalid cast. The supplied value ${valueArg} did not pass the test '${Debug.getFunctionName(test)}'.`);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2883,3 +2912,38 @@ export function isNodeLikeSystem(): boolean {
|
||||
&& !(process as any).browser
|
||||
&& typeof module === "object";
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class Lazy<T> {
|
||||
private static circular = (): any => { throw new Error("Lazy instantiation was circular"); };
|
||||
|
||||
private _value: T | (() => T);
|
||||
private _hasValue: boolean;
|
||||
|
||||
constructor(valueFactory: () => T) {
|
||||
this._value = valueFactory;
|
||||
this._hasValue = false;
|
||||
}
|
||||
|
||||
get hasValue() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
get value() {
|
||||
if (this._hasValue) {
|
||||
return this._value as T;
|
||||
}
|
||||
const valueFactory = this._value as (() => T);
|
||||
try {
|
||||
this._value = Lazy.circular;
|
||||
this._value = valueFactory();
|
||||
this._hasValue = true;
|
||||
return this._value;
|
||||
}
|
||||
finally {
|
||||
if (!this._hasValue) {
|
||||
this._value = valueFactory;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,6 +93,8 @@ import {
|
||||
VarianceFlags,
|
||||
zipWith,
|
||||
} from "./_namespaces/ts";
|
||||
import { isShareableNonPrimitive } from "./sharing/structs/shareable";
|
||||
import { Tag } from "./sharing/structs/taggedStruct";
|
||||
|
||||
/** @internal */
|
||||
export enum LogLevel {
|
||||
@ -106,6 +108,7 @@ export enum LogLevel {
|
||||
/** @internal */
|
||||
export interface LoggingHost {
|
||||
log(level: LogLevel, s: string): void;
|
||||
format?(...args: readonly unknown[]): string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -123,31 +126,41 @@ export namespace Debug {
|
||||
return currentLogLevel <= level;
|
||||
}
|
||||
|
||||
function logMessage(level: LogLevel, s: string): void {
|
||||
function logMessage(level: LogLevel, ...args: unknown[]): void {
|
||||
if (loggingHost && shouldLog(level)) {
|
||||
const s = loggingHost.format?.(...args) ?? args.join(" ");
|
||||
loggingHost.log(level, s);
|
||||
}
|
||||
}
|
||||
|
||||
export function log(s: string): void {
|
||||
logMessage(LogLevel.Info, s);
|
||||
export function format(...args: unknown[]) {
|
||||
const text = loggingHost?.format?.(...args);
|
||||
if (text !== undefined) {
|
||||
return text;
|
||||
}
|
||||
args = args.map(arg => isShareableNonPrimitive(arg) ? "[object SharedStruct]" : arg);
|
||||
return args.join(" ");
|
||||
}
|
||||
|
||||
export function log(...args: unknown[]): void {
|
||||
logMessage(LogLevel.Info, ...args);
|
||||
}
|
||||
|
||||
export namespace log {
|
||||
export function error(s: string): void {
|
||||
logMessage(LogLevel.Error, s);
|
||||
export function error(...args: unknown[]): void {
|
||||
logMessage(LogLevel.Error, ...args);
|
||||
}
|
||||
|
||||
export function warn(s: string): void {
|
||||
logMessage(LogLevel.Warning, s);
|
||||
export function warn(...args: unknown[]): void {
|
||||
logMessage(LogLevel.Warning, ...args);
|
||||
}
|
||||
|
||||
export function log(s: string): void {
|
||||
logMessage(LogLevel.Info, s);
|
||||
export function log(...args: unknown[]): void {
|
||||
logMessage(LogLevel.Info, ...args);
|
||||
}
|
||||
|
||||
export function trace(s: string): void {
|
||||
logMessage(LogLevel.Verbose, s);
|
||||
export function trace(...args: unknown[]): void {
|
||||
logMessage(LogLevel.Verbose, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,13 +205,18 @@ export namespace Debug {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function captureStackTrace<T extends object>(e: T, stackCrawlMark: AnyFunction) {
|
||||
if ((Error as any).captureStackTrace) {
|
||||
(Error as any).captureStackTrace(e, stackCrawlMark || fail);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
export function fail(message?: string, stackCrawlMark?: AnyFunction): never {
|
||||
// eslint-disable-next-line no-debugger
|
||||
debugger;
|
||||
const e = new Error(message ? `Debug Failure. ${message}` : "Debug Failure.");
|
||||
if ((Error as any).captureStackTrace) {
|
||||
(Error as any).captureStackTrace(e, stackCrawlMark || fail);
|
||||
}
|
||||
captureStackTrace(e, stackCrawlMark ?? fail);
|
||||
throw e;
|
||||
}
|
||||
|
||||
@ -433,6 +451,10 @@ export namespace Debug {
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export function formatTag(tag: Tag | undefined): string {
|
||||
return formatEnum(tag, (ts as any).Tag, /*isFlags*/ false);
|
||||
}
|
||||
|
||||
export function formatSyntaxKind(kind: SyntaxKind | undefined): string {
|
||||
return formatEnum(kind, (ts as any).SyntaxKind, /*isFlags*/ false);
|
||||
}
|
||||
@ -548,7 +570,7 @@ export namespace Debug {
|
||||
}
|
||||
}
|
||||
|
||||
let nodeArrayProto: NodeArray<Node> | undefined;
|
||||
const weakNodeArrayPrototype = new WeakMap<object, NodeArray<any>>();
|
||||
|
||||
function attachNodeArrayDebugInfoWorker(array: NodeArray<Node>) {
|
||||
if (!("__tsDebuggerDisplay" in array)) { // eslint-disable-line local/no-in-operator
|
||||
@ -574,8 +596,11 @@ export namespace Debug {
|
||||
if (typeof Object.setPrototypeOf === "function") {
|
||||
// if we're in es2015, attach the method to a shared prototype for `NodeArray`
|
||||
// so the method doesn't show up in the watch window.
|
||||
const prototype = Object.getPrototypeOf(array);
|
||||
let nodeArrayProto = weakNodeArrayPrototype.get(prototype);
|
||||
if (!nodeArrayProto) {
|
||||
nodeArrayProto = Object.create(Array.prototype) as NodeArray<Node>;
|
||||
nodeArrayProto = Object.create(prototype) as NodeArray<Node>;
|
||||
weakNodeArrayPrototype.set(prototype, nodeArrayProto);
|
||||
attachNodeArrayDebugInfoWorker(nodeArrayProto);
|
||||
}
|
||||
Object.setPrototypeOf(array, nodeArrayProto);
|
||||
|
||||
@ -4373,6 +4373,10 @@
|
||||
"category": "Error",
|
||||
"code": 5110
|
||||
},
|
||||
"Option 'maxCpuCount' must be set to 1 or greater.": {
|
||||
"category": "Error",
|
||||
"code": 5111
|
||||
},
|
||||
|
||||
"Generates a sourcemap for each corresponding '.d.ts' file.": {
|
||||
"category": "Message",
|
||||
@ -6151,6 +6155,10 @@
|
||||
"category": "Message",
|
||||
"code": 6804
|
||||
},
|
||||
"Sets the maximum number of worker threads to use during build.": {
|
||||
"category": "Message",
|
||||
"code": 6805
|
||||
},
|
||||
|
||||
"one of:": {
|
||||
"category": "Message",
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import {
|
||||
Node,
|
||||
NodeArray,
|
||||
objectAllocator,
|
||||
SyntaxKind,
|
||||
SyntaxKind
|
||||
} from "../_namespaces/ts";
|
||||
|
||||
/**
|
||||
@ -11,6 +12,7 @@ import {
|
||||
* @internal
|
||||
*/
|
||||
export interface BaseNodeFactory {
|
||||
createBaseNodeArray<T extends Node>(items: readonly T[], hasTrailingComma?: boolean): NodeArray<T>;
|
||||
createBaseSourceFileNode(kind: SyntaxKind.SourceFile): Node;
|
||||
createBaseIdentifierNode(kind: SyntaxKind.Identifier): Node;
|
||||
createBasePrivateIdentifierNode(kind: SyntaxKind.PrivateIdentifier): Node;
|
||||
@ -24,6 +26,7 @@ export interface BaseNodeFactory {
|
||||
* @internal
|
||||
*/
|
||||
export function createBaseNodeFactory(): BaseNodeFactory {
|
||||
let NodeArrayContructor: new <T extends Node>(elements: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
|
||||
let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
|
||||
let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
|
||||
let IdentifierConstructor: new (kind: SyntaxKind.Identifier, pos: number, end: number) => Node;
|
||||
@ -35,7 +38,8 @@ export function createBaseNodeFactory(): BaseNodeFactory {
|
||||
createBaseIdentifierNode,
|
||||
createBasePrivateIdentifierNode,
|
||||
createBaseTokenNode,
|
||||
createBaseNode
|
||||
createBaseNode,
|
||||
createBaseNodeArray,
|
||||
};
|
||||
|
||||
function createBaseSourceFileNode(kind: SyntaxKind.SourceFile): Node {
|
||||
@ -57,4 +61,8 @@ export function createBaseNodeFactory(): BaseNodeFactory {
|
||||
function createBaseNode(kind: SyntaxKind): Node {
|
||||
return new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, /*pos*/ -1, /*end*/ -1);
|
||||
}
|
||||
|
||||
function createBaseNodeArray<T extends Node>(elements: readonly T[], hasTrailingComma?: boolean): NodeArray<T> {
|
||||
return new (NodeArrayContructor || (NodeArrayContructor = objectAllocator.getNodeArrayConstructor()))(elements, hasTrailingComma);
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,6 +475,9 @@ import {
|
||||
WithStatement,
|
||||
YieldExpression,
|
||||
} from "../_namespaces/ts";
|
||||
import { SharedNodeBase } from "../sharing/sharedNode";
|
||||
import { SharedNodeArray } from "../sharing/sharedNodeArray";
|
||||
import { isShareableNonPrimitive } from "../sharing/structs/shareable";
|
||||
|
||||
let nextAutoGenerateId = 0;
|
||||
|
||||
@ -903,7 +906,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
createJsxText,
|
||||
updateJsxText,
|
||||
createJsxOpeningFragment,
|
||||
createJsxJsxClosingFragment,
|
||||
createJsxJsxClosingFragment: createJsxClosingFragment,
|
||||
createJsxClosingFragment,
|
||||
updateJsxFragment,
|
||||
createJsxAttribute,
|
||||
updateJsxAttribute,
|
||||
@ -1053,12 +1057,10 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
// This *was* a `NodeArray`, but the `hasTrailingComma` option differs. Recreate the
|
||||
// array with the same elements, text range, and transform flags but with the updated
|
||||
// value for `hasTrailingComma`
|
||||
const array = elements.slice() as MutableNodeArray<T>;
|
||||
const array = baseFactory.createBaseNodeArray(elements.slice(), hasTrailingComma) as MutableNodeArray<T>;
|
||||
array.pos = elements.pos;
|
||||
array.end = elements.end;
|
||||
array.hasTrailingComma = hasTrailingComma;
|
||||
array.transformFlags = elements.transformFlags;
|
||||
Debug.attachNodeArrayDebugInfo(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
@ -1066,13 +1068,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
// repeatedly calling push(), the list may not have the optimal memory layout. We invoke slice() for
|
||||
// small arrays (1 to 4 elements) to give the VM a chance to allocate an optimal representation.
|
||||
const length = elements.length;
|
||||
const array = (length >= 1 && length <= 4 ? elements.slice() : elements) as MutableNodeArray<T>;
|
||||
array.pos = -1;
|
||||
array.end = -1;
|
||||
array.hasTrailingComma = !!hasTrailingComma;
|
||||
array.transformFlags = TransformFlags.None;
|
||||
const array = baseFactory.createBaseNodeArray(length >= 1 && length <= 4 ? elements.slice() : elements, !!hasTrailingComma) as MutableNodeArray<T>;
|
||||
aggregateChildrenFlags(array);
|
||||
Debug.attachNodeArrayDebugInfo(array);
|
||||
return array;
|
||||
}
|
||||
|
||||
@ -3768,6 +3765,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
node.transformFlags |=
|
||||
propagateChildrenFlags(node.modifiers) |
|
||||
propagateChildFlags(node.declarationList);
|
||||
|
||||
if (modifiersToFlags(node.modifiers) & ModifierFlags.Ambient) {
|
||||
node.transformFlags = TransformFlags.ContainsTypeScript;
|
||||
}
|
||||
@ -5591,7 +5589,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
}
|
||||
|
||||
// @api
|
||||
function createJsxJsxClosingFragment() {
|
||||
function createJsxClosingFragment() {
|
||||
const node = createBaseNode<JsxClosingFragment>(SyntaxKind.JsxClosingFragment);
|
||||
node.transformFlags |= TransformFlags.ContainsJsx;
|
||||
return node;
|
||||
@ -5931,35 +5929,37 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
propagateChildrenFlags(node.statements) |
|
||||
propagateChildFlags(node.endOfFileToken);
|
||||
|
||||
node.locals = undefined; // initialized by binder (LocalsContainer)
|
||||
node.nextContainer = undefined; // initialized by binder (LocalsContainer)
|
||||
node.endFlowNode = undefined;
|
||||
if (!isShareableNonPrimitive(node)) {
|
||||
node.locals = undefined; // initialized by binder (LocalsContainer)
|
||||
node.nextContainer = undefined; // initialized by binder (LocalsContainer)
|
||||
node.endFlowNode = undefined;
|
||||
node.nodeCount = 0;
|
||||
node.identifierCount = 0;
|
||||
node.symbolCount = 0;
|
||||
node.parseDiagnostics = undefined!;
|
||||
node.bindDiagnostics = undefined!;
|
||||
node.bindSuggestionDiagnostics = undefined;
|
||||
node.lineMap = undefined!;
|
||||
node.externalModuleIndicator = undefined;
|
||||
node.setExternalModuleIndicator = undefined;
|
||||
node.pragmas = undefined!;
|
||||
node.checkJsDirective = undefined;
|
||||
node.referencedFiles = undefined!;
|
||||
node.typeReferenceDirectives = undefined!;
|
||||
node.libReferenceDirectives = undefined!;
|
||||
node.amdDependencies = undefined!;
|
||||
node.commentDirectives = undefined;
|
||||
node.identifiers = undefined!;
|
||||
node.packageJsonLocations = undefined;
|
||||
node.packageJsonScope = undefined;
|
||||
node.imports = undefined!;
|
||||
node.moduleAugmentations = undefined!;
|
||||
node.ambientModuleNames = undefined!;
|
||||
node.resolvedModules = undefined;
|
||||
node.classifiableNames = undefined;
|
||||
node.impliedNodeFormat = undefined;
|
||||
}
|
||||
|
||||
node.nodeCount = 0;
|
||||
node.identifierCount = 0;
|
||||
node.symbolCount = 0;
|
||||
node.parseDiagnostics = undefined!;
|
||||
node.bindDiagnostics = undefined!;
|
||||
node.bindSuggestionDiagnostics = undefined;
|
||||
node.lineMap = undefined!;
|
||||
node.externalModuleIndicator = undefined;
|
||||
node.setExternalModuleIndicator = undefined;
|
||||
node.pragmas = undefined!;
|
||||
node.checkJsDirective = undefined;
|
||||
node.referencedFiles = undefined!;
|
||||
node.typeReferenceDirectives = undefined!;
|
||||
node.libReferenceDirectives = undefined!;
|
||||
node.amdDependencies = undefined!;
|
||||
node.commentDirectives = undefined;
|
||||
node.identifiers = undefined!;
|
||||
node.packageJsonLocations = undefined;
|
||||
node.packageJsonScope = undefined;
|
||||
node.imports = undefined!;
|
||||
node.moduleAugmentations = undefined!;
|
||||
node.ambientModuleNames = undefined!;
|
||||
node.resolvedModules = undefined;
|
||||
node.classifiableNames = undefined;
|
||||
node.impliedNodeFormat = undefined;
|
||||
return node;
|
||||
}
|
||||
|
||||
@ -5998,7 +5998,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
|
||||
const node = baseFactory.createBaseSourceFileNode(SyntaxKind.SourceFile) as Mutable<SourceFile>;
|
||||
node.flags |= source.flags & ~NodeFlags.Synthesized;
|
||||
for (const p in source) {
|
||||
if (hasProperty(node, p) || !hasProperty(source, p)) {
|
||||
if (hasProperty(node, p) || !hasProperty(source, p) || p === "__tag__" || p === "__hash__" || p === "__shared__" || p === "__sharedCache__") {
|
||||
continue;
|
||||
}
|
||||
if (p === "emitNode") {
|
||||
@ -7182,10 +7182,11 @@ function propagateChildrenFlags(children: NodeArray<Node> | undefined): Transfor
|
||||
return children ? children.transformFlags : TransformFlags.None;
|
||||
}
|
||||
|
||||
function aggregateChildrenFlags(children: MutableNodeArray<Node>) {
|
||||
function aggregateChildrenFlags(children: MutableNodeArray<Node> | SharedNodeArray<SharedNodeBase>) {
|
||||
let subtreeFlags = TransformFlags.None;
|
||||
for (const child of children) {
|
||||
subtreeFlags |= propagateChildFlags(child);
|
||||
const values = children instanceof SharedNodeArray ? Array.from(children.items) : children;
|
||||
for (const child of values) {
|
||||
subtreeFlags |= propagateChildFlags(child as Node);
|
||||
}
|
||||
children.transformFlags = subtreeFlags;
|
||||
}
|
||||
@ -7280,6 +7281,7 @@ const syntheticFactory: BaseNodeFactory = {
|
||||
createBasePrivateIdentifierNode: kind => makeSynthetic(baseFactory.createBasePrivateIdentifierNode(kind)),
|
||||
createBaseTokenNode: kind => makeSynthetic(baseFactory.createBaseTokenNode(kind)),
|
||||
createBaseNode: kind => makeSynthetic(baseFactory.createBaseNode(kind)),
|
||||
createBaseNodeArray: baseFactory.createBaseNodeArray,
|
||||
};
|
||||
|
||||
export const factory = createNodeFactory(NodeFactoryFlags.NoIndentationOnFreshPropertyAccess, syntheticFactory);
|
||||
|
||||
@ -2,9 +2,11 @@ import {
|
||||
AccessorDeclaration,
|
||||
addRange,
|
||||
addRelatedInfo,
|
||||
AmdDependency,
|
||||
append,
|
||||
ArrayBindingElement,
|
||||
ArrayBindingPattern,
|
||||
arrayFrom,
|
||||
ArrayLiteralExpression,
|
||||
ArrayTypeNode,
|
||||
ArrowFunction,
|
||||
@ -34,6 +36,7 @@ import {
|
||||
CaseOrDefaultClause,
|
||||
CatchClause,
|
||||
CharacterCodes,
|
||||
CheckJsDirective,
|
||||
ClassDeclaration,
|
||||
ClassElement,
|
||||
ClassExpression,
|
||||
@ -65,8 +68,11 @@ import {
|
||||
Diagnostic,
|
||||
DiagnosticArguments,
|
||||
DiagnosticMessage,
|
||||
DiagnosticMessageChain,
|
||||
DiagnosticRelatedInformation,
|
||||
Diagnostics,
|
||||
DiagnosticWithDetachedLocation,
|
||||
DiagnosticWithLocation,
|
||||
DoStatement,
|
||||
DotDotDotToken,
|
||||
ElementAccessExpression,
|
||||
@ -88,6 +94,7 @@ import {
|
||||
ExternalModuleReference,
|
||||
fileExtensionIs,
|
||||
fileExtensionIsOneOf,
|
||||
FileReference,
|
||||
findIndex,
|
||||
firstOrUndefined,
|
||||
forEach,
|
||||
@ -113,8 +120,10 @@ import {
|
||||
HasJSDoc,
|
||||
hasJSDocNodes,
|
||||
HasModifiers,
|
||||
hasProperty,
|
||||
HeritageClause,
|
||||
Identifier,
|
||||
identity,
|
||||
idText,
|
||||
IfStatement,
|
||||
ImportClause,
|
||||
@ -238,7 +247,6 @@ import {
|
||||
LiteralExpression,
|
||||
LiteralLikeNode,
|
||||
LiteralTypeNode,
|
||||
map,
|
||||
mapDefined,
|
||||
MappedTypeNode,
|
||||
MemberExpression,
|
||||
@ -330,6 +338,7 @@ import {
|
||||
setTextRangePos,
|
||||
setTextRangePosEnd,
|
||||
setTextRangePosWidth,
|
||||
SharedMap,
|
||||
ShorthandPropertyAssignment,
|
||||
skipTrivia,
|
||||
some,
|
||||
@ -398,6 +407,11 @@ import {
|
||||
YieldExpression,
|
||||
} from "./_namespaces/ts";
|
||||
import * as performance from "./_namespaces/ts.performance";
|
||||
import { SharedDiagnostic, SharedDiagnosticMessageChain, SharedDiagnosticRelatedInformation, SharedDiagnosticWithLocation } from "./sharing/sharedDiagnostics";
|
||||
import { SharedAmdDependency, SharedCheckJsDirective, SharedCommentDirective, SharedCommentRange, SharedFileReference, SharedNodeBase, SharedPragma, SharedPragmaArguments, SharedPragmaSpan, SharedSourceFile, SharedTextRange } from "./sharing/sharedNode";
|
||||
import { SharedNodeArray } from "./sharing/sharedNodeArray";
|
||||
import { isShareableNonPrimitive } from "./sharing/structs/shareable";
|
||||
import { Tag, TaggedStruct } from "./sharing/structs/taggedStruct";
|
||||
|
||||
const enum SignatureFlags {
|
||||
None = 0,
|
||||
@ -414,6 +428,7 @@ const enum SpeculationKind {
|
||||
Reparse
|
||||
}
|
||||
|
||||
let NodeArrayConstructor: new <T extends Node>(elements: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
|
||||
let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
|
||||
let TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
|
||||
let IdentifierConstructor: new (kind: SyntaxKind.Identifier, pos: number, end: number) => Node;
|
||||
@ -431,6 +446,7 @@ export const parseBaseNodeFactory: BaseNodeFactory = {
|
||||
createBasePrivateIdentifierNode: kind => new (PrivateIdentifierConstructor || (PrivateIdentifierConstructor = objectAllocator.getPrivateIdentifierConstructor()))(kind, -1, -1),
|
||||
createBaseTokenNode: kind => new (TokenConstructor || (TokenConstructor = objectAllocator.getTokenConstructor()))(kind, -1, -1),
|
||||
createBaseNode: kind => new (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor()))(kind, -1, -1),
|
||||
createBaseNodeArray: (elements, hasTrailingComma?) => new (NodeArrayConstructor || (NodeArrayConstructor = objectAllocator.getNodeArrayConstructor()))(elements, hasTrailingComma),
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
@ -1265,16 +1281,17 @@ export function forEachChildRecursively<T>(rootNode: Node, cbNode: (node: Node,
|
||||
while (queue.length !== 0) {
|
||||
const current = queue.pop()!;
|
||||
const parent = parents.pop()!;
|
||||
if (isArray(current)) {
|
||||
if (isArray(current) || current instanceof SharedNodeArray) {
|
||||
const currentArray = current instanceof SharedNodeArray ? arrayFrom(SharedNodeArray.values(current)) as unknown as NodeArray<Node> : current;
|
||||
if (cbNodes) {
|
||||
const res = cbNodes(current, parent);
|
||||
const res = cbNodes(currentArray, parent);
|
||||
if (res) {
|
||||
if (res === "skip") continue;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
for (let i = current.length - 1; i >= 0; --i) {
|
||||
queue.push(current[i]);
|
||||
for (let i = currentArray.length - 1; i >= 0; --i) {
|
||||
queue.push(currentArray[i]);
|
||||
parents.push(parent);
|
||||
}
|
||||
}
|
||||
@ -1327,6 +1344,9 @@ function setExternalModuleIndicator(sourceFile: SourceFile) {
|
||||
sourceFile.externalModuleIndicator = isFileProbablyExternalModule(sourceFile);
|
||||
}
|
||||
|
||||
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile;
|
||||
/** @internal */
|
||||
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes?: boolean, scriptKind?: ScriptKind): SourceFile;
|
||||
export function createSourceFile(fileName: string, sourceText: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
|
||||
tracing?.push(tracing.Phase.Parse, "createSourceFile", { path: fileName }, /*separateBeginAndEnd*/ true);
|
||||
performance.mark("beforeParse");
|
||||
@ -1424,6 +1444,7 @@ namespace Parser {
|
||||
var disallowInAndDecoratorContext = NodeFlags.DisallowInContext | NodeFlags.DecoratorContext;
|
||||
|
||||
// capture constructors in 'initializeState' to avoid null checks
|
||||
let NodeArrayConstructor: new <T extends Node>(elements: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
|
||||
var NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
|
||||
var TokenConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node;
|
||||
var IdentifierConstructor: new (kind: SyntaxKind.Identifier, pos: number, end: number) => Identifier;
|
||||
@ -1442,7 +1463,8 @@ namespace Parser {
|
||||
createBaseIdentifierNode: kind => countNode(new IdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)),
|
||||
createBasePrivateIdentifierNode: kind => countNode(new PrivateIdentifierConstructor(kind, /*pos*/ 0, /*end*/ 0)),
|
||||
createBaseTokenNode: kind => countNode(new TokenConstructor(kind, /*pos*/ 0, /*end*/ 0)),
|
||||
createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0))
|
||||
createBaseNode: kind => countNode(new NodeConstructor(kind, /*pos*/ 0, /*end*/ 0)),
|
||||
createBaseNodeArray: (elements, hasTrailingComma?) => new NodeArrayConstructor(elements, hasTrailingComma),
|
||||
};
|
||||
|
||||
var factory = createNodeFactory(NodeFactoryFlags.NoParenthesizerRules | NodeFactoryFlags.NoNodeConverters | NodeFactoryFlags.NoOriginalNode, baseNodeFactory);
|
||||
@ -1702,6 +1724,7 @@ namespace Parser {
|
||||
}
|
||||
|
||||
function initializeState(_fileName: string, _sourceText: string, _languageVersion: ScriptTarget, _syntaxCursor: IncrementalParser.SyntaxCursor | undefined, _scriptKind: ScriptKind) {
|
||||
NodeArrayConstructor = objectAllocator.getNodeArrayConstructor();
|
||||
NodeConstructor = objectAllocator.getNodeConstructor();
|
||||
TokenConstructor = objectAllocator.getTokenConstructor();
|
||||
IdentifierConstructor = objectAllocator.getIdentifierConstructor();
|
||||
@ -1765,6 +1788,69 @@ namespace Parser {
|
||||
topLevel = true;
|
||||
}
|
||||
|
||||
function shareCommentDirective(directive: CommentDirective): SharedCommentDirective {
|
||||
const sharedRange = new SharedTextRange(directive.range.pos, directive.range.end);
|
||||
const sharedDirective = new SharedCommentDirective(sharedRange, directive.type);
|
||||
return sharedDirective;
|
||||
}
|
||||
|
||||
function shareDiagnosticMessageChain(messageChain: DiagnosticMessageChain): SharedDiagnosticMessageChain {
|
||||
return new SharedDiagnosticMessageChain(
|
||||
messageChain.messageText,
|
||||
messageChain.category,
|
||||
messageChain.code,
|
||||
shareArray(messageChain.next, shareDiagnosticMessageChain)
|
||||
);
|
||||
}
|
||||
|
||||
function shareDiagnosticRelatedInformation(related: Diagnostic | DiagnosticRelatedInformation): SharedDiagnostic | SharedDiagnosticRelatedInformation {
|
||||
if (hasProperty(related, "reportsUnnecessary") ||
|
||||
hasProperty(related, "reportsDeprecated") ||
|
||||
hasProperty(related, "source") ||
|
||||
hasProperty(related, "relatedInformation") ||
|
||||
hasProperty(related, "skippedOn")) {
|
||||
return shareDiagnostic(related);
|
||||
}
|
||||
Debug.assert(related.file === undefined || isShareableNonPrimitive(related.file));
|
||||
return new SharedDiagnosticRelatedInformation(
|
||||
related.category,
|
||||
related.code,
|
||||
related.file as unknown as SharedSourceFile | undefined,
|
||||
related.start,
|
||||
related.length,
|
||||
typeof related.messageText === "string" ? related.messageText : shareDiagnosticMessageChain(related.messageText)
|
||||
);
|
||||
}
|
||||
|
||||
function shareDiagnostic(diagnostic: DiagnosticWithLocation): SharedDiagnosticWithLocation;
|
||||
function shareDiagnostic(diagnostic: Diagnostic): SharedDiagnostic;
|
||||
function shareDiagnostic(diagnostic: Diagnostic): SharedDiagnostic {
|
||||
Debug.assert(diagnostic.file === undefined || isShareableNonPrimitive(diagnostic.file));
|
||||
const sharedDiagnostic = new SharedDiagnostic(
|
||||
diagnostic.category,
|
||||
diagnostic.code,
|
||||
diagnostic.file as unknown as SharedSourceFile | undefined,
|
||||
diagnostic.start,
|
||||
diagnostic.length,
|
||||
typeof diagnostic.messageText === "string" ? diagnostic.messageText : shareDiagnosticMessageChain(diagnostic.messageText)
|
||||
);
|
||||
|
||||
sharedDiagnostic.reportsUnnecessary = diagnostic.reportsUnnecessary === undefined ? undefined : !!diagnostic.reportsUnnecessary;
|
||||
sharedDiagnostic.reportsDeprecated = diagnostic.reportsDeprecated === undefined ? undefined : !!diagnostic.reportsDeprecated;
|
||||
sharedDiagnostic.source = diagnostic.source;
|
||||
sharedDiagnostic.relatedInformation = shareArray(diagnostic.relatedInformation, shareDiagnosticRelatedInformation);
|
||||
sharedDiagnostic.skippedOn = diagnostic.skippedOn;
|
||||
return sharedDiagnostic;
|
||||
}
|
||||
|
||||
function shareMap<K, V, KS extends Shareable, VS extends Shareable>(map: ReadonlyMap<K, V>, shareKey: (key: K) => KS, shareValue: (value: V) => VS) {
|
||||
const sharedMap = new SharedMap<KS, VS>(map.size);
|
||||
for (const [key, value] of map) {
|
||||
SharedMap.set(sharedMap, shareKey(key), shareValue(value));
|
||||
}
|
||||
return sharedMap;
|
||||
}
|
||||
|
||||
function parseSourceFileWorker(languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind, setExternalModuleIndicator: (file: SourceFile) => void): SourceFile {
|
||||
const isDeclarationFile = isDeclarationFileName(fileName);
|
||||
if (isDeclarationFile) {
|
||||
@ -1787,13 +1873,23 @@ namespace Parser {
|
||||
processCommentPragmas(sourceFile as {} as PragmaContext, sourceText);
|
||||
processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic);
|
||||
|
||||
sourceFile.commentDirectives = scanner.getCommentDirectives();
|
||||
sourceFile.nodeCount = nodeCount;
|
||||
sourceFile.identifierCount = identifierCount;
|
||||
sourceFile.identifiers = identifiers;
|
||||
sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile);
|
||||
if (jsDocDiagnostics) {
|
||||
sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile);
|
||||
if (isShareableNonPrimitive(sourceFile)) {
|
||||
sourceFile.identifiers = shareMap(identifiers, identity, identity) as unknown as Map<string, string>;
|
||||
sourceFile.commentDirectives = shareArray(scanner.getCommentDirectives(), shareCommentDirective) as unknown as CommentDirective[];
|
||||
sourceFile.parseDiagnostics = shareArray(attachFileToDiagnostics(parseDiagnostics, sourceFile), shareDiagnostic) as unknown as DiagnosticWithLocation[];
|
||||
if (jsDocDiagnostics) {
|
||||
sourceFile.jsDocDiagnostics = shareArray(attachFileToDiagnostics(jsDocDiagnostics, sourceFile), shareDiagnostic) as unknown as DiagnosticWithLocation[];
|
||||
}
|
||||
}
|
||||
else {
|
||||
sourceFile.identifiers = identifiers;
|
||||
sourceFile.commentDirectives = scanner.getCommentDirectives();
|
||||
sourceFile.parseDiagnostics = attachFileToDiagnostics(parseDiagnostics, sourceFile);
|
||||
if (jsDocDiagnostics) {
|
||||
sourceFile.jsDocDiagnostics = attachFileToDiagnostics(jsDocDiagnostics, sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
if (setParentNodes) {
|
||||
@ -1815,7 +1911,14 @@ namespace Parser {
|
||||
|
||||
Debug.assert(!node.jsDoc); // Should only be called once per node
|
||||
const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
|
||||
if (jsDoc.length) node.jsDoc = jsDoc;
|
||||
if (jsDoc.length) {
|
||||
if (node instanceof SharedNodeBase) {
|
||||
node.jsDoc = shareArray(jsDoc, n => n as unknown as SharedNodeBase) as unknown as JSDoc[];
|
||||
}
|
||||
else {
|
||||
node.jsDoc = jsDoc;
|
||||
}
|
||||
}
|
||||
if (hasDeprecatedTag) {
|
||||
hasDeprecatedTag = false;
|
||||
(node as Mutable<T>).flags |= NodeFlags.Deprecated;
|
||||
@ -1965,7 +2068,7 @@ namespace Parser {
|
||||
|
||||
function setFields(sourceFile: SourceFile) {
|
||||
sourceFile.text = sourceText;
|
||||
sourceFile.bindDiagnostics = [];
|
||||
sourceFile.bindDiagnostics = isShareableNonPrimitive(sourceFile) ? undefined! : [];
|
||||
sourceFile.bindSuggestionDiagnostics = undefined;
|
||||
sourceFile.languageVersion = languageVersion;
|
||||
sourceFile.fileName = fileName;
|
||||
@ -1974,7 +2077,9 @@ namespace Parser {
|
||||
sourceFile.scriptKind = scriptKind;
|
||||
|
||||
setExternalModuleIndicator(sourceFile);
|
||||
sourceFile.setExternalModuleIndicator = setExternalModuleIndicator;
|
||||
if (!isShareableNonPrimitive(sourceFile)) {
|
||||
sourceFile.setExternalModuleIndicator = setExternalModuleIndicator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7366,14 +7471,17 @@ namespace Parser {
|
||||
const pos = getNodePos();
|
||||
const hasJSDoc = hasPrecedingJSDocComment();
|
||||
const modifiers = parseModifiers(/*allowDecorators*/ true);
|
||||
const isAmbient = some(modifiers, isDeclareModifier);
|
||||
const modifiersArray =
|
||||
modifiers instanceof SharedNodeArray ? arrayFrom(SharedNodeArray.values(modifiers) as IterableIterator<Modifier>) :
|
||||
modifiers;
|
||||
const isAmbient = some(modifiersArray, isDeclareModifier);
|
||||
if (isAmbient) {
|
||||
const node = tryReuseAmbientDeclaration(pos);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
|
||||
for (const m of modifiers!) {
|
||||
for (const m of modifiersArray!) {
|
||||
(m as Mutable<Node>).flags |= NodeFlags.Ambient;
|
||||
}
|
||||
return doInsideOfContext(NodeFlags.Ambient, () => parseDeclarationWorker(pos, hasJSDoc, modifiers));
|
||||
@ -9797,7 +9905,7 @@ namespace IncrementalParser {
|
||||
const result = Parser.parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, syntaxCursor, /*setParentNodes*/ true, sourceFile.scriptKind, sourceFile.setExternalModuleIndicator);
|
||||
result.commentDirectives = getNewCommentDirectives(
|
||||
sourceFile.commentDirectives,
|
||||
result.commentDirectives,
|
||||
result.commentDirectives?.slice(),
|
||||
changeRange.span.start,
|
||||
textSpanEnd(changeRange.span),
|
||||
delta,
|
||||
@ -9810,7 +9918,7 @@ namespace IncrementalParser {
|
||||
}
|
||||
|
||||
function getNewCommentDirectives(
|
||||
oldDirectives: CommentDirective[] | undefined,
|
||||
oldDirectives: readonly CommentDirective[] | undefined,
|
||||
newDirectives: CommentDirective[] | undefined,
|
||||
changeStart: number,
|
||||
changeRangeOldEnd: number,
|
||||
@ -10374,41 +10482,131 @@ export function processCommentPragmas(context: PragmaContext, sourceText: string
|
||||
extractPragmas(pragmas, range, comment);
|
||||
}
|
||||
|
||||
context.pragmas = new Map() as PragmaMap;
|
||||
const map = new Map() as PragmaMap;
|
||||
for (const pragma of pragmas) {
|
||||
if (context.pragmas.has(pragma.name)) {
|
||||
const currentValue = context.pragmas.get(pragma.name);
|
||||
if (map.has(pragma.name)) {
|
||||
const currentValue = map.get(pragma.name);
|
||||
if (currentValue instanceof Array) {
|
||||
currentValue.push(pragma.args);
|
||||
}
|
||||
else {
|
||||
context.pragmas.set(pragma.name, [currentValue, pragma.args]);
|
||||
map.set(pragma.name, [currentValue, pragma.args]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
context.pragmas.set(pragma.name, pragma.args);
|
||||
map.set(pragma.name, pragma.args);
|
||||
}
|
||||
|
||||
if (isShareableNonPrimitive(context)) {
|
||||
const sharedMap = new SharedMap<string, SharedArray<SharedPragma> | SharedPragma>();
|
||||
for (const [key, value] of map) {
|
||||
if (isArray(value)) {
|
||||
SharedMap.set(sharedMap, key, shareArray(value, sharePragma));
|
||||
}
|
||||
else {
|
||||
SharedMap.set(sharedMap, key, sharePragma(value));
|
||||
}
|
||||
}
|
||||
context.pragmas = sharedMap as unknown as PragmaMap;
|
||||
}
|
||||
else {
|
||||
context.pragmas = map as PragmaMap;
|
||||
}
|
||||
}
|
||||
|
||||
function sharePragmaValue<T extends string | { pos: number, end: number, value: string } | undefined>(span: T): T extends string ? string : T extends object ? SharedPragmaSpan : undefined;
|
||||
function sharePragmaValue<T extends string | { pos: number, end: number, value: string } | undefined>(span: T): SharedPragmaSpan | string | undefined {
|
||||
if (typeof span === "string" || span === undefined) {
|
||||
return span;
|
||||
}
|
||||
return new SharedPragmaSpan(span.pos, span.end, span.value);
|
||||
}
|
||||
|
||||
function sharePragmaArguments(args: PragmaPseudoMapEntry["args"]["arguments"]) {
|
||||
const sharedArgs = new SharedPragmaArguments();
|
||||
if ("types" in args) sharedArgs.types = sharePragmaValue(args.types);
|
||||
if ("lib" in args) sharedArgs.lib = sharePragmaValue(args.lib);
|
||||
if ("path" in args) sharedArgs.path = sharePragmaValue(args.path);
|
||||
if ("no-default-lib" in args) sharedArgs["no-default-lib"] = sharePragmaValue(args["no-default-lib"]);
|
||||
if ("resolution-mode" in args) sharedArgs["resolution-mode"] = sharePragmaValue(args["resolution-mode"]);
|
||||
if ("name" in args) sharedArgs.name = sharePragmaValue(args.name);
|
||||
if ("factory" in args) sharedArgs.factory = sharePragmaValue(args.factory);
|
||||
return sharedArgs;
|
||||
}
|
||||
|
||||
function shareCommentRange(range: CommentRange) {
|
||||
const sharedRange = new SharedCommentRange();
|
||||
sharedRange.kind = range.kind;
|
||||
sharedRange.pos = range.pos;
|
||||
sharedRange.end = range.end;
|
||||
sharedRange.hasTrailingNewLine = range.hasTrailingNewLine;
|
||||
return sharedRange;
|
||||
}
|
||||
|
||||
function sharePragma(pragma: PragmaPseudoMapEntry["args"]) {
|
||||
return new SharedPragma(
|
||||
sharePragmaArguments(pragma.arguments),
|
||||
shareCommentRange(pragma.range)
|
||||
);
|
||||
}
|
||||
|
||||
function shareArray<T, U extends TaggedStruct<Tag>>(array: T[], shareElement: (value: T) => U): SharedArray<U>;
|
||||
function shareArray<T, U extends TaggedStruct<Tag>>(array: T[] | undefined, shareElement: (value: T) => U): SharedArray<U> | undefined;
|
||||
function shareArray<T, U extends TaggedStruct<Tag>>(array: T[] | undefined, shareElement: (value: T) => U): SharedArray<U> | undefined {
|
||||
if (array) {
|
||||
const sharedArray = new SharedArray<U>(array.length);
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
sharedArray[i] = shareElement(array[i]);
|
||||
}
|
||||
return sharedArray;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
export type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void;
|
||||
|
||||
function shareFileReference(ref: FileReference) {
|
||||
return new SharedFileReference(ref.pos, ref.end, ref.fileName, ref.resolutionMode);
|
||||
}
|
||||
|
||||
function shareAmdDependency(dep: AmdDependency) {
|
||||
return new SharedAmdDependency(dep.path, dep.name);
|
||||
}
|
||||
|
||||
function createFileReference(pos: number, end: number, fileName: string, resolutionMode?: ResolutionMode): FileReference {
|
||||
return { pos, end, fileName, ...(resolutionMode !== undefined ? { resolutionMode } : {}) };
|
||||
}
|
||||
|
||||
function createAmdDependency(path: string, name?: string): AmdDependency {
|
||||
return { path, name };
|
||||
}
|
||||
|
||||
function createCheckJsDirective(pos: number, end: number, enabled: boolean) {
|
||||
return { pos, end, enabled };
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function processPragmasIntoFields(context: PragmaContext, reportDiagnostic: PragmaDiagnosticReporter): void {
|
||||
context.checkJsDirective = undefined;
|
||||
context.referencedFiles = [];
|
||||
context.typeReferenceDirectives = [];
|
||||
context.libReferenceDirectives = [];
|
||||
context.amdDependencies = [];
|
||||
context.hasNoDefaultLib = false;
|
||||
context.pragmas!.forEach((entryOrList, key) => { // TODO: GH#18217
|
||||
const referencedFiles: FileReference[] = [];
|
||||
const typeReferenceDirectives: FileReference[] = [];
|
||||
const libReferenceDirectives: FileReference[] = [];
|
||||
const amdDependencies: AmdDependency[] = [];
|
||||
let checkJsDirective: CheckJsDirective | undefined;
|
||||
let pragmas;
|
||||
if (isShareableNonPrimitive(context)) {
|
||||
const contextPragmas = context.pragmas as unknown as SharedMap<string, SharedArray<SharedPragma> | SharedPragma>;
|
||||
pragmas = SharedMap.entries(contextPragmas);
|
||||
}
|
||||
else {
|
||||
pragmas = context.pragmas!.entries();
|
||||
}
|
||||
|
||||
for (const [key, entryOrList] of pragmas) {
|
||||
// TODO: The below should be strongly type-guarded and not need casts/explicit annotations, since entryOrList is related to
|
||||
// key and key is constrained to a union; but it's not (see GH#21483 for at least partial fix) :(
|
||||
switch (key) {
|
||||
case "reference": {
|
||||
const referencedFiles = context.referencedFiles;
|
||||
const typeReferenceDirectives = context.typeReferenceDirectives;
|
||||
const libReferenceDirectives = context.libReferenceDirectives;
|
||||
forEach(toArray(entryOrList) as PragmaPseudoMap["reference"][], arg => {
|
||||
const { types, lib, path, ["resolution-mode"]: res } = arg.arguments;
|
||||
if (arg.arguments["no-default-lib"]) {
|
||||
@ -10416,13 +10614,13 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
|
||||
}
|
||||
else if (types) {
|
||||
const parsed = parseResolutionMode(res, types.pos, types.end, reportDiagnostic);
|
||||
typeReferenceDirectives.push({ pos: types.pos, end: types.end, fileName: types.value, ...(parsed ? { resolutionMode: parsed } : {}) });
|
||||
typeReferenceDirectives.push(createFileReference(types.pos, types.end, types.value, parsed));
|
||||
}
|
||||
else if (lib) {
|
||||
libReferenceDirectives.push({ pos: lib.pos, end: lib.end, fileName: lib.value });
|
||||
libReferenceDirectives.push(createFileReference(lib.pos, lib.end, lib.value));
|
||||
}
|
||||
else if (path) {
|
||||
referencedFiles.push({ pos: path.pos, end: path.end, fileName: path.value });
|
||||
referencedFiles.push(createFileReference(path.pos, path.end, path.value));
|
||||
}
|
||||
else {
|
||||
reportDiagnostic(arg.range.pos, arg.range.end - arg.range.pos, Diagnostics.Invalid_reference_directive_syntax);
|
||||
@ -10431,9 +10629,9 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
|
||||
break;
|
||||
}
|
||||
case "amd-dependency": {
|
||||
context.amdDependencies = map(
|
||||
toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][],
|
||||
x => ({ name: x.arguments.name, path: x.arguments.path }));
|
||||
for (const x of toArray(entryOrList) as PragmaPseudoMap["amd-dependency"][]) {
|
||||
amdDependencies.push(createAmdDependency(x.arguments.path, x.arguments.name));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "amd-module": {
|
||||
@ -10455,12 +10653,8 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
|
||||
case "ts-check": {
|
||||
// _last_ of either nocheck or check in a file is the "winner"
|
||||
forEach(toArray(entryOrList), entry => {
|
||||
if (!context.checkJsDirective || entry.range.pos > context.checkJsDirective.pos) {
|
||||
context.checkJsDirective = {
|
||||
enabled: key === "ts-check",
|
||||
end: entry.range.end,
|
||||
pos: entry.range.pos
|
||||
};
|
||||
if (!checkJsDirective || entry.range.pos > checkJsDirective.pos) {
|
||||
checkJsDirective = createCheckJsDirective(entry.range.pos, entry.range.end, key === "ts-check");
|
||||
}
|
||||
});
|
||||
break;
|
||||
@ -10472,7 +10666,24 @@ export function processPragmasIntoFields(context: PragmaContext, reportDiagnosti
|
||||
return; // Accessed directly
|
||||
default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future?
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isShareableNonPrimitive(context)) {
|
||||
if (checkJsDirective) {
|
||||
context.checkJsDirective = new SharedCheckJsDirective(checkJsDirective.pos, checkJsDirective.end, checkJsDirective.enabled);
|
||||
}
|
||||
context.referencedFiles = shareArray(referencedFiles, shareFileReference) as unknown as FileReference[];
|
||||
context.typeReferenceDirectives = shareArray(typeReferenceDirectives, shareFileReference) as unknown as FileReference[];
|
||||
context.libReferenceDirectives = shareArray(libReferenceDirectives, shareFileReference) as unknown as FileReference[];
|
||||
context.amdDependencies = shareArray(amdDependencies, shareAmdDependency) as unknown as AmdDependency[];
|
||||
}
|
||||
else {
|
||||
context.checkJsDirective = checkJsDirective;
|
||||
context.referencedFiles = referencedFiles;
|
||||
context.typeReferenceDirectives = typeReferenceDirectives;
|
||||
context.libReferenceDirectives = libReferenceDirectives;
|
||||
context.amdDependencies = amdDependencies;
|
||||
}
|
||||
}
|
||||
|
||||
const namedArgRegExCache = new Map<string, RegExp>();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
356
src/compiler/sharing/collections/hashData.ts
Normal file
356
src/compiler/sharing/collections/hashData.ts
Normal file
@ -0,0 +1,356 @@
|
||||
import { hasProperty } from "../../core";
|
||||
import { Debug } from "../../debug";
|
||||
import { sys } from "../../sys";
|
||||
import { IdentifiableStruct } from "../structs/identifiableStruct";
|
||||
import { Shared, SharedStructBase } from "../structs/sharedStruct";
|
||||
import { xxh32string } from "./xxhash32";
|
||||
|
||||
const seed = sys.stringSeed ?? Math.floor(Math.random() * 0xffffffff) >>> 0;
|
||||
|
||||
function hash(value: Shareable) {
|
||||
if (value === undefined || value === null) { // eslint-disable-line no-null/no-null -- necessary comparison
|
||||
return 0;
|
||||
}
|
||||
switch (typeof value) {
|
||||
case "number":
|
||||
return value >> 0;
|
||||
case "boolean":
|
||||
return value ? 1 : 0;
|
||||
case "string":
|
||||
return xxh32string(value, seed);
|
||||
case "object":
|
||||
if (hasProperty(value, "__hash__")) {
|
||||
return (value as IdentifiableStruct).__hash__ >> 0;
|
||||
}
|
||||
return 0;
|
||||
default:
|
||||
Debug.assertNever(value);
|
||||
}
|
||||
}
|
||||
|
||||
// Portions of the following are derived from esfx and .NET Core. See ThirdPartyNoticeText.txt in the root of this
|
||||
// repository for their respective license notices.
|
||||
|
||||
const MAX_INT32 = (2 ** 31) - 1;
|
||||
const maxPrimeArrayLength = 2146435069;
|
||||
const hashPrime = 101;
|
||||
|
||||
// Table of prime numbers to use as hash table sizes.
|
||||
// A typical resize algorithm would pick the smallest prime number in this array
|
||||
// that is larger than twice the previous capacity.
|
||||
// Suppose our Hashtable currently has capacity x and enough elements are added
|
||||
// such that a resize needs to occur. Resizing first computes 2x then finds the
|
||||
// first prime in the table greater than 2x, i.e. if primes are ordered
|
||||
// p_1, p_2, ..., p_i, ..., it finds p_n such that p_n-1 < 2x < p_n.
|
||||
// Doubling is important for preserving the asymptotic complexity of the
|
||||
// hashtable operations such as add. Having a prime guarantees that double
|
||||
// hashing does not lead to infinite loops. IE, your hash function will be
|
||||
// h1(key) + i*h2(key), 0 <= i < size. h2 and the size must be relatively prime.
|
||||
// We prefer the low computation costs of higher prime numbers over the increased
|
||||
// memory allocation of a fixed prime number i.e. when right sizing a HashSet.
|
||||
const primes: readonly number[] = [
|
||||
3, 7, 11, 17,
|
||||
23, 29, 37, 47,
|
||||
59, 71, 89, 107,
|
||||
131, 163, 197, 239,
|
||||
293, 353, 431, 521,
|
||||
631, 761, 919, 1103,
|
||||
1327, 1597, 1931, 2333,
|
||||
2801, 3371, 4049, 4861,
|
||||
5839, 7013, 8419, 10103,
|
||||
12143, 14591, 17519, 21023,
|
||||
25229, 30293, 36353, 43627,
|
||||
52361, 62851, 75431, 90523,
|
||||
108631, 130363, 156437, 187751,
|
||||
225307, 270371, 324449, 389357,
|
||||
467237, 560689, 672827, 807403,
|
||||
968897, 1162687, 1395263, 1674319,
|
||||
2009191, 2411033, 2893249, 3471899,
|
||||
4166287, 4999559, 5999471, 7199369
|
||||
];
|
||||
|
||||
function isPrime(candidate: number) {
|
||||
if (candidate & 1) {
|
||||
const limit = Math.sqrt(candidate) | 0;
|
||||
for (let divisor = 3; divisor <= limit; divisor += 2) {
|
||||
if (!(candidate % divisor)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return candidate === 2;
|
||||
}
|
||||
|
||||
function getPrime(min: number) {
|
||||
if (min < 0) throw new RangeError();
|
||||
for (const prime of primes) {
|
||||
if (prime >= min) return prime;
|
||||
}
|
||||
for (let i = min | 1; i < MAX_INT32; i += 2) {
|
||||
if (isPrime(i) && (i - 1) % hashPrime) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
function expandPrime(oldSize: number) {
|
||||
const newSize = 2 * oldSize;
|
||||
// Allow the hashtables to grow to maximum possible size (~2G elements) before encountering capacity overflow.
|
||||
// Note that this check works even when _items.Length overflowed thanks to the (uint) cast
|
||||
if (newSize > maxPrimeArrayLength && maxPrimeArrayLength > oldSize) {
|
||||
return maxPrimeArrayLength;
|
||||
}
|
||||
return getPrime(newSize);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class HashEntry<K extends Shareable, V extends Shareable> extends SharedStructBase {
|
||||
@Shared() next = 0;
|
||||
@Shared() prevEntry: HashEntry<K, V> | undefined;
|
||||
@Shared() nextEntry: HashEntry<K, V> | undefined;
|
||||
@Shared() skipNextEntry = false;
|
||||
@Shared() hashCode = 0;
|
||||
@Shared() key!: K;
|
||||
@Shared() value!: V;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class HashData<K extends Shareable, V extends Shareable> extends SharedStructBase {
|
||||
@Shared() buckets: SharedArray<number>;
|
||||
@Shared() entries: SharedArray<HashEntry<K, V>>;
|
||||
@Shared() freeSize = 0;
|
||||
@Shared() freeList = -1;
|
||||
@Shared() size = 0;
|
||||
@Shared() head: HashEntry<K, V>;
|
||||
@Shared() tail: HashEntry<K, V>;
|
||||
|
||||
constructor(capacity: number) {
|
||||
super();
|
||||
capacity = getPrime(capacity);
|
||||
this.buckets = HashData.fill(new SharedArray(capacity), 0);
|
||||
this.entries = new SharedArray(capacity);
|
||||
this.head = new HashEntry<K, V>();
|
||||
this.tail = this.head;
|
||||
}
|
||||
|
||||
private static fill<T extends Shareable>(array: SharedArray<T>, value: T) {
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
array[i] = value;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private static resizeSharedArray<T extends Shareable>(array: SharedArray<T>, newSize: number) {
|
||||
if (array.length === newSize) return array;
|
||||
const newArray = new SharedArray<T>(newSize);
|
||||
const minSize = Math.min(array.length, newSize);
|
||||
for (let i = 0; i < minSize; i++) {
|
||||
newArray[i] = array[i];
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
private static resizeHashData<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, newSize: number) {
|
||||
const size = hashData.size;
|
||||
const buckets = HashData.fill(new SharedArray<number>(newSize), 0);
|
||||
const entries = HashData.resizeSharedArray(hashData.entries, newSize);
|
||||
for (let i = 0; i < size; i++) {
|
||||
const entry = entries[i];
|
||||
if (entry && entry.hashCode >= 0) {
|
||||
const bucket = entry.hashCode % newSize;
|
||||
// Value in buckets is 1-based
|
||||
entry.next = buckets[bucket] - 1;
|
||||
// Value in buckets is 1-based
|
||||
buckets[bucket] = i + 1;
|
||||
}
|
||||
}
|
||||
hashData.buckets = buckets;
|
||||
hashData.entries = entries;
|
||||
}
|
||||
|
||||
static findEntryIndex<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K) {
|
||||
const hashCode = hash(key) & MAX_INT32;
|
||||
// Value in _buckets is 1-based
|
||||
let i = hashData.buckets[hashCode % hashData.buckets.length] - 1;
|
||||
const length = hashData.entries.length;
|
||||
while ((i >>> 0) < length) {
|
||||
const entry = hashData.entries[i];
|
||||
if (entry.hashCode === hashCode && entry.key === key) {
|
||||
break;
|
||||
}
|
||||
i = entry.next;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static findEntryValue<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K) {
|
||||
const index = HashData.findEntryIndex(hashData, key);
|
||||
return index >= 0 ? hashData.entries[index].value : undefined;
|
||||
}
|
||||
|
||||
static insertEntry<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K, value: V) {
|
||||
const hashCode = hash(key) & MAX_INT32;
|
||||
let bucket = hashCode % hashData.buckets.length;
|
||||
// Value in _buckets is 1-based
|
||||
let i = hashData.buckets[bucket] - 1;
|
||||
while ((i >>> 0) < hashData.entries.length) {
|
||||
const entry = hashData.entries[i];
|
||||
if (entry.hashCode === hashCode && entry.key === key) {
|
||||
entry.value = value;
|
||||
return;
|
||||
}
|
||||
i = entry.next;
|
||||
}
|
||||
let updateFreeList = false;
|
||||
let index: number;
|
||||
if (hashData.freeSize > 0) {
|
||||
index = hashData.freeList;
|
||||
updateFreeList = true;
|
||||
hashData.freeSize--;
|
||||
}
|
||||
else {
|
||||
const size = hashData.size;
|
||||
if (size === hashData.entries.length) {
|
||||
HashData.resizeHashData(hashData, expandPrime(hashData.size));
|
||||
bucket = hashCode % hashData.buckets.length;
|
||||
}
|
||||
index = size;
|
||||
hashData.size = size + 1;
|
||||
}
|
||||
const entry = hashData.entries[index] ??= new HashEntry<K, V>();
|
||||
if (updateFreeList) hashData.freeList = entry.next;
|
||||
entry.hashCode = hashCode;
|
||||
// Value in _buckets is 1-based
|
||||
entry.next = hashData.buckets[bucket] - 1;
|
||||
entry.key = key;
|
||||
entry.value = value;
|
||||
entry.skipNextEntry = false;
|
||||
const tail = hashData.tail;
|
||||
tail.nextEntry = entry;
|
||||
entry.prevEntry = tail;
|
||||
hashData.tail = entry;
|
||||
// Value in _buckets is 1-based
|
||||
hashData.buckets[bucket] = index + 1;
|
||||
}
|
||||
|
||||
static deleteEntry<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, key: K) {
|
||||
const hashCode = hash(key) & MAX_INT32;
|
||||
const bucket = hashCode % hashData.buckets.length;
|
||||
let last = -1;
|
||||
let entry: HashEntry<K, V> | undefined;
|
||||
// Value in _buckets is 1-based
|
||||
for (let i = hashData.buckets[bucket] - 1; i >= 0; i = entry.next) {
|
||||
entry = hashData.entries[i];
|
||||
if (entry.hashCode === hashCode && entry.key === key) {
|
||||
if (last < 0) {
|
||||
// Value in _buckets is 1-based
|
||||
hashData.buckets[bucket] = entry.next + 1;
|
||||
}
|
||||
else {
|
||||
hashData.entries[last]!.next = entry.next;
|
||||
}
|
||||
|
||||
const prevEntry = entry.prevEntry!;
|
||||
prevEntry.nextEntry = entry.nextEntry;
|
||||
if (prevEntry.nextEntry) {
|
||||
prevEntry.nextEntry.prevEntry = prevEntry;
|
||||
}
|
||||
if (hashData.tail === entry) {
|
||||
hashData.tail = prevEntry;
|
||||
}
|
||||
entry.hashCode = -1;
|
||||
entry.next = hashData.freeList;
|
||||
entry.key = undefined!;
|
||||
entry.value = undefined!;
|
||||
entry.prevEntry = undefined;
|
||||
entry.nextEntry = prevEntry;
|
||||
entry.skipNextEntry = true;
|
||||
hashData.freeList = i;
|
||||
hashData.freeSize++;
|
||||
return true;
|
||||
}
|
||||
last = i;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static clearEntries<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>) {
|
||||
const size = hashData.size;
|
||||
if (size > 0) {
|
||||
for (let i = 0; i < hashData.buckets.length; i++) hashData.buckets[i] = 0;
|
||||
for (let i = 0; i < hashData.entries.length; i++) hashData.entries[i] = undefined!;
|
||||
let currentEntry = hashData.head.nextEntry;
|
||||
while (currentEntry) {
|
||||
const nextEntry = currentEntry.nextEntry;
|
||||
currentEntry.prevEntry = undefined;
|
||||
currentEntry.nextEntry = hashData.head;
|
||||
currentEntry.skipNextEntry = true;
|
||||
currentEntry = nextEntry;
|
||||
}
|
||||
hashData.head.nextEntry = undefined;
|
||||
hashData.tail = hashData.head;
|
||||
hashData.size = 0;
|
||||
hashData.freeList = -1;
|
||||
hashData.freeSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static ensureCapacity<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, capacity: number) {
|
||||
if (capacity < 0) throw new RangeError();
|
||||
const existingCapacity = hashData.entries.length;
|
||||
if (existingCapacity >= capacity) return existingCapacity;
|
||||
const newCapacity = getPrime(capacity);
|
||||
HashData.resizeHashData(hashData, newCapacity);
|
||||
return newCapacity;
|
||||
}
|
||||
|
||||
static trimExcessEntries<K extends Shareable, V extends Shareable>(hashData: HashData<K, V>, capacity = hashData.size - hashData.freeSize) {
|
||||
if (capacity < hashData.size) throw new RangeError(); // TODO
|
||||
const newCapacity = getPrime(capacity);
|
||||
const existingEntries = hashData.entries;
|
||||
if (newCapacity >= existingEntries.length) return;
|
||||
const oldSize = hashData.size;
|
||||
hashData.freeList = -1;
|
||||
hashData.buckets = new SharedArray(newCapacity);
|
||||
hashData.entries = new SharedArray(newCapacity);
|
||||
let newSize = 0;
|
||||
for (let i = 0; i < oldSize; i++) {
|
||||
const hashCode = existingEntries[i].hashCode;
|
||||
if (hashCode >= 0) {
|
||||
const bucket = hashCode % newCapacity;
|
||||
hashData.entries[newSize] = existingEntries[i];
|
||||
// Value in _buckets is 1-based
|
||||
hashData.entries[newSize].next = hashData.buckets[bucket] - 1;
|
||||
// Value in _buckets is 1-based
|
||||
hashData.buckets[bucket] = newSize + 1;
|
||||
newSize++;
|
||||
}
|
||||
}
|
||||
hashData.size = newSize;
|
||||
hashData.freeSize = 0;
|
||||
}
|
||||
|
||||
static * iterateEntries<K extends Shareable, V extends Shareable, R>(head: HashEntry<K, V>, selector: (entry: HashEntry<K, V>) => R) {
|
||||
let currentEntry: HashEntry<K, V> | undefined = head;
|
||||
while (currentEntry) {
|
||||
const skipNextEntry = currentEntry.skipNextEntry;
|
||||
currentEntry = currentEntry.nextEntry;
|
||||
if (skipNextEntry) continue;
|
||||
if (currentEntry) yield selector(currentEntry);
|
||||
}
|
||||
}
|
||||
|
||||
static selectEntryKey<K extends Shareable, V extends Shareable>(entry: HashEntry<K, V>) {
|
||||
return entry.key;
|
||||
}
|
||||
|
||||
static selectEntryValue<K extends Shareable, V extends Shareable>(entry: HashEntry<K, V>) {
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
static selectEntryEntry<K extends Shareable, V extends Shareable>(entry: HashEntry<K, V>) {
|
||||
return [entry.key, entry.value] as [K, V];
|
||||
}
|
||||
}
|
||||
218
src/compiler/sharing/collections/sharedLinkedList.ts
Normal file
218
src/compiler/sharing/collections/sharedLinkedList.ts
Normal file
@ -0,0 +1,218 @@
|
||||
import { Debug } from "../../debug";
|
||||
import { Shared, SharedStructBase } from "../structs/sharedStruct";
|
||||
|
||||
/**
|
||||
* A shareable data structure that represents a doubly-linked list.
|
||||
* @internal
|
||||
*/
|
||||
@Shared()
|
||||
export class SharedLinkedList<T extends Shareable> extends SharedStructBase {
|
||||
@Shared() head: SharedLinkedListNode<T> | undefined;
|
||||
@Shared() size: number;
|
||||
|
||||
constructor(iterable?: Iterable<T>) {
|
||||
super();
|
||||
this.head = undefined;
|
||||
this.size = 0;
|
||||
if (iterable) {
|
||||
for (const value of iterable) {
|
||||
SharedLinkedList.push(this, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static size(list: SharedLinkedList<any>) {
|
||||
return list.size;
|
||||
}
|
||||
|
||||
static firstNode<T extends Shareable>(list: SharedLinkedList<T>) {
|
||||
return list.head;
|
||||
}
|
||||
|
||||
static lastNode<T extends Shareable>(list: SharedLinkedList<T>) {
|
||||
return list.head?.prev;
|
||||
}
|
||||
|
||||
static previousNode<T extends Shareable>(node: SharedLinkedListNode<T>) {
|
||||
if (node.list && node !== node.list.head) {
|
||||
return node.prev;
|
||||
}
|
||||
}
|
||||
|
||||
static nextNode<T extends Shareable>(node: SharedLinkedListNode<T>) {
|
||||
if (node.list && node !== node.list.head?.prev) {
|
||||
return node.next;
|
||||
}
|
||||
}
|
||||
|
||||
static insertBeforeNode<T extends Shareable>(list: SharedLinkedList<T>, contextNode: SharedLinkedListNode<T> | undefined, newNode: SharedLinkedListNode<T>) {
|
||||
Debug.assert(!contextNode || contextNode.list === list);
|
||||
Debug.assert(!newNode.list);
|
||||
newNode.list = list;
|
||||
if (list.head === undefined) {
|
||||
newNode.next = newNode;
|
||||
newNode.prev = newNode;
|
||||
list.head = newNode;
|
||||
}
|
||||
else {
|
||||
if (contextNode === undefined) {
|
||||
contextNode = list.head;
|
||||
list.head = newNode;
|
||||
}
|
||||
else if (contextNode === list.head) {
|
||||
list.head = newNode;
|
||||
}
|
||||
newNode.next = contextNode;
|
||||
newNode.prev = contextNode.prev;
|
||||
contextNode.prev.next = newNode;
|
||||
contextNode.prev = newNode;
|
||||
}
|
||||
list.size++;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
static insertAfterNode<T extends Shareable>(list: SharedLinkedList<T>, contextNode: SharedLinkedListNode<T> | undefined, newNode: SharedLinkedListNode<T>) {
|
||||
Debug.assert(!contextNode || contextNode.list === list);
|
||||
Debug.assert(!newNode.list);
|
||||
newNode.list = list;
|
||||
if (list.head === undefined) {
|
||||
newNode.next = newNode;
|
||||
newNode.prev = newNode;
|
||||
list.head = newNode;
|
||||
}
|
||||
else {
|
||||
if (contextNode === undefined) {
|
||||
contextNode = list.head.prev!;
|
||||
}
|
||||
newNode.prev = contextNode;
|
||||
newNode.next = contextNode.next;
|
||||
contextNode.next.prev = newNode;
|
||||
contextNode.next = newNode;
|
||||
}
|
||||
list.size++;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
static pushNode<T extends Shareable>(list: SharedLinkedList<T>, node: SharedLinkedListNode<T>) {
|
||||
Debug.assert(!node.list);
|
||||
return this.insertAfterNode(list, /*contextNode*/ undefined, node);
|
||||
}
|
||||
|
||||
static push<T extends Shareable>(list: SharedLinkedList<T>, value: T) {
|
||||
return this.pushNode(list, new SharedLinkedListNode(value));
|
||||
}
|
||||
|
||||
static popNode<T extends Shareable>(list: SharedLinkedList<T>): SharedLinkedListNode<T> | undefined {
|
||||
const node = list.head?.prev;
|
||||
if (node && this.deleteNode(list, node)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
static pop<T extends Shareable>(list: SharedLinkedList<T>): T | undefined {
|
||||
return this.popNode(list)?.value;
|
||||
}
|
||||
|
||||
static unshiftNode<T extends Shareable>(list: SharedLinkedList<T>, node: SharedLinkedListNode<T>) {
|
||||
Debug.assert(!node.list);
|
||||
return this.insertBeforeNode(list, /*contextNode*/ undefined, node);
|
||||
}
|
||||
|
||||
static unshift<T extends Shareable>(list: SharedLinkedList<T>, value: T) {
|
||||
return this.unshiftNode(list, new SharedLinkedListNode(value));
|
||||
}
|
||||
|
||||
static shiftNode<T extends Shareable>(list: SharedLinkedList<T>): SharedLinkedListNode<T> | undefined {
|
||||
const node = list.head;
|
||||
if (node && this.deleteNode(list, node)) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
static shift<T extends Shareable>(list: SharedLinkedList<T>): T | undefined {
|
||||
return this.shiftNode(list)?.value;
|
||||
}
|
||||
|
||||
static deleteNode<T extends Shareable>(list: SharedLinkedList<T>, node: SharedLinkedListNode<T>) {
|
||||
if (node.list !== list) {
|
||||
return undefined;
|
||||
}
|
||||
node.list = undefined;
|
||||
if (node.next === node) {
|
||||
Debug.assert(list.head === node);
|
||||
list.head = undefined;
|
||||
}
|
||||
else {
|
||||
node.next.prev = node.prev;
|
||||
node.prev.next = node.next;
|
||||
if (list.head === node) {
|
||||
list.head = node.next;
|
||||
}
|
||||
}
|
||||
node.next = node;
|
||||
node.prev = node;
|
||||
list.size--;
|
||||
return node;
|
||||
}
|
||||
|
||||
static clear<T extends Shareable>(list: SharedLinkedList<T>) {
|
||||
while (list.head?.prev) {
|
||||
this.deleteNode(list, list.head.prev);
|
||||
}
|
||||
}
|
||||
|
||||
static * nodes<T extends Shareable>(list: SharedLinkedList<T>) {
|
||||
let node: SharedLinkedListNode<T>;
|
||||
let next = list.head;
|
||||
if (next) {
|
||||
do {
|
||||
node = next;
|
||||
next = node.next;
|
||||
yield node;
|
||||
}
|
||||
while (next !== list.head);
|
||||
}
|
||||
}
|
||||
|
||||
static * values<T extends Shareable>(list: SharedLinkedList<T>) {
|
||||
let node: SharedLinkedListNode<T>;
|
||||
let next = list.head;
|
||||
if (next) {
|
||||
do {
|
||||
node = next;
|
||||
next = node.next;
|
||||
yield node.value;
|
||||
}
|
||||
while (next !== list.head);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedLinkedListNode<T extends Shareable> extends SharedStructBase {
|
||||
@Shared() list: SharedLinkedList<T> | undefined;
|
||||
@Shared() prev: SharedLinkedListNode<T>;
|
||||
@Shared() next: SharedLinkedListNode<T>;
|
||||
@Shared() value: T;
|
||||
|
||||
constructor(value: T) {
|
||||
super();
|
||||
this.list = undefined;
|
||||
this.prev = this;
|
||||
this.next = this;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static previous<T extends Shareable>(node: SharedLinkedListNode<T>) {
|
||||
if (node.list && node !== node.list.head) {
|
||||
return node.prev;
|
||||
}
|
||||
}
|
||||
|
||||
static next<T extends Shareable>(node: SharedLinkedListNode<T>) {
|
||||
if (node.list && node !== node.list.head?.prev) {
|
||||
return node.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/compiler/sharing/collections/sharedMap.ts
Normal file
59
src/compiler/sharing/collections/sharedMap.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { Identifiable } from "../structs/identifiableStruct";
|
||||
import { Shared, SharedStructBase } from "../structs/sharedStruct";
|
||||
import { Tag, Tagged } from "../structs/taggedStruct";
|
||||
import { HashData } from "./hashData";
|
||||
|
||||
@Shared()
|
||||
export class SharedMap<K extends Shareable, V extends Shareable> extends Identifiable(Tagged(SharedStructBase, Tag.Map)) {
|
||||
@Shared() private _hashData: HashData<K, V>;
|
||||
|
||||
constructor(capacity = 0) {
|
||||
super();
|
||||
this._hashData = new HashData(capacity);
|
||||
}
|
||||
|
||||
static size<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
|
||||
return self._hashData.size - self._hashData.freeSize;
|
||||
}
|
||||
|
||||
static has<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K) {
|
||||
return HashData.findEntryIndex(self._hashData, key) >= 0;
|
||||
}
|
||||
|
||||
static get<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K) {
|
||||
return HashData.findEntryValue(self._hashData, key);
|
||||
}
|
||||
|
||||
static set<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K, value: V) {
|
||||
HashData.insertEntry(self._hashData, key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
static delete<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, key: K) {
|
||||
return HashData.deleteEntry(self._hashData, key);
|
||||
}
|
||||
|
||||
static clear<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
|
||||
HashData.clearEntries(self._hashData);
|
||||
}
|
||||
|
||||
static ensureCapacity<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, capacity: number) {
|
||||
return HashData.ensureCapacity(self._hashData, capacity);
|
||||
}
|
||||
|
||||
static trimExcess<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>, capacity?: number) {
|
||||
HashData.trimExcessEntries(self._hashData, capacity);
|
||||
}
|
||||
|
||||
static keys<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
|
||||
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryKey);
|
||||
}
|
||||
|
||||
static values<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
|
||||
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryValue);
|
||||
}
|
||||
|
||||
static entries<K extends Shareable, V extends Shareable>(self: SharedMap<K, V>) {
|
||||
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryEntry);
|
||||
}
|
||||
}
|
||||
47
src/compiler/sharing/collections/sharedResizableArray.ts
Normal file
47
src/compiler/sharing/collections/sharedResizableArray.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Identifiable } from "../structs/identifiableStruct";
|
||||
import { Shared, SharedStructBase } from "../structs/sharedStruct";
|
||||
import { Tag, Tagged } from "../structs/taggedStruct";
|
||||
|
||||
// TODO: consider constructing ropes of shared arrays rather than reallocating and copying.
|
||||
|
||||
/**
|
||||
* The default `SharedArray` implementation depends on a fixed-length array. A ResizableSharedArray is an abstraction
|
||||
* over `SharedArray` that allows us to emulate resizing.
|
||||
* @internal
|
||||
*/
|
||||
@Shared()
|
||||
export class SharedResizableArray<T extends Shareable> extends Identifiable(Tagged(SharedStructBase, Tag.ResizableArray)) {
|
||||
@Shared() items: SharedArray<T>;
|
||||
|
||||
constructor(initialSize = 0) {
|
||||
super();
|
||||
this.items = new SharedArray(initialSize);
|
||||
}
|
||||
|
||||
static size<T extends Shareable>(self: SharedResizableArray<T>) {
|
||||
return self.items.length;
|
||||
}
|
||||
|
||||
static get<T extends Shareable>(self: SharedResizableArray<T>, index: number): T {
|
||||
return self.items[index];
|
||||
}
|
||||
|
||||
static set<T extends Shareable>(self: SharedResizableArray<T>, index: number, value: T): SharedResizableArray<T> {
|
||||
if (index >= self.items.length) {
|
||||
this.resize(self, index + 1);
|
||||
}
|
||||
self.items[index] = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
static resize<T extends Shareable>(self: SharedResizableArray<T>, newLength: number) {
|
||||
if (self.items.length !== newLength) {
|
||||
const newArray = new SharedArray<T>(newLength);
|
||||
const minSize = Math.min(self.items.length, newLength);
|
||||
for (let i = 0; i < minSize; i++) {
|
||||
newArray[i] = self.items[i];
|
||||
}
|
||||
}
|
||||
return self;
|
||||
}
|
||||
}
|
||||
55
src/compiler/sharing/collections/sharedSet.ts
Normal file
55
src/compiler/sharing/collections/sharedSet.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { Identifiable } from "../structs/identifiableStruct";
|
||||
import { Shared, SharedStructBase } from "../structs/sharedStruct";
|
||||
import { Tag, Tagged } from "../structs/taggedStruct";
|
||||
import { HashData } from "./hashData";
|
||||
|
||||
@Shared()
|
||||
export class SharedSet<T extends Shareable> extends Identifiable(Tagged(SharedStructBase, Tag.Set)) {
|
||||
@Shared() private _hashData: HashData<T, T>;
|
||||
|
||||
constructor(capacity = 0) {
|
||||
super();
|
||||
this._hashData = new HashData(capacity);
|
||||
}
|
||||
|
||||
static size<T extends Shareable>(self: SharedSet<T>) {
|
||||
return self._hashData.size - self._hashData.freeSize;
|
||||
}
|
||||
|
||||
static has<T extends Shareable>(self: SharedSet<T>, value: T) {
|
||||
return HashData.findEntryIndex(self._hashData, value) >= 0;
|
||||
}
|
||||
|
||||
static add<T extends Shareable>(self: SharedSet<T>, value: T) {
|
||||
HashData.insertEntry(self._hashData, value, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
static delete<T extends Shareable>(self: SharedSet<T>, value: T) {
|
||||
return HashData.deleteEntry(self._hashData, value);
|
||||
}
|
||||
|
||||
static clear<T extends Shareable>(self: SharedSet<T>) {
|
||||
HashData.clearEntries(self._hashData);
|
||||
}
|
||||
|
||||
static ensureCapacity<T extends Shareable>(self: SharedSet<T>, capacity: number) {
|
||||
return HashData.ensureCapacity(self._hashData, capacity);
|
||||
}
|
||||
|
||||
static trimExcess<T extends Shareable>(self: SharedSet<T>, capacity?: number) {
|
||||
HashData.trimExcessEntries(self._hashData, capacity);
|
||||
}
|
||||
|
||||
static keys<T extends Shareable>(self: SharedSet<T>) {
|
||||
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryKey);
|
||||
}
|
||||
|
||||
static values<T extends Shareable>(self: SharedSet<T>) {
|
||||
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryValue);
|
||||
}
|
||||
|
||||
static entries<T extends Shareable>(self: SharedSet<T>) {
|
||||
return HashData.iterateEntries(self._hashData.head, HashData.selectEntryEntry);
|
||||
}
|
||||
}
|
||||
86
src/compiler/sharing/collections/xxhash32.ts
Normal file
86
src/compiler/sharing/collections/xxhash32.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { utf8encodeInto } from "../../utilities";
|
||||
|
||||
const PRIME32_1 = 0x9e3779b1;
|
||||
const PRIME32_2 = 0x85ebca77;
|
||||
const PRIME32_3 = 0xc2b2ae3d;
|
||||
const PRIME32_4 = 0x27d4eb2f;
|
||||
const PRIME32_5 = 0x165667b1;
|
||||
|
||||
/**
|
||||
* Fast non-cryptographic hashing algorithm with good avalanche properties.
|
||||
* https://xxhash.com/
|
||||
* @internal
|
||||
*/
|
||||
export function xxh32(buffer: ArrayBuffer, inputPtr: number, inputLength: number, seed: number): number {
|
||||
if (inputPtr % 4) throw new TypeError("Pointer not aligned");
|
||||
const u32buffer = new Uint32Array(buffer);
|
||||
const end = inputPtr + inputLength;
|
||||
let acc: number;
|
||||
let limit: number;
|
||||
let v1: number;
|
||||
let v2: number;
|
||||
let v3: number;
|
||||
let v4: number;
|
||||
// translate ptr to u32 offset
|
||||
inputPtr >>= 2;
|
||||
if (inputLength >= 16) {
|
||||
limit = (end - 16) >> 2;
|
||||
v1 = (((seed + PRIME32_1) | 0) + PRIME32_2) | 0;
|
||||
v2 = (seed + PRIME32_2) | 0;
|
||||
v3 = (seed + 0) | 0;
|
||||
v4 = (seed + PRIME32_1) | 0;
|
||||
do {
|
||||
v1 = (v1 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
|
||||
v1 = (((v1 << 13) | (v1 >>> 19)) * PRIME32_1) | 0;
|
||||
v2 = (v2 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
|
||||
v2 = (((v2 << 13) | (v2 >>> 19)) * PRIME32_1) | 0;
|
||||
v3 = (v3 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
|
||||
v3 = (((v3 << 13) | (v3 >>> 19)) * PRIME32_1) | 0;
|
||||
v4 = (v4 + (u32buffer[inputPtr++] * PRIME32_2) | 0) | 0;
|
||||
v4 = (((v4 << 13) | (v4 >>> 19)) * PRIME32_1) | 0;
|
||||
}
|
||||
while (inputPtr <= limit);
|
||||
acc = (v1 << 1 | v1 >>> 31) + (v2 << 7 | v2 >>> 25) | (v3 << 12 | v3 >>> 20) | (v4 << 18 | v4 >>> 14);
|
||||
}
|
||||
else {
|
||||
acc = (seed + PRIME32_5) | 0;
|
||||
}
|
||||
acc = (acc + inputLength) | 0;
|
||||
limit = (end - 4) >> 2;
|
||||
while (inputPtr <= limit) {
|
||||
acc = (acc + (u32buffer[inputPtr++] * PRIME32_3) | 0) | 0;
|
||||
acc = ((acc << 17 | acc >>> 15) * PRIME32_4) | 0;
|
||||
}
|
||||
// translate ptr to byte offset
|
||||
inputPtr = inputPtr << 2;
|
||||
if (inputPtr < end) {
|
||||
const u8buffer = new Uint8Array(u32buffer.buffer);
|
||||
do {
|
||||
acc = (acc + (u8buffer[inputPtr++] * PRIME32_5) | 0) | 0;
|
||||
acc = ((acc << 11 | acc >>> 21) * PRIME32_1) | 0;
|
||||
}
|
||||
while (inputPtr < end);
|
||||
}
|
||||
acc = ((acc ^ (acc >>> 15)) * PRIME32_2) | 0;
|
||||
acc = ((acc ^ (acc >>> 13)) * PRIME32_3) | 0;
|
||||
acc = acc ^ (acc >>> 16);
|
||||
return acc >>> 0;
|
||||
}
|
||||
|
||||
let memory: Uint8Array | undefined;
|
||||
|
||||
function ensureCapacity(size: number) {
|
||||
if (!memory || memory.byteLength < size) {
|
||||
memory = new Uint8Array(size + (65536 - size % 65536));
|
||||
}
|
||||
return memory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function xxh32string(x: string, seed: number) {
|
||||
const memory = ensureCapacity(x.length * 3);
|
||||
const written = utf8encodeInto(x, memory);
|
||||
return xxh32(memory.buffer, 0, written, seed);
|
||||
}
|
||||
132
src/compiler/sharing/sharedDiagnostics.ts
Normal file
132
src/compiler/sharing/sharedDiagnostics.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import { CompilerOptions, DiagnosticCategory } from "../types";
|
||||
import { SharedSourceFile } from "./sharedNode";
|
||||
import { Shared, SharedStructBase } from "./structs/sharedStruct";
|
||||
import { Tag, Tagged } from "./structs/taggedStruct";
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedDiagnosticMessage extends SharedStructBase {
|
||||
@Shared() key: string;
|
||||
@Shared() category: DiagnosticCategory;
|
||||
@Shared() code: number;
|
||||
@Shared() message: string;
|
||||
@Shared() reportsUnnecessary?: boolean;
|
||||
@Shared() reportsDeprecated?: boolean;
|
||||
@Shared() elidedInCompatabilityPyramid?: boolean | undefined;
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
category: DiagnosticCategory,
|
||||
code: number,
|
||||
message: string,
|
||||
reportsUnnecessary?: {},
|
||||
reportsDeprecated?: {},
|
||||
elidedInCompatabilityPyramid?: boolean | undefined
|
||||
) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.category = category;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.reportsUnnecessary = !!reportsUnnecessary;
|
||||
this.reportsDeprecated = !!reportsDeprecated;
|
||||
this.elidedInCompatabilityPyramid = elidedInCompatabilityPyramid;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedDiagnosticMessageChain extends Tagged(SharedStructBase, Tag.DiagnosticMessageChain) {
|
||||
@Shared() messageText: string;
|
||||
@Shared() category: DiagnosticCategory;
|
||||
@Shared() code: number;
|
||||
@Shared() next?: SharedArray<SharedDiagnosticMessageChain>;
|
||||
// does this need to be shared somehow?
|
||||
// repopulateInfo?: () => RepopulateDiagnosticChainInfo;
|
||||
|
||||
constructor(messageText: string, category: DiagnosticCategory, code: number, next?: SharedArray<SharedDiagnosticMessageChain>) {
|
||||
super();
|
||||
this.messageText = messageText;
|
||||
this.category = category;
|
||||
this.code = code;
|
||||
this.next = next;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedDiagnosticRelatedInformation extends Tagged(SharedStructBase, Tag.DiagnosticRelatedInformation) {
|
||||
@Shared() category: DiagnosticCategory;
|
||||
@Shared() code: number;
|
||||
@Shared() file: SharedSourceFile | undefined;
|
||||
@Shared() start: number | undefined;
|
||||
@Shared() length: number | undefined;
|
||||
@Shared() messageText: string | SharedDiagnosticMessageChain;
|
||||
|
||||
constructor(
|
||||
category: DiagnosticCategory,
|
||||
code: number,
|
||||
file: SharedSourceFile | undefined,
|
||||
start: number | undefined,
|
||||
length: number | undefined,
|
||||
messageText: string | SharedDiagnosticMessageChain
|
||||
) {
|
||||
super();
|
||||
this.category = category;
|
||||
this.code = code;
|
||||
this.file = file;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
this.messageText = messageText;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedDiagnostic extends Tagged(SharedDiagnosticRelatedInformation, Tag.Diagnostic) {
|
||||
@Shared() reportsUnnecessary?: boolean;
|
||||
@Shared() reportsDeprecated?: boolean;
|
||||
@Shared() source?: string;
|
||||
@Shared() relatedInformation?: SharedArray<SharedDiagnosticRelatedInformation | SharedDiagnostic>;
|
||||
@Shared() skippedOn?: keyof CompilerOptions;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedDiagnosticAndArguments extends SharedStructBase {
|
||||
@Shared() message: SharedDiagnosticMessage;
|
||||
@Shared() args: SharedArray<string | number>;
|
||||
|
||||
constructor(message: SharedDiagnosticMessage, args: SharedArray<string | number>) {
|
||||
super();
|
||||
this.message = message;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface SharedDiagnosticWithLocation extends SharedDiagnostic {
|
||||
file: SharedSourceFile;
|
||||
start: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedDiagnosticWithDetachedLocation extends SharedDiagnostic {
|
||||
@Shared() fileName: string;
|
||||
declare file: undefined;
|
||||
|
||||
constructor(
|
||||
category: DiagnosticCategory,
|
||||
code: number,
|
||||
file: SharedSourceFile | undefined,
|
||||
start: number | undefined,
|
||||
length: number | undefined,
|
||||
messageText: string | SharedDiagnosticMessageChain,
|
||||
fileName: string
|
||||
) {
|
||||
super(category, code, file, start, length, messageText);
|
||||
this.fileName = fileName;
|
||||
}
|
||||
}
|
||||
3420
src/compiler/sharing/sharedNode.ts
Normal file
3420
src/compiler/sharing/sharedNode.ts
Normal file
File diff suppressed because it is too large
Load Diff
2545
src/compiler/sharing/sharedNodeAdapter.ts
Normal file
2545
src/compiler/sharing/sharedNodeAdapter.ts
Normal file
File diff suppressed because one or more lines are too long
29
src/compiler/sharing/sharedNodeArray.ts
Normal file
29
src/compiler/sharing/sharedNodeArray.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { TransformFlags } from "../types";
|
||||
import type { SharedNodeBase } from "./sharedNode";
|
||||
import { Identifiable } from "./structs/identifiableStruct";
|
||||
import { isShareableNonPrimitive } from "./structs/shareable";
|
||||
import { Shared, SharedStructBase } from "./structs/sharedStruct";
|
||||
import { isTaggedStruct, Tag, Tagged } from "./structs/taggedStruct";
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedNodeArray<T extends SharedNodeBase> extends Identifiable(Tagged(SharedStructBase, Tag.NodeArray)) {
|
||||
@Shared() items!: SharedArray<T>;
|
||||
@Shared() pos = -1;
|
||||
@Shared() end = -1;
|
||||
@Shared() hasTrailingComma = false;
|
||||
@Shared() transformFlags = TransformFlags.None;
|
||||
@Shared() isMissingList = false;
|
||||
|
||||
static * values<T extends SharedNodeBase>(self: SharedNodeArray<T>): IterableIterator<T> {
|
||||
for (let i = 0; i < self.items.length; i++) {
|
||||
yield self.items[i];
|
||||
}
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance](value: unknown): value is SharedNodeArray<SharedNodeBase> {
|
||||
return isShareableNonPrimitive(value) &&
|
||||
isTaggedStruct(value) &&
|
||||
value.__tag__ === Tag.NodeArray;
|
||||
}
|
||||
}
|
||||
37
src/compiler/sharing/sharedObjectAllocator.ts
Normal file
37
src/compiler/sharing/sharedObjectAllocator.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Identifier, Node, NodeArray, PrivateIdentifier, SourceFile, SyntaxKind, Token } from "../types";
|
||||
import { ObjectAllocator, objectAllocator } from "../utilities";
|
||||
import { getSharedConstructorForKind, SharedNodeBase } from "./sharedNode";
|
||||
import { SharedNodeArray } from "./sharedNodeArray";
|
||||
|
||||
function NodeArray(items: readonly any[], hasTrailingComma?: boolean) {
|
||||
const array = new SharedNodeArray();
|
||||
array.items = new SharedArray(items.length);
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
array.items[i] = items[i];
|
||||
}
|
||||
array.hasTrailingComma = !!hasTrailingComma;
|
||||
return array;
|
||||
}
|
||||
|
||||
function Node(kind: SyntaxKind, pos: number, end: number) {
|
||||
const SharedNode = getSharedConstructorForKind(kind);
|
||||
const node = new SharedNode() as SharedNodeBase;
|
||||
node.kind = kind;
|
||||
node.pos = pos;
|
||||
node.end = end;
|
||||
return node;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getSharedObjectAllocator(): ObjectAllocator {
|
||||
return {
|
||||
...objectAllocator,
|
||||
getNodeArrayConstructor: () => NodeArray as unknown as new <T extends Node>(items: readonly T[], hasTrailingComma?: boolean | undefined) => NodeArray<T>,
|
||||
getNodeConstructor: () => Node as unknown as new (kind: SyntaxKind, pos: number, end: number) => Node,
|
||||
getTokenConstructor: () => Node as unknown as new <Kind extends SyntaxKind>(kind: Kind, pos: number, end: number) => Token<Kind>,
|
||||
getIdentifierConstructor: () => Node as unknown as new (kind: SyntaxKind.Identifier, pos: number, end: number) => Identifier,
|
||||
getPrivateIdentifierConstructor: () => Node as unknown as new (kind: SyntaxKind.PrivateIdentifier, pos: number, end: number) => PrivateIdentifier,
|
||||
getSourceFileConstructor: () => Node as unknown as new (kind: SyntaxKind.SourceFile, pos: number, end: number) => SourceFile,
|
||||
// getSymbolConstructor: () => SymbolConstructor,
|
||||
};
|
||||
}
|
||||
51
src/compiler/sharing/sharedParserState.ts
Normal file
51
src/compiler/sharing/sharedParserState.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { Condition } from "../threading/condition";
|
||||
import { Mutex } from "../threading/mutex";
|
||||
import { SharedMutex } from "../threading/sharedMutex";
|
||||
import { ResolutionMode, ScriptTarget } from "../types";
|
||||
import { SharedMap } from "./collections/sharedMap";
|
||||
import { SharedSourceFile } from "./sharedNode";
|
||||
import { Shared, SharedStructBase } from "./structs/sharedStruct";
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedParserState extends SharedStructBase {
|
||||
@Shared() sharedMutex = new SharedMutex();
|
||||
@Shared() files = new SharedMap<string, SharedSourceFileEntry>();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedSourceFileEntry extends SharedStructBase {
|
||||
@Shared() parserState: SharedParserState;
|
||||
@Shared() setParentNodes: boolean;
|
||||
@Shared() setFileVersion: boolean;
|
||||
@Shared() fileName: string;
|
||||
@Shared() languageVersion: ScriptTarget;
|
||||
@Shared() impliedNodeFormat: ResolutionMode | undefined;
|
||||
@Shared() shouldCreateNewSourceFile: boolean | undefined;
|
||||
@Shared() done = false;
|
||||
@Shared() error = false;
|
||||
@Shared() file: SharedSourceFile | undefined;
|
||||
@Shared() fileMutex = new Mutex();
|
||||
@Shared() fileCondition = new Condition();
|
||||
|
||||
constructor(
|
||||
parserState: SharedParserState,
|
||||
setFileVersion: boolean,
|
||||
setParentNodes: boolean,
|
||||
fileName: string,
|
||||
languageVersion: ScriptTarget,
|
||||
impliedNodeFormat: ResolutionMode | undefined,
|
||||
shouldCreateNewSourceFile: boolean | undefined,
|
||||
) {
|
||||
super();
|
||||
this.parserState = parserState;
|
||||
this.setFileVersion = setFileVersion;
|
||||
this.setParentNodes = setParentNodes;
|
||||
this.fileName = fileName;
|
||||
this.languageVersion = languageVersion;
|
||||
this.impliedNodeFormat = impliedNodeFormat;
|
||||
this.shouldCreateNewSourceFile = shouldCreateNewSourceFile;
|
||||
}
|
||||
}
|
||||
|
||||
36
src/compiler/sharing/sharedSymbol.ts
Normal file
36
src/compiler/sharing/sharedSymbol.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { __String, SymbolFlags } from "../types";
|
||||
import { SharedResizableArray } from "./collections/sharedResizableArray";
|
||||
import { SharedMap } from "./collections/sharedMap";
|
||||
import { Identifiable } from "./structs/identifiableStruct";
|
||||
import { SharedDeclaration } from "./sharedNode";
|
||||
import { Shared, SharedStructBase } from "./structs/sharedStruct";
|
||||
import { Tag, Tagged } from "./structs/taggedStruct";
|
||||
|
||||
declare global {
|
||||
interface OtherShareablePrimitive {
|
||||
__String: __String;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface SharedSymbolTable extends SharedMap<__String, SharedSymbol> {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedSymbol extends Identifiable(Tagged(SharedStructBase, Tag.Symbol)) {
|
||||
@Shared() flags!: SymbolFlags;
|
||||
@Shared() escapedName!: __String;
|
||||
@Shared() declarations: SharedResizableArray<SharedDeclaration> | undefined;
|
||||
@Shared() valueDeclaration: SharedDeclaration | undefined;
|
||||
@Shared() members: SharedSymbolTable | undefined;
|
||||
@Shared() exports: SharedSymbolTable | undefined;
|
||||
@Shared() globalExports: SharedSymbolTable | undefined;
|
||||
@Shared() parent: SharedSymbol | undefined;
|
||||
@Shared() exportSymbol: SharedSymbol | undefined;
|
||||
@Shared() constEnumOnlyModule: boolean | undefined;
|
||||
@Shared() isReferenced: SymbolFlags | undefined;
|
||||
@Shared() isReplaceableByMethod: boolean | undefined;
|
||||
@Shared() isAssigned: boolean | undefined;
|
||||
@Shared() assignmentDeclarationMembers: SharedMap<number, SharedDeclaration> | undefined;
|
||||
}
|
||||
145
src/compiler/sharing/structs/fakeSharedStruct.ts
Normal file
145
src/compiler/sharing/structs/fakeSharedStruct.ts
Normal file
@ -0,0 +1,145 @@
|
||||
// Brand used to identify a fake shared struct, so that we can emulate the restriction that shared struct fields
|
||||
|
||||
import { isNull } from "../../core";
|
||||
|
||||
// can only contain shareable primitives and other shared structs.
|
||||
const allFakeSharedStructInstances = new WeakSet<object>();
|
||||
const allFakeSharedArrayInstances = new WeakSet<object>();
|
||||
|
||||
// Emulates the behavior of the Shared Structs origin trial, for forwards compatibility.
|
||||
const proxyHandler: ProxyHandler<object> = {
|
||||
set(target, p, value, receiver) {
|
||||
switch (typeof value) {
|
||||
case "undefined":
|
||||
case "string":
|
||||
case "number":
|
||||
case "boolean":
|
||||
break;
|
||||
case "object":
|
||||
if (isNull(value) || allFakeSharedStructInstances.has(value) || allFakeSharedArrayInstances.has(value)) { // eslint-disable-line no-null/no-null
|
||||
break;
|
||||
}
|
||||
// falls through
|
||||
default:
|
||||
throw new TypeError("value not shareable");
|
||||
}
|
||||
return Reflect.set(target, p, value, receiver);
|
||||
},
|
||||
defineProperty(target, property, attributes) {
|
||||
const { value } = attributes;
|
||||
switch (typeof value) {
|
||||
case "undefined":
|
||||
case "string":
|
||||
case "number":
|
||||
case "boolean":
|
||||
break;
|
||||
case "object":
|
||||
if (isNull(value) || allFakeSharedStructInstances.has(value) || allFakeSharedArrayInstances.has(value)) { // eslint-disable-line no-null/no-null
|
||||
break;
|
||||
}
|
||||
// falls through
|
||||
default:
|
||||
throw new TypeError("value not shareable");
|
||||
}
|
||||
return Reflect.defineProperty(target, property, attributes);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new fake SharedStructType constructor that emulates fixed-shape object behavior, though the result can't
|
||||
* actually be shared across threads. Fake shared structs are useful in single-threaded scenarios when SharedStructType
|
||||
* isn't available and allows us to write single threaded code that is forwards-compatible with eventual multi-threaded
|
||||
* code.
|
||||
* @internal
|
||||
*/
|
||||
export const FakeSharedStructType = class FakeSharedStructType {
|
||||
constructor(fields: readonly string[]) {
|
||||
const thisFakeSharedStructInstances = new WeakSet<object>();
|
||||
class FakeSharedStruct { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
constructor() {
|
||||
// Fields must be explicitly defined and are not configurable.
|
||||
for (const field of fields) {
|
||||
Object.defineProperty(this, field, {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: true,
|
||||
value: undefined
|
||||
});
|
||||
}
|
||||
Object.setPrototypeOf(this, null); // eslint-disable-line no-null/no-null
|
||||
Object.preventExtensions(this);
|
||||
|
||||
const fakeSharedStruct = new Proxy(this, proxyHandler);
|
||||
allFakeSharedStructInstances.add(fakeSharedStruct);
|
||||
thisFakeSharedStructInstances.add(fakeSharedStruct);
|
||||
return fakeSharedStruct as this;
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance](value: unknown) {
|
||||
try {
|
||||
return typeof value === "object" && !isNull(value) && thisFakeSharedStructInstances.has(value);
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// augment the interface with the brand from `SharedStruct`.
|
||||
interface FakeSharedStruct extends SharedStruct { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(FakeSharedStruct.prototype, null); // eslint-disable-line no-null/no-null
|
||||
return FakeSharedStruct;
|
||||
}
|
||||
|
||||
static isSharedStruct(value: unknown): value is SharedStruct {
|
||||
try {
|
||||
return typeof value === "object" && !isNull(value) && allFakeSharedStructInstances.has(value);
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} as SharedStructTypeConstructor;
|
||||
|
||||
/**
|
||||
* Creates a new fake SharedArray constructor that emulates fixed-shape object behavior, though the result can't
|
||||
* actually be shared across threads. Fake shared structs are useful in single-threaded scenarios when SharedStructType
|
||||
* isn't available and allows us to write single threaded code that is forwards-compatible with eventual multi-threaded
|
||||
* code.
|
||||
* @internal
|
||||
*/
|
||||
export const FakeSharedArray = class FakeSharedArray {
|
||||
constructor(length: number) {
|
||||
for (let i = 0; i < length; i++) {
|
||||
Object.defineProperty(this, i, {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: true,
|
||||
value: undefined
|
||||
});
|
||||
}
|
||||
Object.defineProperty(this, "length", {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
writable: false,
|
||||
value: length
|
||||
});
|
||||
Object.setPrototypeOf(this, null); // eslint-disable-line no-null/no-null
|
||||
Object.preventExtensions(this);
|
||||
|
||||
const fakeSharedArray = new Proxy(this, proxyHandler);
|
||||
allFakeSharedArrayInstances.add(fakeSharedArray);
|
||||
return fakeSharedArray as this;
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance](value: unknown) {
|
||||
try {
|
||||
return typeof value === "object" && !isNull(value) && allFakeSharedArrayInstances.has(value);
|
||||
}
|
||||
catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} as SharedArrayConstructor;
|
||||
52
src/compiler/sharing/structs/identifiableStruct.ts
Normal file
52
src/compiler/sharing/structs/identifiableStruct.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { workerThreads } from "../../workerThreads";
|
||||
import { Shared } from "./sharedStruct";
|
||||
|
||||
const identifiableConstructors = new WeakSet();
|
||||
|
||||
let typeCount = 0;
|
||||
|
||||
/** @internal */
|
||||
export interface IdentifiableStruct extends SharedStruct {
|
||||
readonly __hash__: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an "identity hash" on a shared struct, allowing it to be used as a key in a `SharedMap` or `SharedSet`.
|
||||
* @internal
|
||||
*/
|
||||
export function Identifiable<C extends abstract new (...args: any) => any>(base: C): C & (abstract new (...args: any) => IdentifiableStruct) {
|
||||
if (isIdentifiableConstructor(base)) {
|
||||
// constructor is already marked as identifiable. Nothing to do here.
|
||||
return base;
|
||||
}
|
||||
|
||||
const threadId = workerThreads?.threadId ?? 0;
|
||||
const typeId = typeCount++;
|
||||
let instanceCount = 0;
|
||||
|
||||
@Shared({ abstract: true })
|
||||
abstract class IdentifiableStruct extends base {
|
||||
@Shared() readonly __hash__: number;
|
||||
constructor(...args: any) {
|
||||
super(...args);
|
||||
const instanceId = instanceCount++;
|
||||
let hc = threadId;
|
||||
hc = ((hc << 7) | (hc >>> 25)) ^ typeId;
|
||||
hc = ((hc << 7) | (hc >>> 25)) ^ instanceId;
|
||||
this.__hash__ = hc;
|
||||
}
|
||||
}
|
||||
|
||||
identifiableConstructors.add(IdentifiableStruct);
|
||||
return IdentifiableStruct;
|
||||
}
|
||||
|
||||
function isIdentifiableConstructor(value: object | null) {
|
||||
while (value) {
|
||||
if (identifiableConstructors.has(value)) {
|
||||
return true;
|
||||
}
|
||||
value = Object.getPrototypeOf(value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
54
src/compiler/sharing/structs/shareable.ts
Normal file
54
src/compiler/sharing/structs/shareable.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { isNull } from "../../core";
|
||||
import { FakeSharedArray, FakeSharedStructType } from "./fakeSharedStruct";
|
||||
|
||||
/**
|
||||
* Returns whether the provided value is a shareable primitive value.
|
||||
* @internal
|
||||
*/
|
||||
export function isShareablePrimitive(value: unknown): value is ShareablePrimitive {
|
||||
switch (typeof value) {
|
||||
case "undefined":
|
||||
case "string":
|
||||
case "number":
|
||||
case "boolean":
|
||||
case "symbol":
|
||||
return true;
|
||||
case "object":
|
||||
return value === null; // eslint-disable-line no-null/no-null -- necessary for comparison
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// You cannot use `instanceof` with shared structs and there is no `isArray`-like test currently. The only way we have
|
||||
// to determine if a value is a shareable non-primitive (i.e., a shared struct, `SharedArray`, `Atomics.Mutex`, or
|
||||
// `Atomics.Condition`) is to try and assign it to a property of a shared struct and catch the exception. This will
|
||||
// hopefully be addressed in a future version of the origin trial.
|
||||
|
||||
const supportsSharedStructs = typeof SharedStructType === "function";
|
||||
const sharedStructTypeConstructor = supportsSharedStructs ? SharedStructType : FakeSharedStructType;
|
||||
const sharedArrayConstructor = supportsSharedStructs ? SharedArray : FakeSharedArray;
|
||||
|
||||
/**
|
||||
* Returns whether the provided value is a shareable non-primitive value.
|
||||
* @internal
|
||||
*/
|
||||
export function isShareableNonPrimitive(value: unknown): value is ShareableNonPrimitive {
|
||||
if (typeof value !== "object" || isNull(value)) return false;
|
||||
return sharedStructTypeConstructor.isSharedStruct(value) ||
|
||||
value instanceof sharedArrayConstructor ||
|
||||
supportsSharedStructs && (value instanceof Atomics.Mutex || value instanceof Atomics.Condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the provided value is a shareable value.
|
||||
* @internal
|
||||
*/
|
||||
export function isShareable(value: unknown): value is Shareable {
|
||||
return isShareablePrimitive(value) || isShareableNonPrimitive(value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isSharedArray(value: unknown): value is SharedArray<Shareable> {
|
||||
return value instanceof SharedArray;
|
||||
}
|
||||
333
src/compiler/sharing/structs/sharedStruct.ts
Normal file
333
src/compiler/sharing/structs/sharedStruct.ts
Normal file
@ -0,0 +1,333 @@
|
||||
import "../../symbolMetadataShim";
|
||||
import "./sharedStructsGlobals";
|
||||
|
||||
import { Debug } from "../../debug";
|
||||
import { FakeSharedStructType } from "./fakeSharedStruct";
|
||||
import { AbstractConstructor } from "../../types";
|
||||
|
||||
// Holds the fields tracked with `@Shared()`
|
||||
const weakSharedFields = new WeakMap<object, string[]>();
|
||||
|
||||
// Holds the `SharedStructType` instances produced for classes tracked with `@Shared()`
|
||||
const weakSharedStructType = new WeakMap<object, SharedStructTypeFactory>();
|
||||
|
||||
// WeakRef and FinalizationRegistry may not exist, so we'll fall back to a non-weak shim when unavailable
|
||||
interface WeakRef<T extends WeakKey> {
|
||||
deref(): T | undefined;
|
||||
}
|
||||
|
||||
interface FinalizationRegistry<T> {
|
||||
register(target: WeakKey, heldValue: T, unregisterToken?: WeakKey): void;
|
||||
unregister(unregisterToken: WeakKey): void;
|
||||
}
|
||||
|
||||
declare var WeakRef: new <T extends WeakKey>(value: T) => WeakRef<T>;
|
||||
declare var FinalizationRegistry: new <T>(cleanupCallback: (heldValue: T) => void) => FinalizationRegistry<T>;
|
||||
|
||||
class FakeWeakRef<T extends WeakKey> implements WeakRef<T> {
|
||||
private _target: T; // NOTE: Not actually weak
|
||||
constructor(target: T) {
|
||||
this._target = target;
|
||||
}
|
||||
deref(): T | undefined {
|
||||
return this._target;
|
||||
}
|
||||
}
|
||||
|
||||
class FakeFinalizationRegistry<T> implements FinalizationRegistry<T> {
|
||||
constructor(cleanupCallback: (heldValue: T) => void) {
|
||||
void cleanupCallback;
|
||||
}
|
||||
register(target: WeakKey, heldValue: T, unregisterToken?: WeakKey): void {
|
||||
void target;
|
||||
void heldValue;
|
||||
void unregisterToken;
|
||||
}
|
||||
unregister(unregisterToken: WeakKey): void {
|
||||
void unregisterToken;
|
||||
}
|
||||
}
|
||||
|
||||
function createWeakRef<T extends WeakKey>(value: T) {
|
||||
return typeof WeakRef === "function" ? new WeakRef(value) : new FakeWeakRef(value);
|
||||
}
|
||||
|
||||
function createFinalizationRegistry<T>(cleanupCallback: (heldValue: T) => void): FinalizationRegistry<T> {
|
||||
return typeof FinalizationRegistry === "function" ? new FinalizationRegistry(cleanupCallback) : new FakeFinalizationRegistry(cleanupCallback);
|
||||
}
|
||||
|
||||
function createSharedStructType(fields: readonly string[]) {
|
||||
// If shared structs are available, create a shared struct type to use when creating a new instance of the
|
||||
// target. Otherwise, create a fake shared struct that emulates the required semantics to ensure we are forwards
|
||||
// compatible.
|
||||
return typeof SharedStructType === "function" ? new SharedStructType(fields) : new FakeSharedStructType(fields);
|
||||
}
|
||||
|
||||
class SharedStructTypeFactory {
|
||||
private _fields: readonly string[];
|
||||
private _structType: WeakRef<new () => SharedStruct> | undefined;
|
||||
private _arrayTypes: Map<number, WeakRef<new () => SharedStruct>> | undefined;
|
||||
private _registry: FinalizationRegistry<number>;
|
||||
constructor(fields: readonly string[]) {
|
||||
this._fields = fields;
|
||||
this._registry = createFinalizationRegistry(length => { this._arrayTypes?.delete(length); });
|
||||
}
|
||||
|
||||
structType(): new () => SharedStruct {
|
||||
let structType = this._structType?.deref();
|
||||
if (!structType) {
|
||||
structType = createSharedStructType(this._fields);
|
||||
this._structType = createWeakRef(structType);
|
||||
}
|
||||
return structType;
|
||||
}
|
||||
|
||||
arrayType(length: number): new () => SharedStruct {
|
||||
let arrayType = this._arrayTypes?.get(length)?.deref();
|
||||
if (!arrayType) {
|
||||
const fields = new Set(this._fields);
|
||||
fields.add("length");
|
||||
for (let i = 0; i < length; i++) {
|
||||
fields.add(`${i}`);
|
||||
}
|
||||
arrayType = createSharedStructType([...fields]);
|
||||
this._arrayTypes ??= new Map();
|
||||
this._arrayTypes.set(length, createWeakRef(arrayType));
|
||||
this._registry.register(arrayType, length);
|
||||
}
|
||||
return arrayType;
|
||||
}
|
||||
}
|
||||
|
||||
interface SharedClassDecorator {
|
||||
<T extends AbstractConstructor>(target: T, context: ClassDecoratorContext<T>): void;
|
||||
<T extends AbstractConstructor>(target: T): void;
|
||||
}
|
||||
|
||||
interface SharedClassOptions {
|
||||
abstract?: boolean;
|
||||
}
|
||||
|
||||
interface SharedFieldDecorator {
|
||||
<This, T extends Shareable>(target: undefined, context: ClassFieldDecoratorContext<This, T> & { private: false, static: false, name: string }): void;
|
||||
<This, T extends Shareable>(target: This, propertyKey: string, descriptor?: TypedPropertyDescriptor<T>): void;
|
||||
}
|
||||
|
||||
function getOrCreateMetadata(target: AbstractConstructor) {
|
||||
if (Object.prototype.hasOwnProperty.call(target, Symbol.metadata)) {
|
||||
return target[Symbol.metadata];
|
||||
}
|
||||
const metadata = Object.create(target[Symbol.metadata] ?? null);
|
||||
Object.defineProperty(target, Symbol.metadata, {
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: metadata
|
||||
});
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* A decorator used to mark a `class` or a non-static public class field as "shared". This is intended to be used to
|
||||
* emulate syntax for shared structs and provide a mechanism to associate types with shared struct fields.
|
||||
* @internal
|
||||
*/
|
||||
export function Shared(): SharedClassDecorator & SharedFieldDecorator;
|
||||
/**
|
||||
* A decorator used to mark a `class` as "shared". This is intended to be used to emulate syntax for shared structs.
|
||||
* @param options.abstract Indicates the `class` is `abstract` and cannot be constructed directly.
|
||||
* @internal
|
||||
*/
|
||||
export function Shared(options: SharedClassOptions): SharedClassDecorator;
|
||||
export function Shared(options?: SharedClassOptions) {
|
||||
function SharedClassDecorator<T extends AbstractConstructor>(target: T, context: ClassDecoratorContext<T>) {
|
||||
if (weakSharedStructType.has(target)) {
|
||||
Debug.fail("@Shared() cannot be applied to the same class more than once.");
|
||||
}
|
||||
|
||||
// Delete the default `constructor` property from the prototype, as shared structs cannot currently have
|
||||
// properties on the prototype.
|
||||
Reflect.deleteProperty(target.prototype, "constructor");
|
||||
|
||||
// Validate the prototype chain. Even though `@Shared` class supertypes will have a null prototype, there may
|
||||
// have been non-`@Shared` abstract classes in between.
|
||||
let prototype: object | null = target.prototype;
|
||||
while (prototype) {
|
||||
if (Reflect.ownKeys(target.prototype).length > 0) {
|
||||
Debug.fail(`Shared struct type '${context.name}' cannot have methods on its prototype.`);
|
||||
}
|
||||
|
||||
prototype = Object.getPrototypeOf(prototype);
|
||||
|
||||
// Break early when we find a `@Shared` supertype since it should have already been validated.
|
||||
if (prototype && weakSharedStructType.has(prototype)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Give the constructor a null-ish prototype. We can't set it directly because the `prototype` property of a
|
||||
// class constructor is non-configurable, non-writable. Instead, we set it's `[[Prototype]]` to `null`.
|
||||
Object.setPrototypeOf(target.prototype, null); // eslint-disable-line no-null/no-null
|
||||
|
||||
// If the class is `abstract`, do not associate a shared struct type with it.
|
||||
if (options?.abstract) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If decorator metadata is not available we cannot emulate shared struct. In this case we are in an environment
|
||||
// where `../symbolMetadataShim` could not execute and likely doesn't support `SharedStructType` anyways. In
|
||||
// that case, we fall back to a regular object with no sharing support.
|
||||
if (!context.metadata) {
|
||||
weakSharedStructType.set(target.prototype, Object as any);
|
||||
return;
|
||||
}
|
||||
|
||||
// Collect all of the `@Shared` fields in declaration order, with supertype fields coming first in the list.
|
||||
let fields: string[] = [];
|
||||
let metadata: DecoratorMetadata | null = context.metadata;
|
||||
while (metadata) {
|
||||
const sharedFields = weakSharedFields.get(metadata);
|
||||
if (sharedFields) {
|
||||
fields = [...sharedFields, ...fields];
|
||||
}
|
||||
metadata = Object.getPrototypeOf(metadata);
|
||||
}
|
||||
|
||||
// Remove duplicate field names
|
||||
fields = [...new Set(fields)];
|
||||
|
||||
weakSharedStructType.set(target.prototype, new SharedStructTypeFactory(fields));
|
||||
return;
|
||||
}
|
||||
|
||||
function SharedFieldDecorator<This, T>(_target: undefined, context: ClassFieldDecoratorContext<This, T>) {
|
||||
Debug.assert(!context.private, "@Shared is not supported on private fields.");
|
||||
Debug.assert(!context.static, "@Shared is not supported on static fields.");
|
||||
Debug.assert(typeof context.name === "string", "@Shared cannot be used on symbol-named fields.");
|
||||
|
||||
// If `context.metadata` does not exists then we cannot record shared field information about the class and
|
||||
// should do nothing. The `@Shared` decorator on the class will perform appropriate fallback behavior.
|
||||
if (!context.metadata) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get or create an array of `@Shared` field names to be used to construct a SharedStructType for the class.
|
||||
let sharedFields = weakSharedFields.get(context.metadata);
|
||||
if (!sharedFields) weakSharedFields.set(context.metadata, sharedFields = []);
|
||||
|
||||
Debug.assert(!sharedFields.includes(context.name), "@Shared cannot be specified more than once on the same declaration.");
|
||||
sharedFields.push(context.name);
|
||||
}
|
||||
|
||||
function decorator(...args:
|
||||
| [target: AbstractConstructor]
|
||||
| [target: AbstractConstructor, context: ClassDecoratorContext]
|
||||
| [target: undefined, context: ClassFieldDecoratorContext]
|
||||
| [target: object, propertyKey: string, descriptor?: PropertyDescriptor]
|
||||
) {
|
||||
if (args.length === 1) {
|
||||
const [target] = args;
|
||||
return SharedClassDecorator(target, {
|
||||
name: target.name,
|
||||
get metadata() { return getOrCreateMetadata(target); }
|
||||
} as ClassDecoratorContext);
|
||||
}
|
||||
else if (typeof args[1] === "string") {
|
||||
const [target, propertyKey, _descriptor] = args;
|
||||
Debug.assert(target);
|
||||
return SharedFieldDecorator(
|
||||
/*target*/ undefined,
|
||||
{
|
||||
kind: "field",
|
||||
name: propertyKey,
|
||||
public: true,
|
||||
static: false,
|
||||
private: false,
|
||||
get metadata() { return getOrCreateMetadata(target.constructor as AbstractConstructor); }
|
||||
} as unknown as ClassFieldDecoratorContext
|
||||
);
|
||||
}
|
||||
else {
|
||||
const [target, context] = args;
|
||||
switch (context.kind) {
|
||||
case "class": return SharedClassDecorator(target as AbstractConstructor, context);
|
||||
case "field": return SharedFieldDecorator(target as undefined, context);
|
||||
default: Debug.fail(`@Shared is not supported on ${(context as DecoratorContext).kind} declarations.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decorator;
|
||||
}
|
||||
|
||||
class NullObject extends null { // eslint-disable-line no-null/no-null
|
||||
constructor() {
|
||||
const self = Object.create(new.target.prototype);
|
||||
Object.setPrototypeOf(self, null); // eslint-disable-line no-null/no-null
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
Object.setPrototypeOf(NullObject.prototype, null); // eslint-disable-line no-null/no-null
|
||||
|
||||
/** @internal */
|
||||
@Shared({ abstract: true })
|
||||
// The compiler doesn't like us using `extends null` in `SharedStructBase` due to the interface extension to
|
||||
// opt into the `SharedStruct` brand. If we use `extends null` below we end up with this error:
|
||||
//
|
||||
// Class static side 'typeof SharedStructBase' incorrectly extends base class static side 'null'.ts(2417)
|
||||
//
|
||||
export abstract class SharedStructBase extends NullObject { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
constructor() {
|
||||
// Find the first `SharedStructTypeFactory` instance associated with this prototype chain
|
||||
let prototype: object | null = new.target.prototype;
|
||||
while (prototype) {
|
||||
const StructType = weakSharedStructType.get(prototype)?.structType();
|
||||
if (StructType) {
|
||||
// This will either be an instance of `SharedStructType` (if shared structs and `context.metadata` are
|
||||
// available), an instance of `FakeSharedStructType` (if shared structs are not available, but
|
||||
// `context.metadata` is), or `Object` (if neither shared structs nor `context.metadata` are available).
|
||||
return new StructType();
|
||||
}
|
||||
prototype = Object.getPrototypeOf(prototype);
|
||||
}
|
||||
Debug.fail("Class not marked @Shareable()");
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
// Reopen `SharedStructBase` to give it the `SharedStruct` brand.
|
||||
/** @internal */
|
||||
export interface SharedStructBase extends SharedStruct { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
}
|
||||
|
||||
// /** @internal */
|
||||
// @Shared({ abstract: true })
|
||||
// export abstract class SharedArrayBase<T extends Shareable> extends SharedStructBase {
|
||||
// @Shared() readonly length!: number;
|
||||
// constructor(length: number) {
|
||||
// // Find the first `SharedStructTypeFactory` instance associated with this prototype chain
|
||||
// let prototype: object | null = new.target.prototype;
|
||||
// while (prototype) {
|
||||
// const StructType = weakSharedStructType.get(prototype)?.arrayType(length);
|
||||
// if (StructType) {
|
||||
// // This will either be an instance of `SharedStructType` (if shared structs and `context.metadata` are
|
||||
// // available), an instance of `FakeSharedStructType` (if shared structs are not available, but
|
||||
// // `context.metadata` is), or `Object` (if neither shared structs nor `context.metadata` are available).
|
||||
// const inst = new StructType() as Mutable<SharedArrayBase<T>>;
|
||||
// inst.length = length;
|
||||
// return inst;
|
||||
// }
|
||||
// prototype = Object.getPrototypeOf(prototype);
|
||||
// }
|
||||
// Debug.fail("Class not marked @Shareable()");
|
||||
// super();
|
||||
// }
|
||||
|
||||
// [index: number]: T;
|
||||
// }
|
||||
|
||||
// // Reopen `SharedArrayBase` to give it the `SharedArray` brand.
|
||||
// /** @internal */
|
||||
// export interface SharedArrayBase<T extends Shareable> extends SharedArray<T> { // eslint-disable-line @typescript-eslint/no-unsafe-declaration-merging
|
||||
// }
|
||||
124
src/compiler/sharing/structs/sharedStructsGlobals.ts
Normal file
124
src/compiler/sharing/structs/sharedStructsGlobals.ts
Normal file
@ -0,0 +1,124 @@
|
||||
export { };
|
||||
|
||||
// NOTE: These types relate to the origin trial implementation of the shared structs proposal and may not be indicative
|
||||
// of the final proposal. To use these types you must pass `--shared-string-table --harmony-struct` to NodeJS.
|
||||
|
||||
// the following brands are used to distinguish Shared Structs-related objects from other objects since they are not
|
||||
// interchangeable with `object`:
|
||||
declare const kGlobalSharedStructBrand: unique symbol;
|
||||
declare const kGlobalSharedArrayBrand: unique symbol;
|
||||
declare const kGlobalMutexBrand: unique symbol;
|
||||
declare const kGlobalConditionBrand: unique symbol;
|
||||
|
||||
declare global {
|
||||
type ShareablePrimitive =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| OtherShareablePrimitive[keyof OtherShareablePrimitive]
|
||||
;
|
||||
|
||||
type ShareableNonPrimitive =
|
||||
| SharedStruct
|
||||
| SharedArray<Shareable>
|
||||
| Atomics.Mutex
|
||||
| Atomics.Condition
|
||||
| OtherShareableNonPrimitive[keyof OtherShareableNonPrimitive]
|
||||
;
|
||||
|
||||
type Shareable =
|
||||
| ShareablePrimitive
|
||||
| ShareableNonPrimitive
|
||||
;
|
||||
|
||||
interface OtherShareablePrimitive {}
|
||||
interface OtherShareableNonPrimitive {}
|
||||
|
||||
interface SharedStruct {
|
||||
[kGlobalSharedStructBrand]: any;
|
||||
}
|
||||
|
||||
type SharedStructType<T extends SharedStruct & { readonly [P in keyof T]: Shareable }> = new () => T;
|
||||
|
||||
interface SharedStructTypeConstructor {
|
||||
new <T extends SharedStruct & { readonly [P in keyof T]: Shareable }>(fields: readonly (keyof T & string)[]): SharedStructType<T>;
|
||||
new (fields: readonly string[]): SharedStructType<SharedStruct>;
|
||||
isSharedStruct(value: unknown): value is SharedStruct;
|
||||
}
|
||||
|
||||
const SharedStructType: SharedStructTypeConstructor;
|
||||
|
||||
interface SharedArray<T extends Shareable> {
|
||||
[kGlobalSharedArrayBrand]: any;
|
||||
[index: number]: T;
|
||||
readonly length: number;
|
||||
}
|
||||
|
||||
interface ReadonlySharedArray<T extends Shareable> extends SharedArray<T> {
|
||||
readonly [index: number]: T;
|
||||
}
|
||||
|
||||
type SharedArrayConstructor = new <T extends Shareable>(length: number) => SharedArray<T>;
|
||||
|
||||
const SharedArray: SharedArrayConstructor;
|
||||
|
||||
namespace Atomics {
|
||||
/**
|
||||
* An opaque object that represents a shareable mutex.
|
||||
*/
|
||||
interface Mutex {
|
||||
[kGlobalMutexBrand]: any;
|
||||
}
|
||||
|
||||
interface MutexConstructor {
|
||||
new (): Mutex;
|
||||
/**
|
||||
* Locks the mutex during the synchronous execution of the provided callback.
|
||||
* @param mutex The mutex object.
|
||||
* @param cb The callback to execute.
|
||||
* @returns The result of executing the callback.
|
||||
*/
|
||||
lock<T>(mutex: Mutex, cb: () => T): T;
|
||||
/**
|
||||
* Tries to lock the mutex during the synchronous execution of the provided callback
|
||||
* @param mutex The mutex object.
|
||||
* @param cb The callback to execute.
|
||||
* @returns `true` if the lock was taken and the callback was executed; otherwise, `false`.
|
||||
*/
|
||||
tryLock(mutex: Mutex, cb: () => void): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* An opaque object that represents a shareable condition variable.
|
||||
*/
|
||||
interface Condition {
|
||||
[kGlobalConditionBrand]: any;
|
||||
}
|
||||
|
||||
interface ConditionConstructor {
|
||||
new (): Condition;
|
||||
/**
|
||||
* Waits for a period of time for notification signal on a Condition.
|
||||
* @param condition The condition to wait for.
|
||||
* @param mutex The mutex to use to wait on.
|
||||
* @param timeout The period of time to wait.
|
||||
* @returns `true` if the condition was notified prior to the timeout elapsing; otherwise, `false`.
|
||||
*/
|
||||
wait(condition: Condition, mutex: Mutex, timeout?: number): boolean;
|
||||
/**
|
||||
* Notify one or more waiters on a condition.
|
||||
* @param condition The condition to notify.
|
||||
* @param count The number of waiters to notify (default: all current waiters).
|
||||
* @returns The number of waiters that were notified.
|
||||
*/
|
||||
notify(condition: Condition, count?: number): number;
|
||||
}
|
||||
}
|
||||
|
||||
interface Atomics {
|
||||
readonly Mutex: Atomics.MutexConstructor;
|
||||
readonly Condition: Atomics.ConditionConstructor;
|
||||
}
|
||||
}
|
||||
68
src/compiler/sharing/structs/taggedStruct.ts
Normal file
68
src/compiler/sharing/structs/taggedStruct.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { hasProperty } from "../../core";
|
||||
import { isShareableNonPrimitive } from "./shareable";
|
||||
import { Shared } from "./sharedStruct";
|
||||
|
||||
/** @internal */
|
||||
export const enum Tag {
|
||||
Mutex,
|
||||
SharedMutex,
|
||||
Condition,
|
||||
ManualResetEvent,
|
||||
CountdownEvent,
|
||||
Map,
|
||||
Set,
|
||||
ResizableArray,
|
||||
NodeArray,
|
||||
Node,
|
||||
Symbol,
|
||||
FileReference,
|
||||
AmdDependency,
|
||||
CheckJsDirective,
|
||||
CommentDirective,
|
||||
TextRange,
|
||||
Diagnostic,
|
||||
DiagnosticMessageChain,
|
||||
DiagnosticRelatedInformation,
|
||||
Pragma,
|
||||
PragmaArguments,
|
||||
PragmaSpan,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface TaggedStruct<TTag extends Tag> extends SharedStruct {
|
||||
readonly __tag__: TTag;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type TaggedStructConstructor<TTag extends Tag> = (abstract new (...args: any) => TaggedStruct<TTag>) & {
|
||||
readonly __tag__: TTag
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
export type Tagged<F extends abstract new (...args: any) => SharedStruct, TTag extends Tag> =
|
||||
(F extends abstract new (...args: infer A) => (infer R extends TaggedStruct<infer _>) ? (abstract new (...args: A) => Omit<R, "__tag__">) : F) & TaggedStructConstructor<TTag>
|
||||
|
||||
/**
|
||||
* Since shared structs do not support instanceof, we often need a way to distingush one shared struct from another to
|
||||
* create the correct proxy class in our membrane. To accomplish this, we can use the Tagged mixin to inject a
|
||||
* `__tag__` field that we can use instead of `instanceof`.
|
||||
* @internal
|
||||
*/
|
||||
export function Tagged<F extends abstract new (...args: any) => SharedStruct, TTag extends Tag>(base: F, tag: TTag): Tagged<F, TTag> {
|
||||
@Shared({ abstract: true })
|
||||
abstract class TaggedStruct extends base {
|
||||
static readonly __tag__ = tag;
|
||||
@Shared() readonly __tag__ = tag;
|
||||
}
|
||||
return TaggedStruct as Tagged<F, TTag>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isTaggedStructObject<TTag extends Tag>(value: ShareableNonPrimitive, tag?: TTag): value is TaggedStruct<TTag> {
|
||||
return hasProperty(value, "__tag__") && (tag === undefined || (value as TaggedStruct<Tag>).__tag__ === tag);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isTaggedStruct<TTag extends Tag>(value: unknown, tag?: TTag): value is TaggedStruct<TTag> {
|
||||
return isShareableNonPrimitive(value) && isTaggedStructObject(value, tag);
|
||||
}
|
||||
12
src/compiler/symbolDisposeShim.ts
Normal file
12
src/compiler/symbolDisposeShim.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/// <reference lib="esnext.disposable" />
|
||||
export {};
|
||||
|
||||
// Shim `Symbol.dispose` so that we can can use `using`.
|
||||
if (!Symbol.dispose) {
|
||||
try {
|
||||
Object.defineProperty(Symbol, "dispose", { configurable: true, writable: true, value: Symbol.for("Symbol.dispose") });
|
||||
}
|
||||
catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
12
src/compiler/symbolMetadataShim.ts
Normal file
12
src/compiler/symbolMetadataShim.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/// <reference lib="esnext.decorators" />
|
||||
export {};
|
||||
|
||||
// Inject `Symbol.metadata`, if it is missing, so that we can leverage it from the `@Shared()` decorator.
|
||||
if (!Symbol.metadata) {
|
||||
try {
|
||||
Object.defineProperty(Symbol, "metadata", { configurable: true, writable: true, value: Symbol.for("Symbol.metadata") });
|
||||
}
|
||||
catch {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
@ -1420,7 +1420,9 @@ export interface System {
|
||||
realpath?(path: string): string;
|
||||
/** @internal */ getEnvironmentVariable(name: string): string;
|
||||
/** @internal */ tryEnableSourceMapsForHost?(): void;
|
||||
/** @internal */ tryEnableSharedStructs?(): void;
|
||||
/** @internal */ debugMode?: boolean;
|
||||
/** @internal */ cpuCount?(): number;
|
||||
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
|
||||
clearTimeout?(timeoutId: any): void;
|
||||
clearScreen?(): void;
|
||||
@ -1433,6 +1435,8 @@ export interface System {
|
||||
// For testing
|
||||
/** @internal */ now?(): Date;
|
||||
/** @internal */ storeFilesChangingSignatureDuringEmit?: boolean;
|
||||
|
||||
/** @internal */ stringSeed?: number;
|
||||
}
|
||||
|
||||
export interface FileWatcher {
|
||||
@ -1454,7 +1458,8 @@ export let sys: System = (() => {
|
||||
const nativePattern = /^native |^\([^)]+\)$|^(internal[\\/]|[a-zA-Z0-9_\s]+(\.js)?$)/;
|
||||
const _fs: typeof import("fs") = require("fs");
|
||||
const _path: typeof import("path") = require("path");
|
||||
const _os = require("os");
|
||||
const _os: typeof import("os") = require("os");
|
||||
const _cp: typeof import("child_process") = require("child_process");
|
||||
// crypto can be absent on reduced node installations
|
||||
let _crypto: typeof import("crypto") | undefined;
|
||||
try {
|
||||
@ -1506,9 +1511,16 @@ export let sys: System = (() => {
|
||||
inodeWatching: isLinuxOrMacOs,
|
||||
sysLog,
|
||||
});
|
||||
|
||||
const stringSeedFromEnv = +(process.env.TS_STRING_SEED || NaN);
|
||||
const stringSeed = isFinite(stringSeedFromEnv) && Math.floor(stringSeedFromEnv) === stringSeedFromEnv ?
|
||||
stringSeedFromEnv >>> 0 :
|
||||
Math.floor(Math.random() * 0xffffffff) >>> 0;
|
||||
|
||||
const nodeSystem: System = {
|
||||
args: process.argv.slice(2),
|
||||
newLine: _os.EOL,
|
||||
stringSeed,
|
||||
useCaseSensitiveFileNames,
|
||||
write(s: string): void {
|
||||
process.stdout.write(s);
|
||||
@ -1587,6 +1599,29 @@ export let sys: System = (() => {
|
||||
// Could not enable source maps.
|
||||
}
|
||||
},
|
||||
tryEnableSharedStructs() {
|
||||
// If `SharedStructType` is available, or if we've already restarted with options, do nothing.
|
||||
if (typeof SharedStructType === "function" || process.env.TS_RESTARTED_WITH_OPTIONS === "1") {
|
||||
return;
|
||||
}
|
||||
|
||||
const execArgv = ["--shared-string-table", "--harmony-struct"];
|
||||
// If a call to `node --version` with the provided args results in a non-zero exit status, then
|
||||
// we cannot restart with the provided options.
|
||||
if (_cp.spawnSync(process.execPath, [...process.execArgv, ...execArgv, "--version"]).status) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the process with the provided arguments, exiting with that process's exit code.
|
||||
process.exit(_cp.spawnSync(
|
||||
process.execPath,
|
||||
[...process.execArgv, ...execArgv, ...process.argv.slice(1)],
|
||||
{ stdio: "inherit", env: { ...process.env, TS_RESTARTED_WITH_OPTIONS: "1" } },
|
||||
).status ?? 0);
|
||||
},
|
||||
cpuCount() {
|
||||
return _os.cpus().length;
|
||||
},
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
clearScreen: () => {
|
||||
@ -1611,7 +1646,6 @@ export let sys: System = (() => {
|
||||
}
|
||||
}
|
||||
};
|
||||
return nodeSystem;
|
||||
|
||||
/**
|
||||
* `throwIfNoEntry` was added so recently that it's not in the node types.
|
||||
@ -1983,6 +2017,8 @@ export let sys: System = (() => {
|
||||
hash.update(data);
|
||||
return hash.digest("hex");
|
||||
}
|
||||
|
||||
return nodeSystem;
|
||||
}
|
||||
|
||||
let sys: System | undefined;
|
||||
|
||||
110
src/compiler/threading/condition.ts
Normal file
110
src/compiler/threading/condition.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { Debug } from "../debug";
|
||||
import { Mutex } from "./mutex";
|
||||
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
|
||||
import { isTaggedStruct, Tag, Tagged } from "../sharing/structs/taggedStruct";
|
||||
import { UniqueLock } from "./uniqueLock";
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class Condition extends Tagged(SharedStructBase, Tag.Condition) {
|
||||
@Shared() private _condition = new Atomics.Condition();
|
||||
|
||||
/**
|
||||
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}.
|
||||
* @param self The condition to wait for.
|
||||
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
|
||||
* @param stopWaiting An optional callback that can be used to continue waiting if a necessary condition is not met.
|
||||
*/
|
||||
static wait(self: Condition, lock: UniqueLock<Mutex>, stopWaiting?: () => boolean): void {
|
||||
Condition.waitUntil(self, lock, Infinity, stopWaiting!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a timeout period
|
||||
* has elapsed.
|
||||
* @param self The condition to wait for.
|
||||
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
|
||||
* @param timeout The number of milliseconds to wait before returning.
|
||||
* @returns `"no-timeout"` if the condition was notified before the timeout elapsed, or `"timeout"` to indicate the
|
||||
* timeout elapsed before the condition was notified.
|
||||
*/
|
||||
static waitFor(self: Condition, lock: UniqueLock<Mutex>, timeout: number): "no-timeout" | "timeout";
|
||||
/**
|
||||
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a timeout period
|
||||
* has elapsed.
|
||||
* @param self The condition to wait for.
|
||||
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
|
||||
* @param timeout The number of milliseconds to wait before returning.
|
||||
* @param stopWaiting An optional callback that can be used to continue waiting if a necessary condition is not met.
|
||||
* @returns `true` if the condition was notified and the `stopWaiting` callback returned `true`. Otherwise,
|
||||
* `false` to indicate that the timeout elapsed before the condition was ready.
|
||||
*/
|
||||
static waitFor(self: Condition, lock: UniqueLock<Mutex>, timeout: number, stopWaiting: () => boolean): boolean;
|
||||
static waitFor(self: Condition, lock: UniqueLock<Mutex>, timeout: number, stopWaiting?: () => boolean): "no-timeout" | "timeout" | boolean {
|
||||
return Condition.waitUntil(self, lock, Date.now() + timeout, stopWaiting!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a specific time has been reached.
|
||||
* @param self The condition to wait for.
|
||||
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
|
||||
* @param absoluteTimeout The number of milliseconds since the UNIX epoch.
|
||||
* @returns `"no-timeout"` if the condition was notified before the timeout elapsed, or `"timeout"` to indicate the
|
||||
* timeout elapsed before the condition was notified.
|
||||
*/
|
||||
static waitUntil(self: Condition, lock: UniqueLock<Mutex>, absoluteTimeout: number): "no-timeout" | "timeout";
|
||||
/**
|
||||
* Waits until a Condition is signaled via a call to {@link notify|`Condition.notify`}, or until a specific time has been reached.
|
||||
* @param self The condition to wait for.
|
||||
* @param lock A {@link UniqueLock} taken for a {@link Mutex}.
|
||||
* @param absoluteTimeout The number of milliseconds since the UNIX epoch.
|
||||
* @param stopWaiting An optional callback that can be used to continue waiting if a necessary condition is not met.
|
||||
* @returns `true` if the condition was notified and the `stopWaiting` callback returned `true`. Otherwise,
|
||||
* `false` to indicate that the timeout elapsed before the condition was ready.
|
||||
*/
|
||||
static waitUntil(self: Condition, lock: UniqueLock<Mutex>, absoluteTimeout: number, stopWaiting: () => boolean): boolean;
|
||||
static waitUntil(self: Condition, lock: UniqueLock<Mutex>, absoluteTimeout: number, stopWaiting?: () => boolean): "no-timeout" | "timeout" | boolean {
|
||||
const mutex = lock.mutex;
|
||||
Debug.assert(mutex);
|
||||
Debug.assert(mutex["_locked"]); // eslint-disable-line dot-notation -- declared `private`
|
||||
const nativeMutex = mutex["_mutex"]; // eslint-disable-line dot-notation -- declared `private`
|
||||
const nativeCondition = mutex["_condition"]; // eslint-disable-line dot-notation -- declared `private`
|
||||
return Atomics.Mutex.lock(nativeMutex, () => {
|
||||
while (!stopWaiting?.()) {
|
||||
mutex["_locked"] = false; // eslint-disable-line dot-notation -- declared `private`
|
||||
Atomics.Condition.notify(nativeCondition);
|
||||
try {
|
||||
const remainingTimeout = isFinite(absoluteTimeout) ? Date.now() - absoluteTimeout : undefined;
|
||||
const result = Atomics.Condition.wait(self._condition, nativeMutex, remainingTimeout) ? "no-timeout" : "timeout";
|
||||
if (result === "timeout") {
|
||||
return stopWaiting ? stopWaiting() : result;
|
||||
}
|
||||
if (!stopWaiting) {
|
||||
return "no-timeout";
|
||||
}
|
||||
}
|
||||
finally {
|
||||
while (mutex["_locked"]) { // eslint-disable-line dot-notation -- declared `private`
|
||||
Atomics.Condition.wait(nativeCondition, nativeMutex);
|
||||
}
|
||||
mutex["_locked"] = true; // eslint-disable-line dot-notation -- declared `private`
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify one or more waiters on a condition.
|
||||
* @param condition The condition to notify.
|
||||
* @param count The number of waiters to notify (default: all current waiters).
|
||||
* @returns The number of waiters that were notified.
|
||||
*/
|
||||
static notify(self: Condition, count?: number) {
|
||||
return Atomics.Condition.notify(self._condition, count);
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance](value: unknown): value is Condition {
|
||||
return isTaggedStruct(value, Tag.Condition);
|
||||
}
|
||||
}
|
||||
118
src/compiler/threading/countdownEvent.ts
Normal file
118
src/compiler/threading/countdownEvent.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { Condition, Debug, isTaggedStruct, Mutex, Shared, SharedStructBase, Tag, Tagged, UniqueLock } from "../_namespaces/ts";
|
||||
|
||||
const MAX_INT32 = 2 ** 31 - 1;
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class CountdownEvent extends Tagged(SharedStructBase, Tag.CountdownEvent) {
|
||||
@Shared() private _mutex: Mutex | undefined = new Mutex();
|
||||
@Shared() private _condition: Condition | undefined = new Condition();
|
||||
@Shared() private _signaled = false;
|
||||
@Shared() private _initialCount: number;
|
||||
@Shared() private _remainingCount: number = 0;
|
||||
|
||||
constructor(initialCount = 0) {
|
||||
super();
|
||||
initialCount |= 0;
|
||||
Debug.assert(initialCount >= 0, "initialCount out of range");
|
||||
this._initialCount = initialCount;
|
||||
this._remainingCount = initialCount;
|
||||
this._signaled = initialCount === 0;
|
||||
}
|
||||
|
||||
static initialCount(self: CountdownEvent) {
|
||||
return self._initialCount;
|
||||
}
|
||||
|
||||
static remainingCount(self: CountdownEvent) {
|
||||
return self._remainingCount;
|
||||
}
|
||||
|
||||
static isSet(self: CountdownEvent) {
|
||||
return self._remainingCount === 0;
|
||||
}
|
||||
|
||||
static add(self: CountdownEvent, count?: number) {
|
||||
Debug.assert(CountdownEvent.tryAdd(self, count), "event is already signaled");
|
||||
}
|
||||
|
||||
static tryAdd(self: CountdownEvent, count = 1) {
|
||||
count |= 0;
|
||||
Debug.assert(count >= 1, "count out of range");
|
||||
Debug.assert(self._mutex, "object disposed");
|
||||
using _ = new UniqueLock(self._mutex);
|
||||
if (self._remainingCount === 0) return false;
|
||||
Debug.assert(self._remainingCount <= MAX_INT32 - count, "count out of range");
|
||||
self._remainingCount += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
static reset(self: CountdownEvent, count?: number) {
|
||||
if (count !== undefined) {
|
||||
count |= 0;
|
||||
Debug.assert(count >= 0, "count out of range");
|
||||
}
|
||||
|
||||
Debug.assert(self._mutex && self._condition, "object disposed");
|
||||
using _ = new UniqueLock(self._mutex);
|
||||
count ??= self._initialCount;
|
||||
self._remainingCount = count;
|
||||
self._initialCount = count;
|
||||
if (count === 0) {
|
||||
if (!self._signaled) {
|
||||
self._signaled = true;
|
||||
Condition.notify(self._condition);
|
||||
}
|
||||
}
|
||||
else {
|
||||
self._signaled = false;
|
||||
}
|
||||
}
|
||||
|
||||
static signal(self: CountdownEvent, count = 1) {
|
||||
count |= 0;
|
||||
Debug.assert(count >= 1, "count out of range");
|
||||
|
||||
Debug.assert(self._mutex && self._condition, "object disposed");
|
||||
using _ = new UniqueLock(self._mutex);
|
||||
Debug.assert(self._remainingCount >= count, "count out of range");
|
||||
self._remainingCount = self._remainingCount - count;
|
||||
if (self._remainingCount === 0) {
|
||||
if (!self._signaled) {
|
||||
self._signaled = true;
|
||||
Condition.notify(self._condition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static wait(self: CountdownEvent, timeout?: number) {
|
||||
if (timeout !== undefined) {
|
||||
timeout = Math.max(0, timeout | 0);
|
||||
}
|
||||
|
||||
Debug.assert(self._mutex && self._condition, "object disposed");
|
||||
if (CountdownEvent.isSet(self)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
using lck = new UniqueLock(self._mutex);
|
||||
if (timeout !== undefined) {
|
||||
return Condition.waitFor(self._condition, lck, timeout) !== "timeout";
|
||||
}
|
||||
else {
|
||||
Condition.wait(self._condition, lck);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static close(self: CountdownEvent): void {
|
||||
self._mutex = undefined;
|
||||
self._condition = undefined;
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance](value: unknown): value is CountdownEvent {
|
||||
return isTaggedStruct(value, Tag.CountdownEvent);
|
||||
}
|
||||
}
|
||||
10
src/compiler/threading/lockable.ts
Normal file
10
src/compiler/threading/lockable.ts
Normal file
@ -0,0 +1,10 @@
|
||||
/** @internal */
|
||||
export interface BasicLockable {
|
||||
lock(): void;
|
||||
unlock(): void;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface Lockable extends BasicLockable {
|
||||
tryLock(): boolean;
|
||||
}
|
||||
41
src/compiler/threading/manualResetEvent.ts
Normal file
41
src/compiler/threading/manualResetEvent.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
|
||||
import { Tag, Tagged } from "../sharing/structs/taggedStruct";
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class ManualResetEvent extends Tagged(SharedStructBase, Tag.ManualResetEvent) {
|
||||
@Shared() private mutex = new Atomics.Mutex();
|
||||
@Shared() private condition = new Atomics.Condition();
|
||||
@Shared() private signaled = false;
|
||||
|
||||
static isSet(self: ManualResetEvent) {
|
||||
return self.signaled;
|
||||
}
|
||||
|
||||
static set(self: ManualResetEvent) {
|
||||
let result = false;
|
||||
Atomics.Mutex.tryLock(self.mutex, () => {
|
||||
if (!self.signaled) {
|
||||
self.signaled = true;
|
||||
Atomics.Condition.notify(self.condition);
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
static reset(self: ManualResetEvent) {
|
||||
let result = false;
|
||||
Atomics.Mutex.tryLock(self.mutex, () => {
|
||||
if (self.signaled) {
|
||||
self.signaled = false;
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
static wait(self: ManualResetEvent, timeout?: number) {
|
||||
return Atomics.Mutex.lock(self.mutex, () => Atomics.Condition.wait(self.condition, self.mutex, timeout));
|
||||
}
|
||||
}
|
||||
154
src/compiler/threading/mutex.ts
Normal file
154
src/compiler/threading/mutex.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import { hasProperty } from "../core";
|
||||
import { Debug } from "../debug";
|
||||
import { Identifiable } from "../sharing/structs/identifiableStruct";
|
||||
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
|
||||
import { Tag, Tagged, TaggedStruct } from "../sharing/structs/taggedStruct";
|
||||
import { Lockable } from "./lockable";
|
||||
|
||||
let tryLock: (self: Mutex, cacheKey?: object) => boolean;
|
||||
let lock: (self: Mutex, cacheKey?: object) => void;
|
||||
let unlock: (self: Mutex, cacheKey?: object) => void;
|
||||
|
||||
interface CallbackCache {
|
||||
tryLock?: () => void;
|
||||
lock?: () => void;
|
||||
unlock?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* This wraps the experimental `Atomics.Mutex` such that locks can be taken via the RAII-compatible `UniqueLock` and
|
||||
* `ScopedLock` primitives.
|
||||
* @internal
|
||||
*/
|
||||
@Shared()
|
||||
export class Mutex extends Identifiable(Tagged(SharedStructBase, Tag.Mutex)) {
|
||||
@Shared() private _mutex = new Atomics.Mutex();
|
||||
@Shared() private _condition = new Atomics.Condition();
|
||||
@Shared() private _locked = false;
|
||||
|
||||
static {
|
||||
const callbackCache = new WeakMap<object, CallbackCache>();
|
||||
|
||||
// we reuse the same lockTaken variable for each call to tryLock in a thread because its not
|
||||
// reentrant and is reset each time the callback is invoked.
|
||||
let lockTaken = false;
|
||||
|
||||
function getCache(key: object | undefined) {
|
||||
if (key) {
|
||||
let cache = callbackCache.get(key);
|
||||
if (!cache) callbackCache.set(key, cache = {});
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
function makeTryLockCallback(self: Mutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
if (!self._locked) {
|
||||
self._locked = true;
|
||||
lockTaken = true;
|
||||
}
|
||||
};
|
||||
return cache ? cache.tryLock = fn : fn;
|
||||
}
|
||||
|
||||
function makeLockCallback(self: Mutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
while (self._locked) {
|
||||
Atomics.Condition.wait(self._condition, self._mutex);
|
||||
}
|
||||
self._locked = true;
|
||||
};
|
||||
return cache ? cache.lock = fn : fn;
|
||||
}
|
||||
|
||||
function makeUnlockCallback(self: Mutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
self._locked = false;
|
||||
};
|
||||
return cache ? cache.unlock = fn : fn;
|
||||
}
|
||||
|
||||
tryLock = (self, cacheKey) => {
|
||||
lockTaken = false;
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.tryLock(self._mutex, cache?.tryLock ?? makeTryLockCallback(self, cache));
|
||||
return lockTaken;
|
||||
};
|
||||
|
||||
lock = (self, cacheKey) => {
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.lock(self._mutex, cache?.lock ?? makeLockCallback(self, cache));
|
||||
};
|
||||
|
||||
unlock = (self, cacheKey) => {
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.lock(self._mutex, cache?.unlock ?? makeUnlockCallback(self, cache));
|
||||
Atomics.Condition.notify(self._condition);
|
||||
};
|
||||
}
|
||||
|
||||
static tryLock(self: Mutex, cb: () => void): boolean {
|
||||
if (!tryLock(self, /*cacheKey*/ undefined)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
cb();
|
||||
return true;
|
||||
}
|
||||
finally {
|
||||
unlock(self, /*cacheKey*/ undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks the mutex and invokes `cb` in the context of the lock. The mutex will always be unlocked when the method
|
||||
* returns, regardless as to whether `cb` throws an exception.
|
||||
*/
|
||||
static lock<T>(self: Mutex, cb: () => T): T {
|
||||
lock(self, /*cacheKey*/ undefined);
|
||||
try {
|
||||
return cb();
|
||||
}
|
||||
finally {
|
||||
unlock(self, /*cacheKey*/ undefined);
|
||||
}
|
||||
}
|
||||
|
||||
static asLockable(self: Mutex): Lockable {
|
||||
return new LockableMutex(self);
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance](value: unknown): value is Mutex {
|
||||
return typeof value === "object" && value !== null && // eslint-disable-line no-null/no-null -- necessary for comparison
|
||||
hasProperty(value, "__tag__") &&
|
||||
(value as TaggedStruct<Tag>).__tag__ === Tag.Mutex;
|
||||
}
|
||||
}
|
||||
|
||||
class LockableMutex implements Lockable {
|
||||
private _mutex: Mutex;
|
||||
private _ownsLock = false;
|
||||
|
||||
constructor(mutex: Mutex) {
|
||||
this._mutex = mutex;
|
||||
}
|
||||
|
||||
tryLock(): boolean {
|
||||
Debug.assert(!this._ownsLock);
|
||||
this._ownsLock = tryLock(this._mutex, this);
|
||||
return this._ownsLock;
|
||||
}
|
||||
|
||||
lock(): void {
|
||||
Debug.assert(!this._ownsLock);
|
||||
lock(this._mutex, this);
|
||||
this._ownsLock = true;
|
||||
}
|
||||
|
||||
unlock(): void {
|
||||
Debug.assert(this._ownsLock);
|
||||
unlock(this._mutex, this);
|
||||
this._ownsLock = false;
|
||||
}
|
||||
}
|
||||
108
src/compiler/threading/scopedLock.ts
Normal file
108
src/compiler/threading/scopedLock.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { Debug } from "../debug";
|
||||
import "../symbolDisposeShim";
|
||||
|
||||
import { Lockable } from "./lockable";
|
||||
import { Mutex } from "./mutex";
|
||||
import { SharedMutex } from "./sharedMutex";
|
||||
|
||||
/** @internal */
|
||||
export class ScopedLock {
|
||||
private _mutexes: readonly Lockable[] | undefined;
|
||||
|
||||
constructor(mutexes: Iterable<Lockable | Mutex | SharedMutex>) {
|
||||
const array: Lockable[] = [];
|
||||
for (const mutex of mutexes) {
|
||||
array.push(
|
||||
mutex instanceof Mutex ? Mutex.asLockable(mutex) :
|
||||
mutex instanceof SharedMutex ? SharedMutex.asLockable(mutex) :
|
||||
mutex);
|
||||
}
|
||||
|
||||
let remaining = array.length;
|
||||
let index = 0;
|
||||
let error;
|
||||
let hasError = false;
|
||||
while (!hasError) {
|
||||
try {
|
||||
if (remaining === 0) {
|
||||
// return all locks taken, in the order they were taken. index should be back at the first lock taken in
|
||||
// this attempt.
|
||||
this._mutexes = [...array.slice(index), ...array.slice(0, index)];
|
||||
return;
|
||||
}
|
||||
|
||||
const lockable = array[index];
|
||||
if (remaining === array.length) {
|
||||
// always wait for the first lock
|
||||
lockable.lock();
|
||||
}
|
||||
else {
|
||||
if (lockable.tryLock()) {
|
||||
// this lock was taken. move to the next lock
|
||||
index = (index + 1) % array.length;
|
||||
remaining--;
|
||||
}
|
||||
else {
|
||||
// if we fail to take a lock, unlock each lock taken so far so that we start over at the current
|
||||
// index.
|
||||
let i = (index + array.length - 1) % array.length;
|
||||
while (remaining < array.length) {
|
||||
// always unlock all locks taken, even if one unlock fails for some reason.
|
||||
try {
|
||||
array[i].unlock();
|
||||
}
|
||||
catch (e) {
|
||||
error = error ? createSuppressedError(e, error) : e;
|
||||
hasError = true;
|
||||
}
|
||||
i = (index + array.length - 1) % array.length;
|
||||
remaining++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
error = error ? createSuppressedError(e, error) : e;
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if we reach this point, an error occurred. unlock all locks taken in reverse order and throw the error.
|
||||
let i = (index + array.length - 1) % array.length;
|
||||
while (remaining < array.length) {
|
||||
// always unlock all locks taken, even if one unlock fails for some reason.
|
||||
try {
|
||||
array[i].unlock();
|
||||
}
|
||||
catch (e) {
|
||||
error = createSuppressedError(e, error);
|
||||
}
|
||||
i = (index + array.length - 1) % array.length;
|
||||
remaining++;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
[Symbol.dispose]() {
|
||||
const mutexes = this._mutexes;
|
||||
if (mutexes) {
|
||||
this._mutexes = undefined;
|
||||
for (let i = mutexes.length - 1; i >= 0; i--) {
|
||||
mutexes[i].unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createSuppressedError(error: unknown, suppressed: unknown) {
|
||||
if (typeof SuppressedError === "function") {
|
||||
return Debug.captureStackTrace(new SuppressedError(error, suppressed), createSuppressedError);
|
||||
}
|
||||
|
||||
const e = new Error("An error suppression occurred.") as SuppressedError;
|
||||
e.error = error;
|
||||
e.suppressed = suppressed;
|
||||
e.name = "SuppressedError";
|
||||
return Debug.captureStackTrace(e, createSuppressedError);
|
||||
}
|
||||
108
src/compiler/threading/sharedLock.ts
Normal file
108
src/compiler/threading/sharedLock.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import "../symbolDisposeShim";
|
||||
|
||||
import { Debug } from "../debug";
|
||||
import { SharedLockable } from "./sharedLockable";
|
||||
import { SharedMutex } from "./sharedMutex";
|
||||
|
||||
/** @internal */
|
||||
export class SharedLock<T extends SharedLockable | SharedMutex> {
|
||||
private _mutex: T | undefined;
|
||||
private _lockable: SharedLockable | undefined;
|
||||
private _ownsLock = false;
|
||||
|
||||
constructor();
|
||||
constructor(mutex: T, t?: "defer-lock" | "try-to-lock" | "adopt-lock");
|
||||
constructor(mutex?: T, t?: "defer-lock" | "try-to-lock" | "adopt-lock") {
|
||||
this._mutex = mutex;
|
||||
this._lockable =
|
||||
mutex instanceof SharedMutex ? SharedMutex.asSharedLockable(mutex) :
|
||||
mutex;
|
||||
if (this._lockable) {
|
||||
switch (t) {
|
||||
case "defer-lock":
|
||||
break;
|
||||
case "try-to-lock":
|
||||
this._ownsLock = this._lockable.tryLockShared();
|
||||
break;
|
||||
case "adopt-lock":
|
||||
this._ownsLock = true;
|
||||
break;
|
||||
case undefined:
|
||||
this._lockable.lockShared();
|
||||
this._ownsLock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get mutex() {
|
||||
return this._mutex;
|
||||
}
|
||||
|
||||
get ownsLock() {
|
||||
return this._ownsLock;
|
||||
}
|
||||
|
||||
tryLock(): boolean {
|
||||
Debug.assert(this._lockable);
|
||||
Debug.assert(!this._ownsLock);
|
||||
this._ownsLock = this._lockable.tryLockShared();
|
||||
return this._ownsLock;
|
||||
}
|
||||
|
||||
lock(): void {
|
||||
Debug.assert(this._lockable);
|
||||
Debug.assert(!this._ownsLock);
|
||||
this._lockable.lockShared();
|
||||
this._ownsLock = true;
|
||||
}
|
||||
|
||||
unlock() {
|
||||
Debug.assert(this._lockable);
|
||||
Debug.assert(this._ownsLock);
|
||||
this._ownsLock = false;
|
||||
this._lockable.unlockShared();
|
||||
}
|
||||
|
||||
release() {
|
||||
this._mutex = undefined;
|
||||
this._lockable = undefined;
|
||||
this._ownsLock = false;
|
||||
}
|
||||
|
||||
swap(other: SharedLock<T>) {
|
||||
const mutex = other._mutex;
|
||||
other._mutex = this._mutex;
|
||||
const lockable = other._lockable;
|
||||
other._lockable = this._lockable;
|
||||
const ownsLock = other._ownsLock;
|
||||
other._ownsLock = this._ownsLock;
|
||||
this._mutex = mutex;
|
||||
this._lockable = lockable;
|
||||
this._ownsLock = ownsLock;
|
||||
}
|
||||
|
||||
move() {
|
||||
const other = new SharedLock<T>();
|
||||
other._mutex = this._mutex;
|
||||
other._lockable = this._lockable;
|
||||
other._ownsLock = this._ownsLock;
|
||||
this._mutex = undefined;
|
||||
this._lockable = undefined;
|
||||
this._ownsLock = false;
|
||||
return other;
|
||||
}
|
||||
|
||||
[Symbol.dispose]() {
|
||||
const lockable = this._lockable;
|
||||
if (lockable) {
|
||||
const ownsLock = this._ownsLock;
|
||||
this._mutex = undefined;
|
||||
this._lockable = undefined;
|
||||
this._ownsLock = false;
|
||||
if (ownsLock) {
|
||||
lockable.unlockShared();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/compiler/threading/sharedLockable.ts
Normal file
6
src/compiler/threading/sharedLockable.ts
Normal file
@ -0,0 +1,6 @@
|
||||
/** @internal */
|
||||
export interface SharedLockable {
|
||||
tryLockShared(): boolean;
|
||||
lockShared(): void;
|
||||
unlockShared(): void;
|
||||
}
|
||||
277
src/compiler/threading/sharedMutex.ts
Normal file
277
src/compiler/threading/sharedMutex.ts
Normal file
@ -0,0 +1,277 @@
|
||||
import "../symbolDisposeShim";
|
||||
|
||||
import { hasProperty } from "../core";
|
||||
import { Debug } from "../debug";
|
||||
import { Identifiable } from "../sharing/structs/identifiableStruct";
|
||||
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
|
||||
import { Tag, Tagged, TaggedStruct } from "../sharing/structs/taggedStruct";
|
||||
import { Lockable } from "./lockable";
|
||||
import { SharedLockable } from "./sharedLockable";
|
||||
|
||||
const EXCLUSIVE = 1 << 31 >>> 0;
|
||||
const SHARED = ~EXCLUSIVE >>> 0;
|
||||
|
||||
let tryLock: (self: SharedMutex, cacheKey: object | undefined) => boolean;
|
||||
let lock: (self: SharedMutex, cacheKey: object | undefined) => void;
|
||||
let unlock: (self: SharedMutex, cacheKey: object | undefined) => void;
|
||||
let tryLockShared: (self: SharedMutex, cacheKey: object | undefined) => boolean;
|
||||
let lockShared: (self: SharedMutex, cacheKey: object | undefined) => void;
|
||||
let unlockShared: (self: SharedMutex, cacheKey: object | undefined) => void;
|
||||
|
||||
interface CallbackCache {
|
||||
tryLock?: () => void;
|
||||
lock?: () => void;
|
||||
unlock?: () => void;
|
||||
tryLockShared?: () => void;
|
||||
lockShared?: () => void;
|
||||
unlockShared?: () => void;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class SharedMutex extends Identifiable(Tagged(SharedStructBase, Tag.SharedMutex)) {
|
||||
@Shared() private _mutex = new Atomics.Mutex();
|
||||
@Shared() private _gate1 = new Atomics.Condition();
|
||||
@Shared() private _gate2 = new Atomics.Condition();
|
||||
@Shared() private _state = 0;
|
||||
|
||||
static {
|
||||
const callbackCache = new WeakMap<object, CallbackCache>();
|
||||
|
||||
// we reuse the same lockTaken variable for each call to tryLock/tryShardLock in a thread because its not
|
||||
// reentrant and is reset each time the callback is invoked.
|
||||
let lockTaken = false;
|
||||
|
||||
function getCache(key: object | undefined) {
|
||||
if (key) {
|
||||
let cache = callbackCache.get(key);
|
||||
if (!cache) callbackCache.set(key, cache = {});
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
function makeTryLockCallback(self: SharedMutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
if (self._state === 0) {
|
||||
self._state |= EXCLUSIVE;
|
||||
lockTaken = true;
|
||||
}
|
||||
};
|
||||
return cache ? cache.tryLock = fn : fn;
|
||||
}
|
||||
|
||||
function makeLockCallback(self: SharedMutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
// take exclusive lock
|
||||
while (self._state & EXCLUSIVE) {
|
||||
Atomics.Condition.wait(self._gate1, self._mutex);
|
||||
}
|
||||
|
||||
self._state |= EXCLUSIVE;
|
||||
|
||||
// wait for readers to drain
|
||||
while (self._state & SHARED) {
|
||||
Atomics.Condition.wait(self._gate2, self._mutex);
|
||||
}
|
||||
};
|
||||
return cache ? cache.lock = fn : fn;
|
||||
}
|
||||
|
||||
function makeUnlockCallback(self: SharedMutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
// release exclusive lock
|
||||
self._state = 0;
|
||||
};
|
||||
return cache ? cache.unlock = fn : fn;
|
||||
}
|
||||
|
||||
function makeTryLockSharedCallback(self: SharedMutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
if (self._state === 0) {
|
||||
self._state |= EXCLUSIVE;
|
||||
lockTaken = true;
|
||||
}
|
||||
};
|
||||
return cache ? cache.tryLockShared = fn : fn;
|
||||
}
|
||||
|
||||
function makeLockSharedCallback(self: SharedMutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
while ((self._state & EXCLUSIVE) || ((self._state & SHARED) >>> 0) === SHARED) {
|
||||
Atomics.Condition.wait(self._gate1, self._mutex);
|
||||
}
|
||||
const readerCount = ((self._state & SHARED) >>> 0) + 1;
|
||||
self._state = (readerCount | (self._state & EXCLUSIVE)) >>> 0;
|
||||
};
|
||||
return cache ? cache.lockShared = fn : fn;
|
||||
}
|
||||
|
||||
function makeUnlockSharedCallback(self: SharedMutex, cache: CallbackCache | undefined) {
|
||||
const fn = () => {
|
||||
const readerCount = ((self._state & SHARED) >>> 0) - 1;
|
||||
self._state = (readerCount | (self._state & EXCLUSIVE)) >>> 0;
|
||||
if (self._state & EXCLUSIVE) {
|
||||
if (readerCount === 0) {
|
||||
Atomics.Condition.notify(self._gate2, 1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (readerCount === SHARED - 1) {
|
||||
Atomics.Condition.notify(self._gate1, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
return cache ? cache.unlockShared = fn : fn;
|
||||
}
|
||||
|
||||
tryLock = (self, cacheKey) => {
|
||||
lockTaken = false;
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.tryLock(self._mutex, cache?.tryLock ?? makeTryLockCallback(self, cache));
|
||||
return lockTaken;
|
||||
};
|
||||
|
||||
lock = (self, cacheKey) => {
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.lock(self._mutex, cache?.lock ?? makeLockCallback(self, cache));
|
||||
};
|
||||
|
||||
unlock = (self, cacheKey) => {
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.lock(self._mutex, cache?.unlock ?? makeUnlockCallback(self, cache));
|
||||
Atomics.Condition.notify(self._gate1);
|
||||
};
|
||||
|
||||
tryLockShared = (self, cacheKey) => {
|
||||
lockTaken = false;
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.tryLock(self._mutex, cache?.tryLockShared ?? makeTryLockSharedCallback(self, cache));
|
||||
return lockTaken;
|
||||
};
|
||||
|
||||
lockShared = (self, cacheKey) => {
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.lock(self._mutex, cache?.lockShared ?? makeLockSharedCallback(self, cache));
|
||||
};
|
||||
|
||||
unlockShared = (self, cacheKey) => {
|
||||
const cache = getCache(cacheKey);
|
||||
Atomics.Mutex.lock(self._mutex, cache?.unlockShared ?? makeUnlockCallback(self, cache));
|
||||
};
|
||||
}
|
||||
|
||||
static tryLock(self: SharedMutex, cb: () => void): boolean {
|
||||
if (!tryLock(self, /*cacheKey*/ undefined)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
cb();
|
||||
return true;
|
||||
}
|
||||
finally {
|
||||
unlock(self, /*cacheKey*/ undefined);
|
||||
}
|
||||
}
|
||||
|
||||
static lock<T>(self: SharedMutex, cb: () => T): T {
|
||||
lock(self, /*cacheKey*/ undefined);
|
||||
try {
|
||||
return cb();
|
||||
}
|
||||
finally {
|
||||
unlock(self, /*cacheKey*/ undefined);
|
||||
}
|
||||
}
|
||||
|
||||
static tryLockShared(self: SharedMutex, cb: () => void): boolean {
|
||||
if (!tryLockShared(self, /*cacheKey*/ undefined)) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
cb();
|
||||
return true;
|
||||
}
|
||||
finally {
|
||||
unlockShared(self, /*cacheKey*/ undefined);
|
||||
}
|
||||
}
|
||||
|
||||
static lockShared<T>(self: SharedMutex, cb: () => T): T {
|
||||
lockShared(self, /*cacheKey*/ undefined);
|
||||
try {
|
||||
return cb();
|
||||
}
|
||||
finally {
|
||||
unlockShared(self, /*cacheKey*/ undefined);
|
||||
}
|
||||
}
|
||||
|
||||
static asLockable(self: SharedMutex): Lockable {
|
||||
return new LockableSharedMutex(self);
|
||||
}
|
||||
|
||||
static asSharedLockable(self: SharedMutex): SharedLockable {
|
||||
return new SharedLockableSharedMutex(self);
|
||||
}
|
||||
|
||||
static [Symbol.hasInstance](value: unknown): value is SharedMutex {
|
||||
return typeof value === "object" && value !== null && // eslint-disable-line no-null/no-null -- necessary for comparision
|
||||
hasProperty(value, "__tag__") &&
|
||||
(value as TaggedStruct<Tag>).__tag__ === Tag.SharedMutex;
|
||||
}
|
||||
}
|
||||
|
||||
class LockableSharedMutex implements Lockable {
|
||||
private _mutex: SharedMutex;
|
||||
private _ownsLock = false;
|
||||
|
||||
constructor(mutex: SharedMutex) {
|
||||
this._mutex = mutex;
|
||||
}
|
||||
|
||||
tryLock(): boolean {
|
||||
Debug.assert(!this._ownsLock);
|
||||
this._ownsLock = tryLock(this._mutex, this);
|
||||
return this._ownsLock;
|
||||
}
|
||||
|
||||
lock(): void {
|
||||
Debug.assert(!this._ownsLock);
|
||||
lock(this._mutex, this);
|
||||
this._ownsLock = true;
|
||||
}
|
||||
|
||||
unlock(): void {
|
||||
Debug.assert(this._ownsLock);
|
||||
unlock(this._mutex, this);
|
||||
this._ownsLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
class SharedLockableSharedMutex implements SharedLockable {
|
||||
private _mutex: SharedMutex;
|
||||
private _ownsLock = false;
|
||||
|
||||
constructor(mutex: SharedMutex) {
|
||||
this._mutex = mutex;
|
||||
}
|
||||
|
||||
tryLockShared(): boolean {
|
||||
Debug.assert(!this._ownsLock);
|
||||
this._ownsLock = tryLockShared(this._mutex, this);
|
||||
return this._ownsLock;
|
||||
}
|
||||
|
||||
lockShared(): void {
|
||||
Debug.assert(!this._ownsLock);
|
||||
lockShared(this._mutex, this);
|
||||
this._ownsLock = true;
|
||||
}
|
||||
|
||||
unlockShared(): void {
|
||||
Debug.assert(this._ownsLock);
|
||||
unlockShared(this._mutex, this);
|
||||
this._ownsLock = false;
|
||||
}
|
||||
}
|
||||
231
src/compiler/threading/threadPool.ts
Normal file
231
src/compiler/threading/threadPool.ts
Normal file
@ -0,0 +1,231 @@
|
||||
import { WorkerThreadsHost, Worker, workerThreads } from "../workerThreads";
|
||||
import { SharedLinkedList } from "../sharing/collections/sharedLinkedList";
|
||||
import { Shared, SharedStructBase } from "../sharing/structs/sharedStruct";
|
||||
import { Mutex } from "./mutex";
|
||||
import { Condition } from "./condition";
|
||||
import { UniqueLock } from "./uniqueLock";
|
||||
import { Debug } from "../debug";
|
||||
import { isNodeLikeSystem } from "../core";
|
||||
import { CountdownEvent } from "./countdownEvent";
|
||||
|
||||
@Shared()
|
||||
class ThreadPoolWorkItem extends SharedStructBase {
|
||||
@Shared() readonly name: string;
|
||||
@Shared() readonly arg: Shareable;
|
||||
|
||||
constructor(name: string, arg?: Shareable) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.arg = arg;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@Shared()
|
||||
export class ThreadPoolState extends SharedStructBase {
|
||||
@Shared() mutex = new Mutex();
|
||||
@Shared() condition = new Condition();
|
||||
@Shared() countdown = new CountdownEvent(1);
|
||||
@Shared() queue = new SharedLinkedList<ThreadPoolWorkItem>();
|
||||
@Shared() active = 0;
|
||||
@Shared() done = false;
|
||||
@Shared() error: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a thread pool of one or more worker threads. A {@link ThreadPool} can only be created on the main thread.
|
||||
* @internal
|
||||
*/
|
||||
export class ThreadPool {
|
||||
readonly poolSize: number;
|
||||
|
||||
private readonly _host: WorkerThreadsHost;
|
||||
private _workers: Worker[] = [];
|
||||
private _state = new ThreadPoolState();
|
||||
|
||||
private _listening = false;
|
||||
private _onUncaughtException = () => {
|
||||
if (!this._state.done) {
|
||||
this.abort();
|
||||
}
|
||||
};
|
||||
|
||||
constructor(poolSize: number, host = workerThreads ?? Debug.fail("Worker threads not available.")) {
|
||||
Debug.assert(poolSize >= 1);
|
||||
Debug.assert(host.isMainThread(), "A new thread pool can only be created on the main thread.");
|
||||
this.poolSize = poolSize;
|
||||
this._host = host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts all threads in the thread pool.
|
||||
*/
|
||||
start(): void {
|
||||
if (this._workers.length < this.poolSize) {
|
||||
this._startListening();
|
||||
}
|
||||
for (let i = this._workers.length; i < this.poolSize; i++) {
|
||||
this._workers.push(this._host.createWorker({
|
||||
name: "ThreadPool Thread",
|
||||
workerData: { type: "ThreadPoolThread", state: this._state }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the addition of new work items in the thread pool and wait for threads to terminate gracefully.
|
||||
*/
|
||||
stop(timeout?: number) {
|
||||
this._shutdown();
|
||||
return CountdownEvent.wait(this._state.countdown, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediately stop all threads in the thread pool and wait for them to terminate.
|
||||
*/
|
||||
async abort() {
|
||||
this._shutdown();
|
||||
const workers = this._workers.splice(0, this._workers.length);
|
||||
await Promise.all(workers.map(worker => worker.terminate()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Queues a work item to execute in a worker thread.
|
||||
* @param name The name of the work item to execute.
|
||||
* @param arg An argument passed to the work item that can be used to communicate and coordinate with the background
|
||||
* thread.
|
||||
*/
|
||||
queueWorkItem(name: string, arg?: Shareable) {
|
||||
{
|
||||
using _ = new UniqueLock(this._state.mutex);
|
||||
SharedLinkedList.push(this._state.queue, new ThreadPoolWorkItem(name, arg));
|
||||
}
|
||||
|
||||
Condition.notify(this._state.condition, 1);
|
||||
}
|
||||
|
||||
private _shutdown() {
|
||||
this._stopListening();
|
||||
|
||||
Debug.log.trace("Shutting down thread pool");
|
||||
if (!this._state.done) {
|
||||
using _ = new UniqueLock(this._state.mutex);
|
||||
this._state.done = true;
|
||||
Condition.notify(this._state.condition);
|
||||
CountdownEvent.signal(this._state.countdown, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private _startListening() {
|
||||
if (isNodeLikeSystem()) {
|
||||
if (this._listening) {
|
||||
return;
|
||||
}
|
||||
this._listening = true;
|
||||
process.on("uncaughtExceptionMonitor", this._onUncaughtException);
|
||||
}
|
||||
}
|
||||
|
||||
private _stopListening() {
|
||||
if (isNodeLikeSystem()) {
|
||||
if (!this._listening) {
|
||||
return;
|
||||
}
|
||||
process.off("uncaughtExceptionMonitor", this._onUncaughtException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a thread in a {@link ThreadPool} and can be used to process work items. A {@link ThreadPoolThread} should
|
||||
* only be used in a worker thread.
|
||||
* @internal
|
||||
*/
|
||||
export class ThreadPoolThread {
|
||||
private _state: ThreadPoolState;
|
||||
private _processWorkItem: (name: string, arg: Shareable) => void;
|
||||
|
||||
constructor(state: ThreadPoolState, processWorkItem: (name: string, arg: Shareable) => void) {
|
||||
this._state = state;
|
||||
this._processWorkItem = processWorkItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the thread pool thread until the {@link ThreadPool} signals the thread should shut down.
|
||||
*/
|
||||
run() {
|
||||
let running = true;
|
||||
let started = false;
|
||||
try {
|
||||
const processWorkItem = this._processWorkItem;
|
||||
if (!CountdownEvent.tryAdd(this._state.countdown, 1)) {
|
||||
Debug.log.trace("thread pool is already shut down");
|
||||
return;
|
||||
}
|
||||
|
||||
while (running) {
|
||||
let workItem: ThreadPoolWorkItem | undefined;
|
||||
{
|
||||
using lck = new UniqueLock(this._state.mutex);
|
||||
|
||||
// decrement the active thread counter before we start waiting for work
|
||||
if (started) {
|
||||
this._state.active--;
|
||||
}
|
||||
|
||||
// wait until we have work to do
|
||||
Condition.wait(this._state.condition, lck, () => this._state.queue.size > 0 || this._state.done);
|
||||
|
||||
// stop the thread if the thread pool is closed
|
||||
if (this._state.done) {
|
||||
if (started) {
|
||||
this._state.active--;
|
||||
}
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// increment the active thread counter before we start processing a work item
|
||||
this._state.active++;
|
||||
started = true;
|
||||
workItem = SharedLinkedList.shift(this._state.queue);
|
||||
}
|
||||
|
||||
// process the workitem
|
||||
if (workItem) {
|
||||
try {
|
||||
processWorkItem(workItem.name, workItem.arg);
|
||||
}
|
||||
catch (e) {
|
||||
Debug.log.trace(e);
|
||||
running = false;
|
||||
using _ = new UniqueLock(this._state.mutex);
|
||||
this._state.active--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
Debug.log.trace(e);
|
||||
}
|
||||
|
||||
// Debug.log.trace(`shutting down.`);
|
||||
CountdownEvent.signal(this._state.countdown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue additional work to be performed in another thread.
|
||||
*/
|
||||
queueWorkItem(name: string, arg?: Shareable) {
|
||||
Debug.assert(!this._state.done);
|
||||
|
||||
{
|
||||
using _ = new UniqueLock(this._state.mutex);
|
||||
Debug.assert(!this._state.done);
|
||||
SharedLinkedList.push(this._state.queue, new ThreadPoolWorkItem(name, arg));
|
||||
}
|
||||
|
||||
Condition.notify(this._state.condition, 1);
|
||||
}
|
||||
}
|
||||
118
src/compiler/threading/uniqueLock.ts
Normal file
118
src/compiler/threading/uniqueLock.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import "../symbolDisposeShim";
|
||||
|
||||
import { hasProperty } from "../core";
|
||||
import { Debug } from "../debug";
|
||||
import { BasicLockable, Lockable } from "./lockable";
|
||||
import { Mutex } from "./mutex";
|
||||
import { SharedMutex } from "./sharedMutex";
|
||||
|
||||
function isLockable(x: BasicLockable): x is Lockable {
|
||||
return x instanceof UniqueLock ? !!x.mutex && isLockable(x.mutex) : hasProperty(x, "tryLock") && typeof (x as Lockable).tryLock === "function";
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export class UniqueLock<T extends BasicLockable | Mutex | SharedMutex> {
|
||||
private _mutex: T | undefined;
|
||||
private _lockable: BasicLockable | undefined;
|
||||
private _ownsLock = false;
|
||||
|
||||
constructor(mutex?: T);
|
||||
constructor(mutex: Extract<T, Lockable | Mutex | SharedMutex>, t?: "defer-lock" | "try-to-lock" | "adopt-lock");
|
||||
constructor(mutex: T, t?: "defer-lock" | "adopt-lock");
|
||||
constructor(mutex?: T, t?: "defer-lock" | "try-to-lock" | "adopt-lock") {
|
||||
this._mutex = mutex;
|
||||
this._lockable =
|
||||
mutex instanceof Mutex ? Mutex.asLockable(mutex) :
|
||||
mutex instanceof SharedMutex ? SharedMutex.asLockable(mutex) :
|
||||
mutex;
|
||||
if (this._lockable) {
|
||||
switch (t) {
|
||||
case "defer-lock":
|
||||
break;
|
||||
case "try-to-lock":
|
||||
Debug.assert(isLockable(this._lockable));
|
||||
this._ownsLock = this._lockable.tryLock();
|
||||
break;
|
||||
case "adopt-lock":
|
||||
this._ownsLock = true;
|
||||
break;
|
||||
case undefined:
|
||||
this._lockable.lock();
|
||||
this._ownsLock = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get mutex() {
|
||||
return this._mutex;
|
||||
}
|
||||
|
||||
get ownsLock() {
|
||||
return this._ownsLock;
|
||||
}
|
||||
|
||||
tryLock(this: UniqueLock<Extract<T, Lockable | Mutex | SharedMutex>>): boolean {
|
||||
Debug.assert(this._lockable);
|
||||
Debug.assert(!this._ownsLock);
|
||||
Debug.assert(isLockable(this._lockable));
|
||||
this._ownsLock = this._lockable.tryLock();
|
||||
return this._ownsLock;
|
||||
}
|
||||
|
||||
lock(): void {
|
||||
Debug.assert(this._lockable);
|
||||
Debug.assert(!this._ownsLock);
|
||||
this._lockable.lock();
|
||||
this._ownsLock = true;
|
||||
}
|
||||
|
||||
unlock() {
|
||||
Debug.assert(this._lockable);
|
||||
Debug.assert(this._ownsLock);
|
||||
this._ownsLock = false;
|
||||
this._lockable.unlock();
|
||||
}
|
||||
|
||||
release() {
|
||||
this._mutex = undefined;
|
||||
this._lockable = undefined;
|
||||
this._ownsLock = false;
|
||||
}
|
||||
|
||||
swap(other: UniqueLock<T>) {
|
||||
const mutex = other._mutex;
|
||||
const lockable = other._lockable;
|
||||
const ownsLock = other._ownsLock;
|
||||
other._mutex = this._mutex;
|
||||
other._lockable = this._lockable;
|
||||
other._ownsLock = this._ownsLock;
|
||||
this._mutex = mutex;
|
||||
this._lockable = lockable;
|
||||
this._ownsLock = ownsLock;
|
||||
}
|
||||
|
||||
move() {
|
||||
const other = new UniqueLock<T>();
|
||||
other._mutex = this._mutex;
|
||||
other._lockable = this._lockable;
|
||||
other._ownsLock = this._ownsLock;
|
||||
this._mutex = undefined;
|
||||
this._lockable = undefined;
|
||||
this._ownsLock = false;
|
||||
return other;
|
||||
}
|
||||
|
||||
[Symbol.dispose]() {
|
||||
const lockable = this._lockable;
|
||||
if (lockable) {
|
||||
const ownsLock = this._ownsLock;
|
||||
this._mutex = undefined;
|
||||
this._lockable = undefined;
|
||||
this._ownsLock = false;
|
||||
if (ownsLock) {
|
||||
lockable.unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,6 @@ import {
|
||||
ForegroundColorEscapeSequences,
|
||||
formatColorAndReset,
|
||||
getAllProjectOutputs,
|
||||
getBuildInfo as ts_getBuildInfo,
|
||||
getBuildInfoFileVersionMap,
|
||||
getConfigFileParsingDiagnostics,
|
||||
getDirectoryPath,
|
||||
@ -61,7 +60,6 @@ import {
|
||||
getFilesInErrorForSummary,
|
||||
getFirstProjectOutput,
|
||||
getLocaleTimeString,
|
||||
getModifiedTime as ts_getModifiedTime,
|
||||
getNormalizedAbsolutePath,
|
||||
getParsedCommandLineOfConfigFile,
|
||||
getPendingEmitKind,
|
||||
@ -111,6 +109,9 @@ import {
|
||||
Status,
|
||||
sys,
|
||||
System,
|
||||
ThreadPool,
|
||||
getBuildInfo as ts_getBuildInfo,
|
||||
getModifiedTime as ts_getModifiedTime,
|
||||
toPath as ts_toPath,
|
||||
TypeReferenceDirectiveResolutionCache,
|
||||
unorderedRemoveItem,
|
||||
@ -126,6 +127,7 @@ import {
|
||||
WatchStatusReporter,
|
||||
WatchType,
|
||||
WildcardDirectoryWatcher,
|
||||
WorkerThreadsHost,
|
||||
writeFile,
|
||||
WriteFileCallback,
|
||||
} from "./_namespaces/ts";
|
||||
@ -155,6 +157,7 @@ export interface BuildOptions {
|
||||
emitDeclarationOnly?: boolean;
|
||||
sourceMap?: boolean;
|
||||
inlineSourceMap?: boolean;
|
||||
maxCpuCount?: number;
|
||||
|
||||
traceResolution?: boolean;
|
||||
/** @internal */ diagnostics?: boolean;
|
||||
@ -302,8 +305,8 @@ export function createBuilderStatusReporter(system: System, pretty?: boolean): D
|
||||
};
|
||||
}
|
||||
|
||||
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter) {
|
||||
const host = createProgramHost(system, createProgram) as SolutionBuilderHostBase<T>;
|
||||
function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool) {
|
||||
const host = createProgramHost(system, createProgram, workerThreads, threadPool) as SolutionBuilderHostBase<T>;
|
||||
host.getModifiedTime = system.getModifiedTime ? path => system.getModifiedTime!(path) : returnUndefined;
|
||||
host.setModifiedTime = system.setModifiedTime ? (path, date) => system.setModifiedTime!(path, date) : noop;
|
||||
host.deleteFile = system.deleteFile ? path => system.deleteFile!(path) : noop;
|
||||
@ -313,14 +316,20 @@ function createSolutionBuilderHostBase<T extends BuilderProgram>(system: System,
|
||||
return host;
|
||||
}
|
||||
|
||||
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderHost<T>;
|
||||
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary): SolutionBuilderHost<T>;
|
||||
/** @internal */
|
||||
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): SolutionBuilderHost<T>;
|
||||
export function createSolutionBuilderHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportErrorSummary?: ReportEmitErrorSummary, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus, workerThreads, threadPool) as SolutionBuilderHost<T>;
|
||||
host.reportErrorSummary = reportErrorSummary;
|
||||
return host;
|
||||
}
|
||||
|
||||
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus) as SolutionBuilderWithWatchHost<T>;
|
||||
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): SolutionBuilderWithWatchHost<T>;
|
||||
/** @internal */
|
||||
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system?: System, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): SolutionBuilderWithWatchHost<T>;
|
||||
export function createSolutionBuilderWithWatchHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram?: CreateProgram<T>, reportDiagnostic?: DiagnosticReporter, reportSolutionBuilderStatus?: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool) {
|
||||
const host = createSolutionBuilderHostBase(system, createProgram, reportDiagnostic, reportSolutionBuilderStatus, workerThreads, threadPool) as SolutionBuilderWithWatchHost<T>;
|
||||
const watchHost = createWatchHost(system, reportWatchStatus);
|
||||
copyProperties(host, watchHost);
|
||||
return host;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{
|
||||
"extends": "../tsconfig-base",
|
||||
"compilerOptions": {
|
||||
"types": ["node"]
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
|
||||
"references": [
|
||||
|
||||
@ -17,7 +17,11 @@ import {
|
||||
ProgramBuildInfo,
|
||||
SymlinkCache,
|
||||
ThisContainer,
|
||||
ThreadPool,
|
||||
WorkerThreadsHost,
|
||||
} from "./_namespaces/ts";
|
||||
import { SharedNodeBase } from "./sharing/sharedNode";
|
||||
import { SharedParserState } from "./sharing/sharedParserState";
|
||||
|
||||
// branded string type used to store absolute, normalized and canonicalized paths
|
||||
// arbitrary file name can be converted to Path via toPath function
|
||||
@ -927,6 +931,7 @@ export interface Node extends ReadonlyTextRange {
|
||||
// `locals` and `nextContainer` have been moved to `LocalsContainer`
|
||||
// `flowNode` has been moved to `FlowContainer`
|
||||
// see: https://github.com/microsoft/TypeScript/pull/51682
|
||||
/** @internal */ __shared__?: SharedNodeBase;
|
||||
}
|
||||
|
||||
export interface JSDocContainer extends Node {
|
||||
@ -935,7 +940,7 @@ export interface JSDocContainer extends Node {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface JSDocArray extends Array<JSDoc> {
|
||||
export interface JSDocArray extends ReadonlyArray<JSDoc> {
|
||||
jsDocCache?: readonly JSDocTag[]; // Cache for getJSDocTags
|
||||
}
|
||||
|
||||
@ -4364,7 +4369,7 @@ export interface SourceFile extends Declaration, LocalsContainer {
|
||||
/** @internal */ lineMap: readonly number[];
|
||||
/** @internal */ classifiableNames?: ReadonlySet<__String>;
|
||||
// Comments containing @ts-* directives, in order.
|
||||
/** @internal */ commentDirectives?: CommentDirective[];
|
||||
/** @internal */ commentDirectives?: readonly CommentDirective[];
|
||||
// Stores a mapping 'external module reference text' -> 'resolved file name' | undefined
|
||||
// It is used to resolve module names in the checker.
|
||||
// Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead
|
||||
@ -4385,6 +4390,8 @@ export interface SourceFile extends Declaration, LocalsContainer {
|
||||
|
||||
/** @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit;
|
||||
/** @internal */ endFlowNode?: FlowNode;
|
||||
|
||||
/** @internal */ __sharedCache__?: ReadonlyMap<ShareableNonPrimitive, object>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -4959,6 +4966,9 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost {
|
||||
|
||||
typesPackageExists(packageName: string): boolean;
|
||||
packageBundlesTypes(packageName: string): boolean;
|
||||
|
||||
workerThreads: WorkerThreadsHost | undefined;
|
||||
threadPool: ThreadPool | undefined;
|
||||
}
|
||||
|
||||
export interface TypeChecker {
|
||||
@ -7147,6 +7157,7 @@ export interface CompilerOptions {
|
||||
/** @internal */listFilesOnly?: boolean;
|
||||
locale?: string;
|
||||
mapRoot?: string;
|
||||
maxCpuCount?: number;
|
||||
maxNodeModuleJsDepth?: number;
|
||||
module?: ModuleKind;
|
||||
moduleResolution?: ModuleResolutionKind;
|
||||
@ -7426,6 +7437,7 @@ export interface CommandLineOptionOfStringType extends CommandLineOptionBase {
|
||||
export interface CommandLineOptionOfNumberType extends CommandLineOptionBase {
|
||||
type: "number";
|
||||
defaultValueDescription: number | undefined | DiagnosticMessage;
|
||||
defaultIfValueMissing?: () => number | undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -7760,6 +7772,7 @@ export type HasInvalidatedLibResolutions = (libFileName: string) => boolean;
|
||||
export type HasChangedAutomaticTypeDirectiveNames = () => boolean;
|
||||
|
||||
export interface CompilerHost extends ModuleResolutionHost {
|
||||
/** @internal */ requestSourceFile?(parserState: SharedParserState, fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, shouldCreateNewSourceFile?: boolean, setFileVersion?: boolean): void;
|
||||
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
|
||||
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
|
||||
getCancellationToken?(): CancellationToken;
|
||||
@ -7836,6 +7849,8 @@ export interface CompilerHost extends ModuleResolutionHost {
|
||||
// For testing:
|
||||
/** @internal */ storeFilesChangingSignatureDuringEmit?: boolean;
|
||||
/** @internal */ getBuildInfo?(fileName: string, configFilePath: string | undefined): BuildInfo | undefined;
|
||||
/** @internal */ workerThreads?: WorkerThreadsHost | undefined;
|
||||
/** @internal */ threadPool?: ThreadPool | undefined;
|
||||
}
|
||||
|
||||
/** true if --out otherwise source file name *
|
||||
@ -8764,7 +8779,8 @@ export interface NodeFactory {
|
||||
createJsxText(text: string, containsOnlyTriviaWhiteSpaces?: boolean): JsxText;
|
||||
updateJsxText(node: JsxText, text: string, containsOnlyTriviaWhiteSpaces?: boolean): JsxText;
|
||||
createJsxOpeningFragment(): JsxOpeningFragment;
|
||||
createJsxJsxClosingFragment(): JsxClosingFragment;
|
||||
/** @deprecated Use `createJsxClosingFragment` */ createJsxJsxClosingFragment(): JsxClosingFragment;
|
||||
createJsxClosingFragment(): JsxClosingFragment;
|
||||
updateJsxFragment(node: JsxFragment, openingFragment: JsxOpeningFragment, children: readonly JsxChild[], closingFragment: JsxClosingFragment): JsxFragment;
|
||||
createJsxAttribute(name: JsxAttributeName, initializer: JsxAttributeValue | undefined): JsxAttribute;
|
||||
updateJsxAttribute(node: JsxAttribute, name: JsxAttributeName, initializer: JsxAttributeValue | undefined): JsxAttribute;
|
||||
@ -10007,3 +10023,9 @@ export interface Queue<T> {
|
||||
dequeue(): T;
|
||||
isEmpty(): boolean;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type Constructor<T = any, A extends any[] = any[]> = new (...args: A) => T;
|
||||
|
||||
/** @internal */
|
||||
export type AbstractConstructor<T = any, A extends any[] = any[]> = abstract new (...args: A) => T;
|
||||
@ -34,6 +34,7 @@ import {
|
||||
BindingElement,
|
||||
BindingElementOfBareOrAccessedRequire,
|
||||
Block,
|
||||
BuildOptions,
|
||||
BundleFileSection,
|
||||
BundleFileSectionKind,
|
||||
BundleFileTextLike,
|
||||
@ -400,6 +401,7 @@ import {
|
||||
ModuleResolutionKind,
|
||||
moduleResolutionOptionDeclarations,
|
||||
MultiMap,
|
||||
MutableNodeArray,
|
||||
NamedDeclaration,
|
||||
NamedExports,
|
||||
NamedImports,
|
||||
@ -501,7 +503,7 @@ import {
|
||||
SuperExpression,
|
||||
SuperProperty,
|
||||
SwitchStatement,
|
||||
Symbol,
|
||||
type Symbol,
|
||||
SymbolFlags,
|
||||
SymbolTable,
|
||||
SyntaxKind,
|
||||
@ -562,6 +564,8 @@ import {
|
||||
WriteFileCallbackData,
|
||||
YieldExpression,
|
||||
} from "./_namespaces/ts";
|
||||
import { SharedModifierLike } from "./sharing/sharedNode";
|
||||
import { SharedNodeArray } from "./sharing/sharedNodeArray";
|
||||
|
||||
/** @internal */
|
||||
export const resolvingEmptyArray: never[] = [];
|
||||
@ -1131,7 +1135,7 @@ export function isPinnedComment(text: string, start: number) {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function createCommentDirectivesMap(sourceFile: SourceFile, commentDirectives: CommentDirective[]): CommentDirectivesMap {
|
||||
export function createCommentDirectivesMap(sourceFile: SourceFile, commentDirectives: readonly CommentDirective[]): CommentDirectivesMap {
|
||||
const directivesByLine = new Map(
|
||||
commentDirectives.map(commentDirective => ([
|
||||
`${getLineAndCharacterOfPosition(sourceFile, commentDirective.range.end).line}`,
|
||||
@ -6945,7 +6949,11 @@ export function getSyntacticModifierFlagsNoCache(node: Node): ModifierFlags {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function modifiersToFlags(modifiers: readonly ModifierLike[] | undefined) {
|
||||
export function modifiersToFlags(modifiers: Iterable<ModifierLike | SharedModifierLike> | SharedNodeArray<SharedModifierLike> | undefined) {
|
||||
if (modifiers instanceof SharedNodeArray) {
|
||||
modifiers = SharedNodeArray.values(modifiers);
|
||||
}
|
||||
|
||||
let flags = ModifierFlags.None;
|
||||
if (modifiers) {
|
||||
for (const modifier of modifiers) {
|
||||
@ -7197,41 +7205,91 @@ function isExportDefaultSymbol(symbol: Symbol): boolean {
|
||||
export function tryExtractTSExtension(fileName: string): string | undefined {
|
||||
return find(supportedTSExtensionsForExtractExtension, extension => fileExtensionIs(fileName, extension));
|
||||
}
|
||||
/**
|
||||
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
|
||||
* representing the UTF-8 encoding of the character, and return the expanded char code list.
|
||||
*/
|
||||
function getExpandedCharCodes(input: string): number[] {
|
||||
const output: number[] = [];
|
||||
const length = input.length;
|
||||
|
||||
function writeByte(output: number[] | Uint8Array | undefined, index: number, value: number) {
|
||||
if (output) {
|
||||
output[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: Use `utf8encodeInto` instead. This function is a fallback implementation and is only exposed for testing
|
||||
* purposes.
|
||||
* @internal
|
||||
*/
|
||||
export function utf8encodeIntoCompat(input: string, output: number[] | Uint8Array | undefined) {
|
||||
const length = input.length;
|
||||
let written = 0;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const charCode = input.charCodeAt(i);
|
||||
let charCode = input.charCodeAt(i);
|
||||
|
||||
// decode UCS2 surrogate pairs
|
||||
if (/*is leading surrogate*/ (charCode & 0xd800) === 0xd800 &&
|
||||
/*is not last character*/ i < length - 1) {
|
||||
const charCode2 = input.charCodeAt(i + 1);
|
||||
if (/*is trailing surrogate*/ (charCode2 & 0xdc00) === 0xdc00) {
|
||||
charCode = ((charCode & 0x3ff) << 10) + (charCode2 & 0x3ff) + 0x10000;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// handle utf8
|
||||
if (charCode < 0x80) {
|
||||
output.push(charCode);
|
||||
writeByte(output, written++, charCode);
|
||||
}
|
||||
else if (charCode < 0x800) {
|
||||
output.push((charCode >> 6) | 0B11000000);
|
||||
output.push((charCode & 0B00111111) | 0B10000000);
|
||||
writeByte(output, written++, (charCode >> 6) | 0B11000000);
|
||||
writeByte(output, written++, (charCode & 0B00111111) | 0B10000000);
|
||||
}
|
||||
else if (charCode < 0x10000) {
|
||||
output.push((charCode >> 12) | 0B11100000);
|
||||
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
|
||||
output.push((charCode & 0B00111111) | 0B10000000);
|
||||
writeByte(output, written++, (charCode >> 12) | 0B11100000);
|
||||
writeByte(output, written++, ((charCode >> 6) & 0B00111111) | 0B10000000);
|
||||
writeByte(output, written++, (charCode & 0B00111111) | 0B10000000);
|
||||
}
|
||||
else if (charCode < 0x20000) {
|
||||
output.push((charCode >> 18) | 0B11110000);
|
||||
output.push(((charCode >> 12) & 0B00111111) | 0B10000000);
|
||||
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
|
||||
output.push((charCode & 0B00111111) | 0B10000000);
|
||||
writeByte(output, written++, (charCode >> 18) | 0B11110000);
|
||||
writeByte(output, written++, ((charCode >> 12) & 0B00111111) | 0B10000000);
|
||||
writeByte(output, written++, ((charCode >> 6) & 0B00111111) | 0B10000000);
|
||||
writeByte(output, written++, (charCode & 0B00111111) | 0B10000000);
|
||||
}
|
||||
else {
|
||||
Debug.assert(false, "Unexpected code point");
|
||||
}
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a string into UTF-8 bytes.
|
||||
* If `output` is a `Uint8Array`, it must have enough capacity to hold the output.
|
||||
* `output` may be `undefined`, in which case only the total number of bytes necessary to encode the input will be
|
||||
* calculated.
|
||||
* @internal
|
||||
*/
|
||||
export const utf8encodeInto = (() => {
|
||||
if (typeof TextEncoder === "function") {
|
||||
const encoder = new TextEncoder();
|
||||
return (input: string, output: number[] | Uint8Array | undefined) => {
|
||||
if (output === undefined || isArray(output)) {
|
||||
return utf8encodeIntoCompat(input, output);
|
||||
}
|
||||
|
||||
const { written = 0 } = encoder.encodeInto(input, output);
|
||||
return written;
|
||||
};
|
||||
}
|
||||
|
||||
return utf8encodeIntoCompat;
|
||||
})();
|
||||
|
||||
/**
|
||||
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
|
||||
* representing the UTF-8 encoding of the character, and return the expanded char code list.
|
||||
*/
|
||||
function getExpandedCharCodes(input: string): number[] {
|
||||
const output: number[] = [];
|
||||
utf8encodeInto(input, output);
|
||||
return output;
|
||||
}
|
||||
|
||||
@ -7275,39 +7333,71 @@ export function convertToBase64(input: string): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getStringFromExpandedCharCodes(codes: number[]): string {
|
||||
/**
|
||||
* NOTE: Use `utf8decode` instead. This function is a fallback implementation and is only exposed for testing
|
||||
* purposes.
|
||||
* @internal
|
||||
*/
|
||||
export function utf8decodeCompat(input: readonly number[] | Uint8Array) {
|
||||
let output = "";
|
||||
let i = 0;
|
||||
const length = codes.length;
|
||||
const length = input.length;
|
||||
while (i < length) {
|
||||
const charCode = codes[i];
|
||||
|
||||
const charCode = input[i];
|
||||
i++;
|
||||
if (charCode < 0x80) {
|
||||
output += String.fromCharCode(charCode);
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
else if ((charCode & 0B11000000) === 0B11000000) {
|
||||
let value = charCode & 0B00111111;
|
||||
i++;
|
||||
let nextCode: number = codes[i];
|
||||
while ((nextCode & 0B11000000) === 0B10000000) {
|
||||
value = (value << 6) | (nextCode & 0B00111111);
|
||||
i++;
|
||||
nextCode = codes[i];
|
||||
}
|
||||
// `value` may be greater than 10FFFF (the maximum unicode codepoint) - JS will just make this into an invalid character for us
|
||||
output += String.fromCharCode(value);
|
||||
|
||||
let value: number;
|
||||
let remaining: number;
|
||||
if ((charCode & 0B11111000) === 0B11110000) {
|
||||
value = charCode & 0B00000111;
|
||||
remaining = 3;
|
||||
}
|
||||
else if ((charCode & 0B11110000) === 0B11100000) {
|
||||
value = charCode & 0B00001111;
|
||||
remaining = 2;
|
||||
}
|
||||
else if ((charCode & 0B11100000) === 0B11000000) {
|
||||
value = charCode & 0B00011111;
|
||||
remaining = 1;
|
||||
}
|
||||
else {
|
||||
// We don't want to kill the process when decoding fails (due to a following char byte not
|
||||
// following a leading char), so we just print the (bad) value
|
||||
output += String.fromCharCode(charCode);
|
||||
i++;
|
||||
value = charCode;
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
while (remaining > 0 && i < input.length) {
|
||||
const nextCode = input[i];
|
||||
i++;
|
||||
remaining--;
|
||||
value = (value << 6) | (nextCode & 0B00111111);
|
||||
}
|
||||
|
||||
// `value` may be greater than 10FFFF (the maximum unicode codepoint) - JS will just make this into an invalid character for us
|
||||
output += String.fromCodePoint(value);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const utf8decode = (() => {
|
||||
if (typeof TextDecoder === "function") {
|
||||
const decoder = new TextDecoder();
|
||||
return (input: readonly number[] | Uint8Array) => {
|
||||
if (isArray(input)) {
|
||||
return decoder.decode(new Uint8Array(input));
|
||||
}
|
||||
return decoder.decode(input);
|
||||
};
|
||||
}
|
||||
return utf8decodeCompat;
|
||||
})();
|
||||
|
||||
/** @internal */
|
||||
export function base64encode(host: { base64encode?(input: string): string } | undefined, input: string): string {
|
||||
if (host && host.base64encode) {
|
||||
@ -7350,7 +7440,7 @@ export function base64decode(host: { base64decode?(input: string): string } | un
|
||||
}
|
||||
i += 4;
|
||||
}
|
||||
return getStringFromExpandedCharCodes(expandedCharCodes);
|
||||
return utf8decode(expandedCharCodes);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -8006,6 +8096,7 @@ export function getLeftmostExpression(node: Expression, stopAtCallExpressions: b
|
||||
|
||||
/** @internal */
|
||||
export interface ObjectAllocator {
|
||||
getNodeArrayConstructor(): new <T extends Node>(items: readonly T[], hasTrailingComma?: boolean) => NodeArray<T>;
|
||||
getNodeConstructor(): new (kind: SyntaxKind, pos: number, end: number) => Node;
|
||||
getTokenConstructor(): new <TKind extends SyntaxKind>(kind: TKind, pos: number, end: number) => Token<TKind>;
|
||||
getIdentifierConstructor(): new (kind: SyntaxKind.Identifier, pos: number, end: number) => Identifier;
|
||||
@ -8017,7 +8108,7 @@ export interface ObjectAllocator {
|
||||
getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource;
|
||||
}
|
||||
|
||||
function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
|
||||
const SymbolConstructor = function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
|
||||
this.flags = flags;
|
||||
this.escapedName = name;
|
||||
this.declarations = undefined;
|
||||
@ -8032,7 +8123,7 @@ function Symbol(this: Symbol, flags: SymbolFlags, name: __String) {
|
||||
this.isReferenced = undefined;
|
||||
this.isAssigned = undefined;
|
||||
(this as any).links = undefined; // used by TransientSymbol
|
||||
}
|
||||
};
|
||||
|
||||
function Type(this: Type, checker: TypeChecker, flags: TypeFlags) {
|
||||
this.flags = flags;
|
||||
@ -8090,14 +8181,61 @@ function SourceMapSource(this: SourceMapSource, fileName: string, text: string,
|
||||
this.skipTrivia = skipTrivia || (pos => pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emulates the prior behavior of `createNodeArray` to convert an existing array into a `NodeArray<T>` while also fixing
|
||||
* the prototype. The resulting array becoms the `this` of any derived class.
|
||||
*/
|
||||
const ArrayAdopter = function <T>(elements: T[]) { // eslint-disable-line local/only-arrow-functions -- arrow functions aren't constructable.
|
||||
if (elements !== emptyArray) {
|
||||
Object.setPrototypeOf(elements, new.target.prototype);
|
||||
return elements;
|
||||
}
|
||||
return Reflect.construct(Array, [0], new.target.prototype);
|
||||
} as typeof Array & (new <T>(elements: T[]) => T[]);
|
||||
|
||||
Object.setPrototypeOf(ArrayAdopter, Array);
|
||||
Object.setPrototypeOf(ArrayAdopter.prototype, Array.prototype);
|
||||
|
||||
declare class NodeArrayImpl<T extends Node> extends Array<T> {
|
||||
hasTrailingComma: boolean;
|
||||
transformFlags: TransformFlags;
|
||||
pos: number;
|
||||
end: number;
|
||||
constructor(elements: readonly T[] | NodeArray<T>, hasTrailingComma?: boolean);
|
||||
}
|
||||
|
||||
const NodeArray = class <T extends Node> extends ArrayAdopter<T> implements MutableNodeArray<T> {
|
||||
declare hasTrailingComma: boolean;
|
||||
declare transformFlags: TransformFlags;
|
||||
declare pos: number;
|
||||
declare end: number;
|
||||
|
||||
constructor(elements: readonly T[] | NodeArray<T>, hasTrailingComma?: boolean) {
|
||||
const length = elements.length;
|
||||
super(length >= 1 && length <= 4 ? elements.slice() : elements as T[]);
|
||||
this.pos = -1;
|
||||
this.end = -1;
|
||||
this.hasTrailingComma = !!hasTrailingComma;
|
||||
this.transformFlags = TransformFlags.None;
|
||||
Debug.attachNodeArrayDebugInfo(this);
|
||||
}
|
||||
|
||||
static {
|
||||
Object.defineProperty(this, Symbol.species, { configurable: true, value: Array });
|
||||
}
|
||||
|
||||
} as typeof NodeArrayImpl;
|
||||
|
||||
|
||||
/** @internal */
|
||||
export const objectAllocator: ObjectAllocator = {
|
||||
getNodeArrayConstructor: () => NodeArray as any,
|
||||
getNodeConstructor: () => Node as any,
|
||||
getTokenConstructor: () => Token as any,
|
||||
getIdentifierConstructor: () => Identifier as any,
|
||||
getPrivateIdentifierConstructor: () => Node as any,
|
||||
getSourceFileConstructor: () => Node as any,
|
||||
getSymbolConstructor: () => Symbol as any,
|
||||
getSymbolConstructor: () => SymbolConstructor as any,
|
||||
getTypeConstructor: () => Type as any,
|
||||
getSignatureConstructor: () => Signature as any,
|
||||
getSourceMapSourceConstructor: () => SourceMapSource as any,
|
||||
@ -8416,32 +8554,47 @@ function isFileForcedToBeModuleByFormat(file: SourceFile): true | undefined {
|
||||
return (file.impliedNodeFormat === ModuleKind.ESNext || (fileExtensionIsOneOf(file.fileName, [Extension.Cjs, Extension.Cts, Extension.Mjs, Extension.Mts]))) && !file.isDeclarationFile ? true : undefined;
|
||||
}
|
||||
|
||||
function forceSetExternalModuleIndicator(file: SourceFile) {
|
||||
file.externalModuleIndicator = isFileProbablyExternalModule(file) || !file.isDeclarationFile || undefined;
|
||||
}
|
||||
|
||||
function legacySetExternalModuleIndicator(file: SourceFile) {
|
||||
file.externalModuleIndicator = isFileProbablyExternalModule(file);
|
||||
}
|
||||
|
||||
function autoSetExternalModuleIndicatorNonJsx(file: SourceFile) {
|
||||
file.externalModuleIndicator = isFileProbablyExternalModule(file) || isFileForcedToBeModuleByFormat(file);
|
||||
}
|
||||
|
||||
function autoSetExternalModuleIndicatorJsx(file: SourceFile) {
|
||||
file.externalModuleIndicator = isFileProbablyExternalModule(file) || isFileModuleFromUsingJSXTag(file) || isFileForcedToBeModuleByFormat(file);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function isSetExternalModuleIndicatorCallbackFromOptions(cb: (file: SourceFile) => void) {
|
||||
return cb === forceSetExternalModuleIndicator ||
|
||||
cb === legacySetExternalModuleIndicator ||
|
||||
cb === autoSetExternalModuleIndicatorNonJsx ||
|
||||
cb === autoSetExternalModuleIndicatorJsx;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getSetExternalModuleIndicator(options: CompilerOptions): (file: SourceFile) => void {
|
||||
// TODO: Should this callback be cached?
|
||||
switch (getEmitModuleDetectionKind(options)) {
|
||||
case ModuleDetectionKind.Force:
|
||||
// All non-declaration files are modules, declaration files still do the usual isFileProbablyExternalModule
|
||||
return (file: SourceFile) => {
|
||||
file.externalModuleIndicator = isFileProbablyExternalModule(file) || !file.isDeclarationFile || undefined;
|
||||
};
|
||||
return forceSetExternalModuleIndicator;
|
||||
case ModuleDetectionKind.Legacy:
|
||||
// Files are modules if they have imports, exports, or import.meta
|
||||
return (file: SourceFile) => {
|
||||
file.externalModuleIndicator = isFileProbablyExternalModule(file);
|
||||
};
|
||||
return legacySetExternalModuleIndicator;
|
||||
case ModuleDetectionKind.Auto:
|
||||
// If module is nodenext or node16, all esm format files are modules
|
||||
// If jsx is react-jsx or react-jsxdev then jsx tags force module-ness
|
||||
// otherwise, the presence of import or export statments (or import.meta) implies module-ness
|
||||
const checks: ((file: SourceFile) => Node | true | undefined)[] = [isFileProbablyExternalModule];
|
||||
if (options.jsx === JsxEmit.ReactJSX || options.jsx === JsxEmit.ReactJSXDev) {
|
||||
checks.push(isFileModuleFromUsingJSXTag);
|
||||
return autoSetExternalModuleIndicatorJsx;
|
||||
}
|
||||
checks.push(isFileForcedToBeModuleByFormat);
|
||||
const combined = or(...checks);
|
||||
const callback = (file: SourceFile) => void (file.externalModuleIndicator = combined(file));
|
||||
return callback;
|
||||
return autoSetExternalModuleIndicatorNonJsx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8704,6 +8857,11 @@ export function getJSXRuntimeImport(base: string | undefined, options: CompilerO
|
||||
return base ? `${base}/${options.jsx === JsxEmit.ReactJSXDev ? "jsx-dev-runtime" : "jsx-runtime"}` : undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getMaxCpuCount(options: CompilerOptions | BuildOptions): number {
|
||||
return options.maxCpuCount ?? 1;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
|
||||
let seenAsterisk = false;
|
||||
@ -9843,7 +10001,13 @@ export function setParent<T extends Node>(child: T | undefined, parent: T["paren
|
||||
/** @internal */
|
||||
export function setParent<T extends Node>(child: T | undefined, parent: T["parent"] | undefined): T | undefined {
|
||||
if (child && parent) {
|
||||
(child as Mutable<T>).parent = parent;
|
||||
try {
|
||||
(child as Mutable<T>).parent = parent;
|
||||
}
|
||||
catch (e) {
|
||||
e.message = Debug.format(e.message, child);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
return child;
|
||||
}
|
||||
@ -10343,3 +10507,15 @@ export function getPropertyNameFromType(type: StringLiteralType | NumberLiteralT
|
||||
}
|
||||
return Debug.fail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the provided string can be parsed as a number.
|
||||
* @param s The string to test.
|
||||
* @param roundTripOnly Indicates the resulting number matches the input when converted back to a string.
|
||||
* @internal
|
||||
*/
|
||||
export function isValidNumberString(s: string, roundTripOnly: boolean): boolean {
|
||||
if (s === "") return false;
|
||||
const n = +s;
|
||||
return isFinite(n) && (!roundTripOnly || "" + n === s);
|
||||
}
|
||||
|
||||
@ -1605,8 +1605,8 @@ export function isEntityName(node: Node): node is EntityName {
|
||||
|| kind === SyntaxKind.Identifier;
|
||||
}
|
||||
|
||||
export function isPropertyName(node: Node): node is PropertyName {
|
||||
const kind = node.kind;
|
||||
/** @internal */
|
||||
export function isPropertyNameKind(kind: SyntaxKind): kind is PropertyName["kind"] {
|
||||
return kind === SyntaxKind.Identifier
|
||||
|| kind === SyntaxKind.PrivateIdentifier
|
||||
|| kind === SyntaxKind.StringLiteral
|
||||
@ -1614,6 +1614,10 @@ export function isPropertyName(node: Node): node is PropertyName {
|
||||
|| kind === SyntaxKind.ComputedPropertyName;
|
||||
}
|
||||
|
||||
export function isPropertyName(node: Node): node is PropertyName {
|
||||
return isPropertyNameKind(node.kind);
|
||||
}
|
||||
|
||||
export function isBindingName(node: Node): node is BindingName {
|
||||
const kind = node.kind;
|
||||
return kind === SyntaxKind.Identifier
|
||||
|
||||
@ -18,6 +18,7 @@ import {
|
||||
createIncrementalCompilerHost,
|
||||
createIncrementalProgram,
|
||||
CreateProgram,
|
||||
createRequestSourceFile,
|
||||
createWriteFileMeasuringIO,
|
||||
CustomTransformers,
|
||||
Debug,
|
||||
@ -95,6 +96,7 @@ import {
|
||||
sys,
|
||||
System,
|
||||
targetOptionDeclaration,
|
||||
ThreadPool,
|
||||
WatchCompilerHost,
|
||||
WatchCompilerHostOfConfigFile,
|
||||
WatchCompilerHostOfFilesAndCompilerOptions,
|
||||
@ -105,6 +107,7 @@ import {
|
||||
WatchOptions,
|
||||
WatchStatusReporter,
|
||||
whitespaceOrMapCommentRegExp,
|
||||
WorkerThreadsHost,
|
||||
WriteFileCallback,
|
||||
} from "./_namespaces/ts";
|
||||
|
||||
@ -741,10 +744,12 @@ export function createWatchFactory<Y = undefined>(host: WatchFactoryHost & { tra
|
||||
export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost {
|
||||
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
|
||||
const compilerHost: CompilerHost = {
|
||||
requestSourceFile: host.threadPool && createRequestSourceFile(host.threadPool, /*setParentNodes*/ undefined),
|
||||
getSourceFile: createGetSourceFile(
|
||||
(fileName, encoding) => !encoding ? compilerHost.readFile(fileName) : host.readFile(fileName, encoding),
|
||||
getCompilerOptions,
|
||||
/*setParentNodes*/ undefined
|
||||
/*setParentNodes*/ undefined,
|
||||
/*overrideObjectAllocator*/ undefined,
|
||||
),
|
||||
getDefaultLibLocation: maybeBind(host, host.getDefaultLibLocation),
|
||||
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
|
||||
@ -767,6 +772,8 @@ export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCom
|
||||
createHash: maybeBind(host, host.createHash),
|
||||
readDirectory: maybeBind(host, host.readDirectory),
|
||||
storeFilesChangingSignatureDuringEmit: host.storeFilesChangingSignatureDuringEmit,
|
||||
workerThreads: host.workerThreads,
|
||||
threadPool: host.threadPool,
|
||||
};
|
||||
return compilerHost;
|
||||
}
|
||||
@ -820,6 +827,12 @@ export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost) {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const originalRequestSourceFile = compilerHost.requestSourceFile;
|
||||
if (originalRequestSourceFile) {
|
||||
compilerHost.requestSourceFile = (parserState, fileName, languageVersionOrOptions, shouldCreateNewSourceFile) => {
|
||||
originalRequestSourceFile.call(compilerHost, parserState, fileName, languageVersionOrOptions, shouldCreateNewSourceFile, /*setFileVersion*/ true);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -827,9 +840,11 @@ export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost) {
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined): ProgramHost<T> {
|
||||
export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined, workerThreads: WorkerThreadsHost | undefined, threadPool: ThreadPool | undefined): ProgramHost<T> {
|
||||
const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath())));
|
||||
return {
|
||||
workerThreads,
|
||||
threadPool,
|
||||
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
|
||||
getNewLine: () => system.newLine,
|
||||
getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
|
||||
@ -855,9 +870,9 @@ export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiag
|
||||
/**
|
||||
* Creates the watch compiler host that can be extended with config file or root file names and options host
|
||||
*/
|
||||
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
|
||||
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool | undefined): WatchCompilerHost<T> {
|
||||
const write = (s: string) => system.write(s + system.newLine);
|
||||
const result = createProgramHost(system, createProgram) as WatchCompilerHost<T>;
|
||||
const result = createProgramHost(system, createProgram, workerThreads, threadPool) as WatchCompilerHost<T>;
|
||||
copyProperties(result, createWatchHost(system, reportWatchStatus));
|
||||
result.afterProgramCreate = builderProgram => {
|
||||
const compilerOptions = builderProgram.getCompilerOptions();
|
||||
@ -892,6 +907,8 @@ export interface CreateWatchCompilerHostInput<T extends BuilderProgram> {
|
||||
createProgram?: CreateProgram<T>;
|
||||
reportDiagnostic?: DiagnosticReporter;
|
||||
reportWatchStatus?: WatchStatusReporter;
|
||||
workerThreads?: WorkerThreadsHost;
|
||||
threadPool?: ThreadPool;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@ -908,10 +925,11 @@ export interface CreateWatchCompilerHostOfConfigFileInput<T extends BuilderProgr
|
||||
*/
|
||||
export function createWatchCompilerHostOfConfigFile<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
|
||||
configFileName, optionsToExtend, watchOptionsToExtend, extraFileExtensions,
|
||||
system, createProgram, reportDiagnostic, reportWatchStatus
|
||||
system, createProgram, reportDiagnostic, reportWatchStatus,
|
||||
workerThreads, threadPool,
|
||||
}: CreateWatchCompilerHostOfConfigFileInput<T>): WatchCompilerHostOfConfigFile<T> {
|
||||
const diagnosticReporter = reportDiagnostic || createDiagnosticReporter(system);
|
||||
const host = createWatchCompilerHost(system, createProgram, diagnosticReporter, reportWatchStatus) as WatchCompilerHostOfConfigFile<T>;
|
||||
const host = createWatchCompilerHost(system, createProgram, diagnosticReporter, reportWatchStatus, workerThreads, threadPool) as WatchCompilerHostOfConfigFile<T>;
|
||||
host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, diagnosticReporter, diagnostic);
|
||||
host.configFileName = configFileName;
|
||||
host.optionsToExtend = optionsToExtend;
|
||||
@ -934,9 +952,10 @@ export interface CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T extends
|
||||
*/
|
||||
export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
|
||||
rootFiles, options, watchOptions, projectReferences,
|
||||
system, createProgram, reportDiagnostic, reportWatchStatus
|
||||
system, createProgram, reportDiagnostic, reportWatchStatus,
|
||||
workerThreads, threadPool,
|
||||
}: CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T>): WatchCompilerHostOfFilesAndCompilerOptions<T> {
|
||||
const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus) as WatchCompilerHostOfFilesAndCompilerOptions<T>;
|
||||
const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus, workerThreads, threadPool) as WatchCompilerHostOfFilesAndCompilerOptions<T>;
|
||||
host.rootFiles = rootFiles;
|
||||
host.options = options;
|
||||
host.watchOptions = watchOptions;
|
||||
|
||||
@ -80,6 +80,7 @@ import {
|
||||
StringLiteralLike,
|
||||
sys,
|
||||
System,
|
||||
ThreadPool,
|
||||
toPath,
|
||||
toPath as ts_toPath,
|
||||
updateErrorForNoInputFiles,
|
||||
@ -92,6 +93,7 @@ import {
|
||||
WatchType,
|
||||
WatchTypeRegistry,
|
||||
WildcardDirectoryWatcher,
|
||||
WorkerThreadsHost,
|
||||
} from "./_namespaces/ts";
|
||||
|
||||
export interface ReadBuildProgramHost {
|
||||
@ -118,8 +120,11 @@ export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadB
|
||||
return createBuilderProgramUsingProgramBuildInfo(buildInfo, buildInfoPath, host);
|
||||
}
|
||||
|
||||
export function createIncrementalCompilerHost(options: CompilerOptions, system = sys): CompilerHost {
|
||||
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system);
|
||||
export function createIncrementalCompilerHost(options: CompilerOptions, system?: System): CompilerHost;
|
||||
/** @internal */
|
||||
export function createIncrementalCompilerHost(options: CompilerOptions, system?: System, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): CompilerHost;
|
||||
export function createIncrementalCompilerHost(options: CompilerOptions, system = sys, workerThreads?: WorkerThreadsHost, threadPool?: ThreadPool): CompilerHost {
|
||||
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, system, workerThreads, threadPool);
|
||||
host.createHash = maybeBind(system, system.createHash);
|
||||
host.storeFilesChangingSignatureDuringEmit = system.storeFilesChangingSignatureDuringEmit;
|
||||
setGetSourceFileAsHashVersioned(host);
|
||||
@ -164,6 +169,8 @@ export interface WatchHost {
|
||||
clearTimeout?(timeoutId: any): void;
|
||||
}
|
||||
export interface ProgramHost<T extends BuilderProgram> {
|
||||
/** @internal */ workerThreads: WorkerThreadsHost | undefined;
|
||||
/** @internal */ threadPool: ThreadPool | undefined;
|
||||
/**
|
||||
* Used to create the program when need for program creation or recreation detected
|
||||
*/
|
||||
|
||||
73
src/compiler/worker.ts
Normal file
73
src/compiler/worker.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { createCompilerHostWorker, Debug, isNodeLikeSystem, setGetSourceFileAsHashVersioned, sys, System, ThreadPoolState, ThreadPoolThread, workerThreads, WorkerThreadWorkerThreadsHost } from "./_namespaces/ts";
|
||||
import { SharedSourceFile } from "./sharing/sharedNode";
|
||||
import { getSharedObjectAllocator } from "./sharing/sharedObjectAllocator";
|
||||
import { SharedSourceFileEntry } from "./sharing/sharedParserState";
|
||||
import { Condition } from "./threading/condition";
|
||||
import { UniqueLock } from "./threading/uniqueLock";
|
||||
|
||||
/** @internal */
|
||||
export function executeWorker(_system: System, host: WorkerThreadWorkerThreadsHost) {
|
||||
// if (process.execArgv.includes("--inspect-brk")) {
|
||||
// const inspector = require("node:inspector") as typeof import("node:inspector");
|
||||
// inspector.open();
|
||||
// inspector.waitForDebugger();
|
||||
// }
|
||||
|
||||
if (isNodeLikeSystem()) {
|
||||
const fs: typeof import("fs") = require("fs");
|
||||
const util: typeof import("util") = require("util");
|
||||
Debug.loggingHost = {
|
||||
log(_level, s) {
|
||||
fs.writeSync(2, `[worker#${host.threadId}] ${s || ""}${sys.newLine}`);
|
||||
},
|
||||
format(...args) {
|
||||
return util.formatWithOptions({ colors: true }, ...args);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const { workerData } = host;
|
||||
Debug.assert(workerData);
|
||||
const { type } = workerData as { type: string };
|
||||
switch (type) {
|
||||
case "ThreadPoolThread": {
|
||||
const { state } = workerData as { state: ThreadPoolState };
|
||||
const thread = new ThreadPoolThread(state, (name, arg) => {
|
||||
switch (name) {
|
||||
case "Program.requestSourceFile":
|
||||
programRequestSourceFile(arg as SharedSourceFileEntry);
|
||||
break;
|
||||
}
|
||||
});
|
||||
thread.run();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Debug.fail(`Unsupported worker type: '${type}'.`);
|
||||
break;
|
||||
}
|
||||
|
||||
function programRequestSourceFile(entry: SharedSourceFileEntry) {
|
||||
let ok = false;
|
||||
let sharedFile: SharedSourceFile | undefined;
|
||||
try {
|
||||
// Debug.log.trace(`parsing: ${entry.fileName}`);
|
||||
const overideObjectAllocator = getSharedObjectAllocator();
|
||||
const host = createCompilerHostWorker({}, entry.setParentNodes, sys, workerThreads, /*threadPool*/ undefined, overideObjectAllocator);
|
||||
if (entry.setFileVersion) {
|
||||
setGetSourceFileAsHashVersioned(host);
|
||||
}
|
||||
const file = host.getSourceFile(entry.fileName, entry.languageVersion, undefined, entry.shouldCreateNewSourceFile);
|
||||
Debug.assert(file === undefined || file instanceof SharedSourceFile);
|
||||
sharedFile = file;
|
||||
ok = true;
|
||||
}
|
||||
finally {
|
||||
using _ = new UniqueLock(entry.fileMutex);
|
||||
entry.error = !ok;
|
||||
entry.done = ok;
|
||||
entry.file = sharedFile;
|
||||
Condition.notify(entry.fileCondition);
|
||||
}
|
||||
}
|
||||
}
|
||||
169
src/compiler/workerThreads.ts
Normal file
169
src/compiler/workerThreads.ts
Normal file
@ -0,0 +1,169 @@
|
||||
import { Debug, isNodeLikeSystem, sys } from "./_namespaces/ts";
|
||||
import "./sharing/structs/sharedStructsGlobals";
|
||||
|
||||
/** @internal */
|
||||
export interface WorkerThreadsHost {
|
||||
readonly workerData: unknown;
|
||||
readonly parentPort: MessagePort | undefined;
|
||||
readonly threadId: number;
|
||||
isWorkerThread(): this is WorkerThreadWorkerThreadsHost;
|
||||
isMainThread(): this is MainThreadWorkerThreadsHost;
|
||||
createWorker(options?: WorkerOptions): Worker;
|
||||
createMessageChannel(): MessageChannel;
|
||||
getHostObject<T>(factory: (host: WorkerThreadsHost) => T): T;
|
||||
addMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void): void;
|
||||
removeMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void): void;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface WorkerThreadWorkerThreadsHost extends WorkerThreadsHost {
|
||||
readonly parentPort: MessagePort;
|
||||
isMainThread(): false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface MainThreadWorkerThreadsHost extends WorkerThreadsHost {
|
||||
readonly parentPort: undefined;
|
||||
isWorkerThread(): false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export type Transferrable =
|
||||
| ArrayBuffer
|
||||
| MessagePort
|
||||
;
|
||||
|
||||
/** @internal */
|
||||
export interface WorkerOptions {
|
||||
/** The path to the worker script. If not provided, then the typescript runtime is assumed. */
|
||||
file?: string;
|
||||
/** Name of the worker for debugging purposes. */
|
||||
name?: string;
|
||||
/** Initial data to pass to the worker. */
|
||||
workerData?: unknown;
|
||||
/** Initial data to transfer to the worker. */
|
||||
transferList?: readonly Transferrable[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface Worker {
|
||||
postMessage(value: unknown, transferList?: readonly Transferrable[]): void;
|
||||
terminate(): Promise<void>;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface MessageChannel {
|
||||
readonly nativeMessageChannel: unknown;
|
||||
readonly port1: MessagePort;
|
||||
readonly port2: MessagePort;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface MessagePort {
|
||||
postMessage(value: unknown, transferList?: readonly Transferrable[]): void;
|
||||
close(): void;
|
||||
}
|
||||
|
||||
function createNodeWorkerThreadHost(): WorkerThreadsHost {
|
||||
const fs: typeof import("fs") = require("fs");
|
||||
const path: typeof import("path") = require("path");
|
||||
const worker_threads: typeof import("worker_threads") = require("worker_threads");
|
||||
const hostObjectCircularitySentinel = {};
|
||||
const hostObjects = new WeakMap<object, unknown>();
|
||||
|
||||
function isWorkerThread() {
|
||||
return !worker_threads.isMainThread;
|
||||
}
|
||||
|
||||
function isMainThread() {
|
||||
return worker_threads.isMainThread;
|
||||
}
|
||||
|
||||
function getEntrypointPath() {
|
||||
const file = sys.getExecutingFilePath();
|
||||
// The executing file path isn't valid when running unbundled, so try to use tsc.js if available.
|
||||
// See `executingFilePath` in sys.ts for more information.
|
||||
if (path.basename(file) === "__fake__.js") {
|
||||
const tscPath = path.join(path.dirname(file), "tsc.js");
|
||||
if (fs.existsSync(tscPath)) {
|
||||
return tscPath;
|
||||
}
|
||||
throw new Error("Cannot determine entrypoint path.");
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
function createWorker({ file = getEntrypointPath(), name, workerData, transferList, }: WorkerOptions = {}): Worker {
|
||||
const execArgv: string[] = [];
|
||||
if (Debug.isDebugging) {
|
||||
execArgv.push("--inspect");
|
||||
}
|
||||
if (process.execArgv.includes("--enable-source-maps")) {
|
||||
execArgv.push("--enable-source-maps");
|
||||
}
|
||||
const worker = new worker_threads.Worker(file, {
|
||||
name,
|
||||
execArgv,
|
||||
workerData,
|
||||
transferList: transferList as unknown as import("worker_threads").TransferListItem[],
|
||||
// stdout: true,
|
||||
// stderr: true,
|
||||
stdout: false,
|
||||
stderr: false,
|
||||
// ensure that we use the same seed value when hashing strings on all threads
|
||||
env: { ...process.env, TS_STRING_SEED: `${sys.stringSeed}` }
|
||||
});
|
||||
// worker.stdout.pipe(process.stdout, { end: false });
|
||||
// worker.stderr.pipe(process.stderr, { end: false });
|
||||
worker.unref();
|
||||
return worker as unknown as Worker;
|
||||
}
|
||||
|
||||
function createMessageChannel(): MessageChannel {
|
||||
return new worker_threads.MessageChannel() as unknown as MessageChannel;
|
||||
}
|
||||
|
||||
function getHostObject<T>(factory: (host: WorkerThreadsHost) => T): T {
|
||||
let value = hostObjects.get(factory);
|
||||
if (value === hostObjectCircularitySentinel) throw new TypeError("Circularity detected in host object allocation");
|
||||
if (value === undefined) {
|
||||
hostObjects.set(factory, hostObjectCircularitySentinel);
|
||||
hostObjects.set(factory, value = factory(workerThreads));
|
||||
}
|
||||
return value as T;
|
||||
}
|
||||
|
||||
function addMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void) {
|
||||
(source as import("worker_threads").MessagePort | import("worker_threads").Worker).addListener("message", handler);
|
||||
}
|
||||
|
||||
function removeMessageListener(source: MessagePort | Worker, handler: (value: unknown) => void) {
|
||||
(source as import("worker_threads").MessagePort | import("worker_threads").Worker).removeListener("message", handler);
|
||||
}
|
||||
|
||||
const workerThreads: WorkerThreadsHost = {
|
||||
workerData: worker_threads.workerData,
|
||||
parentPort: (worker_threads.parentPort ?? undefined) as MessagePort | undefined,
|
||||
threadId: worker_threads.threadId,
|
||||
isWorkerThread,
|
||||
isMainThread,
|
||||
createWorker,
|
||||
createMessageChannel,
|
||||
getHostObject,
|
||||
addMessageListener,
|
||||
removeMessageListener,
|
||||
};
|
||||
return workerThreads;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export let workerThreads: WorkerThreadsHost | undefined = (() => {
|
||||
if (isNodeLikeSystem() && typeof SharedStructType === "function") {
|
||||
return createNodeWorkerThreadHost();
|
||||
}
|
||||
})();
|
||||
|
||||
/** @internal */
|
||||
export function setWorkerThreadsHost(value: WorkerThreadsHost | undefined) {
|
||||
workerThreads = value;
|
||||
}
|
||||
@ -29,7 +29,6 @@ import {
|
||||
createWatchCompilerHostOfConfigFile,
|
||||
createWatchCompilerHostOfFilesAndCompilerOptions,
|
||||
createWatchProgram,
|
||||
createWatchStatusReporter as ts_createWatchStatusReporter,
|
||||
Debug,
|
||||
Diagnostic,
|
||||
DiagnosticMessage,
|
||||
@ -54,6 +53,7 @@ import {
|
||||
getDiagnosticText,
|
||||
getErrorSummaryText,
|
||||
getLineStarts,
|
||||
getMaxCpuCount,
|
||||
getNormalizedAbsolutePath,
|
||||
isIncrementalCompilation,
|
||||
isWatchSet,
|
||||
@ -67,7 +67,6 @@ import {
|
||||
parseCommandLine,
|
||||
parseConfigFileWithSystem,
|
||||
ParsedCommandLine,
|
||||
performIncrementalCompilation as ts_performIncrementalCompilation,
|
||||
Program,
|
||||
reduceLeftIterator,
|
||||
ReportEmitErrorSummary,
|
||||
@ -82,12 +81,16 @@ import {
|
||||
supportedTSExtensionsFlat,
|
||||
sys,
|
||||
System,
|
||||
ThreadPool,
|
||||
toPath,
|
||||
tracing,
|
||||
createWatchStatusReporter as ts_createWatchStatusReporter,
|
||||
performIncrementalCompilation as ts_performIncrementalCompilation,
|
||||
validateLocaleAndSetLanguage,
|
||||
version,
|
||||
WatchCompilerHost,
|
||||
WatchOptions,
|
||||
WorkerThreadsHost,
|
||||
} from "./_namespaces/ts";
|
||||
|
||||
interface Statistic {
|
||||
@ -556,6 +559,7 @@ function executeCommandLineWorker(
|
||||
sys: System,
|
||||
cb: ExecuteCommandLineCallbacks,
|
||||
commandLine: ParsedCommandLine,
|
||||
workerThreads: WorkerThreadsHost | undefined,
|
||||
) {
|
||||
let reportDiagnostic = createDiagnosticReporter(sys);
|
||||
if (commandLine.options.build) {
|
||||
@ -634,6 +638,13 @@ function executeCommandLineWorker(
|
||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
}
|
||||
|
||||
let threadPool: ThreadPool | undefined;
|
||||
const maxCpuCount = getMaxCpuCount(commandLine.options);
|
||||
if (maxCpuCount > 1 && workerThreads?.isMainThread()) {
|
||||
threadPool = new ThreadPool(maxCpuCount, workerThreads);
|
||||
threadPool.start();
|
||||
}
|
||||
|
||||
const currentDirectory = sys.getCurrentDirectory();
|
||||
const commandLineOptions = convertToOptionsWithAbsolutePaths(
|
||||
commandLine.options,
|
||||
@ -671,6 +682,8 @@ function executeCommandLineWorker(
|
||||
commandLineOptions,
|
||||
commandLine.watchOptions,
|
||||
extendedConfigCache,
|
||||
workerThreads,
|
||||
threadPool,
|
||||
);
|
||||
}
|
||||
else if (isIncrementalCompilation(configParseResult.options)) {
|
||||
@ -678,7 +691,9 @@ function executeCommandLineWorker(
|
||||
sys,
|
||||
cb,
|
||||
reportDiagnostic,
|
||||
configParseResult
|
||||
configParseResult,
|
||||
workerThreads,
|
||||
threadPool,
|
||||
);
|
||||
}
|
||||
else {
|
||||
@ -686,7 +701,9 @@ function executeCommandLineWorker(
|
||||
sys,
|
||||
cb,
|
||||
reportDiagnostic,
|
||||
configParseResult
|
||||
configParseResult,
|
||||
workerThreads,
|
||||
threadPool,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -710,6 +727,8 @@ function executeCommandLineWorker(
|
||||
commandLine.fileNames,
|
||||
commandLineOptions,
|
||||
commandLine.watchOptions,
|
||||
workerThreads,
|
||||
threadPool,
|
||||
);
|
||||
}
|
||||
else if (isIncrementalCompilation(commandLineOptions)) {
|
||||
@ -717,7 +736,9 @@ function executeCommandLineWorker(
|
||||
sys,
|
||||
cb,
|
||||
reportDiagnostic,
|
||||
{ ...commandLine, options: commandLineOptions }
|
||||
{ ...commandLine, options: commandLineOptions },
|
||||
workerThreads,
|
||||
threadPool,
|
||||
);
|
||||
}
|
||||
else {
|
||||
@ -725,7 +746,9 @@ function executeCommandLineWorker(
|
||||
sys,
|
||||
cb,
|
||||
reportDiagnostic,
|
||||
{ ...commandLine, options: commandLineOptions }
|
||||
{ ...commandLine, options: commandLineOptions },
|
||||
workerThreads,
|
||||
threadPool,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -744,6 +767,7 @@ export function executeCommandLine(
|
||||
system: System,
|
||||
cb: ExecuteCommandLineCallbacks,
|
||||
commandLineArgs: readonly string[],
|
||||
workerThreads?: WorkerThreadsHost
|
||||
) {
|
||||
if (isBuild(commandLineArgs)) {
|
||||
const { buildOptions, watchOptions, projects, errors } = parseBuildCommand(commandLineArgs.slice(1));
|
||||
@ -754,7 +778,8 @@ export function executeCommandLine(
|
||||
buildOptions,
|
||||
watchOptions,
|
||||
projects,
|
||||
errors
|
||||
errors,
|
||||
workerThreads
|
||||
));
|
||||
}
|
||||
else {
|
||||
@ -764,7 +789,8 @@ export function executeCommandLine(
|
||||
buildOptions,
|
||||
watchOptions,
|
||||
projects,
|
||||
errors
|
||||
errors,
|
||||
workerThreads
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -775,10 +801,11 @@ export function executeCommandLine(
|
||||
system,
|
||||
cb,
|
||||
commandLine,
|
||||
workerThreads,
|
||||
));
|
||||
}
|
||||
else {
|
||||
return executeCommandLineWorker(system, cb, commandLine);
|
||||
return executeCommandLineWorker(system, cb, commandLine, workerThreads);
|
||||
}
|
||||
}
|
||||
|
||||
@ -797,7 +824,8 @@ function performBuild(
|
||||
buildOptions: BuildOptions,
|
||||
watchOptions: WatchOptions | undefined,
|
||||
projects: string[],
|
||||
errors: Diagnostic[]
|
||||
errors: Diagnostic[],
|
||||
workerThreads: WorkerThreadsHost | undefined,
|
||||
) {
|
||||
// Update to pretty if host supports it
|
||||
const reportDiagnostic = updateReportDiagnostic(
|
||||
@ -832,14 +860,23 @@ function performBuild(
|
||||
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
|
||||
}
|
||||
|
||||
let threadPool: ThreadPool | undefined;
|
||||
const maxCpuCount = getMaxCpuCount(buildOptions);
|
||||
if (maxCpuCount > 1 && workerThreads?.isMainThread()) {
|
||||
threadPool = new ThreadPool(maxCpuCount, workerThreads);
|
||||
}
|
||||
|
||||
if (buildOptions.watch) {
|
||||
if (reportWatchModeWithoutSysSupport(sys, reportDiagnostic)) return;
|
||||
threadPool?.start();
|
||||
const buildHost = createSolutionBuilderWithWatchHost(
|
||||
sys,
|
||||
/*createProgram*/ undefined,
|
||||
reportDiagnostic,
|
||||
createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)),
|
||||
createWatchStatusReporter(sys, buildOptions)
|
||||
createWatchStatusReporter(sys, buildOptions),
|
||||
workerThreads,
|
||||
threadPool
|
||||
);
|
||||
const solutionPerformance = enableSolutionPerformance(sys, buildOptions);
|
||||
updateSolutionBuilderHost(sys, cb, buildHost, solutionPerformance);
|
||||
@ -861,12 +898,15 @@ function performBuild(
|
||||
return builder;
|
||||
}
|
||||
|
||||
threadPool?.start();
|
||||
const buildHost = createSolutionBuilderHost(
|
||||
sys,
|
||||
/*createProgram*/ undefined,
|
||||
reportDiagnostic,
|
||||
createBuilderStatusReporter(sys, shouldBePretty(sys, buildOptions)),
|
||||
createReportErrorSummary(sys, buildOptions)
|
||||
createReportErrorSummary(sys, buildOptions),
|
||||
workerThreads,
|
||||
threadPool
|
||||
);
|
||||
const solutionPerformance = enableSolutionPerformance(sys, buildOptions);
|
||||
updateSolutionBuilderHost(sys, cb, buildHost, solutionPerformance);
|
||||
@ -874,6 +914,7 @@ function performBuild(
|
||||
const exitStatus = buildOptions.clean ? builder.clean() : builder.build();
|
||||
reportSolutionBuilderTimes(builder, solutionPerformance);
|
||||
dumpTracingLegend(); // Will no-op if there hasn't been any tracing
|
||||
threadPool?.stop();
|
||||
return sys.exit(exitStatus);
|
||||
}
|
||||
|
||||
@ -887,10 +928,12 @@ function performCompilation(
|
||||
sys: System,
|
||||
cb: ExecuteCommandLineCallbacks,
|
||||
reportDiagnostic: DiagnosticReporter,
|
||||
config: ParsedCommandLine
|
||||
config: ParsedCommandLine,
|
||||
workerThreads: WorkerThreadsHost | undefined,
|
||||
threadPool: ThreadPool | undefined,
|
||||
) {
|
||||
const { fileNames, options, projectReferences } = config;
|
||||
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, sys);
|
||||
const host = createCompilerHostWorker(options, /*setParentNodes*/ undefined, sys, workerThreads, threadPool);
|
||||
const currentDirectory = host.getCurrentDirectory();
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
|
||||
changeCompilerHostLikeToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName));
|
||||
@ -912,6 +955,7 @@ function performCompilation(
|
||||
);
|
||||
reportStatistics(sys, program, /*solutionPerformance*/ undefined);
|
||||
cb(program);
|
||||
threadPool?.stop();
|
||||
return sys.exit(exitStatus);
|
||||
}
|
||||
|
||||
@ -919,11 +963,13 @@ function performIncrementalCompilation(
|
||||
sys: System,
|
||||
cb: ExecuteCommandLineCallbacks,
|
||||
reportDiagnostic: DiagnosticReporter,
|
||||
config: ParsedCommandLine
|
||||
config: ParsedCommandLine,
|
||||
workerThreads: WorkerThreadsHost | undefined,
|
||||
threadPool: ThreadPool | undefined,
|
||||
) {
|
||||
const { options, fileNames, projectReferences } = config;
|
||||
enableStatisticsAndTracing(sys, options, /*isBuildMode*/ false);
|
||||
const host = createIncrementalCompilerHost(options, sys);
|
||||
const host = createIncrementalCompilerHost(options, sys, workerThreads, threadPool);
|
||||
const exitStatus = ts_performIncrementalCompilation({
|
||||
host,
|
||||
system: sys,
|
||||
@ -938,6 +984,7 @@ function performIncrementalCompilation(
|
||||
cb(builderProgram);
|
||||
}
|
||||
});
|
||||
threadPool?.stop();
|
||||
return sys.exit(exitStatus);
|
||||
}
|
||||
|
||||
@ -996,6 +1043,8 @@ function createWatchOfConfigFile(
|
||||
optionsToExtend: CompilerOptions,
|
||||
watchOptionsToExtend: WatchOptions | undefined,
|
||||
extendedConfigCache: Map<string, ExtendedConfigCacheEntry>,
|
||||
workerThreads: WorkerThreadsHost | undefined,
|
||||
threadPool: ThreadPool | undefined,
|
||||
) {
|
||||
const watchCompilerHost = createWatchCompilerHostOfConfigFile({
|
||||
configFileName: configParseResult.options.configFilePath!,
|
||||
@ -1003,7 +1052,9 @@ function createWatchOfConfigFile(
|
||||
watchOptionsToExtend,
|
||||
system,
|
||||
reportDiagnostic,
|
||||
reportWatchStatus: createWatchStatusReporter(system, configParseResult.options)
|
||||
reportWatchStatus: createWatchStatusReporter(system, configParseResult.options),
|
||||
workerThreads,
|
||||
threadPool,
|
||||
});
|
||||
updateWatchCompilationHost(system, cb, watchCompilerHost);
|
||||
watchCompilerHost.configFileParsingResult = configParseResult;
|
||||
@ -1018,6 +1069,8 @@ function createWatchOfFilesAndCompilerOptions(
|
||||
rootFiles: string[],
|
||||
options: CompilerOptions,
|
||||
watchOptions: WatchOptions | undefined,
|
||||
workerThreads: WorkerThreadsHost | undefined,
|
||||
threadPool: ThreadPool | undefined,
|
||||
) {
|
||||
const watchCompilerHost = createWatchCompilerHostOfFilesAndCompilerOptions({
|
||||
rootFiles,
|
||||
@ -1025,7 +1078,9 @@ function createWatchOfFilesAndCompilerOptions(
|
||||
watchOptions,
|
||||
system,
|
||||
reportDiagnostic,
|
||||
reportWatchStatus: createWatchStatusReporter(system, options)
|
||||
reportWatchStatus: createWatchStatusReporter(system, options),
|
||||
workerThreads,
|
||||
threadPool,
|
||||
});
|
||||
updateWatchCompilationHost(system, cb, watchCompilerHost);
|
||||
return createWatchProgram(watchCompilerHost);
|
||||
|
||||
@ -554,6 +554,10 @@ export function patchHostForBuildInfoWrite<T extends ts.System>(sys: T, version:
|
||||
}
|
||||
|
||||
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost<ts.BuilderProgram> {
|
||||
/** @internal */ declare workerThreads: ts.WorkerThreadsHost | undefined;
|
||||
/** @internal */ declare threadPool: ts.ThreadPool | undefined;
|
||||
/** @internal */ declare membrane: ts.Membrane | undefined;
|
||||
|
||||
createProgram: ts.CreateProgram<ts.BuilderProgram>;
|
||||
|
||||
private constructor(sys: System | vfs.FileSystem, options?: ts.CompilerOptions, setParentNodes?: boolean, createProgram?: ts.CreateProgram<ts.BuilderProgram>) {
|
||||
|
||||
@ -225,6 +225,7 @@ import {
|
||||
normalizePath,
|
||||
NumberLiteralType,
|
||||
NumericLiteral,
|
||||
objectAllocator,
|
||||
ObjectAllocator,
|
||||
ObjectFlags,
|
||||
ObjectLiteralElement,
|
||||
@ -1259,6 +1260,7 @@ class SourceMapSourceObject implements SourceMapSource {
|
||||
|
||||
function getServicesObjectAllocator(): ObjectAllocator {
|
||||
return {
|
||||
getNodeArrayConstructor: objectAllocator.getNodeArrayConstructor,
|
||||
getNodeConstructor: () => NodeObject,
|
||||
getTokenConstructor: () => TokenObject,
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import "./unittests/asserts";
|
||||
import "./unittests/utf8";
|
||||
import "./unittests/base64";
|
||||
import "./unittests/builder";
|
||||
import "./unittests/canWatch";
|
||||
@ -18,6 +19,8 @@ import "./unittests/programApi";
|
||||
import "./unittests/publicApi";
|
||||
import "./unittests/reuseProgramStructure";
|
||||
import "./unittests/semver";
|
||||
import "./unittests/sharing/hashData";
|
||||
import "./unittests/sharing/resizableArray";
|
||||
import "./unittests/transform";
|
||||
import "./unittests/typeParameterIsPossiblyReferenced";
|
||||
import "./unittests/config/commandLineParsing";
|
||||
|
||||
15
src/testRunner/unittests/sharing/hashData.ts
Normal file
15
src/testRunner/unittests/sharing/hashData.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { HashData } from "../../../compiler/sharing/collections/hashData";
|
||||
|
||||
describe("unittests:: hashData", () => {
|
||||
describe("findEntryIndex", () => {
|
||||
it("when empty", () => {
|
||||
const hashData = new HashData(0);
|
||||
expect(HashData.findEntryIndex(hashData, 0)).to.equal(-1);
|
||||
});
|
||||
it("when has element", () => {
|
||||
const hashData = new HashData(0);
|
||||
HashData.insertEntry(hashData, "a", "a");
|
||||
expect(HashData.findEntryIndex(hashData, "a")).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
9
src/testRunner/unittests/sharing/resizableArray.ts
Normal file
9
src/testRunner/unittests/sharing/resizableArray.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Membrane, ProxyResizableArray } from "../../_namespaces/ts";
|
||||
|
||||
describe("unittests:: resizableArray", () => {
|
||||
it("push", () => {
|
||||
const membrane = new Membrane();
|
||||
const array = new ProxyResizableArray(membrane);
|
||||
array.push(1);
|
||||
});
|
||||
});
|
||||
55
src/testRunner/unittests/utf8.ts
Normal file
55
src/testRunner/unittests/utf8.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { utf8decodeCompat, utf8encodeIntoCompat } from "../_namespaces/ts";
|
||||
|
||||
describe("unittests:: utf8", () => {
|
||||
describe("utf8encodeIntoCompat matches TextEncoder.encode", () => {
|
||||
const data = [
|
||||
"日本語",
|
||||
"🐱",
|
||||
"\ud83d\udc31",
|
||||
"\u{1F431}",
|
||||
"\x00\x01",
|
||||
"\t\n\r\\\"'\u0062",
|
||||
"====",
|
||||
"",
|
||||
];
|
||||
for (const item of data) {
|
||||
it(item, () => {
|
||||
const buffer1 = new Uint8Array(65536);
|
||||
const buffer2 = new Uint8Array(65536);
|
||||
const written1 = utf8encodeIntoCompat(item, buffer1);
|
||||
const { written: written2 } = new TextEncoder().encodeInto(item, buffer2);
|
||||
const bytes1 = toBytes(buffer1.slice(0, written1)), bytes2 = toBytes(buffer2.slice(0, written2));
|
||||
assert.equal(bytes1, bytes2);
|
||||
});
|
||||
}
|
||||
});
|
||||
function toBytes(buffer: Uint8Array) {
|
||||
let s = "";
|
||||
for (const byte of buffer) {
|
||||
if (byte < 16) s += "0";
|
||||
s += byte.toString(16).toUpperCase();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
describe("utf8decodeCompat matches TextDecoder.decode", () => {
|
||||
const data = [
|
||||
"日本語",
|
||||
"🐱",
|
||||
"\ud83d\udc31",
|
||||
"\u{1F431}",
|
||||
"\x00\x01",
|
||||
"\t\n\r\\\"'\u0062",
|
||||
"====",
|
||||
"",
|
||||
];
|
||||
for (const item of data) {
|
||||
it(item, () => {
|
||||
const buffer = new Uint8Array(65536);
|
||||
const { written } = new TextEncoder().encodeInto(item, buffer);
|
||||
const decoded1 = utf8decodeCompat(buffer.slice(0, written));
|
||||
const decoded2 = new TextDecoder().decode(buffer.slice(0, written));
|
||||
assert.equal(decoded1, decoded2);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -9,6 +9,8 @@ ts.Debug.loggingHost = {
|
||||
}
|
||||
};
|
||||
|
||||
ts.sys.tryEnableSharedStructs?.();
|
||||
|
||||
if (ts.Debug.isDebugging) {
|
||||
ts.Debug.enableDebugInfo();
|
||||
}
|
||||
@ -21,4 +23,9 @@ if (ts.sys.setBlocking) {
|
||||
ts.sys.setBlocking();
|
||||
}
|
||||
|
||||
ts.executeCommandLine(ts.sys, ts.noop, ts.sys.args);
|
||||
if (ts.workerThreads?.isWorkerThread()) {
|
||||
ts.executeWorker(ts.sys, ts.workerThreads);
|
||||
}
|
||||
else {
|
||||
ts.executeCommandLine(ts.sys, ts.noop, ts.sys.args, ts.workerThreads);
|
||||
}
|
||||
|
||||
@ -7218,6 +7218,7 @@ declare namespace ts {
|
||||
lib?: string[];
|
||||
locale?: string;
|
||||
mapRoot?: string;
|
||||
maxCpuCount?: number;
|
||||
maxNodeModuleJsDepth?: number;
|
||||
module?: ModuleKind;
|
||||
moduleResolution?: ModuleResolutionKind;
|
||||
@ -7494,6 +7495,7 @@ declare namespace ts {
|
||||
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined;
|
||||
}
|
||||
interface CompilerHost extends ModuleResolutionHost {
|
||||
requestSourceFile?(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, shouldCreateNewSourceFile?: boolean): void;
|
||||
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
|
||||
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
|
||||
getCancellationToken?(): CancellationToken;
|
||||
@ -9874,6 +9876,7 @@ declare namespace ts {
|
||||
emitDeclarationOnly?: boolean;
|
||||
sourceMap?: boolean;
|
||||
inlineSourceMap?: boolean;
|
||||
maxCpuCount?: number;
|
||||
traceResolution?: boolean;
|
||||
[option: string]: CompilerOptionsValue | undefined;
|
||||
}
|
||||
|
||||
@ -3158,6 +3158,7 @@ declare namespace ts {
|
||||
lib?: string[];
|
||||
locale?: string;
|
||||
mapRoot?: string;
|
||||
maxCpuCount?: number;
|
||||
maxNodeModuleJsDepth?: number;
|
||||
module?: ModuleKind;
|
||||
moduleResolution?: ModuleResolutionKind;
|
||||
@ -3434,6 +3435,7 @@ declare namespace ts {
|
||||
readonly resolvedTypeReferenceDirective: ResolvedTypeReferenceDirective | undefined;
|
||||
}
|
||||
interface CompilerHost extends ModuleResolutionHost {
|
||||
requestSourceFile?(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, shouldCreateNewSourceFile?: boolean): void;
|
||||
getSourceFile(fileName: string, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
|
||||
getSourceFileByPath?(fileName: string, path: Path, languageVersionOrOptions: ScriptTarget | CreateSourceFileOptions, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;
|
||||
getCancellationToken?(): CancellationToken;
|
||||
@ -5814,6 +5816,7 @@ declare namespace ts {
|
||||
emitDeclarationOnly?: boolean;
|
||||
sourceMap?: boolean;
|
||||
inlineSourceMap?: boolean;
|
||||
maxCpuCount?: number;
|
||||
traceResolution?: boolean;
|
||||
[option: string]: CompilerOptionsValue | undefined;
|
||||
}
|
||||
|
||||
0
thread.log
Normal file
0
thread.log
Normal file
Loading…
x
Reference in New Issue
Block a user