Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Wasm] JS modularization #61313

Merged
merged 32 commits into from
Dec 2, 2021
Merged

Conversation

pavelsavara
Copy link
Member

@pavelsavara pavelsavara commented Nov 8, 2021

Modularized dotnet.js

  • By using emcc options MODULARIZE and EXPORT_ES6, depending on WasmEnableES6 to produce ES6 or CommonJS modules respectively.
  • WasmEnableES6 enables WasmBuildNative because dotnet.js need to be re-linked
  • CommonJS version is able to be loaded into global namespace and behaves as before this change.
  • Added new es6/*.js and es6/*.js files which are included in dotnet.js creation. Details are described in modularize-dotnet.md
  • it exports createDotnetRuntime method instead of global Module
    • for usage see src\mono\sample\wasm\browser-es6\runtime.js
    • createDotnetRuntime(factory: (exports) => DotnetModuleConfig)
      takes function which creates Module-like config.
    • createDotnetRuntime returns Promise<{MONO, INTERNAL, BINDING, Module}> exports.
  • New Module extension DotnetModuleConfig has following new fields
    • disableDotNet6Compatibility - when true and if runtime script is loaded as module, it would not pollute global namespace.
    • configSrc - location of mono-config.json as defined by MonoConfig .ts type
    • onConfigLoaded - callback called early during runtime init, when MonoConfig was loaded but runtime didn't start yet.
    • onDotnetReady - callback called when runtime is ready and all artifacts were loaded from MonoConfig. Or not called at all when onRuntimeInitialized is overriden in Blazor.
  • Key aspects are documented in src/mono/wasm/runtime/modularize-dotnet.md of this PR.

Other changes

  • Improved dotnet.d.ts it is now generated into version control too, so that we could observe how it evolves.
  • Removed legacy --testing argument and simplified is_testing logic in the the samples.
  • Added browser-es6 sample app, which uses WasmEnableES6=true to compile dotnet.js as ES6 module.
  • Added browser-legacy sample, which uses dotnet.js CommonJS module and loads it into global namespace.
  • Added package.json into the nupkg
  • Tested that Blazor works with CommonJS-global output
  • Drafted how Blazor could use the new API in [wasm] dotnet.js modularization aspnetcore#38721

Out of scope

  • switch to ES6 as default target
  • update of samples and unit tests to load ES6

NodeJs Usage after the change

import { createRequire } from 'module';
import createDotnetRuntime from 'dotnet'
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { exit } from 'process';

const { BINDING } = await createDotnetRuntime(() => ({
    imports: {
        require: createRequire(import.meta.url)
    },
    scriptDirectory: dirname(fileURLToPath(import.meta.url)) + '/',
    disableDotnet6Compatibility: true,
    configSrc: "./mono-config.json"
}));

try {
    const app_args = process.argv.slice(2);
    const result = await BINDING.call_assembly_entry_point("Wasm.Console.TypeScript.Sample.dll", [app_args], "m");
    exit(result)
} catch (error) {
    console.log("WASM ERROR " + error);
}

@pavelsavara pavelsavara added the arch-wasm WebAssembly architecture label Nov 8, 2021
@pavelsavara pavelsavara added this to the 7.0.0 milestone Nov 8, 2021
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

@ghost
Copy link

ghost commented Nov 8, 2021

Tagging subscribers to 'arch-wasm': @lewing
See info in area-owners.md if you want to be subscribed.

Issue Details

work in progress 7

Author: pavelsavara
Assignees: -
Labels:

arch-wasm

Milestone: 7.0.0

src/mono/wasm/runtime-test.js Outdated Show resolved Hide resolved
Copy link
Contributor

@kg kg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, thanks for doing all this work! You don't need to make all the changes I've requested here

src/mono/sample/wasm/browser-es6/runtime.js Outdated Show resolved Hide resolved
src/mono/sample/wasm/browser/runtime.js Outdated Show resolved Hide resolved
src/mono/wasm/build/README.md Outdated Show resolved Hide resolved
src/mono/wasm/build/WasmApp.Native.targets Outdated Show resolved Hide resolved
src/mono/wasm/build/WasmApp.Native.targets Outdated Show resolved Hide resolved
src/mono/wasm/runtime/exports.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/exports.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/exports.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/modularize-dotnet.pre.js Outdated Show resolved Hide resolved
src/mono/wasm/wasm.proj Show resolved Hide resolved
@pavelsavara
Copy link
Member Author

This is a weird signature - do I understand it right and it's like this? createRuntime (callback : Function) : Promise<{MONO,INTERNAL,BINDING,Module}> and then callback is callback (namespaces : {MONO,INTERNAL,BINDING,Module}) : ConfigObject? How are the namespaces visible to the config getter if the runtime hasn't loaded yet? What will happen if it calls methods on them?

They are both receiving the same API object instances, i.e. same MONO instance.
In the callback MONO and others are empty objects and they become populated before the callbacks are called.
The return from createDotnetRuntime is obviously assigned only after the promise is resolved.

The callback could also be written with return {...} syntax, but adding statements before that return is not useful exactly because MONO and such are just empty shells.

You can think about it as if MONO was populated very early, during exports.ts __exportAPI function. The actual path is bit more complex.

The functionality in the onConfigLoaded is pretty limited as it's before full start of mono VM. It happens during emscripten preInit event, after fetched config.

The onDotNetReady is called after all assets are loaded , mapping to original config.loaded_cb. It happens during emscripten onRuntimeInitialized after monoVm init + globalization + assemblies. This also matches when the top level promise is resolved. The original emscripten Module.ready promise is replaced this this one.

@pavelsavara
Copy link
Member Author

Currently this corrupts some of the xharness testResults.xml files. It seems to me it could be timing or something with console.

@pavelsavara
Copy link
Member Author

I added src/mono/wasm/runtime/modularize-dotnet.md which is probably worth reading for this PR.

@pavelsavara pavelsavara requested a review from kg November 9, 2021 19:28
@pavelsavara
Copy link
Member Author

test failure runtime.js 2:34 Uncaught SyntaxError: Unexpected reserved word helix

Copy link
Contributor

@kg kg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, assuming no one else finds any problems!

@pavelsavara
Copy link
Member Author

@radical please review the msbuild parts.

Copy link
Member

@maraf maraf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have a slight feeling that from user point of view, this syntax may seem a bit odd.

export const { MONO, BINDING } = await createDotnetRuntime(({ MONO, BINDING, Module }) => ...);

I mean "what is the difference between left MONO and right MONO?"

Anyway, I can't find a better solution.

Copy link
Member

@radical radical left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the parts that I could

@radical
Copy link
Member

radical commented Nov 10, 2021

Currently this corrupts some of the xharness testResults.xml files. It seems to me it could be timing or something with console.

The testResults.xml file is incomplete in those instances. WASM EXIT is the last line written, which indicates that the tests have run, and the xml file has been written too. I'm guessing that this is getting written too soon in the updated runtime-test.js.

@radical
Copy link
Member

radical commented Nov 10, 2021

The AOT System.Runtime.Tests fail to run with (log):

[22:32:58] info: Running v8 --expose_wasm --stack-trace-limit=1000 runtime.js -- --run WasmTestRunner.dll System.Runtime.Tests.dll -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing
                 
[22:32:58] fail: console.error: Error executing file
                 
[22:32:58] fail: console.error: failed to load the dotnet.js file
                 
[22:32:58] info: ./dotnet.js:3: SyntaxError: Cannot use 'import.meta' outside a module
[22:32:58] info:   var _scriptDir = import.meta.url;
[22:32:58] info:                           ^^^^
[22:32:58] info: SyntaxError: Cannot use 'import.meta' outside a module
[22:32:58] info:     at loadScript (runtime.js:361:24)
[22:32:58] info:     at loadDotnet (runtime.js:369:12)
[22:32:58] info:     at runtime.js:105:1
[22:32:58] info: 
[22:32:58] info: Incoming arguments: --run WasmTestRunner.dll System.Runtime.Tests.dll -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing
[22:32:58] info: Application arguments: --run WasmTestRunner.dll System.Runtime.Tests.dll -notrait category=IgnoreForCI -notrait category=OuterLoop -notrait category=failing

Also, the app then incorrectly exits with exit code 0, thus it records as tests having passed. This should be fixed so if such an error occurs, then it returns a non-zero exit code.

@radical
Copy link
Member

radical commented Nov 10, 2021

Also, make sure to run all the wasm tests. A lot of them are disabled now for PRs, and run fully only for rolling builds, generally conditioned with isFullMatrix in eng/pipelines/runtime.yml, and eng/pipelines/runtime-staging.yml.

And cc @thaystg to confirm that debugging still works, and the tests pass too.

@pavelsavara pavelsavara requested a review from kg December 1, 2021 05:01
Copy link
Member

@radical radical left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the parts that I could (so, skipped js, and ts!). looking good.
Just some suggestions.

eng/liveBuilds.targets Outdated Show resolved Hide resolved
@@ -7,7 +7,7 @@ var Module = {
onConfigLoaded: function () {
MONO.config.environment_variables["DOTNET_MODIFIABLE_ASSEMBLIES"] = "debug";
},
onDotNetReady: function () {
onDotnetReady: function () {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the change? DotNet is the style used usually, including package names.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my quick survey told me Dotnet is more common, but I don't have strong opinion.
@lewing this would stick with us going forward, what should it be ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will eventually rename it in next PR

<DebugSymbols>true</DebugSymbols>
<DebugType>embedded</DebugType>
<WasmDebugLevel>1</WasmDebugLevel>
<WasmBuildNative>true</WasmBuildNative>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove it for this PR, then. And reverse the property setting here, and in the es6 sample, when es6 becomes default.

Comment on lines +27 to +28
LINK_DEPENDS "${NATIVE_BIN_DIR}/src/emcc-default.rsp;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js;${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js;${NATIVE_BIN_DIR}/src/pal_random.lib.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js;${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js;"
LINK_FLAGS "@${NATIVE_BIN_DIR}/src/emcc-default.rsp ${CONFIGURATION_LINK_FLAGS} -DENABLE_NETCORE=1 --extern-pre-js ${NATIVE_BIN_DIR}/src/cjs/runtime.cjs.iffe.js --pre-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.pre.js --js-library ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.lib.js --js-library ${NATIVE_BIN_DIR}/src/pal_random.lib.js --post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.post.js --extern-post-js ${NATIVE_BIN_DIR}/src/cjs/dotnet.cjs.extpost.js "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list here also should be extracted into variables. Doesn't have to be in this PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@radekdoulik how could I do this ? I'll do it in next PR.

src/mono/wasm/runtime/modularize-dotnet.md Outdated Show resolved Hide resolved
src/mono/wasm/test-main.js Outdated Show resolved Hide resolved
src/mono/wasm/test-main.js Outdated Show resolved Hide resolved
src/mono/wasm/wasm.proj Show resolved Hide resolved
@ghost ghost added the needs-author-action An issue or pull request that requires more info or actions from the author. label Dec 1, 2021
Co-authored-by: Ankit Jain <radical@gmail.com>
@ghost ghost removed the needs-author-action An issue or pull request that requires more info or actions from the author. label Dec 1, 2021
pavelsavara and others added 2 commits December 1, 2021 07:14
Co-authored-by: Ankit Jain <radical@gmail.com>
Copy link
Member

@radical radical left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the bits that I could, and those look good to me. great job 👍

@pavelsavara
Copy link
Member Author

/azp run runtime-manual

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Copy link
Member

@maraf maraf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • debugger-main.js still uses onDotNetReady (capital N)

src/mono/wasm/runtime/polyfills.ts Show resolved Hide resolved
src/mono/wasm/runtime/types/emscripten.ts Outdated Show resolved Hide resolved
src/mono/wasm/runtime/modularize-dotnet.md Outdated Show resolved Hide resolved
src/mono/wasm/runtime/exports.ts Outdated Show resolved Hide resolved
@pavelsavara
Copy link
Member Author

/azp run runtime-manual

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@pavelsavara
Copy link
Member Author

/azp run runtime-manual

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@radical
Copy link
Member

radical commented Dec 1, 2021

Some of the wasm.build.tests have:

[yc2d32mb.kow]          EXEC : warning : invalid item (maybe a typo?) in EXPORTED_RUNTIME_METHODS: MONO [/datadisks/disk1/work/AAF1094C/w/BA0209FA/e/yc2d32mb.kow/rebuild_flags_Release.csproj]
[yc2d32mb.kow]          EXEC : warning : invalid item (maybe a typo?) in EXPORTED_RUNTIME_METHODS: BINDING [/datadisks/disk1/work/AAF1094C/w/BA0209FA/e/yc2d32mb.kow/rebuild_flags_Release.csproj]
[yc2d32mb.kow]          EXEC : warning : invalid item (maybe a typo?) in EXPORTED_RUNTIME_METHODS: INTERNAL [/datadisks/disk1/work/AAF1094C/w/BA0209FA/e/yc2d32mb.kow/rebuild_flags_Release.csproj]

@pavelsavara
Copy link
Member Author

  • EXPORTED_RUNTIME_METHODS - I will fix it
  • I tested debugger unit tests, only well known DebuggerTests.HarnessTests.BrowserClose fails.
  • On CI [FAIL] System.Diagnostics.Tests.StopwatchTests.GetTimestamp is System.Diagnostics.Tests.StopwatchTests.GetTimestamp test fails in the CI #62021
  • On CI Assertion failed: native function malloc called before runtime initialization - is when emcc -s ASSERTIONS=1. Blazor has the same issue I think. It works anyway, when asserts are not enabled. I will change it back to run all in onRuntimeInitialized, but in that case postRun event is running in parallel with loading of assemblies and code execution. This would be consistent with previous runtime behavior.

@pavelsavara
Copy link
Member Author

/azp run runtime-manual

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@pavelsavara pavelsavara merged commit 1bd3630 into dotnet:main Dec 2, 2021
@pavelsavara pavelsavara deleted the wasm_modularization7 branch December 2, 2021 09:42
@ghost ghost locked as resolved and limited conversation to collaborators Jan 1, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
arch-wasm WebAssembly architecture
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants