diff --git a/eng/Subsets.props b/eng/Subsets.props index 3f4d370c30fd..d330115150bc 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -62,7 +62,7 @@ clr.iltools+clr.packages clr.alljits+clr.tools+clr.nativeaotlibs+clr.nativeaotruntime - clr.nativeaotlibs+clr.nativeaotruntime+nativeaot.build + clr.nativeaotlibs+clr.nativeaotruntime+nativeaot.build+mono.wasmruntime mono.llvm+ mono.llvm+ diff --git a/eng/liveBuilds.targets b/eng/liveBuilds.targets index 1a5bd83689a6..caaaf41c844c 100644 --- a/eng/liveBuilds.targets +++ b/eng/liveBuilds.targets @@ -9,6 +9,7 @@ $([MSBuild]::NormalizeDirectory('$(RuntimeArtifactsPath)')) $([MSBuild]::NormalizeDirectory('$(RuntimeArtifactsPath)')) + $(TargetOS)-$(CoreCLRConfiguration)-$(TargetArchitecture) $(TargetOS)-$(LibrariesConfiguration)-$(TargetArchitecture) @@ -27,6 +28,7 @@ $([MSBuild]::NormalizeDirectory('$(CoreCLRArtifactsPath)', '$(BuildArchitecture)', 'ilc')) $([MSBuild]::NormalizeDirectory('$(CoreCLRArtifactsPath)', 'aotsdk')) $([MSBuild]::NormalizeDirectory('$(CoreCLRArtifactsPath)', 'build')) + $([MSBuild]::NormalizeDirectory('$(RepoRoot)', 'artifacts', 'bin', 'native', '$(NetCoreAppCurrent)-$(CoreCLRTargetOSConfigurationArchitecture)')) $([MSBuild]::NormalizeDirectory('$(CoreCLRArtifactsPath)', '$(BuildArchitecture)', 'crossgen2')) @@ -214,6 +216,22 @@ $(LibrariesNativeArtifactsPath)dotnet.native.js.symbols; $(LibrariesNativeArtifactsPath)*.dat;" IsNative="true" /> + + + + + + $(LinkNativeDependsOn);PrepareDotNetJsApiForLinking + $(NativeOutputPath)dotnet.native.js + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_EmccExportedLibraryFunction>"[@(EmccExportedLibraryFunction -> '%27%(Identity)%27', ',')]" + <_EmccExportedRuntimeMethods>"[@(EmccExportedRuntimeMethod -> '%27%(Identity)%27', ',')]" + <_EmccExportedFunctions>@(EmccExportedFunction -> '%(Identity)',',') + + + + + + + + + + + <_DotNetJsLinkerFlag Include="-Wl,--export,__main_argc_argv" /> + <_DotNetJsLinkerFlag Include="-s EXPORT_ES6=1" /> + <_DotNetJsLinkerFlag Include="-s MODULARIZE=1" /> + <_DotNetJsLinkerFlag Include="-s INVOKE_RUN=0" /> + <_DotNetJsLinkerFlag Include="-s EXPORT_NAME="'createDotnetRuntime'"" /> + <_DotNetJsLinkerFlag Include="-s ENVIRONMENT="'web,webview,worker,node,shell'"" /> + <_DotNetJsLinkerFlag Condition="'$(EmccEnvironment)' != ''" Include="-s ENVIRONMENT="$(EmccEnvironment)"" /> + <_DotNetJsLinkerFlag Include="-s FORCE_FILESYSTEM=1" /> + <_DotNetJsLinkerFlag Include="--emit-symbol-map" Condition="'$(WasmEmitSymbolMap)' == 'true'" /> + + + + + + + + + <_WasmExtraJSFile Include="$(IlcFrameworkNativePath)src\es6\*.%(JSFileType.Identity)" Kind="%(JSFileType.Kind)" /> + <_DotNetJsLinkerFlag Include="--%(_WasmExtraJSFile.Kind) "%(_WasmExtraJSFile.Identity)"" Condition="'%(_WasmExtraJSFile.Kind)' != ''" /> + + + <_DotNetJsLinkerFlag Include="-s DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$(_EmccExportedLibraryFunction)" Condition="'$(_EmccExportedLibraryFunction)' != ''" /> + <_DotNetJsLinkerFlag Include="-s EXPORTED_RUNTIME_METHODS=$(_EmccExportedRuntimeMethods)" /> + <_DotNetJsLinkerFlag Include="-s EXPORTED_FUNCTIONS=$(_EmccExportedFunctions)" /> + <_DotNetJsLinkerFlag Include="$(EmccExtraLDFlags)" /> + + + + + + + + <_FilesToCopyToNative Include="$(IlcFrameworkNativePath)\dotnet*.js" /> + <_FilesToCopyToNative Include="$(IlcFrameworkNativePath)\dotnet*.map" Condition="'$(WasmEmitSourceMap)' == 'true'" /> + <_FilesToCopyToNative Include="@(WasmExtraFilesToDeploy)" /> + + + + \ No newline at end of file diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets index 3a2301d38ba7..6a3e4f476f5e 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.Publish.targets @@ -32,7 +32,7 @@ <_NativeIntermediateAssembly Include="@(IntermediateAssembly->'$(NativeOutputPath)%(Filename)$(NativeBinaryExt)')" /> - + @@ -96,13 +96,19 @@ - + - - + + - - + + + + + <_FilesToCopyToNative Include="$(NativeOutputPath)\dotnet.native.js" /> + <_FilesToCopyToNative Include="$(NativeOutputPath)\dotnet.native.wasm" /> + + wasm32-unknown-wasi 1048576 + $(EmccStackSize) 1024 wasm cpp @@ -491,6 +492,8 @@ The .NET Foundation licenses this file to you under the MIT license. strip -no_code_signature_warning $(_StripFlag) "$(NativeBinary)"" /> + + - + @@ -533,6 +536,11 @@ The .NET Foundation licenses this file to you under the MIT license. + + + + + @@ -557,7 +565,7 @@ The .NET Foundation licenses this file to you under the MIT license. - + - framework + framework/%(LibrariesRuntimeFiles.NativeSubDirectory) tools diff --git a/src/mono/wasm/runtime/cwraps.ts b/src/mono/wasm/runtime/cwraps.ts index 4026be432c30..ce75e2782c44 100644 --- a/src/mono/wasm/runtime/cwraps.ts +++ b/src/mono/wasm/runtime/cwraps.ts @@ -1,6 +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 NativeAOT from "consts:nativeAOT"; import MonoWasmThreads from "consts:monoWasmThreads"; import WasmEnableLegacyJsInterop from "consts:wasmEnableLegacyJsInterop"; @@ -348,7 +349,8 @@ function cwrap(name: string, returnType: string | null, argTypes: string[] | und export function init_c_exports(): void { const lfns = WasmEnableLegacyJsInterop && !linkerDisableLegacyJsInterop ? legacy_interop_cwraps : []; - const fns = [...fn_signatures, ...lfns]; + const fns = NativeAOT ? [] : [...fn_signatures, ...lfns]; + for (const sig of fns) { const wf: any = wrapped_c_functions; const [lazyOrSkip, name, returnType, argTypes, opts] = sig; diff --git a/src/mono/wasm/runtime/es6/dotnet.es6.pre.js b/src/mono/wasm/runtime/es6/dotnet.es6.pre.js index 505f4372a58a..2eb494b9ed6e 100644 --- a/src/mono/wasm/runtime/es6/dotnet.es6.pre.js +++ b/src/mono/wasm/runtime/es6/dotnet.es6.pre.js @@ -2,5 +2,5 @@ if (_nativeModuleLoaded) throw new Error("Native module already loaded"); _nativeModuleLoaded = true; // see https://github.com/emscripten-core/emscripten/issues/19832 Module["getMemory"] = function () { return wasmMemory; } -createDotnetRuntime = Module = createDotnetRuntime(Module); +moduleArg = Module = moduleArg(Module); Module["getMemory"] = function () { return wasmMemory; } diff --git a/src/mono/wasm/runtime/jiterpreter-support.ts b/src/mono/wasm/runtime/jiterpreter-support.ts index ab828d5dc3a5..7bb7f1b0054d 100644 --- a/src/mono/wasm/runtime/jiterpreter-support.ts +++ b/src/mono/wasm/runtime/jiterpreter-support.ts @@ -1,6 +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 NativeAOT from "consts:nativeAOT"; import MonoWasmThreads from "consts:monoWasmThreads"; import { NativePointer, ManagedPointer, VoidPtr } from "./types/emscripten"; import { Module, mono_assert, runtimeHelpers, linkerRunAOTCompilation } from "./globals"; @@ -2010,6 +2011,8 @@ function jiterpreter_allocate_table(type: JiterpreterTable, base: number, size: let jiterpreter_tables_allocated = false; export function jiterpreter_allocate_tables(module: any) { + if (NativeAOT) + return; if (jiterpreter_tables_allocated) return; jiterpreter_tables_allocated = true; diff --git a/src/mono/wasm/runtime/jiterpreter.ts b/src/mono/wasm/runtime/jiterpreter.ts index f47149dda1eb..f1dae8cb2176 100644 --- a/src/mono/wasm/runtime/jiterpreter.ts +++ b/src/mono/wasm/runtime/jiterpreter.ts @@ -1,6 +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 NativeAOT from "consts:nativeAOT"; import { MonoMethod } from "./types/internal"; import { NativePointer } from "./types/emscripten"; import { Module, mono_assert, runtimeHelpers } from "./globals"; @@ -1088,6 +1089,9 @@ export function mono_jiterp_free_method_data_js( } export function jiterpreter_dump_stats(b?: boolean, concise?: boolean) { + if (NativeAOT) { + return; + } if (!runtimeHelpers.runtimeReady) { return; } diff --git a/src/mono/wasm/runtime/loader/run.ts b/src/mono/wasm/runtime/loader/run.ts index 9d26827a83f4..589df2bc71cc 100644 --- a/src/mono/wasm/runtime/loader/run.ts +++ b/src/mono/wasm/runtime/loader/run.ts @@ -1,6 +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 NativeAOT from "consts:nativeAOT"; import BuildConfiguration from "consts:configuration"; import type { MonoConfig, DotnetHostBuilder, DotnetModuleConfig, RuntimeAPI, LoadBootResourceCallback } from "../types"; @@ -477,6 +478,22 @@ async function initializeModules(es6Modules: [RuntimeModuleExportsInternal, Nati } async function createEmscriptenMain(): Promise { + if (NativeAOT && !loaderHelpers.config?.resources) { + if (!loaderHelpers.config) { + loaderHelpers.config = {}; + } + + loaderHelpers.config.resources = { + assembly: {}, + jsModuleNative: { "dotnet.native.js": "" }, + jsModuleWorker: {}, + jsModuleRuntime: { "dotnet.runtime.js": "" }, + wasmNative: { "dotnet.native.wasm": "" }, + vfs: {}, + satelliteResources: {}, + }; + } + if (!emscriptenModule.configSrc && (!loaderHelpers.config || Object.keys(loaderHelpers.config).length === 0 || (!loaderHelpers.config.assets && !loaderHelpers.config.resources))) { // if config file location nor assets are provided emscriptenModule.configSrc = "./blazor.boot.json"; diff --git a/src/mono/wasm/runtime/managed-exports.ts b/src/mono/wasm/runtime/managed-exports.ts index fee9ed61fecc..1fda35eeaabf 100644 --- a/src/mono/wasm/runtime/managed-exports.ts +++ b/src/mono/wasm/runtime/managed-exports.ts @@ -1,6 +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 NativeAOT from "consts:nativeAOT"; import MonoWasmThreads from "consts:monoWasmThreads"; import { GCHandle, MarshalerToCs, MarshalerToJs, MarshalerType, MonoMethod } from "./types/internal"; @@ -13,6 +14,9 @@ import { marshal_int32_to_js, marshal_string_to_js, marshal_task_to_js } from ". import { do_not_force_dispose } from "./gc-handles"; export function init_managed_exports(): void { + if (NativeAOT) { + return; + } const exports_fqn_asm = "System.Runtime.InteropServices.JavaScript"; runtimeHelpers.runtime_interop_module = cwraps.mono_wasm_assembly_load(exports_fqn_asm); if (!runtimeHelpers.runtime_interop_module) diff --git a/src/mono/wasm/runtime/method-calls.ts b/src/mono/wasm/runtime/method-calls.ts deleted file mode 100644 index a16d64c6b2e5..000000000000 --- a/src/mono/wasm/runtime/method-calls.ts +++ /dev/null @@ -1,652 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -import { mono_wasm_new_root, mono_wasm_new_root_buffer, WasmRoot, WasmRootBuffer, mono_wasm_new_external_root } from "./roots"; -import { - JSHandle, MonoArray, MonoMethod, MonoObject, - MonoObjectNull, MonoString, coerceNull as coerceNull, - VoidPtrNull, MonoStringNull, MonoObjectRef, - MonoStringRef -} from "./types"; -import { BINDING, INTERNAL, Module, MONO, runtimeHelpers } from "./imports"; -import { mono_array_root_to_js_array, unbox_mono_obj_root } from "./cs-to-js"; -import { get_js_obj, mono_wasm_get_jsobj_from_js_handle } from "./gc-handles"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore used by unsafe export -import { js_array_to_mono_array, js_to_mono_obj_root } from "./js-to-cs"; -import { - mono_bind_method, - Converter, _compile_converter_for_marshal_string, - _decide_if_result_is_marshaled, find_method, - BoundMethodToken -} from "./method-binding"; -import { conv_string, conv_string_root, js_string_to_mono_string, js_string_to_mono_string_root } from "./strings"; -import cwraps from "./cwraps"; -import { bindings_lazy_init } from "./startup"; -import { _create_temp_frame, _release_temp_frame } from "./memory"; -import { VoidPtr, Int32Ptr, EmscriptenModule } from "./types/emscripten"; - -function _verify_args_for_method_call(args_marshal: string/*ArgsMarshalString*/, args: any) { - const has_args = args && (typeof args === "object") && args.length > 0; - const has_args_marshal = typeof args_marshal === "string"; - - if (has_args) { - if (!has_args_marshal) - throw new Error("No signature provided for method call."); - else if (args.length > args_marshal.length) - throw new Error("Too many parameter values. Expected at most " + args_marshal.length + " value(s) for signature " + args_marshal); - } - - return has_args_marshal && has_args; -} - -export function _get_buffer_for_method_call(converter: Converter, token: BoundMethodToken | null): VoidPtr | undefined { - if (!converter) - return VoidPtrNull; - - let result = VoidPtrNull; - if (token !== null) { - result = token.scratchBuffer || VoidPtrNull; - token.scratchBuffer = VoidPtrNull; - } else { - result = converter.scratchBuffer || VoidPtrNull; - converter.scratchBuffer = VoidPtrNull; - } - return result; -} - -export function _get_args_root_buffer_for_method_call(converter: Converter, token: BoundMethodToken | null): WasmRootBuffer | undefined { - if (!converter) - return undefined; - - if (!converter.needs_root_buffer) - return undefined; - - let result = null; - if (token !== null) { - result = token.scratchRootBuffer; - token.scratchRootBuffer = null; - } else { - result = converter.scratchRootBuffer; - converter.scratchRootBuffer = null; - } - - if (result === null) { - // TODO: Expand the converter's heap allocation and then use - // mono_wasm_new_root_buffer_from_pointer instead. Not that important - // at present because the scratch buffer will be reused unless we are - // recursing through a re-entrant call - result = mono_wasm_new_root_buffer(converter.steps.length); - // FIXME - (result).converter = converter; - } - - return result; -} - -function _release_args_root_buffer_from_method_call( - converter?: Converter, token?: BoundMethodToken | null, argsRootBuffer?: WasmRootBuffer -) { - if (!argsRootBuffer || !converter) - return; - - // Store the arguments root buffer for re-use in later calls - if (token && (token.scratchRootBuffer === null)) { - argsRootBuffer.clear(); - token.scratchRootBuffer = argsRootBuffer; - } else if (!converter.scratchRootBuffer) { - argsRootBuffer.clear(); - converter.scratchRootBuffer = argsRootBuffer; - } else { - argsRootBuffer.release(); - } -} - -function _release_buffer_from_method_call( - converter: Converter | undefined, token?: BoundMethodToken | null, buffer?: VoidPtr -) { - if (!converter || !buffer) - return; - - if (token && !token.scratchBuffer) - token.scratchBuffer = buffer; - else if (!converter.scratchBuffer) - converter.scratchBuffer = coerceNull(buffer); - else if (buffer) - Module._free(buffer); -} - -function _convert_exception_for_method_call(result: WasmRoot, exception: WasmRoot) { - if (exception.value === MonoObjectNull) - return null; - - const msg = conv_string_root(result); - const err = new Error(msg!); //the convention is that invoke_method ToString () any outgoing exception - // console.warn (`error ${msg} at location ${err.stack}); - return err; -} - -/* -args_marshal is a string with one character per parameter that tells how to marshal it, here are the valid values: - -i: int32 -j: int32 - Enum with underlying type of int32 -l: int64 -k: int64 - Enum with underlying type of int64 -f: float -d: double -s: string -S: interned string -o: js object will be converted to a C# object (this will box numbers/bool/promises) -m: raw mono object. Don't use it unless you know what you're doing - -to suppress marshaling of the return value, place '!' at the end of args_marshal, i.e. 'ii!' instead of 'ii' -*/ -export function call_method_ref(method: MonoMethod, this_arg: WasmRoot | MonoObjectRef | undefined, args_marshal: string/*ArgsMarshalString*/, args: ArrayLike): any { - // HACK: Sometimes callers pass null or undefined, coerce it to 0 since that's what wasm expects - let this_arg_ref : MonoObjectRef | undefined = undefined; - if (typeof (this_arg) === "number") - this_arg_ref = this_arg; - else if (typeof (this_arg) === "object") - this_arg_ref = (this_arg).address; - else - this_arg_ref = coerceNull(this_arg); - - // Detect someone accidentally passing the wrong type of value to method - if (typeof method !== "number") - throw new Error(`method must be an address in the native heap, but was '${method}'`); - if (!method) - throw new Error("no method specified"); - if (typeof (this_arg_ref) !== "number") - throw new Error(`this_arg must be a root instance, the address of a root, or undefined, but was ${this_arg}`); - - const needs_converter = _verify_args_for_method_call(args_marshal, args); - - let buffer = VoidPtrNull, converter = undefined, argsRootBuffer = undefined; - let is_result_marshaled = true; - - // TODO: Only do this if the signature needs marshaling - _create_temp_frame(); - - // check if the method signature needs argument mashalling - if (needs_converter) { - converter = _compile_converter_for_marshal_string(args_marshal); - - is_result_marshaled = _decide_if_result_is_marshaled(converter, args.length); - - argsRootBuffer = _get_args_root_buffer_for_method_call(converter, null); - - const scratchBuffer = _get_buffer_for_method_call(converter, null); - - buffer = converter.compiled_variadic_function!(scratchBuffer, argsRootBuffer, method, args); - } - return _call_method_with_converted_args(method, this_arg_ref, converter, null, buffer, is_result_marshaled, argsRootBuffer); -} - - -export function _handle_exception_for_call( - converter: Converter | undefined, token: BoundMethodToken | null, - buffer: VoidPtr, resultRoot: WasmRoot, - exceptionRoot: WasmRoot, argsRootBuffer?: WasmRootBuffer -): void { - const exc = _convert_exception_for_method_call(resultRoot, exceptionRoot); - if (!exc) - return; - - _teardown_after_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer); - throw exc; -} - -function _handle_exception_and_produce_result_for_call( - converter: Converter | undefined, token: BoundMethodToken | null, - buffer: VoidPtr, resultRoot: WasmRoot, - exceptionRoot: WasmRoot, argsRootBuffer: WasmRootBuffer | undefined, - is_result_marshaled: boolean -): any { - _handle_exception_for_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer); - - let result: any; - - if (is_result_marshaled) - result = unbox_mono_obj_root(resultRoot); - else - result = resultRoot.value; - - _teardown_after_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer); - return result; -} - -export function _teardown_after_call( - converter: Converter | undefined, token: BoundMethodToken | null, - buffer: VoidPtr, resultRoot: WasmRoot, - exceptionRoot: WasmRoot, argsRootBuffer?: WasmRootBuffer -): void { - _release_temp_frame(); - _release_args_root_buffer_from_method_call(converter, token, argsRootBuffer); - _release_buffer_from_method_call(converter, token, buffer); - - if (resultRoot) { - resultRoot.clear(); - if ((token !== null) && (token.scratchResultRoot === null)) - token.scratchResultRoot = resultRoot; - else - resultRoot.release(); - } - if (exceptionRoot) { - exceptionRoot.clear(); - if ((token !== null) && (token.scratchExceptionRoot === null)) - token.scratchExceptionRoot = exceptionRoot; - else - exceptionRoot.release(); - } -} - -function _call_method_with_converted_args( - method: MonoMethod, this_arg_ref: MonoObjectRef, converter: Converter | undefined, - token: BoundMethodToken | null, buffer: VoidPtr, - is_result_marshaled: boolean, argsRootBuffer?: WasmRootBuffer -): any { - const resultRoot = mono_wasm_new_root(), exceptionRoot = mono_wasm_new_root(); - cwraps.mono_wasm_invoke_method_ref(method, this_arg_ref, buffer, exceptionRoot.address, resultRoot.address); - return _handle_exception_and_produce_result_for_call(converter, token, buffer, resultRoot, exceptionRoot, argsRootBuffer, is_result_marshaled); -} - -export function call_static_method(fqn: string, args: any[], signature: string/*ArgsMarshalString*/): any { - bindings_lazy_init();// TODO remove this once Blazor does better startup - - const method = mono_method_resolve(fqn); - - if (typeof signature === "undefined") - signature = mono_method_get_call_signature_ref(method, undefined); - - return call_method_ref(method, undefined, signature, args); -} - -export function mono_bind_static_method(fqn: string, signature?: string/*ArgsMarshalString*/): Function { - bindings_lazy_init();// TODO remove this once Blazor does better startup - - const method = mono_method_resolve(fqn); - - if (typeof signature === "undefined") - signature = mono_method_get_call_signature_ref(method, undefined); - - return mono_bind_method(method, null, signature!, fqn); -} - -export function mono_bind_assembly_entry_point(assembly: string, signature?: string/*ArgsMarshalString*/): Function { - bindings_lazy_init();// TODO remove this once Blazor does better startup - - const asm = cwraps.mono_wasm_assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - - const method = cwraps.mono_wasm_assembly_get_entry_point(asm); - if (!method) - throw new Error("Could not find entry point for assembly: " + assembly); - - if (!signature) - signature = mono_method_get_call_signature_ref(method, undefined); - - return async function (...args: any[]) { - if (args.length > 0 && Array.isArray(args[0])) - args[0] = js_array_to_mono_array(args[0], true, false); - return call_method_ref(method, undefined, signature!, args); - }; -} - -export function mono_call_assembly_entry_point(assembly: string, args?: any[], signature?: string/*ArgsMarshalString*/): number { - if (!args) { - args = [[]]; - } - return mono_bind_assembly_entry_point(assembly, signature)(...args); -} - -export function mono_wasm_invoke_js_with_args_ref(js_handle: JSHandle, method_name: MonoStringRef, args: MonoObjectRef, is_exception: Int32Ptr, result_address: MonoObjectRef): any { - const argsRoot = mono_wasm_new_external_root(args), - nameRoot = mono_wasm_new_external_root(method_name), - resultRoot = mono_wasm_new_external_root(result_address); - try { - const js_name = conv_string_root(nameRoot); - if (!js_name || (typeof (js_name) !== "string")) { - wrap_error_root(is_exception, "ERR12: Invalid method name object @" + nameRoot.value, resultRoot); - return; - } - - const obj = get_js_obj(js_handle); - if (!obj) { - wrap_error_root(is_exception, "ERR13: Invalid JS object handle '" + js_handle + "' while invoking '" + js_name + "'", resultRoot); - return; - } - - const js_args = mono_array_root_to_js_array(argsRoot); - - try { - const m = obj[js_name]; - if (typeof m === "undefined") - throw new Error("Method: '" + js_name + "' not found for: '" + Object.prototype.toString.call(obj) + "'"); - const res = m.apply(obj, js_args); - - js_to_mono_obj_root(res, resultRoot, true); - } catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - } - } finally { - argsRoot.release(); - nameRoot.release(); - resultRoot.release(); - } -} - -export function mono_wasm_get_object_property_ref(js_handle: JSHandle, property_name: MonoStringRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const nameRoot = mono_wasm_new_external_root(property_name), - resultRoot = mono_wasm_new_external_root(result_address); - try { - const js_name = conv_string_root(nameRoot); - if (!js_name) { - wrap_error_root(is_exception, "Invalid property name object '" + nameRoot.value + "'", resultRoot); - return; - } - - const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!obj) { - wrap_error_root(is_exception, "ERR01: Invalid JS object handle '" + js_handle + "' while geting '" + js_name + "'", resultRoot); - return; - } - - const m = obj[js_name]; - js_to_mono_obj_root(m, resultRoot, true); - } catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - nameRoot.release(); - } -} - -export function mono_wasm_set_object_property_ref(js_handle: JSHandle, property_name: MonoStringRef, value: MonoObjectRef, createIfNotExist: boolean, hasOwnProperty: boolean, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const valueRoot = mono_wasm_new_external_root(value), - nameRoot = mono_wasm_new_external_root(property_name), - resultRoot = mono_wasm_new_external_root(result_address); - try { - - const property = conv_string_root(nameRoot); - if (!property) { - wrap_error_root(is_exception, "Invalid property name object '" + property_name + "'", resultRoot); - return; - } - - const js_obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!js_obj) { - wrap_error_root(is_exception, "ERR02: Invalid JS object handle '" + js_handle + "' while setting '" + property + "'", resultRoot); - return; - } - - let result = false; - - const js_value = unbox_mono_obj_root(valueRoot); - - if (createIfNotExist) { - js_obj[property] = js_value; - result = true; - } - else { - result = false; - if (!createIfNotExist) { - if (!Object.prototype.hasOwnProperty.call(js_obj, property)) { - js_to_mono_obj_root(false, resultRoot, false); - return; - } - } - if (hasOwnProperty === true) { - if (Object.prototype.hasOwnProperty.call(js_obj, property)) { - js_obj[property] = js_value; - result = true; - } - } - else { - js_obj[property] = js_value; - result = true; - } - } - js_to_mono_obj_root(result, resultRoot, false); - } catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - nameRoot.release(); - valueRoot.release(); - } -} - -export function mono_wasm_get_by_index_ref(js_handle: JSHandle, property_index: number, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const resultRoot = mono_wasm_new_external_root(result_address); - try { - const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!obj) { - wrap_error_root(is_exception, "ERR03: Invalid JS object handle '" + js_handle + "' while getting [" + property_index + "]", resultRoot); - return; - } - - const m = obj[property_index]; - js_to_mono_obj_root(m, resultRoot, true); - } catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - } -} - -export function mono_wasm_set_by_index_ref(js_handle: JSHandle, property_index: number, value: MonoObjectRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const valueRoot = mono_wasm_new_external_root(value), - resultRoot = mono_wasm_new_external_root(result_address); - try { - const obj = mono_wasm_get_jsobj_from_js_handle(js_handle); - if (!obj) { - wrap_error_root(is_exception, "ERR04: Invalid JS object handle '" + js_handle + "' while setting [" + property_index + "]", resultRoot); - return; - } - - const js_value = unbox_mono_obj_root(valueRoot); - obj[property_index] = js_value; - resultRoot.clear(); - } catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - valueRoot.release(); - } -} - -export function mono_wasm_get_global_object_ref(global_name: MonoStringRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const nameRoot = mono_wasm_new_external_root(global_name), - resultRoot = mono_wasm_new_external_root(result_address); - try { - const js_name = conv_string_root(nameRoot); - - let globalObj; - - if (!js_name) { - globalObj = globalThis; - } - else { - globalObj = (globalThis)[js_name]; - } - - // TODO returning null may be useful when probing for browser features - if (globalObj === null || typeof globalObj === undefined) { - wrap_error_root(is_exception, "Global object '" + js_name + "' not found.", resultRoot); - return; - } - - js_to_mono_obj_root(globalObj, resultRoot, true); - } catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - nameRoot.release(); - } -} - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -function _wrap_error_flag(is_exception: Int32Ptr | null, ex: any): string { - let res = "unknown exception"; - if (ex) { - res = ex.toString(); - const stack = ex.stack; - if (stack) { - // Some JS runtimes insert the error message at the top of the stack, some don't, - // so normalize it by using the stack as the result if it already contains the error - if (stack.startsWith(res)) - res = stack; - else - res += "\n" + stack; - } - - res = INTERNAL.mono_wasm_symbolicate_string(res); - } - if (is_exception) { - Module.setValue(is_exception, 1, "i32"); - } - return res; -} - -/** - * @deprecated Not GC or thread safe - */ -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function wrap_error(is_exception: Int32Ptr | null, ex: any): MonoString { - const res = _wrap_error_flag(is_exception, ex); - return js_string_to_mono_string(res)!; -} - -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function wrap_error_root(is_exception: Int32Ptr | null, ex: any, result: WasmRoot): void { - const res = _wrap_error_flag(is_exception, ex); - js_string_to_mono_string_root(res, result); -} - -export function mono_method_get_call_signature_ref(method: MonoMethod, mono_obj?: WasmRoot): string/*ArgsMarshalString*/ { - return call_method_ref( - runtimeHelpers.get_call_sig_ref, undefined, "im", - [method, mono_obj ? mono_obj.address : runtimeHelpers._null_root.address] - ); -} - -export function parseFQN(fqn: string) - : { assembly: string, namespace: string, classname: string, methodname: string } { - const assembly = fqn.substring(fqn.indexOf("[") + 1, fqn.indexOf("]")).trim(); - fqn = fqn.substring(fqn.indexOf("]") + 1).trim(); - - const methodname = fqn.substring(fqn.indexOf(":") + 1); - fqn = fqn.substring(0, fqn.indexOf(":")).trim(); - - let namespace = ""; - let classname = fqn; - if (fqn.indexOf(".") != -1) { - const idx = fqn.lastIndexOf("."); - namespace = fqn.substring(0, idx); - classname = fqn.substring(idx + 1); - } - - if (!assembly.trim()) - throw new Error("No assembly name specified"); - if (!classname.trim()) - throw new Error("No class name specified"); - if (!methodname.trim()) - throw new Error("No method name specified"); - return { assembly, namespace, classname, methodname }; -} - -export function mono_method_resolve(fqn: string): MonoMethod { - const { assembly, namespace, classname, methodname } = parseFQN(fqn); - - const asm = cwraps.mono_wasm_assembly_load(assembly); - if (!asm) - throw new Error("Could not find assembly: " + assembly); - - const klass = cwraps.mono_wasm_assembly_find_class(asm, namespace, classname); - if (!klass) - throw new Error("Could not find class: " + namespace + ":" + classname + " in assembly " + assembly); - - const method = find_method(klass, methodname, -1); - if (!method) - throw new Error("Could not find method: " + methodname); - return method; -} - -// Blazor specific custom routine -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export function mono_wasm_invoke_js_blazor(exceptionMessage: Int32Ptr, callInfo: any, arg0: any, arg1: any, arg2: any): void | number { - try { - const blazorExports = (globalThis).Blazor; - if (!blazorExports) { - throw new Error("The blazor.webassembly.js library is not loaded."); - } - - return blazorExports._internal.invokeJSFromDotNet(callInfo, arg0, arg1, arg2); - } catch (ex: any) { - const exceptionJsString = ex.message + "\n" + ex.stack; - const exceptionRoot = mono_wasm_new_root(); - js_string_to_mono_string_root(exceptionJsString, exceptionRoot); - exceptionRoot.copy_to_address(exceptionMessage); - exceptionRoot.release(); - return 0; - } -} - -// code like `App.call_test_method();` -export function mono_wasm_invoke_js(code: MonoString, is_exception: Int32Ptr): MonoString | null { - if (code === MonoStringNull) - return MonoStringNull; - - const js_code = conv_string(code)!; - - try { - const closedEval = function (Module: EmscriptenModule, MONO: any, BINDING: any, INTERNAL: any, code: string) { - return eval(code); - }; - const res = closedEval(Module, MONO, BINDING, INTERNAL, js_code); - Module.setValue(is_exception, 0, "i32"); - if (typeof res === "undefined" || res === null) - return MonoStringNull; - - return js_string_to_mono_string(res.toString()); - } catch (ex) { - return wrap_error(is_exception, ex); - } -} - -// TODO is this unused code ? -// Compiles a JavaScript function from the function data passed. -// Note: code snippet is not a function definition. Instead it must create and return a function instance. -// code like `return function() { App.call_test_method(); };` -export function mono_wasm_compile_function_ref(code: MonoStringRef, is_exception: Int32Ptr, result_address: MonoObjectRef): void { - const codeRoot = mono_wasm_new_external_root(code), - resultRoot = mono_wasm_new_external_root(result_address); - - const js_code = conv_string_root(codeRoot); - if (!js_code) { - js_to_mono_obj_root(MonoStringNull, resultRoot, true); - return; - } - - try { - const closure = { - Module, MONO, BINDING, INTERNAL - }; - const fn_body_template = `const {Module, MONO, BINDING, INTERNAL} = __closure; ${js_code} ;`; - const fn_defn = new Function("__closure", fn_body_template); - const res = fn_defn(closure); - if (!res || typeof res !== "function") { - wrap_error_root(is_exception, "Code must return an instance of a JavaScript function. Please use `return` statement to return a function.", resultRoot); - return; - } - Module.setValue(is_exception, 0, "i32"); - - js_to_mono_obj_root(res, resultRoot, true); - } catch (ex) { - wrap_error_root(is_exception, ex, resultRoot); - } finally { - resultRoot.release(); - } -} diff --git a/src/mono/wasm/runtime/rollup.config.js b/src/mono/wasm/runtime/rollup.config.js index 50bcfc3110d9..233982d29216 100644 --- a/src/mono/wasm/runtime/rollup.config.js +++ b/src/mono/wasm/runtime/rollup.config.js @@ -19,6 +19,7 @@ const isContinuousIntegrationBuild = process.env.ContinuousIntegrationBuild === const productVersion = process.env.ProductVersion || "8.0.0-dev"; const nativeBinDir = process.env.NativeBinDir ? process.env.NativeBinDir.replace(/"/g, "") : "bin"; const wasmObjDir = process.env.WasmObjDir ? process.env.WasmObjDir.replace(/"/g, "") : "obj"; +const nativeAOT = process.env.NATIVE_AOT === "1" ? true : false; const monoWasmThreads = process.env.MonoWasmThreads === "true" ? true : false; const wasmEnableSIMD = process.env.WASM_ENABLE_SIMD === "1" ? true : false; const wasmEnableExceptionHandling = process.env.WASM_ENABLE_EH === "1" ? true : false; @@ -97,6 +98,7 @@ try { const envConstants = { productVersion, configuration, + nativeAOT, monoWasmThreads, wasmEnableSIMD, wasmEnableExceptionHandling, diff --git a/src/mono/wasm/runtime/run.ts b/src/mono/wasm/runtime/run.ts index 41444a5251f8..b49b2b9f4065 100644 --- a/src/mono/wasm/runtime/run.ts +++ b/src/mono/wasm/runtime/run.ts @@ -1,7 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -import { ENVIRONMENT_IS_NODE, loaderHelpers, runtimeHelpers } from "./globals"; +import NativeAOT from "consts:nativeAOT"; +import { ENVIRONMENT_IS_NODE, Module, loaderHelpers, runtimeHelpers } from "./globals"; import { mono_wasm_wait_for_debugger } from "./debug"; import { mono_wasm_set_main_args } from "./startup"; import cwraps from "./cwraps"; @@ -48,7 +49,11 @@ export async function mono_run_main(main_assembly_name: string, args?: string[]) args = []; } } - + + if (NativeAOT) { + return (Module as any)["callMain"](args); + } + mono_wasm_set_main_args(main_assembly_name, args); if (runtimeHelpers.waitForDebugger == -1) { mono_log_info("waiting for debugger..."); diff --git a/src/mono/wasm/runtime/startup.ts b/src/mono/wasm/runtime/startup.ts index 7fec1577dedc..68e025bcf96b 100644 --- a/src/mono/wasm/runtime/startup.ts +++ b/src/mono/wasm/runtime/startup.ts @@ -1,6 +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 NativeAOT from "consts:nativeAOT"; import MonoWasmThreads from "consts:monoWasmThreads"; import WasmEnableLegacyJsInterop from "consts:wasmEnableLegacyJsInterop"; @@ -442,6 +443,10 @@ export function mono_wasm_setenv(name: string, value: string): void { } export function mono_wasm_set_runtime_options(options: string[]): void { + if (NativeAOT) { + return; + } + if (!Array.isArray(options)) throw new Error("Expected runtimeOptions to be an array of strings"); @@ -526,6 +531,10 @@ async function mono_wasm_before_memory_snapshot() { return; } + if (NativeAOT) { + runtimeHelpers.config.environmentVariables = {}; + } + for (const k in runtimeHelpers.config.environmentVariables) { const v = runtimeHelpers.config.environmentVariables![k]; if (typeof (v) === "string") @@ -568,6 +577,9 @@ async function maybeSaveInterpPgoTable () { } export function mono_wasm_load_runtime(unused?: string, debugLevel?: number): void { + if (NativeAOT) { + return; + } mono_log_debug("mono_wasm_load_runtime"); try { const mark = startMeasure(); diff --git a/src/mono/wasm/runtime/types/consts.d.ts b/src/mono/wasm/runtime/types/consts.d.ts index c7a6f740ae65..c8efddfa246a 100644 --- a/src/mono/wasm/runtime/types/consts.d.ts +++ b/src/mono/wasm/runtime/types/consts.d.ts @@ -7,6 +7,11 @@ declare module "consts:*" { export default constant; } +declare module "consts:nativeAOT" { + const constant: boolean; + export default constant; +} + declare module "consts:monoWasmThreads" { const constant: boolean; export default constant; diff --git a/src/mono/wasm/wasm.proj b/src/mono/wasm/wasm.proj index 6ed392341e6b..0f1675345319 100644 --- a/src/mono/wasm/wasm.proj +++ b/src/mono/wasm/wasm.proj @@ -3,6 +3,8 @@ + $(EMSDK) + browser-wasm $([MSBuild]::NormalizeDirectory('$(ArtifactsBinDir)', 'native', '$(NetCoreAppCurrent)-$(TargetOS)-$(Configuration)-$(TargetArchitecture)')) @@ -26,6 +28,7 @@ $([MSBuild]::NormalizeDirectory('$(PkgMicrosoft_NETCore_Runtime_ICU_Transport)', 'runtimes', 'browser-wasm-threads', 'native', 'lib')) true true + false true false emcc @@ -50,7 +53,7 @@ - + $(WasmObjDir)\pinvoke-table.h $(WasmObjDir)\wasm_m2n_invoke.g.h @@ -97,6 +100,9 @@ /usr/share/zoneinfo/$([MSBuild]::MakeRelative($(_WasmTimezonesPath), %(_WasmTimezonesInternal.Identity)).Replace('\','/')) + + + @@ -443,6 +452,7 @@ Overwrite="false" /> @@ -453,8 +463,10 @@ SkipUnchangedFiles="true" /> @@ -473,11 +485,15 @@ $(NativeBinDir)dotnet.js.map; $(NativeBinDir)dotnet.runtime.js; $(NativeBinDir)dotnet.runtime.js.map; - $(NativeBinDir)dotnet.native.js; $(NativeBinDir)dotnet.d.ts; $(NativeBinDir)dotnet-legacy.d.ts; - $(NativeBinDir)package.json; + $(NativeBinDir)package.json" + DestinationFolder="$(MicrosoftNetCoreAppRuntimePackNativeDir)" + SkipUnchangedFiles="true" /> + + @@ -487,6 +503,7 @@ SkipUnchangedFiles="true" /> @@ -546,6 +563,8 @@ <_MonoRollupEnvironmentVariable Include="WASM_ENABLE_EH:0" Condition="'$(WasmEnableExceptionHandling)' == 'false'" /> <_MonoRollupEnvironmentVariable Include="DISABLE_LEGACY_JS_INTEROP:1" Condition="'$(WasmEnableLegacyJsInterop)' == 'false'" /> <_MonoRollupEnvironmentVariable Include="DISABLE_LEGACY_JS_INTEROP:0" Condition="'$(WasmEnableLegacyJsInterop)' != 'false'" /> + <_MonoRollupEnvironmentVariable Include="NATIVE_AOT:0" Condition="'$(RuntimeFlavor)' != 'CoreCLR'" /> + <_MonoRollupEnvironmentVariable Include="NATIVE_AOT:1" Condition="'$(RuntimeFlavor)' == 'CoreCLR'" /> <_MonoRollupEnvironmentVariable Include="MonoDiagnosticsMock:$(MonoDiagnosticsMock)" /> <_MonoRollupEnvironmentVariable Include="ContinuousIntegrationBuild:$(ContinuousIntegrationBuild)" /> diff --git a/src/tests/Common/scripts/nativeaottest.cmd b/src/tests/Common/scripts/nativeaottest.cmd index 09a52929d4ba..85e87781b2f9 100644 --- a/src/tests/Common/scripts/nativeaottest.cmd +++ b/src/tests/Common/scripts/nativeaottest.cmd @@ -21,6 +21,10 @@ if not exist %__JsFilePath% ( set __JsFilePath=%__JsFilePath:~0,-3%.mjs ) +if not exist %__JsFilePath% ( + set __JsFilePath=%1\native\main.js +) + set __WasmFileName=%2 set __WasmFileName=%__WasmFileName:~0,-4%.wasm set __WasmFilePath=%1native\%__WasmFileName% diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj b/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj new file mode 100644 index 000000000000..6f412ade2443 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/DotnetJs.csproj @@ -0,0 +1,13 @@ + + + Exe + BuildAndRun + true + true + true + + + + + + diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs new file mode 100644 index 000000000000..cdaf3d606e2f --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/Program.cs @@ -0,0 +1,15 @@ +using System; + +class Program +{ + static int Main(string[] args) + { + Console.WriteLine("Hello, DotnetJs!"); + Console.WriteLine($"Args {String.Join(", ", args)}"); + + if (args.Length != 3 || args[0] != "A" || args[1] != "B" || args[2] != "C") + return 1; + + return 100; + } +} \ No newline at end of file diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/index.html b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/index.html new file mode 100644 index 000000000000..1d1ae6ccf449 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js new file mode 100644 index 000000000000..3d91ca60294c --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/main.js @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +import { dotnet, exit } from './dotnet.js' + +const { runMain } = await dotnet + .withApplicationArguments("A", "B", "C") + .create(); + +var result = await runMain(); +console.log(`Exit code ${result}`); +exit(result); \ No newline at end of file diff --git a/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/package.json b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/package.json new file mode 100644 index 000000000000..96ae6e57eb39 --- /dev/null +++ b/src/tests/nativeaot/SmokeTests/DotnetJs/wwwroot/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} \ No newline at end of file