diff --git a/MODULE.bazel b/MODULE.bazel index 1b431dca..48fb106f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -261,6 +261,18 @@ use_repo(meld, "meld_toolchain") register_toolchains("@meld_toolchain//:meld_toolchain") +# Loom toolchain for WebAssembly component optimization (native binary, v1.x). +# wasm_optimize consumes this (loom_toolchain_type); the old @loom_wasm 0.3.0 +# component path was removed (#512). +loom = use_extension("//wasm:extensions.bzl", "loom") +loom.register( + name = "loom", + version = "1.1.14", +) +use_repo(loom, "loom_toolchain") + +register_toolchains("@loom_toolchain//:loom_toolchain") + # spar toolchain: AADL architecture model -> WIT generation spar = use_extension("//wasm:extensions.bzl", "spar") spar.register( @@ -331,13 +343,9 @@ wasm_component_download( version = "0.7.0", ) -# LOOM WebAssembly optimizer (version in //checksums/tools/loom.json) -wasm_component_download( - name = "loom_wasm", - filename = "loom.wasm", - tool_name = "loom", - version = "0.3.0", -) +# LOOM: consumed via the native loom toolchain (@loom_toolchain, above). +# The old @loom_wasm 0.3.0 component download was removed — loom v1.x is +# native-only and wasm_optimize now runs the native binary directly (#512). # WASM Tools Component toolchain for universal wasm-tools operations register_toolchains("//toolchains:wasm_tools_component_toolchain_local") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 6f4841d8..b81cd26b 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -218,7 +218,7 @@ "moduleExtensions": { "//toolchains:extensions.bzl%wasm_tool_repositories": { "general": { - "bzlTransitiveDigest": "t2mOVh2g8u6qWKLYZ1RD9rQ4Wvl4nsYm51KJSVawqTQ=", + "bzlTransitiveDigest": "GVpK79XDY26ixQ+99LlGDPx1maOeGRkQYSxUIq2k1TQ=", "usagesDigest": "clQqyOwvm/I5edLjq5P4LnONWTjXO0N1AnoQTVKRaOY=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -284,7 +284,7 @@ }, "//wasm:extensions.bzl%binaryen": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "c5GClIZ+xfHSKFn9WL/02Ag7wuihInOMqTGH5CxR/U8=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -308,7 +308,7 @@ }, "//wasm:extensions.bzl%cpp_component": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "ZtIrdMAeTaET/8t3kmG14kQp8U4FKMqhGB1+JS825Do=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -333,7 +333,7 @@ }, "//wasm:extensions.bzl%jco": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "MRHYkIS73wv1wYllXhdZBYX6dRIp7VySTL4edmOH2/M=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -356,9 +356,33 @@ ] } }, + "//wasm:extensions.bzl%loom": { + "general": { + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", + "usagesDigest": "DFU/sKFAEZQ+zav+qZj4HmWRMKHuW16wFNoirPl+iLQ=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "loom_toolchain": { + "repoRuleId": "@@//toolchains:loom_toolchain.bzl%loom_repository", + "attributes": { + "version": "1.1.14" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, "//wasm:extensions.bzl%meld": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "Nc45Hq7pjpIzzA1UXlHsAEAgQHpg1jE3/IUlCJYcFgU=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -382,7 +406,7 @@ }, "//wasm:extensions.bzl%spar": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "HteVAnfZ8Jv2xAL/oX+kiaSB6HLWRIt9g2CRFKUEz9k=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -406,7 +430,7 @@ }, "//wasm:extensions.bzl%synth": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "XTIJ1SQ/Se7btOFYHYJX2DjviC7xWk2ugwNS0Q3Icho=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -430,7 +454,7 @@ }, "//wasm:extensions.bzl%tinygo": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "esnFdrH+qxI9awhZ/uW4dIkm843wWmTCzO4b1pdmifs=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -454,7 +478,7 @@ }, "//wasm:extensions.bzl%wasi_sdk": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "4CDKvALAslODuYVBSBBBKgDbmaqWRdwJJ4yfDrzWeYg=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -839,7 +863,7 @@ }, "//wasm:extensions.bzl%wasm_toolchain": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "Keg+j4249N8787+5NDDpsnW8S0P3hp9HSaC26H2SeJg=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -873,7 +897,7 @@ }, "//wasm:extensions.bzl%wasmtime": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "ApoHDOpNvMY+Neo28RYxqriytCle29d6O6ibK6MJ7u0=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -898,7 +922,7 @@ }, "//wasm:extensions.bzl%witness": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "vz4JVaRGcdcwU0XOHYN0kb54nuI69CDjEu/MXFK6r/g=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, @@ -922,7 +946,7 @@ }, "//wasm:extensions.bzl%wkg": { "general": { - "bzlTransitiveDigest": "MPtqvx8t7gZJVOuxHU4eGcG9yk/A4QiX6cXUSrRp1fM=", + "bzlTransitiveDigest": "0qEi+vG8SVFzA/pNsQsrTAkpTnn15fiVjSNm5DZLt+U=", "usagesDigest": "ks+Q/IL0nntNP6PabzUcF0ruF4X9hTKhbmck/ueoPTg=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/test/p3/BUILD.bazel b/test/p3/BUILD.bazel index a47b62ff..a890ef2d 100644 --- a/test/p3/BUILD.bazel +++ b/test/p3/BUILD.bazel @@ -6,6 +6,7 @@ P2 tests verify no regression, P3 tests verify the new attribute is accepted. load("@bazel_skylib//rules:build_test.bzl", "build_test") load("@rules_wasm_component//rust:defs.bzl", "rust_wasm_component", "rust_wasm_component_bindgen") +load("@rules_wasm_component//wasm:defs.bzl", "wasm_optimize") load("@rules_wasm_component//wit:defs.bzl", "wit_library") package(default_visibility = ["//visibility:public"]) @@ -38,6 +39,20 @@ build_test( targets = [":hello_p2"], ) +# ============================================================================ +# Native loom optimize (covers wasm_optimize on the native loom toolchain, #512) +# ============================================================================ + +wasm_optimize( + name = "hello_p2_optimized", + component = ":hello_p2", +) + +build_test( + name = "loom_optimize_build_test", + targets = [":hello_p2_optimized"], +) + # ============================================================================ # P3 build (experimental — async bindings via wit-bindgen crate runtime) # ============================================================================ diff --git a/toolchains/BUILD.bazel b/toolchains/BUILD.bazel index d24d1885..7a9f215b 100644 --- a/toolchains/BUILD.bazel +++ b/toolchains/BUILD.bazel @@ -90,6 +90,12 @@ toolchain_type( visibility = ["//visibility:public"], ) +# Toolchain type for Loom (WebAssembly component optimization) +toolchain_type( + name = "loom_toolchain_type", + visibility = ["//visibility:public"], +) + # Toolchain type for spar (AADL architecture model -> WIT generation) toolchain_type( name = "spar_toolchain_type", @@ -349,6 +355,14 @@ bzl_library( deps = [":tool_registry"], ) +# Bzl library for Loom toolchain implementation +bzl_library( + name = "loom_toolchain", + srcs = ["loom_toolchain.bzl"], + visibility = ["//visibility:public"], + deps = [":tool_registry"], +) + # Export .bzl files for testing exports_files([ "tool_registry.bzl", diff --git a/toolchains/loom_toolchain.bzl b/toolchains/loom_toolchain.bzl new file mode 100644 index 00000000..444b2692 --- /dev/null +++ b/toolchains/loom_toolchain.bzl @@ -0,0 +1,128 @@ +# Copyright 2026 Ralf Anton Beier. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +"""Loom toolchain: native binary for WebAssembly component optimization. + +loom optimizes a WebAssembly component (constant folding, CSE, inlining, DCE, +fused-component passes, optional Z3 verification). As of v1.x loom is +distributed as native per-OS binaries (the v0.x `loom.wasm` component was +dropped), so wasm_optimize runs the native binary directly instead of running +loom.wasm under wasmtime. Mirrors toolchains/meld_toolchain.bzl, but loom ships +tarballs (extracted) rather than a bare binary. +""" + +load("//checksums:registry.bzl", "validate_tool_exists") +load("//toolchains:tool_registry.bzl", "tool_registry") + +# Platforms where loom native binaries are downloaded + extracted here. loom +# also ships a Windows binary (loom.exe), but that path is not wired yet +# (binary-name differs); Windows gets the stub for now (see #512 follow-up). +_SUPPORTED_PLATFORMS = [ + "darwin_amd64", + "darwin_arm64", + "linux_amd64", +] + +def _loom_toolchain_impl(ctx): + """Implementation of loom_toolchain rule.""" + return [platform_common.ToolchainInfo( + loom = ctx.file.loom, + )] + +loom_toolchain = rule( + implementation = _loom_toolchain_impl, + attrs = { + "loom": attr.label( + allow_single_file = True, + executable = True, + cfg = "exec", + doc = "loom binary for component optimization", + ), + }, + doc = "Declares a Loom toolchain for WebAssembly component optimization", +) + +_STUB_BUILD = '''"""Loom toolchain stub: unsupported platform. + +loom has no wired native binary for this host, so we register a toolchain that +is marked incompatible with any target. Toolchain resolution for wasm_optimize +targets fails cleanly here; builds that never touch wasm_optimize are unaffected. +""" + +load("@rules_wasm_component//toolchains:loom_toolchain.bzl", "loom_toolchain") + +package(default_visibility = ["//visibility:public"]) + +exports_files(["loom_stub"]) + +loom_toolchain( + name = "loom_toolchain_impl", + loom = "loom_stub", +) + +toolchain( + name = "loom_toolchain", + target_compatible_with = ["@platforms//:incompatible"], + toolchain = ":loom_toolchain_impl", + toolchain_type = "@rules_wasm_component//toolchains:loom_toolchain_type", +) +''' + +def _loom_repository_impl(repository_ctx): + """Download + extract the loom native binary and create a toolchain repo.""" + platform = tool_registry.detect_platform(repository_ctx) + version = repository_ctx.attr.version + + if platform not in _SUPPORTED_PLATFORMS or not validate_tool_exists(repository_ctx, "loom", version, platform): + print("Loom: no wired native binary for platform {} (version {}); emitting stub".format(platform, version)) + repository_ctx.file("loom_stub", content = "", executable = True) + repository_ctx.file("BUILD.bazel", _STUB_BUILD) + return + + print("Setting up loom {} for platform {}".format(version, platform)) + + # loom ships a tarball containing ./loom; tool_registry extracts it (the + # 1.1.14 registry entries have no `binary: true`) and returns the binary + # path. strip_prefix is "" (flat archive) — see _calculate_strip_prefix. + tool_registry.download( + repository_ctx, + "loom", + version, + platform, + ) + + repository_ctx.file("BUILD.bazel", '''"""Loom toolchain repository""" + +load("@rules_wasm_component//toolchains:loom_toolchain.bzl", "loom_toolchain") + +package(default_visibility = ["//visibility:public"]) + +loom_toolchain( + name = "loom_toolchain_impl", + loom = ":loom", +) + +toolchain( + name = "loom_toolchain", + exec_compatible_with = [], + target_compatible_with = [], + toolchain = ":loom_toolchain_impl", + toolchain_type = "@rules_wasm_component//toolchains:loom_toolchain_type", +) +''') + +loom_repository = repository_rule( + implementation = _loom_repository_impl, + attrs = { + "version": attr.string( + default = "1.1.14", + doc = "Loom version to download", + ), + }, + doc = "Downloads loom and creates a toolchain repository", +) diff --git a/toolchains/tool_registry.bzl b/toolchains/tool_registry.bzl index fb233706..82656ad9 100644 --- a/toolchains/tool_registry.bzl +++ b/toolchains/tool_registry.bzl @@ -143,6 +143,11 @@ _URL_PATTERNS = { "filename": "{suffix}", # e.g., "meld-aarch64-apple-darwin" "is_binary": True, }, + "loom": { + # loom v1.x releases are tarballs (extracted; url_suffix IS the filename) + "base": "https://github.com/{repo}/releases/download/v{version}", + "filename": "{suffix}", # e.g., "loom-v1.1.14-aarch64-apple-darwin.tar.gz" + }, "spar": { # spar releases are tar.gz/zip archives: spar-v{version}-{triple}.{ext} "base": "https://github.com/{repo}/releases/download/v{version}", diff --git a/wasm/extensions.bzl b/wasm/extensions.bzl index da8acec1..705713f5 100644 --- a/wasm/extensions.bzl +++ b/wasm/extensions.bzl @@ -5,6 +5,7 @@ load("//toolchains:componentize_py_toolchain.bzl", "componentize_py_toolchain_re load("//toolchains:cpp_component_toolchain.bzl", "cpp_component_toolchain_repository") load("//toolchains:jco_toolchain.bzl", "jco_toolchain_repository") load("//toolchains:meld_toolchain.bzl", "meld_repository") +load("//toolchains:loom_toolchain.bzl", "loom_repository") load("//toolchains:spar_toolchain.bzl", "spar_repository") load("//toolchains:witness_toolchain.bzl", "witness_repository") load("//toolchains:synth_toolchain.bzl", "synth_repository") @@ -702,6 +703,45 @@ meld = module_extension( }, ) +def _loom_extension_impl(module_ctx): + """Implementation of Loom module extension.""" + registrations = {} + + for mod in module_ctx.modules: + for registration in mod.tags.register: + registrations[registration.name] = registration + + for name, registration in registrations.items(): + loom_repository( + name = name + "_toolchain", + version = registration.version, + ) + + if not registrations: + loom_repository( + name = "loom_toolchain", + version = "1.1.14", + ) + +# Module extension for Loom (WebAssembly component optimization) +loom = module_extension( + implementation = _loom_extension_impl, + tag_classes = { + "register": tag_class( + attrs = { + "name": attr.string( + doc = "Name for this Loom registration", + default = "loom", + ), + "version": attr.string( + doc = "Loom version to use", + default = "1.1.14", + ), + }, + ), + }, +) + def _spar_extension_impl(module_ctx): """Implementation of the spar module extension.""" registrations = {} diff --git a/wasm/private/wasm_optimize.bzl b/wasm/private/wasm_optimize.bzl index 4ef32e60..fb063201 100644 --- a/wasm/private/wasm_optimize.bzl +++ b/wasm/private/wasm_optimize.bzl @@ -12,32 +12,13 @@ def _wasm_optimize_impl(ctx): input_wasm = ctx.file.component output_wasm = ctx.actions.declare_file(ctx.label.name + ".wasm") - # Get LOOM WASM component, the wrapper that runs it, and wasmtime. - loom_wasm = ctx.file._loom_wasm - loom_wrapper = ctx.executable._loom_wrapper - wasmtime = ctx.toolchains["@rules_wasm_component//toolchains:wasmtime_toolchain_type"].wasmtime - - # Build command arguments for loom_wrapper, which prepends `wasmtime run` - # and the resolved `--dir` preopens (issue #490). - # - # loom reads input paths via WASI, and a fetched/adopted input is staged - # as a symlink whose target escapes a plain `--dir=.` preopen, so wasmtime - # refuses to follow it. The wrapper resolves the symlink on the host and - # preopens the real directory. - # - # The wasmtime binary and loom.wasm are passed as the first two arguments - # rather than located via the wrapper's runfiles: a hardcoded runfiles - # Rlocation embeds the canonical repo name, which differs when - # rules_wasm_component is the root module vs a dependency (the latter gains - # a `rules_wasm_component+` prefix), so the lookup fails for downstream - # consumers. wasmtime opens both files natively, so neither needs a WASI - # mount. - # - # Note: loom v0.3.0 does NOT accept the `--` separator between the wasm - # module path and the subcommand when run under `wasmtime run`. + # Native loom binary (v1.x is native per-OS, not the loom.wasm component). + # Running loom natively reads the input file directly through the OS, so the + # wasmtime/WASI-preopen/symlink workaround the old loom.wasm path needed + # (issue #490, loom_wrapper) no longer applies. + loom = ctx.toolchains["@rules_wasm_component//toolchains:loom_toolchain_type"].loom + args = ctx.actions.args() - args.add(wasmtime) - args.add(loom_wasm) args.add("optimize") args.add(input_wasm) args.add("-o", output_wasm) @@ -61,9 +42,9 @@ def _wasm_optimize_impl(ctx): args.add("--passes", ",".join(ctx.attr.passes)) ctx.actions.run( - inputs = [input_wasm, loom_wasm, wasmtime], + inputs = [input_wasm], outputs = [output_wasm], - executable = loom_wrapper, + executable = loom, arguments = [args], mnemonic = "LoomOptimize", progress_message = "Optimizing WebAssembly component with LOOM: %{label}", @@ -124,19 +105,8 @@ Available passes: precompute, constant-folding, cse, inline, advanced, branches, dce, merge-blocks, vacuum, simplify-locals""", default = [], ), - "_loom_wasm": attr.label( - doc = "LOOM WebAssembly component", - default = "@loom_wasm//file:file", - allow_single_file = True, - ), - "_loom_wrapper": attr.label( - doc = "Wrapper that runs loom.wasm under wasmtime with symlink-resolved WASI mounts", - default = "//tools/loom_wrapper", - executable = True, - cfg = "exec", - ), }, - toolchains = ["@rules_wasm_component//toolchains:wasmtime_toolchain_type"], + toolchains = ["@rules_wasm_component//toolchains:loom_toolchain_type"], doc = """Optimize a WebAssembly component using LOOM. LOOM performs expression-level optimizations including: