https://gcc.gnu.org/g:d3a85a4724b3c5fd36ee71649ae1f53dd21cf995
commit r17-5434-gd3a85a4724b3c5fd36ee71649ae1f53dd21cf995 Author: Yap Zhi Heng <[email protected]> Date: Sun Sep 20 15:02:28 2026 +0800 gccrs: Initial implementation of hotpatching rustc-lib Addresses Rust-GCC/gccrs#4835 libgrust/ChangeLog: * rustc-lib-patches/patches/0001-core-ffi-c-str.for-rfl-patch: Backported patch to support CStr parsing. * rustc-lib-patches/patches/0002-alloc-vec-extract-if.for-rfl-patch: Backported vec's implementation of extract_if. * rustc-lib-patches/rustc-lib-patch.py: Script to apply the patch files to rustc-lib. * rustc-lib-patches/README.md: the README file for this dir. Signed-off-by: Yap Zhi Heng <[email protected]> Diff: --- libgrust/rustc-lib-patches/README.md | 23 +++ .../patches/0001-core-ffi-c-str.for-rfl-patch | 55 ++++++ .../0002-alloc-vec-extract-if.for-rfl-patch | 150 ++++++++++++++++ libgrust/rustc-lib-patches/rustc-lib-patch.py | 193 +++++++++++++++++++++ 4 files changed, 421 insertions(+) diff --git a/libgrust/rustc-lib-patches/README.md b/libgrust/rustc-lib-patches/README.md new file mode 100644 index 000000000000..44f6af80f8dc --- /dev/null +++ b/libgrust/rustc-lib-patches/README.md @@ -0,0 +1,23 @@ +# Rustc lib patches + +This directory contains backported patches that are required for Rust for Linux to compile. This is because our current target version for `core` is 1.49.0, while Rust for Linux requires 1.85.0 (since Linux kernel 7.1). + +It should be noted that the patches are intentionally kept separate from the rustc-lib directory that contains version 1.49 of the `core` library. This is because gccrs is also not only aiming to compile `core` 1.49, but also currently experimenting with compiling Rust for Linux. **Only use the patches in this directory if you are compiling Rust for Linux with gccrs.** + +## Usage + +Run `rustc-lib-patch.py` to apply the patches to the `rustc-lib` directory in your local GCC repository. + +If you want to do it outside of the tree, specify `-o` to output the patched library directory to a different location without affecting the original `rustc-lib` directory. For example: + +```bash +python3 rustc-lib-patch.py -o /path/to/output/rustc-lib +``` + +## Contributing new patches + +1. First, apply all the existing patches to your local `rustc-lib` directory with the python script. Make sure that the library is in a clean state (no uncommitted changes) before applying the patches. +2. Make the necessary changes to the library and run `rustc-lib-patch.py --generate 0003-my-new-patch.for-rfl-patch` to generate a new patch file. +3. Submit a pull request with the new patch file added to `patches/`. + +The more advanced git users can just run `git diff` to generate a patch, but the script ensures that the patch file is generated in a way that is compatible with the existing patches. diff --git a/libgrust/rustc-lib-patches/patches/0001-core-ffi-c-str.for-rfl-patch b/libgrust/rustc-lib-patches/patches/0001-core-ffi-c-str.for-rfl-patch new file mode 100644 index 000000000000..8e045fb7b1e2 --- /dev/null +++ b/libgrust/rustc-lib-patches/patches/0001-core-ffi-c-str.for-rfl-patch @@ -0,0 +1,55 @@ +diff --git a/libgrust/rustc-lib/core/src/ffi.rs b/libgrust/rustc-lib/core/src/ffi.rs +index 4b303acfd3b..9638b8faa1a 100644 +--- a/libgrust/rustc-lib/core/src/ffi.rs ++++ b/libgrust/rustc-lib/core/src/ffi.rs +@@ -405,3 +405,49 @@ extern "rust-intrinsic" { + /// argument `ap` points to. + fn va_arg<T: sealed_trait::VaArgSafe>(ap: &mut VaListImpl<'_>) -> T; + } ++ ++/// Minimal implementation of CStr to make CStr parsing work on 1.49.0 ++crate::cfg_select! { ++ all( ++ not(windows), ++ not(target_vendor = "apple"), ++ not(target_os = "vita"), ++ any( ++ target_arch = "aarch64", ++ target_arch = "arm", ++ target_arch = "csky", ++ target_arch = "hexagon", ++ target_arch = "msp430", ++ target_arch = "powerpc", ++ target_arch = "powerpc64", ++ target_arch = "riscv32", ++ target_arch = "riscv64", ++ target_arch = "s390x", ++ target_arch = "xtensa", ++ ) ++ ) => { ++ pub type c_char = u8; ++ } ++ // On every other target, c_char is signed. ++ _ => { ++ pub type c_char = i8; ++ } ++} ++ ++#[derive(PartialEq, Eq, Hash)] ++#[stable(feature = "core_c_str", since = "1.64.0")] ++#[rustc_diagnostic_item = "cstr_type"] ++#[lang = "CStr"] ++#[repr(transparent)] ++pub struct CStr { ++ inner: [c_char], ++} ++ ++#[stable(feature = "cstr_default", since = "1.10.0")] ++#[rustc_const_unstable(feature = "const_default", issue = "143894")] ++impl Default for &CStr { ++ #[inline] ++ fn default() -> Self { ++ c"" ++ } ++} +\ No newline at end of file diff --git a/libgrust/rustc-lib-patches/patches/0002-alloc-vec-extract-if.for-rfl-patch b/libgrust/rustc-lib-patches/patches/0002-alloc-vec-extract-if.for-rfl-patch new file mode 100644 index 000000000000..eec842126d5c --- /dev/null +++ b/libgrust/rustc-lib-patches/patches/0002-alloc-vec-extract-if.for-rfl-patch @@ -0,0 +1,150 @@ +diff --git a/libgrust/rustc-lib/alloc/src/vec.rs b/libgrust/rustc-lib/alloc/src/vec.rs +index 2c8bc3d53ef..e17be0e431c 100644 +--- a/libgrust/rustc-lib/alloc/src/vec.rs ++++ b/libgrust/rustc-lib/alloc/src/vec.rs +@@ -2500,6 +2500,15 @@ impl<T> Vec<T> { + + DrainFilter { vec: self, idx: 0, del: 0, old_len, pred: filter, panic_flag: false } + } ++ ++ #[stable(feature = "extract_if", since = "1.87.0")] ++ pub fn extract_if<F, R>(&mut self, range: R, filter: F) -> ExtractIf<'_, T, F> ++ where ++ F: FnMut(&mut T) -> bool, ++ R: RangeBounds<usize>, ++ { ++ ExtractIf::new(self, filter, range) ++ } + } + + /// Extend implementation that copies elements out of references before pushing them onto the Vec. +@@ -3437,3 +3446,129 @@ where + } + } + } ++ ++// Backported minimal implementation of extract_if ++// Note that the allocator-related code removed for 1.49 compatibility ++ ++/// An iterator which uses a closure to determine if an element should be removed. ++/// ++/// This struct is created by [`Vec::extract_if`]. ++/// See its documentation for more. ++/// ++/// # Example ++/// ++/// ``` ++/// let mut v = vec![0, 1, 2]; ++/// let iter: std::vec::ExtractIf<'_, _, _> = v.extract_if(.., |x| *x % 2 == 0); ++/// ``` ++#[stable(feature = "extract_if", since = "1.87.0")] ++pub struct ExtractIf< ++ 'a, ++ T, ++ F ++> { ++ vec: &'a mut Vec<T>, ++ /// The index of the item that will be inspected by the next call to `next`. ++ idx: usize, ++ /// Elements at and beyond this point will be retained. Must be equal or smaller than `old_len`. ++ end: usize, ++ /// The number of items that have been drained (removed) thus far. ++ del: usize, ++ /// The original length of `vec` prior to draining. ++ old_len: usize, ++ /// The filter test predicate. ++ pred: F, ++} ++ ++#[stable(feature = "extract_if", since = "1.87.0")] ++impl<'a, T, F> ExtractIf<'a, T, F> { ++ pub(super) fn new<R: RangeBounds<usize>>(vec: &'a mut Vec<T>, pred: F, range: R) -> Self { ++ let old_len = vec.len(); ++ // BACKPORT: updated slice::range to range.assert_len for 1.49 compatibility ++ let Range { start, end } = range.assert_len(old_len); ++ ++ // Guard against the vec getting leaked (leak amplification) ++ // SAFETY: Setting length to 0 is always okay. ++ unsafe { ++ vec.set_len(0); ++ } ++ ExtractIf { vec, idx: start, del: 0, end, old_len, pred } ++ } ++} ++ ++#[stable(feature = "extract_if", since = "1.87.0")] ++impl<T, F> Iterator for ExtractIf<'_, T, F> ++where ++ F: FnMut(&mut T) -> bool, ++{ ++ type Item = T; ++ ++ fn next(&mut self) -> Option<T> { ++ while self.idx < self.end { ++ let i = self.idx; ++ // SAFETY: ++ // We know that `i < self.end` from the if guard and that `self.end <= self.old_len` from ++ // the validity of `Self`. Therefore `i` points to an element within `vec`. ++ // ++ // Additionally, the i-th element is valid because each element is visited at most once ++ // and it is the first time we access vec[i]. ++ // ++ // Note: we can't use `vec.get_unchecked_mut(i)` here since the precondition for that ++ // function is that i < vec.len(), but we've set vec's length to zero. ++ let cur = unsafe { &mut *self.vec.as_mut_ptr().add(i) }; ++ let drained = (self.pred)(cur); ++ // Update the index *after* the predicate is called. If the index ++ // is updated prior and the predicate panics, the element at this ++ // index would be leaked. ++ self.idx += 1; ++ if drained { ++ self.del += 1; ++ // SAFETY: We never touch this element again after returning it. ++ return Some(unsafe { ptr::read(cur) }); ++ } else if self.del > 0 { ++ // SAFETY: `self.del` > 0, so the hole slot must not overlap with current element. ++ // We use copy for move, and never touch this element again. ++ unsafe { ++ let hole_slot = self.vec.as_mut_ptr().add(i - self.del); ++ ptr::copy_nonoverlapping(cur, hole_slot, 1); ++ } ++ } ++ } ++ None ++ } ++ ++ fn size_hint(&self) -> (usize, Option<usize>) { ++ (0, Some(self.end - self.idx)) ++ } ++} ++ ++#[stable(feature = "extract_if", since = "1.87.0")] ++impl<T, F> Drop for ExtractIf<'_, T, F> { ++ fn drop(&mut self) { ++ if self.del > 0 { ++ // SAFETY: Trailing unchecked items must be valid since we never touch them. ++ unsafe { ++ ptr::copy( ++ self.vec.as_ptr().add(self.idx), ++ self.vec.as_mut_ptr().add(self.idx - self.del), ++ self.old_len - self.idx, ++ ); ++ } ++ } ++ // SAFETY: After filling holes, all items are in contiguous memory. ++ unsafe { ++ self.vec.set_len(self.old_len - self.del); ++ } ++ } ++} ++ ++// BACKPORT: minimized Debug impl to also reduce chance of compilation error ++#[stable(feature = "extract_if", since = "1.87.0")] ++impl<T, F> fmt::Debug for ExtractIf<'_, T, F> ++where ++ T: fmt::Debug, ++{ ++ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ++ f.write_str("ExtractIf { .. }") ++ } ++} diff --git a/libgrust/rustc-lib-patches/rustc-lib-patch.py b/libgrust/rustc-lib-patches/rustc-lib-patch.py new file mode 100755 index 000000000000..437cbb955f42 --- /dev/null +++ b/libgrust/rustc-lib-patches/rustc-lib-patch.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2026 Free Software Foundation, Inc. +# +# This file is part of GCC. +# +# GCC is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation; either version 3, or (at your option) any later +# version. +# +# GCC is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# for more details. +# +# You should have received a copy of the GNU General Public License +# along with GCC; see the file COPYING3. If not see +# <http://www.gnu.org/licenses/>. + + +"""Apply the adjacent patch series to the vendored Rust libraries.""" + +import argparse +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile + +SCRIPT_DIR = Path(__file__).resolve().parent +RUSTC_LIB_DIR = SCRIPT_DIR.parent / "rustc-lib" +PATCHES_DIR = SCRIPT_DIR / "patches" +PATCH_FILENAME_SUFFIX = ".for-rfl-patch" + + +def git(directory, *arguments, data=None, check=False): + result = subprocess.run( + ["git", "-C", str(directory), *arguments], + input=data, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if check and result.returncode: + raise RuntimeError(result.stderr.decode(errors="replace").strip()) + return result + + +def apply_patch(directory, data, reverse=False, check=False): + # strip a/libgrust/rustc-lib/ from repository-relative patch paths + arguments = ["apply", "-p3"] + prefix = git(directory, "rev-parse", "--show-prefix") + + if prefix.returncode == 0 and prefix.stdout.strip(): + arguments.append(f"--directory={prefix.stdout.decode().strip()}") + if reverse: + arguments.append("--reverse") + if check: + arguments.append("--check") + return git(directory, *arguments, data=data) + + +def patch_in_place(directory=RUSTC_LIB_DIR): + for patch in sorted(PATCHES_DIR.glob(f"*{PATCH_FILENAME_SUFFIX}")): + data = patch.read_bytes() + if apply_patch(directory, data, check=True).returncode: + # skip patches that can't be applied because they might have already been applied + continue + result = apply_patch(directory, data) + if result.returncode: + raise RuntimeError( + f"failed to apply {patch.name}: " + f"{result.stderr.decode(errors='replace').strip()}" + ) + + +def patch_library(output=None): + if output is not None: + shutil.copytree(RUSTC_LIB_DIR, output) + patch_in_place(output) + else: + patch_in_place() + + +def generate_patch(name): + if not name.endswith(PATCH_FILENAME_SUFFIX): + raise RuntimeError(f"patch filename should end with {PATCH_FILENAME_SUFFIX}") + output_patch = PATCHES_DIR / name + if output_patch.exists(): + raise RuntimeError(f"patch filename already exists: {output_patch}") + prefix = ( + git( + RUSTC_LIB_DIR, + "rev-parse", + "--show-prefix", + check=True, + ) + .stdout.decode() + .strip() + ) + tree = git( + RUSTC_LIB_DIR, + "rev-parse", + f"HEAD:{prefix.rstrip('/')}", + check=True, + ).stdout + objects = git( + RUSTC_LIB_DIR, + "rev-parse", + "--git-path", + "objects", + check=True, + ).stdout + objects = (RUSTC_LIB_DIR / objects.decode().strip()).resolve() + patches = sorted(PATCHES_DIR.glob(f"*{PATCH_FILENAME_SUFFIX}")) + + # using a temporary repository so generation cannot change the user's index + with tempfile.TemporaryDirectory(prefix="rustc-lib-patch-") as temporary: + directory = Path(temporary) + git(directory, "init", "--quiet", check=True) + (directory / ".git/objects/info/alternates").write_text(str(objects) + "\n") + git(directory, "read-tree", tree.decode().strip(), check=True) + for patch in patches: + result = git(directory, "apply", "--cached", "-p3", data=patch.read_bytes()) + if result.returncode: + raise RuntimeError( + f"failed to apply {patch.name} to HEAD: " + f"{result.stderr.decode(errors='replace').strip()}" + ) + baseline = git(directory, "write-tree", check=True).stdout.decode().strip() + git( + directory, + f"--work-tree={RUSTC_LIB_DIR}", + "add", + "-A", + "--", + ".", + check=True, + ) + data = git( + directory, + "diff", + "--cached", + "--binary", + "--no-ext-diff", + "--no-textconv", + "--no-renames", + "--no-color", + "--src-prefix=a/libgrust/rustc-lib/", + "--dst-prefix=b/libgrust/rustc-lib/", + baseline, + "--", + ".", + check=True, + ).stdout + + if not data: + print("No changes beyond the existing patch series.") + return + + with output_patch.open("xb") as output: + output.write(data) + print(f"Generated {output_patch}") + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + mode = parser.add_mutually_exclusive_group() + mode.add_argument( + "-o", + "--output", + type=Path, + help="write a patched copy to a new directory instead of patching in place", + ) + mode.add_argument( + "--generate", + metavar=f"NAME{PATCH_FILENAME_SUFFIX}", + help=f"write local changes beyond the existing patches to patches/NAME{PATCH_FILENAME_SUFFIX}", + ) + args = parser.parse_args(argv) + try: + if args.generate is not None: + generate_patch(args.generate) + else: + patch_library(args.output) + except (RuntimeError, OSError) as error: + print(f"{parser.prog}: error: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main())
