Skip to content

Commit 2d66af3

Browse files
authored
Add CoreCLR WASM R2R performance lane (#5297)
* Add CoreCLR WASM R2R performance lane Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Align CoreCLR WASM toolchain package versions Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Ensure CoreCLR WASM R2R images are produced Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Use trimmed staging for WASM R2R benchmarks Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Align ILLink with the WASM toolchain cohort Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Support regular SDK WASM R2R builds Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Exclude invalid Jil WASM R2R output Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Validate resolved WASM runtime packs Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 * Update CoreCLR WASM prerequisite test Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360 --------- Copilot-Session: 4f286edf-0a3c-43dc-92fe-26a0a57d5360
1 parent 2e0afbc commit 2d66af3

8 files changed

Lines changed: 364 additions & 16 deletions

File tree

eng/pipelines/runtime-wasm-perf-jobs.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,28 @@ jobs:
154154
performanceRepoAlias: ${{ parameters.performanceRepoAlias }}
155155
${{ each parameter in parameters.jobParameters }}:
156156
${{ parameter.key }}: ${{ parameter.value }}
157+
158+
# Run CoreCLR WASM R2R microbenchmarks using the same runtime payload.
159+
# Browser WASM supports per-assembly R2R rather than composite R2R.
160+
- ${{ if not(startswith(variables['Build.SourceBranch'], 'refs/heads/release')) }}:
161+
- template: /eng/pipelines/common/platform-matrix.yml@${{ parameters.runtimeRepoAlias }}
162+
parameters:
163+
jobTemplate: /eng/pipelines/templates/runtime-perf-job.yml@${{ parameters.performanceRepoAlias }}
164+
buildConfig: release
165+
runtimeFlavor: coreclr
166+
platforms:
167+
- linux_x64
168+
jobParameters:
169+
liveLibrariesBuildConfig: Release
170+
runtimeType: wasm_coreclr
171+
codeGenType: 'wasm'
172+
r2rRunType: 'r2r'
173+
runKind: micro
174+
logicalMachine: 'perfviper'
175+
javascriptEngine: 'v8'
176+
additionalJobIdentifier: coreclr_r2r_v8
177+
downloadSpecificBuild: ${{ parameters.downloadSpecificBuild }}
178+
runtimeRepoAlias: ${{ parameters.runtimeRepoAlias }}
179+
performanceRepoAlias: ${{ parameters.performanceRepoAlias }}
180+
${{ each parameter in parameters.jobParameters }}:
181+
${{ parameter.key }}: ${{ parameter.value }}

scripts/build_runtime_payload.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,16 @@ def build_wasm_payload(
325325
def build_wasm_coreclr_payload(
326326
browser_wasm_coreclr_archive_or_dir: str,
327327
payload_parent_dir: str,
328-
) -> None:
328+
) -> str:
329329
"""Create a WASM CoreCLR-only payload (dotnet).
330330
331331
This is a self-contained payload for running CoreCLR WASM benchmarks without
332332
requiring Mono artifacts. The archive/directory layout is expected to contain
333333
a `staging/` folder with `dotnet-none` (SDK) and
334334
`microsoft.netcore.app.runtime.browser-wasm` (CoreCLR runtime pack) subfolders.
335+
336+
Returns:
337+
The shared version of the locally built WebAssembly SDK and Crossgen2 packages.
335338
"""
336339

337340
wasm_dotnet_dir = os.path.join(payload_parent_dir, "dotnet")
@@ -346,6 +349,7 @@ def build_wasm_coreclr_payload(
346349
extract_archive_or_copy(
347350
browser_wasm_coreclr_archive_or_dir, wasm_built_nugets_dir, prefix="staging/built-nugets/"
348351
)
352+
local_package_version = _get_wasm_local_package_version(wasm_built_nugets_dir)
349353

350354
# Determine version from the runtime pack directory structure
351355
runtime_pack_src = os.path.join(
@@ -373,6 +377,45 @@ def build_wasm_coreclr_payload(
373377
getLogger().warning("Microsoft.NETCore.App.Ref pack not found – cannot determine version")
374378

375379
_set_permissions_recursive([wasm_dotnet_dir, wasm_built_nugets_dir], mode=0o664)
380+
return local_package_version
381+
382+
383+
def _get_wasm_local_package_version(built_nugets_dir: str) -> str:
384+
package_prefix = "Microsoft.NET.Sdk.WebAssembly.Pack."
385+
wasm_sdk_packages = [
386+
package for package in Path(built_nugets_dir).glob(f"{package_prefix}*.nupkg")
387+
if not package.name.endswith(".symbols.nupkg")
388+
]
389+
if len(wasm_sdk_packages) != 1:
390+
raise ValueError(
391+
f"Expected one WebAssembly SDK package in {built_nugets_dir}, found {len(wasm_sdk_packages)}")
392+
393+
package_version = wasm_sdk_packages[0].name[len(package_prefix):-len(".nupkg")]
394+
crossgen2_packages = [
395+
package for package in Path(built_nugets_dir).glob("Microsoft.NETCore.App.Crossgen2.*.nupkg")
396+
if not package.name.endswith(".symbols.nupkg")
397+
]
398+
if len(crossgen2_packages) != 1:
399+
raise ValueError(
400+
f"Expected one Crossgen2 package in {built_nugets_dir}, found {len(crossgen2_packages)}")
401+
if not crossgen2_packages[0].name.endswith(f".{package_version}.nupkg"):
402+
raise ValueError(
403+
f"WebAssembly SDK and Crossgen2 package versions do not match: "
404+
f"{wasm_sdk_packages[0].name}, {crossgen2_packages[0].name}")
405+
406+
illink_packages = [
407+
package for package in Path(built_nugets_dir).glob("Microsoft.NET.ILLink.Tasks.*.nupkg")
408+
if not package.name.endswith(".symbols.nupkg")
409+
]
410+
if len(illink_packages) != 1:
411+
raise ValueError(
412+
f"Expected one ILLink package in {built_nugets_dir}, found {len(illink_packages)}")
413+
if illink_packages[0].name != f"Microsoft.NET.ILLink.Tasks.{package_version}.nupkg":
414+
raise ValueError(
415+
f"WebAssembly SDK and ILLink package versions do not match: "
416+
f"{wasm_sdk_packages[0].name}, {illink_packages[0].name}")
417+
418+
return package_version
376419

377420

378421
def build_r2r_interpreter_payload(

scripts/micro_benchmarks.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from argparse import SUPPRESS
1010
from io import StringIO
1111
from logging import getLogger
12-
from os import path
12+
from os import environ, path
1313
from subprocess import CalledProcessError
1414
from traceback import format_exc
1515
from typing import Any
@@ -149,6 +149,15 @@ def __get_bdn_arguments(user_input: str) -> list[str]:
149149
help='Runtime flavor for WASM benchmarks: Mono (default) or CoreCLR'
150150
)
151151

152+
parser.add_argument(
153+
'--wasm-ready-to-run',
154+
dest='wasm_ready_to_run',
155+
required=False,
156+
default=False,
157+
action='store_true',
158+
help='Publish CoreCLR WASM benchmarks as ReadyToRun'
159+
)
160+
152161
parser.add_argument(
153162
'--bdn-arguments',
154163
dest='bdn_arguments',
@@ -233,7 +242,28 @@ def __process_arguments(args: list[str]):
233242
)
234243

235244
parser = add_arguments(parser)
236-
return parser.parse_args(args)
245+
parsed_args = parser.parse_args(args)
246+
247+
try:
248+
validate_wasm_ready_to_run(parsed_args)
249+
except ArgumentTypeError as error:
250+
parser.error(str(error))
251+
252+
return parsed_args
253+
254+
255+
def validate_wasm_ready_to_run(args: Any) -> None:
256+
if args.wasm_ready_to_run and (not args.wasm or args.wasm_runtime_flavor != 'CoreCLR'):
257+
raise ArgumentTypeError('--wasm-ready-to-run requires --wasm --wasm-runtime-flavor CoreCLR')
258+
259+
260+
def configure_wasm_ready_to_run(args: Any) -> None:
261+
validate_wasm_ready_to_run(args)
262+
263+
# BenchmarkDotNet builds generated projects in child processes. MSBuild
264+
# imports environment variables as properties, which lets the generated
265+
# WASM project opt into R2R without requiring a new BDN command-line option.
266+
environ['PERFLAB_WASM_READY_TO_RUN'] = str(args.wasm_ready_to_run).lower()
237267

238268

239269
def __get_benchmarkdotnet_arguments(framework: str, args: Any) -> list[str]:
@@ -363,6 +393,8 @@ def run(
363393
framework
364394
))
365395

396+
configure_wasm_ready_to_run(args)
397+
366398
# dotnet exec
367399
run_args = __get_benchmarkdotnet_arguments(framework, args)
368400
target_framework_moniker = dotnet.get_target_framework_moniker(

scripts/run_performance_job.py

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ def get_pre_commands(
208208
runtime_type: str,
209209
codegen_type: str,
210210
build_config: str,
211-
v8_version: str):
211+
v8_version: str,
212+
wasm_local_package_version: Optional[str] = None):
212213
helix_pre_commands: list[str] = []
213214

214215
# Remember the previous PYTHONPATH that was set so it can be restored in the post commands
@@ -284,7 +285,14 @@ def get_pre_commands(
284285
]
285286

286287
# Set up everything needed for WASM runs (both Mono and CoreCLR)
287-
if runtime_type in ("wasm", "wasm_coreclr"):
288+
if runtime_type in ("wasm", "wasm_coreclr"):
289+
if runtime_type == "wasm_coreclr":
290+
if not wasm_local_package_version:
291+
raise ValueError("CoreCLR WASM requires a local WebAssembly toolchain package version")
292+
install_prerequisites += [
293+
f"export PERFLAB_WASM_PACKAGE_VERSION={wasm_local_package_version}"
294+
]
295+
288296
if os_distro == "azurelinux":
289297
# Azure Linux uses tdnf package manager
290298
install_prerequisites += [
@@ -644,6 +652,8 @@ def get_run_configurations(
644652

645653
if r2r_run_type == "nor2r":
646654
configurations["R2RType"] = "nor2r"
655+
elif r2r_run_type == "r2r":
656+
configurations["R2RType"] = "r2r"
647657

648658
if runtime_type == "coreclr_r2r_interpreter":
649659
configurations["R2RType"] = "r2r_interpreter"
@@ -690,7 +700,17 @@ def get_run_configurations(
690700

691701
return configurations
692702

693-
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, only_sanity_check: bool = False):
703+
def get_work_item_command(
704+
os_group: str,
705+
target_csproj: str,
706+
architecture: str,
707+
perf_lab_framework: str,
708+
internal: bool,
709+
wasm: bool,
710+
bdn_artifacts_dir: str,
711+
wasm_coreclr: bool = False,
712+
wasm_ready_to_run: bool = False,
713+
only_sanity_check: bool = False):
694714
if os_group == "windows":
695715
work_item_command = [
696716
"python",
@@ -720,6 +740,8 @@ def get_work_item_command(os_group: str, target_csproj: str, architecture: str,
720740
work_item_command += ["--run-isolated", "--wasm", "--dotnet-path", "$HELIX_CORRELATION_PAYLOAD/dotnet/"]
721741
if wasm_coreclr:
722742
work_item_command += ["--wasm-runtime-flavor", "CoreCLR"]
743+
if wasm_ready_to_run:
744+
work_item_command += ["--wasm-ready-to-run"]
723745

724746
work_item_command += ["--bdn-artifacts", bdn_artifacts_dir]
725747

@@ -940,13 +962,14 @@ def run_performance_job(args: RunPerformanceJobArgs):
940962
shutil.copytree(args.mono_dotnet_dir, mono_dotnet_path, dirs_exist_ok=True)
941963

942964
v8_version = ""
965+
wasm_local_package_version = None
943966
if wasm_coreclr:
944967
if args.libraries_download_dir is None:
945968
raise Exception("Libraries not downloaded for wasm_coreclr runs")
946969

947970
getLogger().info("Building wasm_coreclr payload directory")
948971
browser_wasm_coreclr_dir = os.path.join(args.libraries_download_dir, "BrowserWasmCoreCLR")
949-
build_wasm_coreclr_payload(
972+
wasm_local_package_version = build_wasm_coreclr_payload(
950973
browser_wasm_coreclr_dir,
951974
payload_dir,
952975
)
@@ -1133,7 +1156,15 @@ def run_performance_job(args: RunPerformanceJobArgs):
11331156
else:
11341157
agent_python = "python3"
11351158

1136-
helix_pre_commands = get_pre_commands(args.os_group, args.os_distro, args.internal, args.runtime_type, args.codegen_type, args.build_config, v8_version)
1159+
helix_pre_commands = get_pre_commands(
1160+
args.os_group,
1161+
args.os_distro,
1162+
args.internal,
1163+
args.runtime_type,
1164+
args.codegen_type,
1165+
args.build_config,
1166+
v8_version,
1167+
wasm_local_package_version)
11371168
helix_post_commands = get_post_commands(args.os_group, args.internal, args.runtime_type)
11381169

11391170
# Point ML.NET at the SSWE model that was pre-downloaded into the correlation payload above, so it
@@ -1372,7 +1403,17 @@ def get_bdn_args_for_coreroot_dir(coreroot_dir: Optional[str]):
13721403

13731404
def get_work_item_command_for_artifact_dir(artifact_dir: str):
13741405
assert args.target_csproj is not None
1375-
return get_work_item_command(args.os_group, args.target_csproj, args.architecture, perf_lab_framework, args.internal, wasm, artifact_dir, wasm_coreclr, args.only_sanity_check)
1406+
return get_work_item_command(
1407+
args.os_group,
1408+
args.target_csproj,
1409+
args.architecture,
1410+
perf_lab_framework,
1411+
args.internal,
1412+
wasm,
1413+
artifact_dir,
1414+
wasm_coreclr,
1415+
wasm_coreclr and args.r2r_run_type == "r2r",
1416+
args.only_sanity_check)
13761417

13771418
work_item_command = get_work_item_command_for_artifact_dir(bdn_artifacts_directory)
13781419
baseline_work_item_command = get_work_item_command_for_artifact_dir(bdn_baseline_artifacts_dir)

scripts/tests/test_run_performance_job.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def get_generated_apt_commands(*, internal: bool, runtime_type: str) -> list[str
1212
codegen_type="jit",
1313
build_config="Release",
1414
v8_version="12.0.0",
15+
wasm_local_package_version="11.0.0-ci" if runtime_type == "wasm_coreclr" else None,
1516
)
1617

1718
return [

0 commit comments

Comments
 (0)