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