diff --git a/.github/workflows/cranelift-release-branch.yml b/.github/workflows/cranelift-release-branch.yml new file mode 100644 index 0000000000..2d597be22a --- /dev/null +++ b/.github/workflows/cranelift-release-branch.yml @@ -0,0 +1,119 @@ +name: Test upcoming Cranelift release branch + +on: + schedule: + # Run daily so we don't miss the release branch cut. + - cron: "0 3 * * *" + workflow_dispatch: {} + +defaults: + run: + shell: bash + +permissions: {} + +env: + CARGO_BUILD_INCREMENTAL: false + RUSTFLAGS: "-Dwarnings" + +jobs: + test_upcoming_cranelift_release: + runs-on: ubuntu-latest + timeout-minutes: 90 + + steps: + - uses: actions/checkout@v6 + + - name: Determine latest Wasmtime release branch + id: wasmtime_release_branch + run: | + set -euo pipefail + branches="$( + git ls-remote --heads https://github.com/bytecodealliance/wasmtime.git "refs/heads/release-*" \ + | awk '{print $2}' \ + | sed 's#refs/heads/##' \ + | sort -V + )" + if [[ -z "${branches}" ]]; then + echo "No wasmtime release branches found" + exit 1 + fi + latest="$(echo "${branches}" | tail -n 1)" + echo "Latest release branch: ${latest}" + echo "branch=${latest}" >> "$GITHUB_OUTPUT" + + - name: Skip if already tested + id: tested_cache + uses: actions/cache@v5 + with: + path: .ci/cranelift-release-tested + key: cranelift-release-tested-${{ steps.wasmtime_release_branch.outputs.branch }} + + - name: Mark tested (cache payload) + if: steps.tested_cache.outputs.cache-hit != 'true' + run: | + mkdir -p .ci/cranelift-release-tested + echo "${{ steps.wasmtime_release_branch.outputs.branch }}" > .ci/cranelift-release-tested/branch.txt + + - name: Patch Cargo.toml to use release branch Cranelift + if: steps.tested_cache.outputs.cache-hit != 'true' + run: | + set -euo pipefail + branch="${{ steps.wasmtime_release_branch.outputs.branch }}" + + python3 - <<'PY' + import pathlib, re, os + cargo_toml = pathlib.Path("Cargo.toml") + s = cargo_toml.read_text(encoding="utf-8") + + branch = os.environ["WASMTIME_RELEASE_BRANCH"] + patch_lines = "\n".join([ + f'cranelift-codegen = {{ git = "https://github.com/bytecodealliance/wasmtime.git", branch = "{branch}" }}', + f'cranelift-frontend = {{ git = "https://github.com/bytecodealliance/wasmtime.git", branch = "{branch}" }}', + f'cranelift-module = {{ git = "https://github.com/bytecodealliance/wasmtime.git", branch = "{branch}" }}', + f'cranelift-native = {{ git = "https://github.com/bytecodealliance/wasmtime.git", branch = "{branch}" }}', + f'cranelift-jit = {{ git = "https://github.com/bytecodealliance/wasmtime.git", branch = "{branch}" }}', + f'cranelift-object = {{ git = "https://github.com/bytecodealliance/wasmtime.git", branch = "{branch}" }}', + ]) + + # Ensure a [patch.crates-io] section exists. + if "[patch.crates-io]" not in s: + s += "\n\n[patch.crates-io]\n" + + # Remove any previous CI-inserted patch block. + s = re.sub( + r"(?ms)^\n?# BEGIN CI WASMTIME RELEASE PATCH\n.*?^\# END CI WASMTIME RELEASE PATCH\n", + "\n", + s, + ) + + # Insert immediately after the [patch.crates-io] header. + s = re.sub( + r"(?m)^\[patch\.crates-io\]\s*$", + "[patch.crates-io]\n" + "# BEGIN CI WASMTIME RELEASE PATCH\n" + f"{patch_lines}\n" + "# END CI WASMTIME RELEASE PATCH", + s, + count=1, + ) + + cargo_toml.write_text(s, encoding="utf-8") + PY + env: + WASMTIME_RELEASE_BRANCH: ${{ steps.wasmtime_release_branch.outputs.branch }} + + - name: Prepare dependencies + if: steps.tested_cache.outputs.cache-hit != 'true' + run: ./y.sh prepare + + - name: Build (sysroot none) + if: steps.tested_cache.outputs.cache-hit != 'true' + run: ./y.sh build --sysroot none + + - name: Test + if: steps.tested_cache.outputs.cache-hit != 'true' + env: + TARGET_TRIPLE: x86_64-unknown-linux-gnu + run: ./y.sh test + diff --git a/src/abi/mod.rs b/src/abi/mod.rs index 13f5ad5157..e916ea0d29 100644 --- a/src/abi/mod.rs +++ b/src/abi/mod.rs @@ -12,7 +12,7 @@ use cranelift_codegen::ir::{ }; use cranelift_codegen::isa::CallConv; use cranelift_module::ModuleError; -use rustc_abi::{CanonAbi, ExternAbi, X86Call}; +use rustc_abi::{Align, CanonAbi, ExternAbi, X86Call}; use rustc_codegen_ssa::base::is_call_from_compiler_builtins_to_upstream_monomorphization; use rustc_codegen_ssa::errors::CompilerBuiltinsCannotCall; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; @@ -243,10 +243,17 @@ pub(crate) fn codegen_fn_prelude<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, start_ self::returning::codegen_return_param(fx, &ssa_analyzed, &mut block_params_iter); assert_eq!(fx.local_map.push(ret_place), RETURN_PLACE); - // None means pass_mode == NoPass + struct ArgValue<'tcx> { + value: Option>, + /// If set, the argument is a byval/byref pointer whose pointee alignment is smaller than + /// the Rust ABI alignment. In that case the argument must be copied to a sufficiently + /// aligned local stack slot before it can be treated as a place. + underaligned_pointee_align: Option, + } + enum ArgKind<'tcx> { - Normal(Option>), - Spread(Vec>>), + Normal(ArgValue<'tcx>), + Spread(Vec>), } // FIXME implement variadics in cranelift @@ -280,17 +287,29 @@ pub(crate) fn codegen_fn_prelude<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, start_ let mut params = Vec::new(); for (i, _arg_ty) in tupled_arg_tys.iter().enumerate() { let arg_abi = arg_abis_iter.next().unwrap(); - let param = + let value = cvalue_for_param(fx, Some(local), Some(i), arg_abi, &mut block_params_iter); - params.push(param); + let underaligned_pointee_align = match arg_abi.mode { + PassMode::Indirect { attrs, .. } => attrs + .pointee_align + .filter(|&pointee_align| pointee_align < arg_abi.layout.align.abi), + _ => None, + }; + params.push(ArgValue { value, underaligned_pointee_align }); } (local, ArgKind::Spread(params), arg_ty) } else { let arg_abi = arg_abis_iter.next().unwrap(); - let param = + let value = cvalue_for_param(fx, Some(local), None, arg_abi, &mut block_params_iter); - (local, ArgKind::Normal(param), arg_ty) + let underaligned_pointee_align = match arg_abi.mode { + PassMode::Indirect { attrs, .. } => attrs + .pointee_align + .filter(|&pointee_align| pointee_align < arg_abi.layout.align.abi), + _ => None, + }; + (local, ArgKind::Normal(ArgValue { value, underaligned_pointee_align }), arg_ty) } }) .collect::, Ty<'tcx>)>>(); @@ -311,7 +330,9 @@ pub(crate) fn codegen_fn_prelude<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, start_ for (local, arg_kind, ty) in func_params { // While this is normally an optimization to prevent an unnecessary copy when an argument is // not mutated by the current function, this is necessary to support unsized arguments. - if let ArgKind::Normal(Some(val)) = arg_kind { + if let ArgKind::Normal(ArgValue { value: Some(val), underaligned_pointee_align: None }) = + arg_kind + { if let Some((addr, meta)) = val.try_to_ptr() { // Ownership of the value at the backing storage for an argument is passed to the // callee per the ABI, so it is fine to borrow the backing storage of this argument @@ -336,15 +357,74 @@ pub(crate) fn codegen_fn_prelude<'tcx>(fx: &mut FunctionCx<'_, '_, 'tcx>, start_ assert_eq!(fx.local_map.push(place), local); match arg_kind { - ArgKind::Normal(param) => { - if let Some(param) = param { + ArgKind::Normal(ArgValue { value: Some(param), underaligned_pointee_align }) => { + if let Some(pointee_align) = underaligned_pointee_align + && let Some(dst_ptr) = place.try_to_ptr() + && let Some((src_ptr, None)) = param.try_to_ptr() + && layout.size != Size::ZERO + { + let mut flags = MemFlags::new(); + flags.set_notrap(); + + let to_addr = dst_ptr.get_addr(fx); + let from_addr = src_ptr.get_addr(fx); + let size = layout.size.bytes(); + + // `emit_small_memory_copy` uses `u8` for alignments, just use the maximum + // alignment that fits in a `u8` if the actual alignment is larger. + let dst_align = layout.align.bytes().try_into().unwrap_or(128); + let src_align = pointee_align.bytes().try_into().unwrap_or(128); + + fx.bcx.emit_small_memory_copy( + fx.target_config, + to_addr, + from_addr, + size, + dst_align, + src_align, + true, + flags, + ); + } else { place.write_cvalue(fx, param); } } + ArgKind::Normal(ArgValue { value: None, underaligned_pointee_align: _ }) => {} ArgKind::Spread(params) => { - for (i, param) in params.into_iter().enumerate() { + for (i, ArgValue { value: param, underaligned_pointee_align }) in + params.into_iter().enumerate() + { if let Some(param) = param { - place.place_field(fx, FieldIdx::new(i)).write_cvalue(fx, param); + let field_place = place.place_field(fx, FieldIdx::new(i)); + let field_layout = field_place.layout(); + if let Some(pointee_align) = underaligned_pointee_align + && let Some(dst_ptr) = field_place.try_to_ptr() + && let Some((src_ptr, None)) = param.try_to_ptr() + && field_layout.size != Size::ZERO + { + let mut flags = MemFlags::new(); + flags.set_notrap(); + + let to_addr = dst_ptr.get_addr(fx); + let from_addr = src_ptr.get_addr(fx); + let size = field_layout.size.bytes(); + + let dst_align = field_layout.align.bytes().try_into().unwrap_or(128); + let src_align = pointee_align.bytes().try_into().unwrap_or(128); + + fx.bcx.emit_small_memory_copy( + fx.target_config, + to_addr, + from_addr, + size, + dst_align, + src_align, + true, + flags, + ); + } else { + field_place.write_cvalue(fx, param); + } } } }