diff options
author | Pavel Savara <pavel.savara@gmail.com> | 2022-05-20 21:49:54 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-20 21:49:54 +0300 |
commit | e7886b24ba2ca7c9eff86a842ad3d97dd30380e4 (patch) | |
tree | 6e5e8c392205842f6b0d92f2eb31bce0be8a5150 /src/mono/wasm/runtime | |
parent | 810a7f9cd78d6611c0473940cb733231f3eabc06 (diff) |
[wasm] improve memory access and marshaling range checks (#64845)
* new memory accessors setI52, setI64Big, getI52, getI64Big
* removed support for long form automatic marshaler. It has impact to mono_bind_static_method
* made assert silent on Release config
* test for not marshaling long from C# to JS
* negative test for marshaling NaN as long
* fixed marshaling of uint32, uint16 and byte to be unsigned
* implemented range check on all set memory operations
* implemented also uint52
* differentiated bool marshaling because it shoud have different validation and message
* inlined asserts
* rename assert to mono_assert
Diffstat (limited to 'src/mono/wasm/runtime')
-rw-r--r-- | src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js | 3 | ||||
-rw-r--r-- | src/mono/wasm/runtime/cwraps.ts | 4 | ||||
-rw-r--r-- | src/mono/wasm/runtime/dotnet.d.ts | 34 | ||||
-rw-r--r-- | src/mono/wasm/runtime/driver.c | 4 | ||||
-rw-r--r-- | src/mono/wasm/runtime/es6/dotnet.es6.lib.js | 3 | ||||
-rw-r--r-- | src/mono/wasm/runtime/exports.ts | 23 | ||||
-rw-r--r-- | src/mono/wasm/runtime/memory.ts | 116 | ||||
-rw-r--r-- | src/mono/wasm/runtime/method-binding.ts | 26 | ||||
-rw-r--r-- | src/mono/wasm/runtime/rollup.config.js | 62 | ||||
-rw-r--r-- | src/mono/wasm/runtime/startup.ts | 18 | ||||
-rw-r--r-- | src/mono/wasm/runtime/types.ts | 9 |
11 files changed, 254 insertions, 48 deletions
diff --git a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js index de07811bdd6..79116860e1c 100644 --- a/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js +++ b/src/mono/wasm/runtime/cjs/dotnet.cjs.lib.js @@ -10,11 +10,12 @@ const DotnetSupportLib = { // we replace implementation of readAsync and fetch // replacement of require is there for consistency with ES6 code $DOTNET__postset: ` -let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require}; +let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews}; let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports( { isESM:false, isGlobal:ENVIRONMENT_IS_GLOBAL, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:Promise.resolve(require)}, { mono:MONO, binding:BINDING, internal:INTERNAL, module:Module }, __dotnet_replacements); +updateGlobalBufferAndViews = __dotnet_replacements.updateGlobalBufferAndViews; readAsync = __dotnet_replacements.readAsync; var fetch = __dotnet_replacements.fetch; require = __dotnet_replacements.requireOut; diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index 52663f7f39a..0c205b47e49 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. import { - assert, + mono_assert, MonoArray, MonoAssembly, MonoClass, MonoMethod, MonoObject, MonoString, MonoType, MonoObjectRef, MonoStringRef @@ -197,7 +197,7 @@ export default wrapped_c_functions; export function wrap_c_function(name: string): Function { const wf: any = wrapped_c_functions; const sig = fn_signatures.find(s => s[0] === name); - assert(sig, () => `Function ${name} not found`); + mono_assert(sig, () => `Function ${name} not found`); const fce = Module.cwrap(sig[0], sig[1], sig[2], sig[3]); wf[sig[0]] = fce; return fce; diff --git a/src/mono/wasm/runtime/dotnet.d.ts b/src/mono/wasm/runtime/dotnet.d.ts index 07c1db8ac7c..3c4c491a340 100644 --- a/src/mono/wasm/runtime/dotnet.d.ts +++ b/src/mono/wasm/runtime/dotnet.d.ts @@ -303,22 +303,40 @@ declare function mono_wasm_load_bytes_into_heap(bytes: Uint8Array): VoidPtr; declare type _MemOffset = number | VoidPtr | NativePointer | ManagedPointer; declare type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; +declare function setB32(offset: _MemOffset, value: number | boolean): void; declare function setU8(offset: _MemOffset, value: number): void; declare function setU16(offset: _MemOffset, value: number): void; declare function setU32(offset: _MemOffset, value: _NumberOrPointer): void; declare function setI8(offset: _MemOffset, value: number): void; declare function setI16(offset: _MemOffset, value: number): void; -declare function setI32(offset: _MemOffset, value: _NumberOrPointer): void; -declare function setI64(offset: _MemOffset, value: number): void; +declare function setI32(offset: _MemOffset, value: number): void; +/** + * Throws for values which are not 52 bit integer. See Number.isSafeInteger() + */ +declare function setI52(offset: _MemOffset, value: number): void; +/** + * Throws for values which are not 52 bit integer or are negative. See Number.isSafeInteger(). + */ +declare function setU52(offset: _MemOffset, value: number): void; +declare function setI64Big(offset: _MemOffset, value: bigint): void; declare function setF32(offset: _MemOffset, value: number): void; declare function setF64(offset: _MemOffset, value: number): void; +declare function getB32(offset: _MemOffset): boolean; declare function getU8(offset: _MemOffset): number; declare function getU16(offset: _MemOffset): number; declare function getU32(offset: _MemOffset): number; declare function getI8(offset: _MemOffset): number; declare function getI16(offset: _MemOffset): number; declare function getI32(offset: _MemOffset): number; -declare function getI64(offset: _MemOffset): number; +/** + * Throws for Number.MIN_SAFE_INTEGER > value > Number.MAX_SAFE_INTEGER + */ +declare function getI52(offset: _MemOffset): number; +/** + * Throws for Number.MIN_SAFE_INTEGER > value > Number.MAX_SAFE_INTEGER + */ +declare function getU52(offset: _MemOffset): number; +declare function getI64Big(offset: _MemOffset): bigint; declare function getF32(offset: _MemOffset): number; declare function getF64(offset: _MemOffset): number; @@ -343,19 +361,25 @@ declare const MONO: { mono_wasm_load_runtime: (unused: string, debug_level: number) => void; config: MonoConfig | MonoConfigError; loaded_files: string[]; + setB32: typeof setB32; setI8: typeof setI8; setI16: typeof setI16; setI32: typeof setI32; - setI64: typeof setI64; + setI52: typeof setI52; + setU52: typeof setU52; + setI64Big: typeof setI64Big; setU8: typeof setU8; setU16: typeof setU16; setU32: typeof setU32; setF32: typeof setF32; setF64: typeof setF64; + getB32: typeof getB32; getI8: typeof getI8; getI16: typeof getI16; getI32: typeof getI32; - getI64: typeof getI64; + getI52: typeof getI52; + getU52: typeof getU52; + getI64Big: typeof getI64Big; getU8: typeof getU8; getU16: typeof getU16; getU32: typeof getU32; diff --git a/src/mono/wasm/runtime/driver.c b/src/mono/wasm/runtime/driver.c index 3a5977ee2c9..596e4f05f3c 100644 --- a/src/mono/wasm/runtime/driver.c +++ b/src/mono/wasm/runtime/driver.c @@ -875,13 +875,13 @@ _marshal_type_from_mono_type (int mono_type, MonoClass *klass, MonoType *type) case MONO_TYPE_PTR: return MARSHAL_TYPE_POINTER; case MONO_TYPE_I1: - case MONO_TYPE_U1: case MONO_TYPE_I2: - case MONO_TYPE_U2: case MONO_TYPE_I4: return MARSHAL_TYPE_INT; case MONO_TYPE_CHAR: return MARSHAL_TYPE_CHAR; + case MONO_TYPE_U1: + case MONO_TYPE_U2: case MONO_TYPE_U4: // The distinction between this and signed int is // important due to how numbers work in JavaScript return MARSHAL_TYPE_UINT32; diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js index a110268beeb..f886800ab50 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.lib.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.lib.js @@ -14,7 +14,7 @@ const DotnetSupportLib = { // Emscripten's getBinaryPromise is not async for NodeJs, but we would like to have it async, so we replace it. // We also replace implementation of readAsync and fetch $DOTNET__postset: ` -let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require}; +let __dotnet_replacements = {readAsync, fetch: globalThis.fetch, require, updateGlobalBufferAndViews}; if (ENVIRONMENT_IS_NODE) { __dotnet_replacements.requirePromise = import('module').then(mod => { const require = mod.createRequire(import.meta.url); @@ -52,6 +52,7 @@ let __dotnet_exportedAPI = __dotnet_runtime.__initializeImportsAndExports( { isESM:true, isGlobal:false, isNode:ENVIRONMENT_IS_NODE, isShell:ENVIRONMENT_IS_SHELL, isWeb:ENVIRONMENT_IS_WEB, locateFile, quit_, ExitStatus, requirePromise:__dotnet_replacements.requirePromise }, { mono:MONO, binding:BINDING, internal:INTERNAL, module:Module }, __dotnet_replacements); +updateGlobalBufferAndViews = __dotnet_replacements.updateGlobalBufferAndViews; readAsync = __dotnet_replacements.readAsync; var fetch = __dotnet_replacements.fetch; require = __dotnet_replacements.requireOut; diff --git a/src/mono/wasm/runtime/exports.ts b/src/mono/wasm/runtime/exports.ts index e370c2e97c3..ae7efdfab33 100644 --- a/src/mono/wasm/runtime/exports.ts +++ b/src/mono/wasm/runtime/exports.ts @@ -58,10 +58,10 @@ import { mono_wasm_release_cs_owned_object } from "./gc-handles"; import { mono_wasm_web_socket_open_ref, mono_wasm_web_socket_send, mono_wasm_web_socket_receive, mono_wasm_web_socket_close_ref, mono_wasm_web_socket_abort } from "./web-socket"; import cwraps from "./cwraps"; import { - setI8, setI16, setI32, setI64, + setI8, setI16, setI32, setI52, setU8, setU16, setU32, setF32, setF64, - getI8, getI16, getI32, getI64, - getU8, getU16, getU32, getF32, getF64, + getI8, getI16, getI32, getI52, + getU8, getU16, getU32, getF32, getF64, afterUpdateGlobalBufferAndViews, getI64Big, setI64Big, getU52, setU52, setB32, getB32, } from "./memory"; import { create_weak_ref } from "./weak-ref"; import { fetch_like, readAsync_like } from "./polyfills"; @@ -93,19 +93,25 @@ const MONO = { loaded_files: <string[]>[], // memory accessors + setB32, setI8, setI16, setI32, - setI64, + setI52, + setU52, + setI64Big, setU8, setU16, setU32, setF32, setF64, + getB32, getI8, getI16, getI32, - getI64, + getI52, + getU52, + getI64Big, getU8, getU16, getU32, @@ -179,7 +185,7 @@ let exportedAPI: DotnetPublicAPI; function initializeImportsAndExports( imports: { isESM: boolean, isGlobal: boolean, isNode: boolean, isShell: boolean, isWeb: boolean, locateFile: Function, quit_: Function, ExitStatus: ExitStatusError, requirePromise: Promise<Function> }, exports: { mono: any, binding: any, internal: any, module: any }, - replacements: { fetch: any, readAsync: any, require: any, requireOut: any, noExitRuntime: boolean }, + replacements: { fetch: any, readAsync: any, require: any, requireOut: any, noExitRuntime: boolean, updateGlobalBufferAndViews: Function }, ): DotnetPublicAPI { const module = exports.module as DotnetModule; const globalThisAny = globalThis as any; @@ -236,6 +242,11 @@ function initializeImportsAndExports( replacements.fetch = runtimeHelpers.fetch; replacements.readAsync = readAsync_like; replacements.requireOut = module.imports.require; + const originalUpdateGlobalBufferAndViews = replacements.updateGlobalBufferAndViews; + replacements.updateGlobalBufferAndViews = (buffer: Buffer) => { + originalUpdateGlobalBufferAndViews(buffer); + afterUpdateGlobalBufferAndViews(buffer); + }; replacements.noExitRuntime = ENVIRONMENT_IS_WEB; diff --git a/src/mono/wasm/runtime/memory.ts b/src/mono/wasm/runtime/memory.ts index e524bb48046..8e43d133e47 100644 --- a/src/mono/wasm/runtime/memory.ts +++ b/src/mono/wasm/runtime/memory.ts @@ -1,10 +1,12 @@ import { Module } from "./imports"; +import { mono_assert } from "./types"; import { VoidPtr, NativePointer, ManagedPointer } from "./types/emscripten"; import * as cuint64 from "./cuint64"; const alloca_stack: Array<VoidPtr> = []; const alloca_buffer_size = 32 * 1024; let alloca_base: VoidPtr, alloca_offset: VoidPtr, alloca_limit: VoidPtr; +let HEAPI64: BigInt64Array = <any>null; function _ensure_allocated(): void { if (alloca_base) @@ -14,6 +16,8 @@ function _ensure_allocated(): void { alloca_limit = <VoidPtr>(<any>alloca_base + alloca_buffer_size); } +const is_bingint_supported = typeof BigInt !== "undefined" && typeof BigInt64Array !== "undefined"; + export function temp_malloc(size: number): VoidPtr { _ensure_allocated(); if (!alloca_stack.length) @@ -41,33 +45,86 @@ export function _release_temp_frame(): void { type _MemOffset = number | VoidPtr | NativePointer | ManagedPointer; type _NumberOrPointer = number | VoidPtr | NativePointer | ManagedPointer; +function is_int_in_range(value: Number, min: Number, max: Number) { + mono_assert(typeof value === "number", () => `Value is not integer but ${typeof value}`); + mono_assert(Number.isInteger(value), "Value is not integer but float"); + mono_assert(value >= min && value <= max, () => `Overflow: value ${value} is out of ${min} ${max} range`); +} + +export function setB32(offset: _MemOffset, value: number | boolean): void { + mono_assert(typeof value === "boolean", () => `Value is not boolean but ${typeof value}`); + Module.HEAP32[<any>offset >>> 2] = <any>!!value; +} + export function setU8(offset: _MemOffset, value: number): void { + is_int_in_range(value, 0, 0xFF); Module.HEAPU8[<any>offset] = value; } export function setU16(offset: _MemOffset, value: number): void { + is_int_in_range(value, 0, 0xFFFF); Module.HEAPU16[<any>offset >>> 1] = value; } export function setU32(offset: _MemOffset, value: _NumberOrPointer): void { + is_int_in_range(<any>value, 0, 0xFFFF_FFFF); Module.HEAPU32[<any>offset >>> 2] = <number><any>value; } export function setI8(offset: _MemOffset, value: number): void { + is_int_in_range(value, -0x80, 0x7F); Module.HEAP8[<any>offset] = value; } export function setI16(offset: _MemOffset, value: number): void { + is_int_in_range(value, -0x8000, 0x7FFF); Module.HEAP16[<any>offset >>> 1] = value; } -export function setI32(offset: _MemOffset, value: _NumberOrPointer): void { - Module.HEAP32[<any>offset >>> 2] = <number><any>value; +export function setI32(offset: _MemOffset, value: number): void { + is_int_in_range(<any>value, -0x8000_0000, 0x7FFF_FFFF); + Module.HEAP32[<any>offset >>> 2] = value; +} + +/** + * Throws for values which are not 52 bit integer. See Number.isSafeInteger() + */ +export function setI52(offset: _MemOffset, value: number): void { + // 52 bits = 0x1F_FFFF_FFFF_FFFF + mono_assert(!Number.isNaN(value), "Can't convert Number.Nan into Int64"); + mono_assert(Number.isSafeInteger(value), "Overflow: value out of Number.isSafeInteger range"); + let hi: number; + let lo: number; + if (value < 0) { + value = -1 - value; + hi = 0x8000_0000 + ((value >>> 32) ^ 0x001F_FFFF); + lo = (value & 0xFFFF_FFFF) ^ 0xFFFF_FFFF; + } + else { + hi = value >>> 32; + lo = value & 0xFFFF_FFFF; + } + Module.HEAPU32[1 + <any>offset >>> 2] = hi; + Module.HEAPU32[<any>offset >>> 2] = lo; } -// NOTE: Accepts a number, not a BigInt, so values over Number.MAX_SAFE_INTEGER will be corrupted -export function setI64(offset: _MemOffset, value: number): void { - Module.setValue(<VoidPtr><any>offset, value, "i64"); +/** + * Throws for values which are not 52 bit integer or are negative. See Number.isSafeInteger(). + */ +export function setU52(offset: _MemOffset, value: number): void { + // 52 bits = 0x1F_FFFF_FFFF_FFFF + mono_assert(!Number.isNaN(value), "Can't convert Number.Nan into UInt64"); + mono_assert(Number.isSafeInteger(value), "Overflow: value out of Number.isSafeInteger range"); + mono_assert(value >= 0, "Can't convert negative Number into UInt64"); + const hi = value >>> 32; + const lo = value & 0xFFFF_FFFF; + Module.HEAPU32[1 + <any>offset >>> 2] = hi; + Module.HEAPU32[<any>offset >>> 2] = lo; +} + +export function setI64Big(offset: _MemOffset, value: bigint): void { + mono_assert(is_bingint_supported, "BigInt is not supported."); + HEAPI64[<any>offset >>> 3] = value; } export function setF32(offset: _MemOffset, value: number): void { @@ -79,6 +136,10 @@ export function setF64(offset: _MemOffset, value: number): void { } +export function getB32(offset: _MemOffset): boolean { + return !!(Module.HEAP32[<any>offset >>> 2]); +} + export function getU8(offset: _MemOffset): number { return Module.HEAPU8[<any>offset]; } @@ -103,9 +164,42 @@ export function getI32(offset: _MemOffset): number { return Module.HEAP32[<any>offset >>> 2]; } -// NOTE: Returns a number, not a BigInt. This means values over Number.MAX_SAFE_INTEGER will be corrupted -export function getI64(offset: _MemOffset): number { - return Module.getValue(<number><any>offset, "i64"); +/** + * Throws for Number.MIN_SAFE_INTEGER > value > Number.MAX_SAFE_INTEGER + */ +export function getI52(offset: _MemOffset): number { + // 52 bits = 0x1F_FFFF_FFFF_FFFF + const hi = Module.HEAPU32[1 + (<any>offset >>> 2)]; + const lo = Module.HEAPU32[<any>offset >>> 2]; + const sign = hi & 0x8000_0000; + const exp = hi & 0x7FE0_0000; + if (sign) { + mono_assert(exp === 0x7FE0_0000, "Overflow: value out of Number.isSafeInteger range"); + const nhi = (hi & 0x000F_FFFF) ^ 0x000F_FFFF; + const nlo = lo ^ 0xFFFF_FFFF; + return -1 - ((nhi * 0x1_0000_0000) + nlo); + } + else { + mono_assert(exp === 0, "Overflow: value out of Number.isSafeInteger range"); + return (hi * 0x1_0000_0000) + lo; + } +} + +/** + * Throws for Number.MIN_SAFE_INTEGER > value > Number.MAX_SAFE_INTEGER + */ +export function getU52(offset: _MemOffset): number { + // 52 bits = 0x1F_FFFF_FFFF_FFFF + const hi = Module.HEAPU32[1 + (<any>offset >>> 2)]; + const lo = Module.HEAPU32[<any>offset >>> 2]; + const exp_sign = hi & 0xFFE0_0000; + mono_assert(exp_sign === 0, "Overflow: value out of Number.isSafeInteger range"); + return (hi * 0x1_0000_0000) + lo; +} + +export function getI64Big(offset: _MemOffset): bigint { + mono_assert(is_bingint_supported, "BigInt is not supported."); + return HEAPI64[<any>offset >>> 3]; } export function getF32(offset: _MemOffset): number { @@ -116,6 +210,12 @@ export function getF64(offset: _MemOffset): number { return Module.HEAPF64[<any>offset >>> 3]; } +export function afterUpdateGlobalBufferAndViews(buffer: Buffer): void { + if (is_bingint_supported) { + HEAPI64 = new BigInt64Array(buffer); + } +} + export function getCU64(offset: _MemOffset): cuint64.CUInt64 { const lo = getU32(offset); const hi = getU32(<any>offset + 4); diff --git a/src/mono/wasm/runtime/method-binding.ts b/src/mono/wasm/runtime/method-binding.ts index 6dc3daec9f7..d5cea1c6f0d 100644 --- a/src/mono/wasm/runtime/method-binding.ts +++ b/src/mono/wasm/runtime/method-binding.ts @@ -10,7 +10,7 @@ import { _unbox_mono_obj_root_with_known_nonprimitive_type } from "./cs-to-js"; import { _create_temp_frame, getI32, getU32, getF32, getF64, - setI32, setU32, setF32, setF64, setI64, + setI32, setU32, setF32, setF64, setI52, setU52, setB32, getB32 } from "./memory"; import { _get_args_root_buffer_for_method_call, _get_buffer_for_method_call, @@ -130,8 +130,11 @@ export function _create_primitive_converters(): void { // result.set ('k', { steps: [{ convert: js_to_mono_enum.bind (this), indirect: 'i64'}], size: 8}); result.set("j", { steps: [{ convert: js_to_mono_enum.bind(BINDING), indirect: "i32" }], size: 8 }); + result.set("b", { steps: [{ indirect: "bool" }], size: 8 }); result.set("i", { steps: [{ indirect: "i32" }], size: 8 }); - result.set("l", { steps: [{ indirect: "i64" }], size: 8 }); + result.set("I", { steps: [{ indirect: "u32" }], size: 8 }); + result.set("l", { steps: [{ indirect: "i52" }], size: 8 }); + result.set("L", { steps: [{ indirect: "u52" }], size: 8 }); result.set("f", { steps: [{ indirect: "float" }], size: 8 }); result.set("d", { steps: [{ indirect: "double" }], size: 8 }); } @@ -218,7 +221,9 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args setU32, setF32, setF64, - setI64, + setU52, + setI52, + setB32, scratchValueRoot: converter.scratchValueRoot }; let indirectLocalOffset = 0; @@ -276,6 +281,9 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args const offsetText = `(indirectStart + ${indirectLocalOffset})`; switch (step.indirect) { + case "bool": + body.push(`setB32(${offsetText}, ${valueKey});`); + break; case "u32": body.push(`setU32(${offsetText}, ${valueKey});`); break; @@ -288,8 +296,11 @@ export function _compile_converter_for_marshal_string(args_marshal: string/*Args case "double": body.push(`setF64(${offsetText}, ${valueKey});`); break; - case "i64": - body.push(`setI64(${offsetText}, ${valueKey});`); + case "i52": + body.push(`setI52(${offsetText}, ${valueKey});`); + break; + case "u52": + body.push(`setU52(${offsetText}, ${valueKey});`); break; default: throw new Error("Unimplemented indirect type: " + step.indirect); @@ -421,6 +432,7 @@ export function mono_bind_method(method: MonoMethod, this_arg: null, args_marsha token, unbox_buffer, unbox_buffer_size, + getB32, getI32, getU32, getF32, @@ -522,7 +534,7 @@ export function mono_bind_method(method: MonoMethod, this_arg: null, args_marsha ` case ${MarshalType.FP64}:`, " result = getF64(unbox_buffer); break;", ` case ${MarshalType.BOOL}:`, - " result = getI32(unbox_buffer) !== 0; break;", + " result = getB32(unbox_buffer); break;", ` case ${MarshalType.CHAR}:`, " result = String.fromCharCode(getI32(unbox_buffer)); break;", ` case ${MarshalType.NULL}:`, @@ -584,7 +596,7 @@ export type ArgsMarshalString = "" | `${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${ArgsMarshal}${_ExtraArgsMarshalOperators}`; */ -type ConverterStepIndirects = "u32" | "i32" | "float" | "double" | "i64" | "reference" +type ConverterStepIndirects = "u32" | "i32" | "float" | "double" | "u52" | "i52" | "reference" | "bool" export type Converter = { steps: { diff --git a/src/mono/wasm/runtime/rollup.config.js b/src/mono/wasm/runtime/rollup.config.js index 7225a2c954e..f37712aa155 100644 --- a/src/mono/wasm/runtime/rollup.config.js +++ b/src/mono/wasm/runtime/rollup.config.js @@ -7,6 +7,7 @@ import * as path from "path"; import { createHash } from "crypto"; import dts from "rollup-plugin-dts"; import consts from "rollup-plugin-consts"; +import { createFilter } from "@rollup/pluginutils"; const configuration = process.env.Configuration; const isDebug = configuration !== "Release"; @@ -45,7 +46,19 @@ const banner_dts = banner + "//!\n//! This is generated file, see src/mono/wasm/ // emcc doesn't know how to load ES6 module, that's why we need the whole rollup.js const format = "iife"; const name = "__dotnet_runtime"; - +const inlineAssert = [ + { + pattern: /mono_assert\(([^,]*), *"([^"]*)"\);/gm, + // eslint-disable-next-line quotes + replacement: 'if (!($1)) throw new Error("Assert failed: $2"); // inlined mono_assert' + }, + { + pattern: /mono_assert\(([^,]*), \(\) => *`([^`]*)`\);/gm, + replacement: "if (!($1)) throw new Error(`Assert failed: $2`); // inlined mono_assert" + }, { + pattern: /^\s*mono_assert/gm, + failure: "previous regexp didn't inline all mono_assert statements" + }]; const iffeConfig = { treeshake: !isDebug, input: "exports.ts", @@ -72,7 +85,7 @@ const iffeConfig = { handler(warning); }, - plugins: [consts({ productVersion, configuration }), typescript()] + plugins: [regexReplace(inlineAssert), consts({ productVersion, configuration }), typescript()] }; const typesConfig = { input: "./export-types.ts", @@ -159,4 +172,47 @@ function checkFileExists(file) { return fs.promises.access(file, fs.constants.F_OK) .then(() => true) .catch(() => false); -}
\ No newline at end of file +} + +function regexReplace(replacements = []) { + const filter = createFilter("**/*.ts"); + + return { + name: "replace", + + renderChunk(code, chunk) { + const id = chunk.fileName; + if (!filter(id)) return null; + return executeReplacement(this, code, id); + }, + + transform(code, id) { + if (!filter(id)) return null; + return executeReplacement(this, code, id); + } + }; + + function executeReplacement(self, code, id) { + // TODO use MagicString for sourcemap support + let fixed = code; + for (const rep of replacements) { + const { pattern, replacement, failure } = rep; + if (failure) { + const match = pattern.test(fixed); + if (match) { + self.error(failure + " " + id, pattern.lastIndex); + return null; + } + } + else { + fixed = fixed.replace(pattern, replacement); + } + } + + if (fixed == code) { + return null; + } + + return { code: fixed }; + } +} diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 3871aa5fb16..3a34af2244f 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -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 { AllAssetEntryTypes, assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol, MonoObject } from "./types"; +import { AllAssetEntryTypes, mono_assert, AssetEntry, CharPtrNull, DotnetModule, GlobalizationMode, MonoConfig, MonoConfigError, wasm_type_symbol, MonoObject } from "./types"; import { ENVIRONMENT_IS_ESM, ENVIRONMENT_IS_NODE, ENVIRONMENT_IS_SHELL, INTERNAL, locateFile, Module, MONO, requirePromise, runtimeHelpers } from "./imports"; import cwraps from "./cwraps"; import { mono_wasm_raise_debug_event, mono_wasm_runtime_ready } from "./debug"; @@ -36,7 +36,7 @@ export function configure_emscripten_startup(module: DotnetModule, exportedAPI: (typeof (globalThis.document.createElement) === "function") ) { // blazor injects a module preload link element for dotnet.[version].[sha].js - const blazorDotNetJS = Array.from (document.head.getElementsByTagName("link")).filter(elt => elt.rel !== undefined && elt.rel == "modulepreload" && elt.href !== undefined && elt.href.indexOf("dotnet") != -1 && elt.href.indexOf (".js") != -1); + const blazorDotNetJS = Array.from(document.head.getElementsByTagName("link")).filter(elt => elt.rel !== undefined && elt.rel == "modulepreload" && elt.href !== undefined && elt.href.indexOf("dotnet") != -1 && elt.href.indexOf(".js") != -1); if (blazorDotNetJS.length == 1) { const hr = blazorDotNetJS[0].href; console.log("determined url of main script to be " + hr); @@ -191,8 +191,8 @@ export function mono_wasm_set_runtime_options(options: string[]): void { // this need to be run only after onRuntimeInitialized event, when the memory is ready function _handle_fetched_asset(asset: AssetEntry, url?: string) { - assert(ctx, "Context is expected"); - assert(asset.buffer, "asset.buffer is expected"); + mono_assert(ctx, "Context is expected"); + mono_assert(asset.buffer, "asset.buffer is expected"); const bytes = new Uint8Array(asset.buffer); if (ctx.tracing) @@ -304,7 +304,7 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi const moduleExt = Module as DotnetModule; - if(!Module.disableDotnet6Compatibility && Module.exports){ + if (!Module.disableDotnet6Compatibility && Module.exports) { // Export emscripten defined in module through EXPORTED_RUNTIME_METHODS // Useful to export IDBFS or other similar types generally exposed as // global types when emscripten is not modularized. @@ -312,10 +312,10 @@ function finalize_startup(config: MonoConfig | MonoConfigError | undefined): voi const exportName = Module.exports[i]; const exportValue = (<any>Module)[exportName]; - if(exportValue) { + if (exportValue) { globalThisAny[exportName] = exportValue; } - else{ + else { console.warn(`MONO_WASM: The exported symbol ${exportName} could not be found in the emscripten module`); } } @@ -586,8 +586,8 @@ async function mono_download_assets(config: MonoConfig | MonoConfigError | undef } function finalize_assets(config: MonoConfig | MonoConfigError | undefined): void { - assert(config && !config.isError, "Expected config"); - assert(ctx && ctx.downloading_count == 0, "Expected assets to be downloaded"); + mono_assert(config && !config.isError, "Expected config"); + mono_assert(ctx && ctx.downloading_count == 0, "Expected assets to be downloaded"); try { for (const fetch_result of ctx.resolved_promises!) { diff --git a/src/mono/wasm/runtime/types.ts b/src/mono/wasm/runtime/types.ts index d26277e38fb..e649ebc0f49 100644 --- a/src/mono/wasm/runtime/types.ts +++ b/src/mono/wasm/runtime/types.ts @@ -82,7 +82,7 @@ export type MonoConfig = { aot_profiler_options?: AOTProfilerOptions, // dictionary-style Object. If omitted, aot profiler will not be initialized. coverage_profiler_options?: CoverageProfilerOptions, // dictionary-style Object. If omitted, coverage profiler will not be initialized. ignore_pdb_load_errors?: boolean, - wait_for_debugger ?: number + wait_for_debugger?: number }; export type MonoConfigError = { @@ -220,12 +220,13 @@ export type DotnetModuleConfigImports = { url?: any; } -export function assert(condition: unknown, messageFactory: string | (() => string)): asserts condition { +// see src\mono\wasm\runtime\rollup.config.js +// inline this, because the lambda could allocate closure on hot path otherwise +export function mono_assert(condition: unknown, messageFactory: string | (() => string)): asserts condition { if (!condition) { const message = typeof messageFactory === "string" ? messageFactory : messageFactory(); - console.error(`Assert failed: ${message}`); throw new Error(`Assert failed: ${message}`); } } @@ -276,6 +277,6 @@ export const enum MarshalError { // Evaluates whether a value is nullish (same definition used as the ?? operator, // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator) -export function is_nullish<T> (value: T | null | undefined): value is null | undefined { +export function is_nullish<T>(value: T | null | undefined): value is null | undefined { return (value === undefined) || (value === null); } |