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

Reply via email to