Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/native/corehost/browserhost/loader/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import GitHash from "consts:gitHash";
import { loaderConfig, getLoaderConfig } from "./config";
import { exit, isExited, isRuntimeRunning, addOnExitListener, registerExit, quitNow } from "./exit";
import { invokeLibraryInitializers } from "./lib-initializers";
import { check, error, info, warn, debug } from "./logging";
import { check, error, info, warn, debug, fastCheck } from "./logging";

import { dotnetAssert, dotnetLoaderExports, dotnetLogger, dotnetUpdateInternals, dotnetUpdateInternalsSubscriber } from "./cross-module";
import { rejectRunMainPromise, resolveRunMainPromise, getRunMainPromise, abortStartup } from "./run";
Expand Down Expand Up @@ -77,6 +77,7 @@ export function dotnetInitializeModule(): RuntimeAPI {
Object.assign(dotnetLogger, logger);
const assert: AssertType = {
check,
fastCheck,
};
Object.assign(dotnetAssert, assert);

Expand All @@ -99,6 +100,7 @@ export function dotnetInitializeModule(): RuntimeAPI {
logger.warn,
logger.error,
assert.check,
assert.fastCheck,
dotnetLoaderExports.resolveRunMainPromise,
dotnetLoaderExports.rejectRunMainPromise,
dotnetLoaderExports.getRunMainPromise,
Expand Down
13 changes: 10 additions & 3 deletions src/native/corehost/browserhost/loader/logging.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

// WASM-TODO: inline the code
export function check(condition: unknown, message: string): asserts condition {
if (!condition) {
throw new Error(`dotnetAssert failed: ${message}`);
}
}

export function check(condition: unknown, messageFactory: string | (() => string)): asserts condition {
// calls to fastCheck will be inlined by rollup
// so that the string formatting or allocation of a closure would only happen in failure cases
// this is important for performance sensitive code paths
export function fastCheck(condition: unknown, messageFactory: (() => string)): asserts condition {
if (!condition) {
const message = typeof messageFactory === "string" ? messageFactory : messageFactory();
const message = messageFactory();
throw new Error(`dotnetAssert failed: ${message}`);
}
}
Expand Down
23 changes: 12 additions & 11 deletions src/native/libs/Common/JavaScript/cross-module/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,20 @@ export function dotnetUpdateInternalsSubscriber() {
};
const assertLocal: AssertType = {
check: table[4],
fastCheck: table[5],
};
const loaderExportsLocal: LoaderExports = {
resolveRunMainPromise: table[5],
rejectRunMainPromise: table[6],
getRunMainPromise: table[7],
createPromiseCompletionSource: table[8],
isControllablePromise: table[9],
getPromiseCompletionSource: table[10],
isExited: table[11],
isRuntimeRunning: table[12],
addOnExitListener: table[13],
abortStartup: table[14],
quitNow: table[15],
resolveRunMainPromise: table[6],
rejectRunMainPromise: table[7],
getRunMainPromise: table[8],
createPromiseCompletionSource: table[9],
isControllablePromise: table[10],
getPromiseCompletionSource: table[11],
isExited: table[12],
isRuntimeRunning: table[13],
addOnExitListener: table[14],
abortStartup: table[15],
quitNow: table[16],
};
Object.assign(dotnetLoaderExports, loaderExportsLocal);
Object.assign(logger, loggerLocal);
Expand Down
4 changes: 3 additions & 1 deletion src/native/libs/Common/JavaScript/types/exchange.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

import type { check, error, info, warn, debug } from "../../../../corehost/browserhost/loader/logging";
import type { check, error, info, warn, debug, fastCheck } from "../../../../corehost/browserhost/loader/logging";
import type { resolveRunMainPromise, rejectRunMainPromise, getRunMainPromise, abortStartup } from "../../../../corehost/browserhost/loader/run";
import type { addOnExitListener, isExited, isRuntimeRunning, quitNow } from "../../../../corehost/browserhost/loader/exit";

Expand Down Expand Up @@ -29,6 +29,7 @@ export type LoggerType = {

export type AssertType = {
check: typeof check,
fastCheck: typeof fastCheck,
}

export type LoaderExports = {
Expand All @@ -51,6 +52,7 @@ export type LoaderExportsTable = [
typeof warn,
typeof error,
typeof check,
typeof fastCheck,
typeof resolveRunMainPromise,
typeof rejectRunMainPromise,
typeof getRunMainPromise,
Expand Down
18 changes: 9 additions & 9 deletions src/native/libs/System.Native.Browser/utils/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ const min_int64_big = BigInt("-9223372036854775808");
const sharedArrayBufferDefined = typeof SharedArrayBuffer !== "undefined";

export function assertIntInRange(value: Number, min: Number, max: Number) {
dotnetAssert.check(Number.isSafeInteger(value), () => `Value is not an integer: ${value} (${typeof (value)})`);
dotnetAssert.check(value >= min && value <= max, () => `Overflow: value ${value} is out of ${min} ${max} range`);
dotnetAssert.fastCheck(Number.isSafeInteger(value), () => `Value is not an integer: ${value} (${typeof (value)})`);
dotnetAssert.fastCheck(value >= min && value <= max, () => `Overflow: value ${value} is out of ${min} ${max} range`);
}

/** note: boolean is 8 bits not 32 bits when inside a structure or array */
Expand Down Expand Up @@ -82,33 +82,33 @@ export function setHeapI32(offset: MemOffset, value: number): void {
* Throws for values which are not 52 bit integer. See Number.isSafeInteger()
*/
export function setHeapI52(offset: MemOffset, value: number): void {
dotnetAssert.check(Number.isSafeInteger(value), () => `Value is not a safe integer: ${value} (${typeof (value)})`);
dotnetAssert.fastCheck(Number.isSafeInteger(value), () => `Value is not a safe integer: ${value} (${typeof (value)})`);
throw new Error("WASM-TODO");
}

/**
* Throws for values which are not 52 bit integer or are negative. See Number.isSafeInteger().
*/
export function setHeapU52(offset: MemOffset, value: number): void {
dotnetAssert.check(Number.isSafeInteger(value), () => `Value is not a safe integer: ${value} (${typeof (value)})`);
dotnetAssert.check(value >= 0, "Can't convert negative Number into UInt64");
dotnetAssert.fastCheck(Number.isSafeInteger(value), () => `Value is not a safe integer: ${value} (${typeof (value)})`);
dotnetAssert.fastCheck(value >= 0, () => "Can't convert negative Number into UInt64");
throw new Error("WASM-TODO");
}

export function setHeapI64Big(offset: MemOffset, value: bigint): void {
dotnetAssert.check(typeof value === "bigint", () => `Value is not an bigint: ${value} (${typeof (value)})`);
dotnetAssert.check(value >= min_int64_big && value <= max_int64_big, () => `Overflow: value ${value} is out of ${min_int64_big} ${max_int64_big} range`);
dotnetAssert.fastCheck(typeof value === "bigint", () => `Value is not an bigint: ${value} (${typeof (value)})`);
dotnetAssert.fastCheck(value >= min_int64_big && value <= max_int64_big, () => `Overflow: value ${value} is out of ${min_int64_big} ${max_int64_big} range`);

Module.HEAP64[<any>offset >>> 3] = value;
}

export function setHeapF32(offset: MemOffset, value: number): void {
dotnetAssert.check(typeof value === "number", () => `Value is not a Number: ${value} (${typeof (value)})`);
dotnetAssert.fastCheck(typeof value === "number", () => `Value is not a Number: ${value} (${typeof (value)})`);
Module.HEAPF32[<any>offset >>> 2] = value;
}

export function setHeapF64(offset: MemOffset, value: number): void {
dotnetAssert.check(typeof value === "number", () => `Value is not a Number: ${value} (${typeof (value)})`);
dotnetAssert.fastCheck(typeof value === "number", () => `Value is not a Number: ${value} (${typeof (value)})`);
Module.HEAPF64[<any>offset >>> 3] = value;
}

Expand Down
12 changes: 12 additions & 0 deletions src/native/rollup.config.defines.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,15 @@ export const envConstants = {
gitHash,
isContinuousIntegrationBuild,
};

/* eslint-disable quotes */
export const inlinefastCheck = [
{
pattern: 'dotnetAssert\\.fastCheck\\(([^,]*), \\(\\) => *`([^`]*)`\\);',
replacement: (match) => `if (!(${match[1]})) throw new Error(\`Assert failed: ${match[2]}\`); // inlined fastCheck`
},
{
pattern: 'dotnetAssert\\.fastCheck\\(([^,]*), \\(\\) => *"([^"]*)"\\);',
replacement: (match) => `if (!(${match[1]})) throw new Error(\`Assert failed: ${match[2]}\`); // inlined fastCheck`
},
];
6 changes: 4 additions & 2 deletions src/native/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import dts from "rollup-plugin-dts";
import {
externalDependencies, envConstants, banner, banner_dts,
isDebug, staticLibDestination,
keep_classnames, keep_fnames, reserved
keep_classnames, keep_fnames, reserved,
inlinefastCheck,
} from "./rollup.config.defines.js";
import { terserPlugin, writeOnChangePlugin, consts, onwarn, alwaysLF, iife2fe, emsAmbient, sourcemapPathTransform } from "./rollup.config.plugins.js";
import { terserPlugin, writeOnChangePlugin, consts, onwarn, alwaysLF, iife2fe, emsAmbient, regexReplace, sourcemapPathTransform } from "./rollup.config.plugins.js";
import { promises as fs } from "fs";

const dotnetDTS = {
Expand Down Expand Up @@ -189,6 +190,7 @@ function configure({ input, output, terser, external }) {
external: external ? [...external, ...externalDependencies] : externalDependencies,
plugins: [
nodeResolve(),
regexReplace([...inlinefastCheck]),
consts(envConstants),
typescript({
tsconfig: "./tsconfig.json",
Expand Down
51 changes: 48 additions & 3 deletions src/native/rollup.config.plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import virtual from "@rollup/plugin-virtual";
import * as fs from "fs";
import * as path from "path";
import terser from "@rollup/plugin-terser";
import MagicString from "magic-string";

import { isContinuousIntegrationBuild, gitHash } from "./rollup.config.defines.js";

Expand Down Expand Up @@ -36,6 +37,50 @@ export const writeOnChangePlugin = () => ({
generateBundle: writeWhenChanged
});

export function regexReplace(replacements = []) {
return {
name: "regexReplace",

renderChunk(code, chunk) {
const id = chunk.fileName;
return executeReplacement(this, code, id);
},

transform(code, id) {
return executeReplacement(this, code, id);
}
};

function executeReplacement(_, code, id) {
const magicString = new MagicString(code);
if (!codeHasReplacements(code, id, magicString)) {
return null;
}

const result = { code: magicString.toString() };
result.map = magicString.generateMap({ hires: true });
return result;
}

function codeHasReplacements(code, id, magicString) {
let result = false;
let match;
for (const rep of replacements) {
const { pattern, replacement } = rep;
const rx = new RegExp(pattern, "gm");
while ((match = rx.exec(code))) {
result = true;
const updated = replacement(match);
const start = match.index;
const end = start + match[0].length;
magicString.overwrite(start, end, updated);
}
}

return result;
}
}

// Drop invocation from IIFE
export function iife2fe() {
return {
Expand All @@ -45,10 +90,10 @@ export function iife2fe() {
if (name.endsWith(".map")) return;
const asset = bundle[name];
const code = asset.code;
//throw new Error("iife2fe " + code);
asset.code = code
.replace(/}\({}\);/, "};") // }({}); ->};
.replace(/}\)\({}\);/, "});"); // })({}); ->});
// spaces are there to preserve source mappings
.replace(/}\({}\);/, "} ;") // }({}); -> } ;
.replace(/}\)\({}\);/, "}) ;"); // })({}); -> }) ;
}
};
}
Expand Down
Loading