Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/benchmarking-workflow-dotnet-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,20 @@ recorded in that SDK's `Microsoft.NETCoreSdk.BundledVersions.props`.
`PERFLAB_WASM_PACKAGE_VERSION` should remain unset; that override is reserved
for non-official runtime payloads.

`--wasm-ready-to-run` publishes per-assembly ReadyToRun images. Replace it with
`--wasm-ready-to-run-composite` to compile the benchmark closure into a single
composite image (`PublishReadyToRunComposite=true`, published as
`<entry>.r2r.wasm` and loaded through the boot config's `coreAssembly`
resources). Composite mode requires a WebAssembly SDK with composite support
(dotnet/runtime#134618) and ReadyToRun SDK tasks with wasm output naming
(dotnet/sdk#56395). Until that SDK change is available, set
`PERFLAB_WASM_CROSSGEN2_TASKS_DIR` to a directory containing dotnet/runtime's
`Crossgen2Tasks` shim (`artifacts/bin/Crossgen2Tasks/<Configuration>/` from a
runtime build, or `staging/Crossgen2Tasks/` in the `BrowserWasmCoreCLR` perf
artifact); composite runs then pass it to the WebAssembly SDK through
`Crossgen2SdkOverridePropsPath`/`Crossgen2SdkOverrideTargetsPath`. CI reports
the two modes as `R2RType=r2r` and `R2RType=r2r_composite`.

#### Note about "file ... being used by another process" error

If you are seeing warnings like:
Expand Down
29 changes: 27 additions & 2 deletions eng/pipelines/runtime-wasm-perf-jobs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,7 @@ jobs:
${{ each parameter in parameters.jobParameters }}:
${{ parameter.key }}: ${{ parameter.value }}

# Run CoreCLR WASM R2R microbenchmarks using the same runtime payload.
# Browser WASM supports per-assembly R2R rather than composite R2R.
# Run CoreCLR WASM per-assembly R2R microbenchmarks using the same runtime payload.
- ${{ if not(startswith(variables['Build.SourceBranch'], 'refs/heads/release')) }}:
- template: /eng/pipelines/common/platform-matrix.yml@${{ parameters.runtimeRepoAlias }}
parameters:
Expand All @@ -182,3 +181,29 @@ jobs:
performanceRepoAlias: ${{ parameters.performanceRepoAlias }}
${{ each parameter in parameters.jobParameters }}:
${{ parameter.key }}: ${{ parameter.value }}

# Run CoreCLR WASM composite R2R microbenchmarks using the same runtime payload.
# The whole benchmark closure is compiled into one <entry>.r2r.wasm image (PublishReadyToRunComposite=true)
# and reported as R2RType=r2r_composite, a history separate from per-assembly R2RType=r2r.
- ${{ if not(startswith(variables['Build.SourceBranch'], 'refs/heads/release')) }}:
- 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'
r2rRunType: 'r2r_composite'
runKind: micro
logicalMachine: 'perfviper'
javascriptEngine: 'v8'
additionalJobIdentifier: coreclr_r2r_composite_v8
downloadSpecificBuild: ${{ parameters.downloadSpecificBuild }}
runtimeRepoAlias: ${{ parameters.runtimeRepoAlias }}
performanceRepoAlias: ${{ parameters.performanceRepoAlias }}
${{ each parameter in parameters.jobParameters }}:
${{ parameter.key }}: ${{ parameter.value }}
4 changes: 2 additions & 2 deletions eng/pipelines/templates/run-performance-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ parameters:
isCompare: false # optional -- If the job is comparing two configurations
onlySanityCheck: false # optional -- Whether to only run a sanity check for pipeline validation, not performance testing
pgoRunType: '' # optional -- PGO run type (e.g. nodynamicpgo)
r2rRunType: '' # optional -- R2R run type (e.g. nor2r)
r2rRunType: '' # optional -- R2R run type (e.g. nor2r, r2r, r2r_composite)
experimentName: '' # optional -- Name of the experiment
javascriptEngine: '' # optional -- JavaScript engine to use
iOSLlvmBuild: false # optional -- Whether to build iOS with LLVM
Expand Down Expand Up @@ -242,7 +242,7 @@ jobs:
- '--cross-build'
- ${{ if ne(parameters.additionalSetupParameters, '') }}:
- '${{ parameters.additionalSetupParameters }}'
- ${{ if and(ne(parameters.wasmWorkloadSource, ''), eq(parameters.runtimeType, 'wasm_coreclr'), eq(parameters.r2rRunType, 'r2r')) }}:
- ${{ if and(ne(parameters.wasmWorkloadSource, ''), eq(parameters.runtimeType, 'wasm_coreclr'), in(parameters.r2rRunType, 'r2r', 'r2r_composite')) }}:
- '--wasm-workload-source "${{ parameters.wasmWorkloadSource }}"'
- template: /eng/pipelines/templates/send-to-helix-step.yml
parameters:
Expand Down
5 changes: 3 additions & 2 deletions scripts/benchmarks_ci.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,11 @@ def main(argv: list[str]):
if args.wasm_workload_source and not (
args.wasm
and args.wasm_runtime_flavor == 'CoreCLR'
and args.wasm_ready_to_run):
and micro_benchmarks.is_wasm_ready_to_run(args)):
raise ValueError(
'--wasm-workload-source requires --wasm '
'--wasm-runtime-flavor CoreCLR --wasm-ready-to-run')
'--wasm-runtime-flavor CoreCLR --wasm-ready-to-run '
'(or --wasm-ready-to-run-composite)')

# Acquire necessary tools (dotnet)
if not args.dotnet_path:
Expand Down
60 changes: 58 additions & 2 deletions scripts/build_runtime_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"build_r2r_interpreter_payload",
"build_wasm_payload",
"build_wasm_coreclr_payload",
"WASM_CROSSGEN2_TASKS_PAYLOAD_DIR",
]


Expand Down Expand Up @@ -322,6 +323,55 @@ def build_wasm_payload(
_set_permissions_recursive([wasm_dotnet_dir, wasm_built_nugets_dir], mode=0o664) # rw-rw-r--


# dotnet/runtime's wasm-aware ReadyToRun SDK task shim (Crossgen2Tasks.dll plus the
# Microsoft.NET.CrossGen.props/.targets that load it). Composite CoreCLR WASM R2R needs it until
# dotnet/sdk#56395 reaches the SDK shipped in the BrowserWasmCoreCLR artifact.
WASM_CROSSGEN2_TASKS_PAYLOAD_DIR = "crossgen2-tasks"
WASM_CROSSGEN2_TASKS_FILES = (
"Crossgen2Tasks.dll",
"Microsoft.NET.CrossGen.props",
"Microsoft.NET.CrossGen.targets",
)


def _stage_wasm_crossgen2_tasks(
browser_wasm_coreclr_archive_or_dir: str,
payload_parent_dir: str,
) -> bool:
"""Copy the optional ``staging/Crossgen2Tasks`` shim into the payload.

Returns True when the shim was staged, False when the artifact predates it
(no ``staging/Crossgen2Tasks`` content at all). A shim that is present but
missing any required file raises ``ValueError``.
"""
dest_dir = os.path.join(payload_parent_dir, WASM_CROSSGEN2_TASKS_PAYLOAD_DIR)
if (os.path.isdir(browser_wasm_coreclr_archive_or_dir)
and not os.path.isdir(os.path.join(browser_wasm_coreclr_archive_or_dir, "staging", "Crossgen2Tasks"))):
getLogger().info("BrowserWasmCoreCLR artifact does not contain the Crossgen2Tasks shim")
return False

extract_archive_or_copy(
browser_wasm_coreclr_archive_or_dir, dest_dir, prefix="staging/Crossgen2Tasks/"
)
# Archives can't be probed for a folder up front; nothing extracted means no shim.
if (not os.path.isdir(browser_wasm_coreclr_archive_or_dir)
and not any(path.is_file() for path in Path(dest_dir).rglob("*"))):
shutil.rmtree(dest_dir, ignore_errors=True)
getLogger().info("BrowserWasmCoreCLR artifact does not contain the Crossgen2Tasks shim")
return False

missing = [
name for name in WASM_CROSSGEN2_TASKS_FILES
if not os.path.isfile(os.path.join(dest_dir, name))
]
if missing:
raise ValueError(
f"Incomplete Crossgen2Tasks shim in BrowserWasmCoreCLR artifact, missing: {', '.join(missing)}")

getLogger().info("Staged Crossgen2Tasks shim into %s", dest_dir)
return True


def build_wasm_coreclr_payload(
browser_wasm_coreclr_archive_or_dir: str,
payload_parent_dir: str,
Expand All @@ -331,7 +381,8 @@ def build_wasm_coreclr_payload(
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.
`microsoft.netcore.app.runtime.browser-wasm` (CoreCLR runtime pack) subfolders,
and optionally `Crossgen2Tasks` (staged as ``WASM_CROSSGEN2_TASKS_PAYLOAD_DIR``).

Returns:
The shared version of the locally built WebAssembly SDK and Crossgen2 packages.
Expand All @@ -351,6 +402,8 @@ def build_wasm_coreclr_payload(
)
local_package_version = _get_wasm_local_package_version(wasm_built_nugets_dir)
_ensure_wasm_tool_framework_version(wasm_dotnet_dir, local_package_version)
has_crossgen2_tasks = _stage_wasm_crossgen2_tasks(
browser_wasm_coreclr_archive_or_dir, payload_parent_dir)

# Determine version from the runtime pack directory structure
runtime_pack_src = os.path.join(
Expand All @@ -377,7 +430,10 @@ def build_wasm_coreclr_payload(
else:
getLogger().warning("Microsoft.NETCore.App.Ref pack not found – cannot determine version")

_set_permissions_recursive([wasm_dotnet_dir, wasm_built_nugets_dir], mode=0o664)
permission_dirs = [wasm_dotnet_dir, wasm_built_nugets_dir]
if has_crossgen2_tasks:
permission_dirs.append(os.path.join(payload_parent_dir, WASM_CROSSGEN2_TASKS_PAYLOAD_DIR))
_set_permissions_recursive(permission_dirs, mode=0o664)
return local_package_version


Expand Down
74 changes: 70 additions & 4 deletions scripts/micro_benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,17 @@ def __get_bdn_arguments(user_input: str) -> list[str]:
required=False,
default=False,
action='store_true',
help='Publish CoreCLR WASM benchmarks as ReadyToRun'
help='Publish CoreCLR WASM benchmarks as per-assembly ReadyToRun'
)

parser.add_argument(
'--wasm-ready-to-run-composite',
dest='wasm_ready_to_run_composite',
required=False,
default=False,
action='store_true',
help='Publish CoreCLR WASM benchmarks as a single composite '
'ReadyToRun image (implies ReadyToRun)'
)

parser.add_argument(
Expand Down Expand Up @@ -252,9 +262,21 @@ def __process_arguments(args: list[str]):
return parsed_args


def is_wasm_ready_to_run_composite(args: Any) -> bool:
return bool(getattr(args, 'wasm_ready_to_run_composite', False))


def is_wasm_ready_to_run(args: Any) -> bool:
'''True for either per-assembly or composite CoreCLR WASM ReadyToRun.'''
return bool(args.wasm_ready_to_run) or is_wasm_ready_to_run_composite(args)


def validate_wasm_ready_to_run(args: Any) -> None:
if args.wasm_ready_to_run and (not args.wasm or args.wasm_runtime_flavor != 'CoreCLR'):
raise ArgumentTypeError('--wasm-ready-to-run requires --wasm --wasm-runtime-flavor CoreCLR')
if not args.wasm or args.wasm_runtime_flavor != 'CoreCLR':
if is_wasm_ready_to_run_composite(args):
raise ArgumentTypeError('--wasm-ready-to-run-composite requires --wasm --wasm-runtime-flavor CoreCLR')
if args.wasm_ready_to_run:
raise ArgumentTypeError('--wasm-ready-to-run requires --wasm --wasm-runtime-flavor CoreCLR')


def configure_wasm_ready_to_run(args: Any) -> None:
Expand All @@ -263,7 +285,51 @@ def configure_wasm_ready_to_run(args: Any) -> None:
# BenchmarkDotNet builds generated projects in child processes. MSBuild
# imports environment variables as properties, which lets the generated
# WASM project opt into R2R without requiring a new BDN command-line option.
environ['PERFLAB_WASM_READY_TO_RUN'] = str(args.wasm_ready_to_run).lower()
# Both variables are always written so a stale value from the parent
# environment cannot select a different mode.
environ['PERFLAB_WASM_READY_TO_RUN'] = str(is_wasm_ready_to_run(args)).lower()
environ['PERFLAB_WASM_READY_TO_RUN_COMPOSITE'] = str(is_wasm_ready_to_run_composite(args)).lower()
configure_wasm_crossgen2_sdk_override(args)


WASM_CROSSGEN2_TASKS_DIR_VARIABLE = 'PERFLAB_WASM_CROSSGEN2_TASKS_DIR'
WASM_CROSSGEN2_SDK_OVERRIDE_VARIABLES = (
'Crossgen2SdkOverridePropsPath',
'Crossgen2SdkOverrideTargetsPath',
)


def configure_wasm_crossgen2_sdk_override(args: Any) -> None:
'''Activate dotnet/runtime's wasm-aware ReadyToRun task shim for composite R2R.

Composite CoreCLR WASM R2R needs SDK ReadyToRun tasks that name the owner
image <entry>.r2r.wasm (dotnet/sdk#56395). Until that reaches the SDK in
use, the CoreCLR WASM payload ships the shim and exports its directory in
PERFLAB_WASM_CROSSGEN2_TASKS_DIR. The WebAssembly SDK reads the override
paths during props evaluation, before BenchmarkDotNet imports
MicroBenchmarks.Wasm.targets, so they are passed as environment
properties. TODO: remove with dotnet/runtime#135023.

Inherited override paths are always cleared first so per-assembly R2R
keeps the SDK's tasks and composite never uses a stale shim; set
PERFLAB_WASM_CROSSGEN2_TASKS_DIR to select a shim.
'''
for variable in WASM_CROSSGEN2_SDK_OVERRIDE_VARIABLES:
environ.pop(variable, None)

tasks_dir = environ.get(WASM_CROSSGEN2_TASKS_DIR_VARIABLE)
if not is_wasm_ready_to_run_composite(args) or not tasks_dir:
return

props_path = path.join(tasks_dir, 'Microsoft.NET.CrossGen.props')
targets_path = path.join(tasks_dir, 'Microsoft.NET.CrossGen.targets')
for required in (path.join(tasks_dir, 'Crossgen2Tasks.dll'), props_path, targets_path):
if not path.isfile(required):
raise FileNotFoundError(
f'{WASM_CROSSGEN2_TASKS_DIR_VARIABLE} does not contain {path.basename(required)}: {tasks_dir}')

environ['Crossgen2SdkOverridePropsPath'] = props_path
environ['Crossgen2SdkOverrideTargetsPath'] = targets_path


def __get_benchmarkdotnet_arguments(framework: str, args: Any) -> list[str]:
Expand Down
Loading
Loading