From: Yap Zhi Heng <[email protected]>
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]>
Tested on x86_64-pc-linux-gnu.
---
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(+)
create mode 100644 libgrust/rustc-lib-patches/README.md
create mode 100644
libgrust/rustc-lib-patches/patches/0001-core-ffi-c-str.for-rfl-patch
create mode 100644
libgrust/rustc-lib-patches/patches/0002-alloc-vec-extract-if.for-rfl-patch
create mode 100755 libgrust/rustc-lib-patches/rustc-lib-patch.py
diff --git a/libgrust/rustc-lib-patches/README.md
b/libgrust/rustc-lib-patches/README.md
new file mode 100644
index 00000000000..44f6af80f8d
--- /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 00000000000..8e045fb7b1e
--- /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 00000000000..eec842126d5
--- /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 00000000000..437cbb955f4
--- /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())
--
2.55.0