Skip to content

Commit df26db3

Browse files
authored
[browser] bind JS methods to minified WASM imports (#90145)
1 parent 26c7d40 commit df26db3

File tree

13 files changed

+330
-280
lines changed

13 files changed

+330
-280
lines changed

src/mono/wasm/runtime/diagnostics/server_pthread/stream-queue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type SyncSendClose = () => void;
3535
const STREAM_CLOSE_SENTINEL = -1;
3636

3737
export class StreamQueue {
38-
readonly workAvailable: EventTarget = new EventTarget();
38+
readonly workAvailable: EventTarget = new globalThis.EventTarget();
3939
readonly signalWorkAvailable = this.signalWorkAvailableImpl.bind(this);
4040

4141
constructor(readonly queue_addr: VoidPtr, readonly syncSendBuffer: SyncSendBuffer, readonly syncSendClose: SyncSendClose) {

src/mono/wasm/runtime/es6/dotnet.es6.lib.js

Lines changed: 37 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44

55
"use strict";
66

7+
// -- this javascript file is evaluated by emcc during compilation! --
78

89
// because we can't pass custom define symbols to acorn optimizer, we use environment variables to pass other build options
910
const DISABLE_LEGACY_JS_INTEROP = process.env.DISABLE_LEGACY_JS_INTEROP === "1";
1011
const WASM_ENABLE_SIMD = process.env.WASM_ENABLE_SIMD === "1";
1112
const WASM_ENABLE_EH = process.env.WASM_ENABLE_EH === "1";
1213
const ENABLE_BROWSER_PROFILER = process.env.ENABLE_BROWSER_PROFILER === "1";
1314
const ENABLE_AOT_PROFILER = process.env.ENABLE_AOT_PROFILER === "1";
15+
var methodIndexByName = undefined;
16+
var gitHash = undefined;
1417

1518
function setup(linkerSetup) {
1619
const pthreadReplacements = {};
@@ -61,127 +64,46 @@ function setup(linkerSetup) {
6164
#endif
6265
}
6366

64-
const postset = `
65-
DOTNET.setup({ `+
66-
`linkerDisableLegacyJsInterop: ${DISABLE_LEGACY_JS_INTEROP ? "true" : "false"},` +
67-
`linkerWasmEnableSIMD: ${WASM_ENABLE_SIMD ? "true" : "false"},` +
68-
`linkerWasmEnableEH: ${WASM_ENABLE_EH ? "true" : "false"},` +
69-
`linkerEnableAotProfiler: ${ENABLE_AOT_PROFILER ? "true" : "false"}, ` +
70-
`linkerEnableBrowserProfiler: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}` +
71-
`});
72-
`;
73-
7467
const DotnetSupportLib = {
7568
$DOTNET: { setup },
76-
$DOTNET__postset: postset
69+
icudt68_dat: function () { throw new Error('dummy link symbol') },
7770
};
7871

79-
// the methods would be visible to EMCC linker
80-
// --- keep in sync with exports.ts ---
81-
let linked_functions = [
82-
// mini-wasm.c
83-
"mono_wasm_schedule_timer",
84-
85-
// mini-wasm-debugger.c
86-
"mono_wasm_asm_loaded",
87-
"mono_wasm_fire_debugger_agent_message_with_data",
88-
"mono_wasm_debugger_log",
89-
"mono_wasm_add_dbg_command_received",
90-
"mono_wasm_set_entrypoint_breakpoint",
91-
92-
// mono-threads-wasm.c
93-
"schedule_background_exec",
94-
95-
// interp.c
96-
"mono_wasm_profiler_enter",
97-
"mono_wasm_profiler_leave",
98-
99-
// driver.c
100-
"mono_wasm_trace_logger",
101-
"mono_wasm_event_pipe_early_startup_callback",
102-
103-
// jiterpreter.c / interp.c / transform.c
104-
"mono_interp_tier_prepare_jiterpreter",
105-
"mono_interp_record_interp_entry",
106-
"mono_interp_jit_wasm_entry_trampoline",
107-
"mono_interp_jit_wasm_jit_call_trampoline",
108-
"mono_interp_invoke_wasm_jit_call_trampoline",
109-
"mono_interp_flush_jitcall_queue",
110-
"mono_jiterp_do_jit_call_indirect",
111-
112-
// corebindings.c
113-
"mono_wasm_release_cs_owned_object",
114-
"mono_wasm_bind_js_function",
115-
"mono_wasm_invoke_bound_function",
116-
"mono_wasm_invoke_import",
117-
"mono_wasm_bind_cs_function",
118-
"mono_wasm_marshal_promise",
119-
"mono_wasm_change_case_invariant",
120-
"mono_wasm_change_case",
121-
"mono_wasm_compare_string",
122-
"mono_wasm_starts_with",
123-
"mono_wasm_ends_with",
124-
"mono_wasm_index_of",
125-
"mono_wasm_get_calendar_info",
126-
"mono_wasm_get_culture_info",
127-
"mono_wasm_get_first_day_of_week",
128-
"mono_wasm_get_first_week_of_year",
129-
130-
"icudt68_dat",
131-
];
132-
133-
#if USE_PTHREADS
134-
linked_functions = [...linked_functions,
135-
// mono-threads-wasm.c
136-
"mono_wasm_pthread_on_pthread_attached",
137-
"mono_wasm_pthread_on_pthread_detached",
138-
// threads.c
139-
"mono_wasm_eventloop_has_unsettled_interop_promises",
140-
// diagnostics_server.c
141-
"mono_wasm_diagnostic_server_on_server_thread_created",
142-
"mono_wasm_diagnostic_server_on_runtime_server_init",
143-
"mono_wasm_diagnostic_server_stream_signal_work_available",
144-
// corebindings.c
145-
"mono_wasm_install_js_worker_interop",
146-
"mono_wasm_uninstall_js_worker_interop",
147-
]
148-
#endif
149-
150-
if (ENABLE_AOT_PROFILER) {
151-
linked_functions = [...linked_functions,
152-
"mono_wasm_invoke_js_with_args_ref",
153-
"mono_wasm_get_object_property_ref",
154-
"mono_wasm_set_object_property_ref",
155-
"mono_wasm_get_by_index_ref",
156-
"mono_wasm_set_by_index_ref",
157-
"mono_wasm_get_global_object_ref",
158-
"mono_wasm_create_cs_owned_object_ref",
159-
"mono_wasm_typed_array_to_array_ref",
160-
"mono_wasm_typed_array_from_ref",
161-
"mono_wasm_invoke_js_blazor",
162-
]
72+
function createWasmImportStubsFrom(collection) {
73+
for (let functionName in collection) {
74+
if (functionName in DotnetSupportLib) throw new Error(`Function ${functionName} is already defined`);
75+
const runtime_idx = collection[functionName]
76+
const stub_fn = new Function(`return {runtime_idx:${runtime_idx}};//${functionName}`);
77+
DotnetSupportLib[functionName] = stub_fn;
78+
}
16379
}
16480

165-
if (!DISABLE_LEGACY_JS_INTEROP) {
166-
linked_functions = [...linked_functions,
167-
"mono_wasm_invoke_js_with_args_ref",
168-
"mono_wasm_get_object_property_ref",
169-
"mono_wasm_set_object_property_ref",
170-
"mono_wasm_get_by_index_ref",
171-
"mono_wasm_set_by_index_ref",
172-
"mono_wasm_get_global_object_ref",
173-
"mono_wasm_create_cs_owned_object_ref",
174-
"mono_wasm_typed_array_to_array_ref",
175-
"mono_wasm_typed_array_from_ref",
176-
"mono_wasm_invoke_js_blazor",
177-
]
178-
}
81+
// the JS methods would be visible to EMCC linker and become imports of the WASM module
82+
// we generate simple stub for each exported function so that emcc will include them in the final output
83+
// we will replace them with the real implementation in replace_linker_placeholders
84+
function injectDependencies() {
85+
createWasmImportStubsFrom(methodIndexByName.mono_wasm_imports);
17986

180-
// -- this javascript file is evaluated by emcc during compilation! --
181-
// we generate simple proxy for each exported function so that emcc will include them in the final output
182-
for (let linked_function of linked_functions) {
183-
DotnetSupportLib[linked_function] = new Function('throw new Error("unreachable");');
87+
#if USE_PTHREADS
88+
createWasmImportStubsFrom(methodIndexByName.mono_wasm_threads_imports);
89+
#endif
90+
91+
if (!DISABLE_LEGACY_JS_INTEROP) {
92+
createWasmImportStubsFrom(methodIndexByName.mono_wasm_legacy_interop_imports);
93+
}
94+
95+
DotnetSupportLib["$DOTNET__postset"] = `DOTNET.setup({ ` +
96+
`linkerDisableLegacyJsInterop: ${DISABLE_LEGACY_JS_INTEROP ? "true" : "false"},` +
97+
`linkerWasmEnableSIMD: ${WASM_ENABLE_SIMD ? "true" : "false"},` +
98+
`linkerWasmEnableEH: ${WASM_ENABLE_EH ? "true" : "false"},` +
99+
`linkerEnableAotProfiler: ${ENABLE_AOT_PROFILER ? "true" : "false"}, ` +
100+
`linkerEnableBrowserProfiler: ${ENABLE_BROWSER_PROFILER ? "true" : "false"}, ` +
101+
`gitHash: "${gitHash}", ` +
102+
`});`;
103+
104+
autoAddDeps(DotnetSupportLib, "$DOTNET");
105+
mergeInto(LibraryManager.library, DotnetSupportLib);
184106
}
185107

186-
autoAddDeps(DotnetSupportLib, "$DOTNET");
187-
mergeInto(LibraryManager.library, DotnetSupportLib);
108+
109+
// var methodIndexByName wil be appended below by the MSBuild in wasm.proj
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
import MonoWasmThreads from "consts:monoWasmThreads";
5+
import WasmEnableLegacyJsInterop from "consts:wasmEnableLegacyJsInterop";
6+
7+
import { mono_wasm_debugger_log, mono_wasm_add_dbg_command_received, mono_wasm_set_entrypoint_breakpoint, mono_wasm_fire_debugger_agent_message_with_data, mono_wasm_fire_debugger_agent_message_with_data_to_pause } from "./debug";
8+
import { mono_wasm_release_cs_owned_object } from "./gc-handles";
9+
import { mono_wasm_bind_cs_function } from "./invoke-cs";
10+
import { mono_wasm_bind_js_function, mono_wasm_invoke_bound_function, mono_wasm_invoke_import } from "./invoke-js";
11+
import { mono_interp_tier_prepare_jiterpreter } from "./jiterpreter";
12+
import { mono_interp_jit_wasm_entry_trampoline, mono_interp_record_interp_entry } from "./jiterpreter-interp-entry";
13+
import { mono_interp_jit_wasm_jit_call_trampoline, mono_interp_invoke_wasm_jit_call_trampoline, mono_interp_flush_jitcall_queue, mono_jiterp_do_jit_call_indirect } from "./jiterpreter-jit-call";
14+
import { mono_wasm_marshal_promise } from "./marshal-to-js";
15+
import { mono_wasm_eventloop_has_unsettled_interop_promises } from "./pthreads/shared/eventloop";
16+
import { mono_wasm_pthread_on_pthread_attached, mono_wasm_pthread_on_pthread_detached } from "./pthreads/worker";
17+
import { mono_wasm_schedule_timer, schedule_background_exec } from "./scheduling";
18+
import { mono_wasm_asm_loaded } from "./startup";
19+
import { mono_wasm_diagnostic_server_on_server_thread_created } from "./diagnostics/server_pthread";
20+
import { mono_wasm_diagnostic_server_on_runtime_server_init, mono_wasm_event_pipe_early_startup_callback } from "./diagnostics";
21+
import { mono_wasm_diagnostic_server_stream_signal_work_available } from "./diagnostics/server_pthread/stream-queue";
22+
import { mono_log_debug, mono_log_warn, mono_wasm_trace_logger } from "./logging";
23+
import { mono_wasm_profiler_leave, mono_wasm_profiler_enter } from "./profiler";
24+
import { mono_wasm_change_case, mono_wasm_change_case_invariant } from "./hybrid-globalization/change-case";
25+
import { mono_wasm_compare_string, mono_wasm_ends_with, mono_wasm_starts_with, mono_wasm_index_of } from "./hybrid-globalization/collations";
26+
import { mono_wasm_get_calendar_info } from "./hybrid-globalization/calendar";
27+
import { mono_wasm_install_js_worker_interop, mono_wasm_uninstall_js_worker_interop } from "./pthreads/shared";
28+
29+
import {
30+
mono_wasm_invoke_js_blazor, mono_wasm_invoke_js_with_args_ref, mono_wasm_get_object_property_ref, mono_wasm_set_object_property_ref,
31+
mono_wasm_get_by_index_ref, mono_wasm_set_by_index_ref, mono_wasm_get_global_object_ref
32+
} from "./net6-legacy/method-calls";
33+
import { mono_wasm_create_cs_owned_object_ref } from "./net6-legacy/cs-to-js";
34+
import { mono_wasm_typed_array_to_array_ref } from "./net6-legacy/js-to-cs";
35+
import { mono_wasm_typed_array_from_ref } from "./net6-legacy/buffers";
36+
import { mono_wasm_get_culture_info } from "./hybrid-globalization/culture-info";
37+
import { mono_wasm_get_first_day_of_week, mono_wasm_get_first_week_of_year } from "./hybrid-globalization/locales";
38+
39+
// the JS methods would be visible to EMCC linker and become imports of the WASM module
40+
41+
export const mono_wasm_threads_imports = !MonoWasmThreads ? [] : [
42+
// mono-threads-wasm.c
43+
mono_wasm_pthread_on_pthread_attached,
44+
mono_wasm_pthread_on_pthread_detached,
45+
// threads.c
46+
mono_wasm_eventloop_has_unsettled_interop_promises,
47+
// diagnostics_server.c
48+
mono_wasm_diagnostic_server_on_server_thread_created,
49+
mono_wasm_diagnostic_server_on_runtime_server_init,
50+
mono_wasm_diagnostic_server_stream_signal_work_available,
51+
52+
// corebindings.c
53+
mono_wasm_install_js_worker_interop,
54+
mono_wasm_uninstall_js_worker_interop,
55+
];
56+
57+
export const mono_wasm_legacy_interop_imports = !WasmEnableLegacyJsInterop ? [] : [
58+
// corebindings.c
59+
mono_wasm_invoke_js_with_args_ref,
60+
mono_wasm_get_object_property_ref,
61+
mono_wasm_set_object_property_ref,
62+
mono_wasm_get_by_index_ref,
63+
mono_wasm_set_by_index_ref,
64+
mono_wasm_get_global_object_ref,
65+
mono_wasm_create_cs_owned_object_ref,
66+
mono_wasm_typed_array_to_array_ref,
67+
mono_wasm_typed_array_from_ref,
68+
mono_wasm_invoke_js_blazor,
69+
];
70+
71+
export const mono_wasm_imports = [
72+
// mini-wasm.c
73+
mono_wasm_schedule_timer,
74+
75+
// mini-wasm-debugger.c
76+
mono_wasm_asm_loaded,
77+
mono_wasm_debugger_log,
78+
mono_wasm_add_dbg_command_received,
79+
mono_wasm_fire_debugger_agent_message_with_data,
80+
mono_wasm_fire_debugger_agent_message_with_data_to_pause,
81+
// mono-threads-wasm.c
82+
schedule_background_exec,
83+
84+
// interp.c and jiterpreter.c
85+
mono_interp_tier_prepare_jiterpreter,
86+
mono_interp_record_interp_entry,
87+
mono_interp_jit_wasm_entry_trampoline,
88+
mono_interp_jit_wasm_jit_call_trampoline,
89+
mono_interp_invoke_wasm_jit_call_trampoline,
90+
mono_interp_flush_jitcall_queue,
91+
mono_jiterp_do_jit_call_indirect,
92+
93+
mono_wasm_profiler_enter,
94+
mono_wasm_profiler_leave,
95+
96+
// driver.c
97+
mono_wasm_trace_logger,
98+
mono_wasm_set_entrypoint_breakpoint,
99+
mono_wasm_event_pipe_early_startup_callback,
100+
101+
// corebindings.c
102+
mono_wasm_release_cs_owned_object,
103+
mono_wasm_bind_js_function,
104+
mono_wasm_invoke_bound_function,
105+
mono_wasm_invoke_import,
106+
mono_wasm_bind_cs_function,
107+
mono_wasm_marshal_promise,
108+
mono_wasm_change_case_invariant,
109+
mono_wasm_change_case,
110+
mono_wasm_compare_string,
111+
mono_wasm_starts_with,
112+
mono_wasm_ends_with,
113+
mono_wasm_index_of,
114+
mono_wasm_get_calendar_info,
115+
mono_wasm_get_culture_info,
116+
mono_wasm_get_first_day_of_week,
117+
mono_wasm_get_first_week_of_year,
118+
];
119+
120+
const wasmImports: Function[] = [
121+
...mono_wasm_imports,
122+
// threading exports, if threading is enabled
123+
...mono_wasm_threads_imports,
124+
// legacy interop exports, if enabled
125+
...mono_wasm_legacy_interop_imports
126+
];
127+
128+
export function replace_linker_placeholders(imports: WebAssembly.Imports) {
129+
// the output from emcc contains wrappers for these linker imports which add overhead,
130+
// but now we have what we need to replace them with the actual functions
131+
// By default the imports all live inside of 'env', but emscripten minification could rename it to 'a'.
132+
// See https://github.com/emscripten-core/emscripten/blob/c5d1a856592b788619be11bbdc1dd119dec4e24c/src/preamble.js#L933-L936
133+
const env = imports.env || imports.a;
134+
if (!env) {
135+
mono_log_warn("WARNING: Neither imports.env or imports.a were present when instantiating the wasm module. This likely indicates an emscripten configuration issue.");
136+
return;
137+
}
138+
139+
// the import names could be minified by applyImportAndExportNameChanges in emcc
140+
// we call each stub function to get the runtime_idx, which is the index into the wasmImports array
141+
const indexToNameMap: string[] = new Array(wasmImports.length);
142+
for (const shortName in env) {
143+
const stub_fn = env[shortName] as Function;
144+
if (typeof stub_fn === "function" && stub_fn.toString().indexOf("runtime_idx") !== -1) {
145+
try {
146+
const { runtime_idx } = stub_fn();
147+
if (indexToNameMap[runtime_idx] !== undefined) throw new Error(`Duplicate runtime_idx ${runtime_idx}`);
148+
indexToNameMap[runtime_idx] = shortName;
149+
} catch {
150+
// no-action
151+
}
152+
}
153+
}
154+
155+
for (const [idx, realFn] of wasmImports.entries()) {
156+
const shortName = indexToNameMap[idx];
157+
// if it's not found it means the emcc linker didn't include it, which is fine
158+
if (shortName !== undefined) {
159+
const stubFn = env[shortName];
160+
if (typeof stubFn !== "function") throw new Error(`Expected ${shortName} to be a function`);
161+
env[shortName] = realFn;
162+
mono_log_debug(`Replaced WASM import ${shortName} stub ${stubFn.name} with ${realFn.name || "minified implementation"}`);
163+
}
164+
}
165+
166+
}

0 commit comments

Comments
 (0)