From 1e0203a52a6d74b115534ecd1bf93f6469589c0d Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Feb 2026 11:00:59 +0100 Subject: [PATCH 1/4] Correct WasmMainJSPath to match changes in https://github.com/dotnet/BenchmarkDotNet/pull/2998. --- src/benchmarks/micro/MicroBenchmarks.Wasm.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/benchmarks/micro/MicroBenchmarks.Wasm.props b/src/benchmarks/micro/MicroBenchmarks.Wasm.props index bffdc16baed..d361a4e475c 100644 --- a/src/benchmarks/micro/MicroBenchmarks.Wasm.props +++ b/src/benchmarks/micro/MicroBenchmarks.Wasm.props @@ -1,7 +1,7 @@ true - $(WasmDataDir)\test-main.js + $(MSBuildProjectDirectory)/wwwroot/test-main.js partial true true From 08c07a984c9dcaca4191846efe1de1487d2b82d8 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Thu, 12 Feb 2026 15:35:10 +0100 Subject: [PATCH 2/4] Move responsibility for test-main to BDN. --- src/benchmarks/micro/MicroBenchmarks.Wasm.props | 2 -- src/benchmarks/micro/MicroBenchmarks.Wasm.targets | 5 ----- 2 files changed, 7 deletions(-) diff --git a/src/benchmarks/micro/MicroBenchmarks.Wasm.props b/src/benchmarks/micro/MicroBenchmarks.Wasm.props index d361a4e475c..d5730feae77 100644 --- a/src/benchmarks/micro/MicroBenchmarks.Wasm.props +++ b/src/benchmarks/micro/MicroBenchmarks.Wasm.props @@ -1,7 +1,5 @@ - true - $(MSBuildProjectDirectory)/wwwroot/test-main.js partial true true diff --git a/src/benchmarks/micro/MicroBenchmarks.Wasm.targets b/src/benchmarks/micro/MicroBenchmarks.Wasm.targets index cf9978d836f..6489b14732f 100644 --- a/src/benchmarks/micro/MicroBenchmarks.Wasm.targets +++ b/src/benchmarks/micro/MicroBenchmarks.Wasm.targets @@ -8,7 +8,6 @@ it can get into a situation where the emsdk version doesn't match between runtime, and the workload pack. --> <_WasmStrictVersionMatch>false - true @@ -25,9 +24,5 @@ - - - - From 354584466180c48d256d1be14d747f91d153d0b1 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 13 Feb 2026 15:27:11 +0100 Subject: [PATCH 3/4] Add switch for coreclr per tests. --- scripts/micro_benchmarks.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/micro_benchmarks.py b/scripts/micro_benchmarks.py index a92fde9261e..8dee94e11cd 100755 --- a/scripts/micro_benchmarks.py +++ b/scripts/micro_benchmarks.py @@ -140,6 +140,15 @@ def __get_bdn_arguments(user_input: str) -> list[str]: help='Tests should be run with the wasm runtime' ) + parser.add_argument( + '--wasm-coreclr', + dest='wasm_coreclr', + required=False, + default=False, + action='store_true', + help='Use CoreCLR runtime pack instead of Mono for WASM benchmarks' + ) + parser.add_argument( '--bdn-arguments', dest='bdn_arguments', @@ -273,6 +282,8 @@ def __get_benchmarkdotnet_arguments(framework: str, args: Any) -> list[str]: run_args += ['--runtimes', 'wasmnet11_0'] else: raise ArgumentTypeError('Framework {} is not supported for wasm'.format(framework)) + if args.wasm_coreclr: + run_args += ['--wasmCoreCLR'] # Increase default 2 min build timeout to accommodate slow (or even very slow) hardware if not args.bdn_arguments or '--buildTimeout' not in args.bdn_arguments: From 5ff7790c6b008c47a37a3353659f39c065873dd3 Mon Sep 17 00:00:00 2001 From: Ilona Tomkowicz Date: Fri, 20 Feb 2026 16:41:15 +0100 Subject: [PATCH 4/4] Add CoreCLR WASM benchmark support. --- eng/pipelines/runtime-wasm-perf-jobs.yml | 22 ++++++ eng/pipelines/templates/runtime-perf-job.yml | 21 +++++- scripts/build_runtime_payload.py | 63 ++++++++++++++++- scripts/run_performance_job.py | 71 +++++++++++++++++--- 4 files changed, 166 insertions(+), 11 deletions(-) diff --git a/eng/pipelines/runtime-wasm-perf-jobs.yml b/eng/pipelines/runtime-wasm-perf-jobs.yml index 9fc2e46e690..b7b32a9ed97 100644 --- a/eng/pipelines/runtime-wasm-perf-jobs.yml +++ b/eng/pipelines/runtime-wasm-perf-jobs.yml @@ -130,3 +130,25 @@ jobs: performanceRepoAlias: ${{ parameters.performanceRepoAlias }} ${{ each parameter in parameters.jobParameters }}: ${{ parameter.key }}: ${{ parameter.value }} + + # Run CoreCLR WASM microbenchmarks perf job + - template: /eng/pipelines/common/platform-matrix.yml@${{ parameters.runtimeRepoAlias }} + parameters: + jobTemplate: /eng/pipelines/templates/runtime-perf-job.yml@${{ parameters.performanceRepoAlias }} + buildConfig: release + runtimeFlavor: coreclr + platforms: + - linux_x64 + jobParameters: + liveLibrariesBuildConfig: Release + runtimeType: wasm_coreclr + codeGenType: 'wasm' + runKind: micro + logicalMachine: 'perfviper' + javascriptEngine: 'v8' + additionalJobIdentifier: coreclr_v8 + downloadSpecificBuild: ${{ parameters.downloadSpecificBuild }} + runtimeRepoAlias: ${{ parameters.runtimeRepoAlias }} + performanceRepoAlias: ${{ parameters.performanceRepoAlias }} + ${{ each parameter in parameters.jobParameters }}: + ${{ parameter.key }}: ${{ parameter.value }} diff --git a/eng/pipelines/templates/runtime-perf-job.yml b/eng/pipelines/templates/runtime-perf-job.yml index 99c63d8e159..6c6d1d5040e 100644 --- a/eng/pipelines/templates/runtime-perf-job.yml +++ b/eng/pipelines/templates/runtime-perf-job.yml @@ -90,7 +90,7 @@ jobs: displayName: Add Properties To Pipeline Env - ${{ if eq(parameters.runtimeType, 'wasm') }}: - # Download wasm + # Download wasm (Mono) - ${{ if eq(parameters.downloadSpecificBuild.buildId, '') }}: - template: /eng/pipelines/templates/download-artifact-step.yml parameters: @@ -106,6 +106,25 @@ jobs: artifactName: BrowserWasm displayName: BrowserWasm ${{ insert }}: ${{ parameters.downloadSpecificBuild }} + + - ${{ if eq(parameters.runtimeType, 'wasm_coreclr') }}: + # Download wasm (CoreCLR) + - ${{ if eq(parameters.downloadSpecificBuild.buildId, '') }}: + - template: /eng/pipelines/templates/download-artifact-step.yml + parameters: + unpackFolder: $(librariesDownloadDir)/BrowserWasmCoreCLR + artifactFileName: BrowserWasmCoreCLR.tar.gz + artifactName: BrowserWasmCoreCLR + displayName: BrowserWasmCoreCLR + - ${{ if ne(parameters.downloadSpecificBuild.buildId, '') }}: + - template: /eng/pipelines/templates/download-specific-artifact-step.yml + parameters: + unpackFolder: $(librariesDownloadDir)/BrowserWasmCoreCLR + artifactFileName: BrowserWasmCoreCLR.tar.gz + artifactName: BrowserWasmCoreCLR + displayName: BrowserWasmCoreCLR + ${{ insert }}: ${{ parameters.downloadSpecificBuild }} + - ${{ elseif and(eq(parameters.codeGenType, 'AOT'), not(eq(parameters.runtimeType, 'AndroidMono'))) }}: - template: /eng/pipelines/templates/download-artifact-step.yml parameters: diff --git a/scripts/build_runtime_payload.py b/scripts/build_runtime_payload.py index 1217d6deb57..71354668ca7 100644 --- a/scripts/build_runtime_payload.py +++ b/scripts/build_runtime_payload.py @@ -20,6 +20,7 @@ "build_mono_payload", "build_monoaot_payload", "build_wasm_payload", + "build_wasm_coreclr_payload", ] @@ -332,4 +333,64 @@ def build_wasm_payload( os.makedirs(wasm_data_dir, exist_ok=True) shutil.copy(test_main_js_path, os.path.join(wasm_data_dir, "test-main.js")) - _set_permissions_recursive([wasm_dotnet_dir, wasm_built_nugets_dir, wasm_data_dir], mode=0o664) # rw-rw-r-- \ No newline at end of file + _set_permissions_recursive([wasm_dotnet_dir, wasm_built_nugets_dir, wasm_data_dir], mode=0o664) # rw-rw-r-- + + +def build_wasm_coreclr_payload( + browser_wasm_coreclr_archive_or_dir: str, + payload_parent_dir: str, + test_main_js_path: Optional[str] = None, + runtime_repo_dir: Optional[str] = None, +) -> None: + """Create a WASM CoreCLR-only payload (dotnet, wasm-data). + + This is a self-contained payload for running CoreCLR WASM benchmarks without + requiring Mono artifacts. The archive/directory layout is expected to contain + a `staging/` folder with `dotnet-none` (SDK) and + `microsoft.netcore.app.runtime.browser-wasm` (CoreCLR runtime pack) subfolders. + """ + if test_main_js_path is None: + if runtime_repo_dir is None: + raise Exception("Please provide a path to the test-main.js or runtime repository") + test_main_js_path = os.path.join(runtime_repo_dir, "src", "mono", "browser", "test-main.js") + + if not os.path.exists(test_main_js_path): + raise Exception(f"test-main.js not found in expected location: {test_main_js_path}") + + wasm_dotnet_dir = os.path.join(payload_parent_dir, "dotnet") + wasm_data_dir = os.path.join(payload_parent_dir, "wasm-data") + + # Extract the SDK from dotnet-none + extract_archive_or_copy( + browser_wasm_coreclr_archive_or_dir, wasm_dotnet_dir, prefix="staging/dotnet-none/" + ) + + # Determine version from the runtime pack directory structure + runtime_pack_src = os.path.join( + browser_wasm_coreclr_archive_or_dir if os.path.isdir(browser_wasm_coreclr_archive_or_dir) else "", + "staging", "microsoft.netcore.app.runtime.browser-wasm", "Release" + ) + if os.path.isdir(runtime_pack_src): + # Get version from NuGet.config or infer from directory + # For now, read version from the SDK's Microsoft.NETCore.App.Ref pack + ref_pack_parent = os.path.join(wasm_dotnet_dir, "packs", "Microsoft.NETCore.App.Ref") + if os.path.isdir(ref_pack_parent): + versions = os.listdir(ref_pack_parent) + if versions: + pack_version = versions[0] + coreclr_pack_dest = os.path.join( + wasm_dotnet_dir, "packs", "Microsoft.NETCore.App.Runtime.browser-wasm", pack_version + ) + extract_archive_or_copy( + browser_wasm_coreclr_archive_or_dir, + coreclr_pack_dest, + prefix="staging/microsoft.netcore.app.runtime.browser-wasm/Release/", + ) + getLogger().info("Installed CoreCLR browser-wasm runtime pack version %s", pack_version) + else: + getLogger().warning("Microsoft.NETCore.App.Ref pack not found – cannot determine version") + + os.makedirs(wasm_data_dir, exist_ok=True) + shutil.copy(test_main_js_path, os.path.join(wasm_data_dir, "test-main.js")) + + _set_permissions_recursive([wasm_dotnet_dir, wasm_data_dir], mode=0o664) \ No newline at end of file diff --git a/scripts/run_performance_job.py b/scripts/run_performance_job.py index 3fad795a973..374c91110a2 100644 --- a/scripts/run_performance_job.py +++ b/scripts/run_performance_job.py @@ -200,8 +200,8 @@ def get_pre_commands( "sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates" ] - # Set up everything needed for WASM runs - if runtime_type == "wasm": + # Set up everything needed for WASM runs (both Mono and CoreCLR) + if runtime_type in ("wasm", "wasm_coreclr"): if os_distro == "azurelinux": # Azure Linux uses tdnf package manager install_prerequisites += [ @@ -442,7 +442,6 @@ def get_bdn_arguments( "--wasmEngine", javascript_engine_path, f"\\\"--wasmArgs={' '.join(wasm_args)}\\\"", "--cli", "$HELIX_CORRELATION_PAYLOAD/dotnet/dotnet", - "--wasmDataDir", "$HELIX_CORRELATION_PAYLOAD/wasm-data" ] if is_aot: @@ -451,6 +450,22 @@ def get_bdn_arguments( "--buildTimeout", "3600" ] + if runtime_type == "wasm_coreclr": + category_exclusions += ["NoWASM", "NoMono"] + + wasm_args = ["--expose_wasm"] + if javascript_engine == "v8": + wasm_args += ["--module"] + + assert javascript_engine_path is not None + bdn_arguments += [ + "--wasmEngine", javascript_engine_path, + f"\\\"--wasmArgs={' '.join(wasm_args)}\\\"", + "--cli", "$HELIX_CORRELATION_PAYLOAD/dotnet/dotnet", + "--wasmCoreCLR", + "--buildTimeout", "1200" + ] + if category_exclusions: bdn_arguments += ["--category-exclusion-filter", *set(category_exclusions)] @@ -560,7 +575,7 @@ def get_run_configurations( return configurations -def get_work_item_command(os_group: str, target_csproj: str, architecture: str, perf_lab_framework: str, internal: bool, wasm: bool, bdn_artifacts_dir: str): +def get_work_item_command(os_group: str, target_csproj: str, architecture: str, perf_lab_framework: str, internal: bool, wasm: bool, bdn_artifacts_dir: str, wasm_coreclr: bool = False): if os_group == "windows": work_item_command = [ "python", @@ -588,6 +603,8 @@ def get_work_item_command(os_group: str, target_csproj: str, architecture: str, if wasm: work_item_command += ["--run-isolated", "--wasm", "--dotnet-path", "$HELIX_CORRELATION_PAYLOAD/dotnet/"] + if wasm_coreclr: + work_item_command += ["--wasm-coreclr"] work_item_command += ["--bdn-artifacts", bdn_artifacts_dir] @@ -646,8 +663,9 @@ def run_performance_job(args: RunPerformanceJobArgs): is_mono = args.runtime_type == "mono" mono_aot = is_mono and is_aot mono_dotnet = is_mono and not is_aot - wasm = args.runtime_type == "wasm" - wasm_aot = wasm and is_aot + wasm_coreclr = args.runtime_type == "wasm_coreclr" + wasm = args.runtime_type == "wasm" or wasm_coreclr # wasm_coreclr also uses wasm infrastructure + wasm_aot = wasm and is_aot and not wasm_coreclr working_dir = os.path.join(args.performance_repo_dir, "CorrelationStaging") # folder in which the payload and workitem directories will be made work_item_dir = os.path.join(working_dir, "workitem", "") # Folder in which the work item commands will be run in @@ -779,13 +797,43 @@ def run_performance_job(args: RunPerformanceJobArgs): shutil.copytree(args.mono_dotnet_dir, mono_dotnet_path, dirs_exist_ok=True) v8_version = "" - if wasm: + if wasm_coreclr: + if args.libraries_download_dir is None: + raise Exception("Libraries not downloaded for wasm_coreclr runs") + + getLogger().info("Building wasm_coreclr payload directory") + browser_wasm_coreclr_dir = os.path.join(args.libraries_download_dir, "BrowserWasmCoreCLR") + build_wasm_coreclr_payload( + browser_wasm_coreclr_dir, + payload_dir, + runtime_repo_dir=args.runtime_repo_dir, + ) + + if args.javascript_engine == "v8": + if args.browser_versions_props_path is None: + if args.runtime_repo_dir is None: + raise Exception("BrowserVersions.props must be present for wasm runs") + args.browser_versions_props_path = os.path.join(args.runtime_repo_dir, "eng", "testing", "BrowserVersions.props") + + with open(args.browser_versions_props_path) as f: + for line in f: + match = re.search(r"linux_V8Version>([^<]*)<", line) + if match: + v8_version = match.group(1) + v8_version = ".".join(v8_version.split(".")[:3]) + break + + elif wasm: if args.libraries_download_dir is None: raise Exception("Libraries not downloaded for wasm runs") getLogger().info("Copying wasm bundle directory to payload directory") browser_wasm_dir = os.path.join(args.libraries_download_dir, "BrowserWasm") - build_wasm_payload(browser_wasm_dir, payload_dir, runtime_repo_dir=args.runtime_repo_dir) + build_wasm_payload( + browser_wasm_dir, + payload_dir, + runtime_repo_dir=args.runtime_repo_dir, + ) if args.javascript_engine == "v8": if args.browser_versions_props_path is None: @@ -961,6 +1009,11 @@ def run_performance_job(args: RunPerformanceJobArgs): ci_setup_arguments.output_file = os.path.join(root_payload_dir, "machine-setup") if args.is_scenario: ci_setup_arguments.install_dir = os.path.join(payload_dir, "dotnet") + elif wasm_coreclr: + # For wasm_coreclr, we already have the SDK in the payload - skip downloading + wasm_dotnet_path = os.path.join(payload_dir, "dotnet") + ci_setup_arguments.dotnet_path = wasm_dotnet_path + ci_setup_arguments.install_dir = wasm_dotnet_path else: tools_dir = os.path.join(performance_payload_dir, "tools") ci_setup_arguments.install_dir = os.path.join(tools_dir, "dotnet", args.architecture) @@ -1157,7 +1210,7 @@ def get_bdn_args_for_coreroot_dir(coreroot_dir: Optional[str]): def get_work_item_command_for_artifact_dir(artifact_dir: str): assert args.target_csproj is not None - return get_work_item_command(args.os_group, args.target_csproj, args.architecture, perf_lab_framework, args.internal, wasm, artifact_dir) + return get_work_item_command(args.os_group, args.target_csproj, args.architecture, perf_lab_framework, args.internal, wasm, artifact_dir, wasm_coreclr) work_item_command = get_work_item_command_for_artifact_dir(bdn_artifacts_directory) baseline_work_item_command = get_work_item_command_for_artifact_dir(bdn_baseline_artifacts_dir)