About the Project
Welcome to the final work product submission for my Google Summer of Code project.
This book serves as the comprehensive final report and technical documentation for my work on the gccrs compiler (the Rust front-end for GCC).
Below is a summary of the project. For in-depth technical details, methodologies, and code architecture, please navigate through the chapters using the sidebar.
What Were the Project Goals?
The primary goal of this project was to implement the compiler infrastructure required to compile Rust’s alloc crate natively within gccrs. This involved identifying, designing, and implementing missing lang items, compiler intrinsics, built-in attributes, and complex architectural features (like DSTs, Fat Pointers, and Unsized Coercions). For the underlying reasoning, refer to Why Compile the ‘alloc’ Crate?.
What Was Accomplished?
I addressed all the missing pieces required by the alloc crate. During development, my primary priority was always a full, end-to-end implementation.
In cases where a full implementation was strictly blocked by upstream compiler limitations, features were safely registered as stubs. Every stub was a deliberate, well-considered decision made to unblock standard library parsing without introducing unsound behavior. Detailed breakdowns can be found in What Has Been Done? and the complete list of PRs in Contributions.
What is the Current State?
The architectural groundwork for alloc is firmly in place. Currently, we can successfully compile the alloc crate up to the name resolution phase using a custom mock core module. Every individual feature and intrinsic implemented during this project is fully functional and covered by isolated regression tests in the gccrs test suite. This architecture is detailed in Current State & Mock Core Architecture.
What is Left to Do?
From a strictly alloc-specific perspective, the fundamental infrastructure is complete. The remaining work involves routine maintenance on the newly merged patches. The ultimate goal—compiling alloc end-to-end—is currently bottlenecked by the ongoing development of the upstream core crate support. As the gccrs mature, the temporary stubs we established will be cleanly replaced with their final backend implementations. For future projections, see Roadmap & Next Steps.
Note
To Reproduce the Work: If you wish to build the compiler and test these changes locally, I have created a fully reproducible Nix flake environment. View Setup Instructions
Why Compile the ‘alloc’ Crate?
The gccrs project has already made significant strides in compiling the core crate. Successfully compiling the alloc crate is the natural and most critical next milestone for several strategic reasons.
The “Rust for Linux” Initiative
One of the primary driving forces behind gccrs is the ability to compile the Linux kernel (Rust for Linux). While kernel development strictly avoids the standard library (std), it heavily relies on the core crate and a specialized subset of alloc for dynamic memory management. For gccrs to compile kernel drivers, the compiler must fully understand alloc’s underlying mechanisms, such as dynamic sized types (DSTs), allocator intrinsics, and pointer coercions.
The Bridge to std
The Rust standard library is built as a layered architecture:
┌──────────────┐
│ std │
│ ┌──────────┐ │
│ │ alloc │ │
│ │ ┌──────┐ │ │
│ │ │ core │ │ │
│ │ └──────┘ │ │
│ └──────────┘ │
└──────────────┘
Compiling alloc is a strict prerequisite. We cannot achieve our long-term goal of compiling the full std library without first establishing a robust foundation for memory allocation.
Real-World Applications
Almost all practical, user-space Rust program relies on dynamic memory. Data structures like Vec<T>, String, Box<T>, and Rc<T> all live in alloc. Teaching the compiler how to handle these structures is essential for gccrs to become a viable alternative for compiling everyday Rust programs.
Note
The motivations outlined here reflect the technical context of my GSoC project. For the official and most up-to-date roadmap of the overall compiler, please refer to the documentation and presentations published by the
gccrscore team.
Why I Chose This Challenge
As a student interested in compiler engineering—and a passionate Rustacean—working on the alloc crate presented an ideal set of technical challenges.
Compiling alloc goes far beyond simply registering missing lang items or attributes. Each feature required a complete engineering loop:
- Analyzing
rustcsource code, language specifications, and documentation to verify expected behaviors. - Debugging and extending various compiler passes—ranging from AST lowering and early type checking to vtable generation and backend code generation.
- Translating Rust’s memory semantics into GCC’s internal representations (such as GIMPLE and Tree structures).
This project provided a unique hands-on opportunity to gain deep insights into Rust’s type system, layout engine, and dynamic allocation mechanics while directly contributing to production compiler infrastructure.
About the ‘alloc’ Crate
The alloc crate introduces dynamic memory (heap) allocation capabilities to the Rust language. While the core crate is strictly limited to stack-based and statically sized operations, alloc provides the foundational primitives for heap management, such as the Box<T>, Vec<T>, and Reference Counted pointers (Rc<T>, Arc<T>).
However, enabling these capabilities comes with a high cost for the compiler.
To successfully compile alloc, the compiler must flawlessly support several advanced language features:
- Dynamic Sized Types (DSTs): Handling types whose size is unknown at compile time (like
[T]andstr). - Fat Pointers & Vtables: Generating and managing the memory layout for trait objects (dyn Trait).
- Unsized Coercions & Auto-Deref: Automatically converting thin pointers to fat pointers and resolving method calls on heap-allocated structs (like
Box).
The “Mock Core” Strategy
Architecturally, alloc sits directly on top of core and strictly demands full core support to function. Currently, gccrs is not yet able to compile the entire standard core crate end-to-end.
To bypass this blocker and make independent progress, we employ a “mock core” strategy in our test suite. By injecting a stripped-down, primitive core implementation as a submodule, we can isolate and test the new compiler features we built for alloc.
Missing Pieces
When the project started, the goal was clear: compile the alloc crate. However, alloc is a massive codebase heavily coupled with core. To understand exactly what gccrs was missing, we needed to map out the crate’s internal dependencies and reverse-engineer its requirements from the bottom up.
The Dependency Analysis Methodology
Instead of blindly feeding the entire crate to the compiler and chasing a wall of errors, I performed a strict static analysis of the alloc source code.
Mapping core Usage: I scanned every file in alloc (e.g., alloc.rs, boxed.rs, vec.rs) to extract all core:: import statements, macros, lang items, and built-in attributes.
Grouping by Logical Chunks: alloc contains deeply intertwined files. By analyzing internal dependencies (e.g., vec.rs relies on raw_vec.rs, which relies on alloc.rs), I divided the entire crate into 5 logical, manageable chunks.
Filtering Supported Features: I cross-referenced the extracted list against the current gccrs codebase to filter out features that were already supported (like standard traits, core::fmt, basic macros, and standard attributes).
What remained was the exact recipe of technical gaps blocking the compilation of alloc.
The Missing Pieces: What We Found
We categorized these missing pieces into three major domains:
Missing Lang Items
alloc_layout, box_free, coerce_unsized, dispatch_from_dyn, drop, drop_in_place, exchange_malloc, future_trait, generator, generator_state, maybe_uninit, oom, owned_box, pending, pin, poll, range_inclusive_new, ready, unsize, unpin, unsafe_cell,
Missing Intrinsics
arith_offset, assert_zero_valid, min_align_of, min_align_of_val, size_of_val, write_bytes
Missing Attributes
needs_allocator, rustc_allocator, rustc_allocator_nounwind, rustc_conversion_suggestion, rustc_std_internal_symbol
Missing Infrastructure
Dynamic Sized Types (DSTs): Slices ([T]) and Trait Objects (dyn Trait) don’t have a known size at compile time. This is supported, but it needs to be extended to include ADTs.
Fat Pointers & Vtables: Pointers to DSTs require two machine words (data address + size/vtable address). The compiler’s vtable generation needed a major refactor.
Unsized Coercions: The ability to automatically coerce a thin pointer to an array (&[i32; 3]) into a fat pointer to a slice (&[i32]), or to a trait object.
If you are a compiler developer and want to see the exact file-by-file raw data, dependency mappings, and how the crate was grouped, just continue to the next page.
Current Status & Progress: Skip the raw data and see what we actually accomplished.
Dependency Analysis
This page contains the raw data and dependency mappings generated during the initial static analysis of the alloc crate. This data was used to isolate the compiler’s missing pieces and construct the “mock core” needed for testing.
Note
Click on the sections below to expand and view the raw data.
First, I identified which core structures each alloc file uses to establish a baseline of requirements.
Core Dependencies by File
#![allow(unused)]
fn main() {
// lib.rs
pub use core::format_args; // built-in
pub use core::ops; // RangeFull
// alloc.rs
use core::intrinsics::{min_align_of_val, size_of_val, assume};
use core::ptr::{ NonNull, Unique, copy_nonoverlapping };
pub use core::alloc::*;
panic!
debug_assert!
#[lang = "oom"];
#[rustc_std_internal_symbol]
#[rustc_allocator]
#[rustc_allocator_nounwind]
#[lang = "exchange_malloc"]
// borrow.rs
use core::cmp::Ordering;
use core::hash::{Hash, Hasher};
use core::ops::{Add, AddAssign, Deref};
pub use core::borrow::{Borrow, BorrowMut};
unrachable!
// boxed.rs
use core::any::Any;
use core::borrow::{Borrow, BorrowMut};
use core::cmp::Ordering;
use core::convert::{From, TryFrom};
use core::fmt::{Display, Debug,Formatter, Result,Pointer};
use core::future::Future;
use core::hash::{Hash, Hasher};
use core::iter::{FromIterator, FusedIterator, Iterator};
use core::marker::{Unpin, Unsize};
use core::mem::{ManuallyDrop,MaybeUninit};
use core::ops::{
CoerceUnsized, Deref, DerefMut, DispatchFromDyn, Generator, GeneratorState, Receiver,
};
use core::pin::Pin;
use core::ptr::{read,copy_nonoverlapping, Unique};
use core::task::{Context, Poll};
#[lang = "owned_box"]
#[fundamental]
// fmt.rs
pub use core::fmt::rt; // ? non-used
pub use core::fmt::Alignment;
pub use core::fmt::Error;
pub use core::fmt::{write, ArgumentV1, Arguments};
pub use core::fmt::{Binary, Octal};
pub use core::fmt::{Debug, Display};
pub use core::fmt::{DebugList, DebugMap, DebugSet, DebugStruct, DebugTuple};
pub use core::fmt::{Formatter, Result, Write};
pub use core::fmt::{LowerExp, UpperExp};
pub use core::fmt::{LowerHex, Pointer, UpperHex};
// macros.rs
macro_rules!
#[macro_export]
// raw_vec.rs
use core::alloc::LayoutErr;
use core::cmp::{max};
use core::intrinsics::{assume};
use core::mem::{size_of,align_of, ManuallyDrop, MaybeUninit};
use core::ops::Drop;
use core::ptr::{read, NonNull, Unique};
use core::slice::from_raw_parts_mut;
debug_assert_ne!
debug_assert!
assert!
debug_assert_eq!
panic!
#[may_dangle]
// rc.rs
use core::any::Any;
use core::borrow::Borrow;
use core::cell::Cell;
use core::cmp::Ordering;
use core::convert::{From, TryFrom};
use core::fmt::{Display, Debug,Formatter, Result,Pointer};
use core::hash::{Hash, Hasher};
use core::intrinsics::abort;
use core::iter::{FromIterator,IntoIterator,TrustedLen,Iterator};
use core::marker::{Send,Sync, PhantomData, Unpin, Unsize};
use core::mem::{MaybeUninit,forget,ManuallyDrop,swap, align_of_val_raw, forget, size_of_val};
use core::ops::{CoerceUnsized, Deref, DispatchFromDyn, Receiver};
use core::pin::Pin;
use core::ptr::{read,drop_in_place,write,copy_nonoverlapping,slice_from_raw_parts_mut, NonNull};
use core::slice::from_raw_parts_mut;
debug_assert_eq!
write!
#[repr(C)]
#[may_dangle]
#[rustc_unsafe_specialization_marker]
// slice.rs
use core::borrow::{Borrow, BorrowMut};
use core::cmp::Ordering;
use core::cmp::Ordering::Less;
use core::mem::{size_of, ManuallyDrop};
use core::ptr::{copy_nonoverlapping,read};
pub use core::slice::ArrayChunks;
pub use core::slice::ArrayChunksMut;
pub use core::slice::ArrayWindows;
pub use core::slice::SliceIndex;
pub use core::slice::{from_mut, from_ref};
pub use core::slice::{from_raw_parts, from_raw_parts_mut};
pub use core::slice::{Chunks, Windows};
pub use core::slice::{ChunksExact, ChunksExactMut};
pub use core::slice::{ChunksMut, Split, SplitMut};
pub use core::slice::{Iter, IterMut};
pub use core::slice::{RChunks, RChunksExact, RChunksExactMut, RChunksMut};
pub use core::slice::{RSplit, RSplitMut};
pub use core::slice::{RSplitN, RSplitNMut, SplitN, SplitNMut};
sort_by_key! __not_core!
#[lang = "slice_u8_alloc"]
#[lang = "slice_alloc"]
#[derive(Clone, Copy)]
#[rustc_conversion_suggestion]
// str.rs
use core::borrow::{Borrow, BorrowMut};
use core::iter::FusedIterator;
use core::mem::{take};
use core::ptr; // ? non-used
use core::str::pattern::{DoubleEndedSearcher, Pattern, ReverseSearcher, Searcher};
use core::unicode::conversions::{to_lower,to_upper};
pub use core::str::pattern;
pub use core::str::EncodeUtf16;
pub use core::str::SplitAsciiWhitespace;
pub use core::str::SplitWhitespace;
pub use core::str::{from_utf8, from_utf8_mut, Bytes, CharIndices, Chars};
pub use core::str::{from_utf8_unchecked, from_utf8_unchecked_mut, ParseBoolError};
pub use core::str::{EscapeDebug, EscapeDefault, EscapeUnicode};
pub use core::str::{FromStr, Utf8Error};
pub use core::str::{Lines, LinesAny};
pub use core::str::{MatchIndices, RMatchIndices};
pub use core::str::{Matches, RMatches};
pub use core::str::{RSplit, Split};
pub use core::str::{RSplitN, SplitN};
pub use core::str::{RSplitTerminator, SplitTerminator};
use core::unicode::derived_property::{Case_Ignorable, Cased};
debug_assert!
spezialize_for_lengths! __not_core!
assert!
copy_slice_and_advance! __not_core!
#[lang = "str_alloc"]
// string.rs
use core::char::{decode_utf16, REPLACEMENT_CHARACTER};
use core::fmt::{Formatter,Debug,Result,Display,Write};
use core::hash::{Hash, Hasher};
use core::iter::{FromIterator, FusedIterator};
use core::ops::Bound::{Excluded, Included, Unbounded};
use core::ops::{Index,Range,RangeTo,RangeFrom,RangeFull,RangeInclusive,RangeToInclusive,IndexMut,Deref,DerefMut, Add, AddAssign, Index, IndexMut, Range, RangeBounds};
use core::ptr::copy;
use core::str::{lossy, pattern::Pattern};
core::convert::Infallible;
panic!
debug_assert!
assert!
format_args!
#[rustc_conversion_suggestion]
#[derive(Debug, Clone, PartialEq, Eq,)]
#[derive(PartialOrd, Ord)]
// sync.rs
use core::any::Any;
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::convert::{From, TryFrom};
use core::fmt::{Pointer,Formatter,Result,Debug,Result};
use core::hash::{Hash, Hasher};
use core::hint::spin_loop;
use core::intrinsics::abort;
use core::iter::{Iterator,FromIterator,IntoIterator, TrustedLen};
use core::marker::{PhantomData, Unpin, Unsize};
use core::mem::{swap,drop,MaybeUninit,forget,ManuallyDrop, align_of_val, size_of_val};
use core::ops::{CoerceUnsized, Deref, DispatchFromDyn, Receiver};
use core::pin::Pin;
use core::ptr::{write,drop_in_place,read,copy_nonoverlapping,slice_from_raw_parts_mut, NonNull};
use core::slice::from_raw_parts_mut;
use core::sync::atomic::{fence,AtamicUsize};
use core::sync::atomic::Ordering::{Acquire, Relaxed, Release, SeqCst};
write!
debug_assert_eq!
acquire! __non_core!
debug_assert!
#[may_dangle]
#[repr(C)]
// task.rs
use core::mem::ManuallyDrop;
use core::task::{RawWaker, RawWakerVTable, Waker};
// vec.rs
use core::cmp::{max, Ordering};
use core::convert::TryFrom;
use core::fmt::{Debug,Formatter,Result,Display};
use core::hash::{Hash, Hasher};
use core::intrinsics::{arith_offset, assume};
use core::iter::{ FromIterator, FusedIterator, InPlaceIterable, SourceIter, TrustedLen, TrustedRandomAccess };
use core::marker::PhantomData;
use core::mem::{size_of,zeroed,forget,replace,align_of, ManuallyDrop, MaybeUninit};
use core::ops::{DerefMut,Deref, Index, IndexMut, Range, RangeBounds};
use core::ptr::{replace,copy,write,read,write_bytes ,copy_nonoverlapping ,slice_from_raw_parts_mut,drop_in_place, NonNull};
use core::slice::{from_raw_parts_mut,from_raw_parts,to_vec,into_vec,Iter,IterMut,, SliceIndex};
debug_assert!
panic!
impl_is_zero! __not_core!
debug_assert_eq!
macro_rules!
#[derive(Debug)]
#[rustc_specialization_trait]
#[rustc_unsafe_specialization_marker]
#[cold]
// collections/mod.rs
use core::fmt::Display;
core::result::Result;
core::fmt::Error
// collections/linked_list.rs
use core::cmp::Ordering;
use core::fmt::{Debug,Formatter, Result};
use core::hash::{Hash, Hasher};
use core::iter::{FromIterator, FusedIterator};
use core::marker::PhantomData;
use core::mem::{forget,replace,swap,take,};
use core::ptr::NonNull;
core::hint::unreachable_unchecked()
assert!
#[may_dangle]
// collections/vec_deque.rs
use core::array::IntoIter;
use core::cmp::{min,max,, Ordering};
use core::fmt::{Debug,Formatter, Result};
use core::hash::{Hash, Hasher};
use core::iter::{repeat_with, FromIterator, FusedIterator};
use core::marker::PhantomData;
use core::mem::{size_of, replace, ManuallyDrop};
use core::ops::{Index, IndexMut, Range, RangeBounds, Try};
use core::ptr::{drop_in_place,read,write,copy,copy_nonoverlapping,swap,slice_from_raw_parts_mut, NonNull};
use core::slice::{from_raw_parts_mut,from_raw_parts};
core::mem::size_of
macro_rules!
debug_assert_eq!
assert!
debug_assert!
#[may_dangle]
#[derive(Clone)]
// collections/vec_deque/drain.rs
use core::iter::FusedIterator;
use core::ptr::{NonNull,read,};
use core::fmt::{Debug,Formatter, Result};
use core::mem::forget;
// collections/btree/append.rs
use core::iter::FusedIterator;
unrachable!
// collections/btree/borrow.rs
use core::marker::PhantomData;
use core::ptr::NonNull;
// collections/btree/map.rs
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::fmt::{Debug,Formatter,Result};
use core::hash::{Hash, Hasher};
use core::iter::{FromIterator, FusedIterator};
use core::marker::PhantomData;
use core::mem::{replace,swap,take,forget, ManuallyDrop};
use core::ops::{Index, RangeBounds};
use core::ptr::read;
unreachable!
#[may_dangle]
#[derive(Debug)]
// collections/btree/map/entry.rs
use core::fmt::{Result,Formatter, Debug};
use core::marker::PhantomData;
use core::mem::replace;
// collections/btree/mem.rs
use core::intrinsics::abort;
use core::mem::forget;
use core::ptr::{read,write};
// collections/btree/merge_iter.rs
use core::cmp::Ordering;
use core::fmt::{Formatter,Result, Debug};
use core::iter::FusedIterator;
#[derive(Clone, Debug)]
// collections/btree/mod.rs
core::intrinsics::unreachable();
cfg!
panic!
// collections/btree/navigate.rs
use core::borrow::Borrow;
use core::cmp::Ordering;
use core::ops::Bound::{Excluded, Included, Unbounded};
use core::ops::RangeBounds;
use core::ptr::read;
panic!
unreachable!
// collections/btree/node.rs
use core::cmp::Ordering;
use core::marker::PhantomData;
use core::mem::{replace, MaybeUninit};
use core::ptr::{read,copy,copy_nonoverlapping, NonNull, Unique};
use core::marker::PhantomData;
assert!
unreachable!
debug_assert!
debug_assert_eq!
#[repr(C)]
// collections/btree/remove.rs
use core::mem::replace;
use core::ptr::read;
unreachable!
// collections/btree/search.rs
use core::borrow::Borrow;
use core::cmp::Ordering;
// collections/btree/set.rs
use core::borrow::Borrow;
use core::cmp::Ordering::{Equal, Greater, Less};
use core::cmp::{max, min};
use core::fmt::{Formatter,Result, Debug};
use core::iter::{FromIterator, FusedIterator, Peekable};
use core::ops::{BitAnd, BitOr, BitXor, RangeBounds, Sub};
#[derive(Debug)]
// collections/btree/split.rs
use core::borrow::Borrow;
debug_assert!
unreachable!
// collections/binary_heap.rs
use core::fmt::{Debug,Formatter, Result};
use core::iter::{FromIterator, FusedIterator, InPlaceIterable, SourceIter, TrustedLen};
use core::mem::{forget, swap, ManuallyDrop};
use core::ops::{Deref, DerefMut};
use core::ptr::{swap,read,copy_nonoverlapping};
debug_assert!
#[derive(Clone, Debug)]
}
The initial raw list contained significant repetition and was difficult to track. Furthermore, it didn’t distinguish between features already supported by the compiler and those that were missing.
To resolve this, I analyzed the internal dependencies within alloc itself. By grouping interdependent files together and deduplicating the required structures, I created a much cleaner recipe. I then cross-referenced this recipe against the gccrs codebase to check off the structures that were already supported.
Result
// lib.rs
pub use core::format_args; ok
pub use core::ops; ok
// target files
1 | alloc.rs
5 | macros.rs
22 | collections/btree/borrow.rs
25 | collections/btree/mem.rs
26 | collections/btree/merge_iter.rs
-----------------------------------------------------------
// use statements
pub use core::alloc::*; ok
core::intrinsics::min_align_of_val; !intrinsic
core::intrinsics::size_of_val; !intrinsic
core::intrinsics::assume; ok
core::ptr::NonNull; ok
core::ptr::Unique; ok
core::ptr::copy_nonoverlapping; #1 ok
core::marker::PhantomData; #22 ok
core::intrinsics::abort; ok
core::mem::forget; ok
core::ptr::read; ok
core::ptr::write; #25 ok
core::cmp::Ordering; ok
core::fmt::Formatter ok
core::fmt::Result; ok
core::fmt::Debug; ok
core::iter::FusedIterator; #26 ok
-----------------------------------------------------------
// macros
panic! ok
debug_assert! #1 ok
macro_rules! #5 ok
-----------------------------------------------------------
// lang items
#[lang = "box_free"] !
#[lang = "oom"]; !
#[lang = "exchange_malloc"] #1 !
-----------------------------------------------------------
// derive macros
#[derive(Clone)] ok
#[derive(Debug)] #26 ok
-----------------------------------------------------------
// attributes
#[rustc_allocator] !
#[rustc_allocator_nounwind] !
#[rustc_std_internal_symbol] #1 !
7 | rc.rs
16 | prelude/v1.rs
24 | collections/btree/map/entry.rs
34 | collections/binary_heap.rs
-----------------------------------------------------------
core::cell::Cell; ok
core::iter::IntoIterator; ok
core::marker::Send; ok
core::marker::Sync; ok
core::mem::align_of_val_raw; !intrinsic
core::mem::size_of_val; #7 !intrinsic
-----------------------------------------------------------
write! ok
2 | borrow.rs
3 | boxed.rs
4 | fmt.rs
6 | raw_vec.rs
8 | slice.rs
9 | str.rs
10 | string.rs
14 | vec.rs
17 | collections/mod.rs
18 | collections/linked_list.rs
19 | collections/vec_deque.rs
20 | collections/vec_deque/drain.rs
21 | collections/append.rs
23 | collections/map.rs
27 | collections/btree/mod.rs
28 | collections/btree/navigate.rs
29 | collections/btree/node.rs
30 | collections/btree/remove.rs
31 | collections/btree/search.rs
32 | collections/btree/set.rs
33 | collections/btree/split.rs
-----------------------------------------------------------
core::borrow::Borrow; ok
core::borrow::BorrowMut; ok
core::hash::Hash; ok
core::hash::Hasher; ok
core::ops::Add; ok
core::ops::AddAssign; ok
core::ops::Deref; ok
core::any::Any; ok
core::convert::From; ok
core::convert::TryFrom; ok
core::fmt::Display; ok
core::fmt::Debug; ok
core::fmt::Pointer; ok
core::future::Future; !lang
core::iter::FromIterator; ok
core::iter::Iterator; ok
core::marker::Unpin; !lang
core::marker::Unsize; !lang
core::mem::ManuallyDrop; ok
core::mem::MaybeUninit; !lang
core::ops::DerefMut; ok
core::ops::DispatchFromDyn; !lang
core::ops::Generator; !lang
core::ops::GeneratorState; !lang
core::ops::Receiver; ok
core::ops::CoerceUnsized; !lang
core::pin::Pin; !lang
core::task::Context; ok
core::task::Poll; !lang
core::fmt::rt; // ok
core::fmt::Alignment; ok
core::fmt::Error; ok
core::fmt::write; ok
core::fmt::ArgumentV1; ok
core::fmt::Arguments; ok
core::fmt::Binary; ok
core::fmt::Octal; ok
core::fmt::DebugList; ok
core::fmt::DebugMap; ok
core::fmt::DebugSet; ok
core::fmt::DebugStruct; ok
core::fmt::DebugTuple; ok
core::fmt::Formatter; ok
core::fmt::Write; ok
core::fmt::LowerExp; ok
core::fmt::UpperExp; ok
core::fmt::LowerHex; ok
core::fmt::UpperHex; ok
core::alloc::LayoutErr; ok
core::cmp::max; ok
core::mem::size_of ok
core::mem::align_of; !intrinsic
core::ops::Drop; !lang
core::slice::from_raw_parts_mut; ok
core::slice::ArrayChunks; ok
core::slice::ArrayChunksMut; ok
core::slice::ArrayWindows; ok
core::slice::SliceIndex; ok
core::slice::from_mut; ok
core::slice::from_ref; ok
core::slice::from_raw_parts; ok
core::slice::Chunks; ok
core::slice::Windows; ok
core::slice::ChunksExact; ok
core::slice::ChunksExactMut; ok
core::slice::ChunksMut; ok
core::slice::Split; ok
core::slice::SplitMut; ok
core::slice::Iter; ok
core::slice::IterMut; ok
core::slice::RChunks; ok
core::slice::RChunksExact; ok
core::slice::RChunksExactMut; ok
core::slice::RChunksMut; ok
core::slice::RSplit; ok
core::slice::RSplitMut; ok
core::slice::RSplitN; ok
core::slice::RSplitNMut; ok
core::slice::SplitN; ok
core::slice::SplitNMut; ok
core::mem::take; ok
core::str::pattern::DoubleEndedSearcher; ok
core::str::pattern::Pattern; ok
core::str::pattern::ReverseSearcher; ok
core::str::pattern::Searcher; ok
core::unicode::conversions::to_upper; ok
core::unicode::conversions::to_lower; ok
core::str::pattern; ok
core::str::EncodeUtf16; ok
core::str::SplitAsciiWhitespace; ok
core::str::SplitWhitespace; ok
core::str::from_utf8; ok
core::str::from_utf8_mut; ok
core::str::Bytes; ok
core::str::CharIndices; ok
core::str::Chars; ok
core::str::from_utf8_unchecked; ok
core::str::from_utf8_unchecked_mut; ok
core::str::ParseBoolError; ok
core::str::EscapeDebug; ok
core::str::EscapeDefault; ok
core::str::EscapeUnicode; ok
core::str::FromStr; ok
core::str::Utf8Error; ok
core::str::Lines; ok
core::str::LinesAny; ok
core::str::MatchIndices; ok
core::str::RMatchIndices; ok
core::str::Matches; ok
core::str::RMatches; ok
core::str::Split; ok
core::str::RSplit; ok
core::str::RSplitN; ok
core::str::SplitN; ok
core::str::RSplitTerminator; ok
core::str::SplitTerminator; ok
core::unicode::derived_property::Case_Ignorable; ok
core::unicode::derived_property::Cased; ok
core::char::decode_utf16; ok
core::char::REPLACEMENT_CHARACTER; ok
core::ops::Bound::Excluded; ok
core::ops::Bound::Included; ok
core::ops::Bound::Unbounded; ok
core::ops::Index; ok
core::ops::Range; ok
core::ops::RangeTo; ok
core::ops::RangeFrom; ok
core::ops::RangeFull; ok
core::ops::RangeInclusive; !lang
core::ops::RangeToInclusive; ok
core::ops::IndexMut; ok
core::ops::RangeBounds; ok
core::ptr::copy; ok
core::str::lossy::Utf8Lossy; ok
core::str::lossy::Utf8LossyChunk ok
core::convert::Infallible; ok
core::intrinsics::arith_offset; !intrinsic
core::iter::InPlaceIterable; ok
core::iter::SourceIter; ok
core::iter::TrustedLen; ok
core::iter::TrustedRandomAccess; ok
core::mem::zeroed; !intrinsic
core::mem::forget; ok
core::mem::replace; ok
core::ptr::replace; ok
core::ptr::write_bytes; !intrinsic
core::ptr::slice_from_raw_parts_mut; ok
core::ptr::drop_in_place; !lang
core::slice::to_vec; ok
core::slice::into_vec; ok
core::result::Result; ok
core::mem::swap; ok
core::hint::unreachable_unchecked(); ok
core::array::IntoIter; ok
core::cmp::min; ok
core::iter::repeat_with; ok
core::ops::Try; ok
core::ptr::swap; ok
core::intrinsics::unreachable; ok
core::iter::Peekable; ok
core::ops::BitAnd; ok
core::ops::BitOr; ok
core::ops::BitXor; ok
core::ops::Sub; ok
-----------------------------------------------------------
unreachable! ok
debug_assert_ne! ok
assert! ok
debug_assert_eq! ok
format_args! ok
cfg! ok
-----------------------------------------------------------
#[lang = "owned_box"] !
#[lang = "slice_u8_alloc"] ok
#[lang = "slice_alloc"] ok
#[lang = "str_alloc"] ok
#[lang = "slice"] ok
#[lang = "slice_u8"] ok
#[lang = "str"] ok
#[lang = "char"] ok
-----------------------------------------------------------
#[derive(Copy)] ok
#[derive(PartialEq)] ok
#[derive(Eq)] ok
#[derive(PartialOrd)] ok
#[derive(Ord)] ok
#[repr(C)] ok
-----------------------------------------------------------
#[fundamental] ok
#[may_dangle] ok
#[rustc_specialization_trait] ok
#[rustc_unsafe_specialization_marker] ok
#[cold] ok
11 | sync.rs
15 | prelude/mod.rs
-----------------------------------------------------------
core::hint::spin_loop; ok
core::mem::drop; ok
core::mem::align_of_val; ok
core::sync::atomic::fence; ok
core::sync::atomic::AtomicUsize; ok
core::sync::atomic::Ordering::Acquire; ok
core::sync::atomic::Ordering::Relaxed; ok
core::sync::atomic::Ordering::Release; ok
core::sync::atomic::Ordering::SeqCst; ok
12 | task.rs
-----------------------------------------------------------
core::task::RawWaker; ok
core::task::RawWakerVTable; ok
core::task::Waker; ok
Some of the structures marked here don’t require specific compiler magic, but identifying these gaps was crucial. It provided a clear blueprint of exactly which core structures needed to be implemented in our “mock core” to allow
allocto compile.
With this step, the massive alloc crate was successfully divided into 5 manageable chunks, giving us a precise roadmap of the compiler’s shortcomings.
If you are wondering about the index numbers at the beginning of the file names in the previous section, those are internal IDs I assigned to avoid clutter during the dependency resolution process. Here is the raw internal dependency mapping I extracted from alloc:
Internal Dependencies of alloc
* lib.rs
*
1 alloc.rs | 1:
2 borrow.rs | 2: 4 10
3 boxed.rs | 3: 1 2 6 9 14
4 fmt.rs | 4: 10
5 macros.rs | 5:
6 raw_vec.rs | 6: 1 3 17
7 rc.rs | 7: 1 2 3 10 14
8 slice.rs | 8: 2 3 14
9 str.rs | 9: 2 3 8 10 14
10 string.rs | 10: 2 3 9 14 17
11 sync.rs | 11: 1 2 3 7 10 14
12 task.rs | 12: 11
14 vec.rs | 14: 2 3 6 17
15 prelude/mod.rs | 15: 16
16 prelude/v1.rs | 16: 2 3 10 14
17 collections/mod.rs | 17: 1 18 19 27
18 collections/linked_list.rs | 18: 3 17
19 collections/vec_deque.rs | 19: 6 14 17 20
20 collections/vec_deque/drain.rs | 20: 19
21 collections/btree/append.rs | 21: 23 26 29
22 collections/btree/borrow.rs | 22:
23 collections/btree/map.rs | 23: 22 27 29 31
24 collections/btree/map/entry.rs | 24: 22 23 29
25 collections/btree/mem.rs | 25:
26 collections/btree/merge_iter.rs | 26:
27 collections/btree/mod.rs | 27: 21 22 23 25 26 28 29 30 31 32 33
28 collections/btree/navigate.rs | 28: 27 29 31
29 collections/btree/node.rs | 29: 1 3
30 collections/btree/remove.rs | 30: 23 27 29
31 collections/btree/search.rs | 31: 29
32 collections/btree/set.rs | 32: 23 26 27
33 collections/btree/split.rs | 33: 23 29 31
34 collections/binary_heap.rs | 34: 8 14 17
What Has Been Done?
We have made significant progress in bringing alloc support to gccrs. You can inspect all individual patches in detail in the Contributions Overview section.
Resolved Lang Items
exchange_malloc: Fully implemented. Resolves the allocation method called duringBoxheap allocation.owned_box: Fully implemented end-to-end for box expressions. This includes the underlying DST support, auto-dereferencing, and method dispatch logic.pin/unpin: Implemented the necessary auto-trait and negative trait implementation infrastructure. The actual pinning logic is handled by library code, so no further compiler magic was required.alloc_layout/oom: Supported (generates the necessary symbols).unsize: Fully supported.coerce_unsized: The backend code generation is fully functional. Currently, the type-checking phase relies on hardcoded compiler rules rather than being fully trait-based. This is a temporary measure; once thegccrstrait engine matures, delegating this check will be straightforward.dispatch_from_dyn: Partially implemented (currently supported specifically forBox).unsafe_cell: Supported. Essential for safe interior mutability.range_inclusive_new: Fully implemented. Successfully lowers the..=syntax sugar to theRangeInclusive::new()method.
Stubs & Unsupported Items:
maybe_uninit,box_free,drop,drop_in_place: Registered as stubs to allow compilation to proceed. (The drop-related items fall under the broader, ongoingDropimplementation scope).future_trait,ready,pending,poll,generator,generator_state: Registered as stubs because Async & Generators are not yet supported.
Resolved Intrinsics
min_align_of: Implemented (alignment calculation for statically sized types).size_of_val/min_align_of_val: Fully supported for both sized and dynamically sized types (DSTs). If the incoming type is a DST, the compiler successfully extracts the required size and alignment information from the fat pointer’s metadata.arith_offset: Implemented for safe pointer offset calculations without triggering undefined behavior.write_bytes: Implemented (lowers tomemsetin the backend).assert_zero_valid: Registered as a stub. It is meant to abort if the zero value is invalid for a given type, but currently acts as a no-op. See the corresponding PR for details.
Resolved Attributes
rustc_allocator/rustc_allocator_nounwind/rustc_std_internal_symbol: Fully implemented with proper symbol generation.needs_allocator/rustc_conversion_suggestion: Registered as temporary stubs to unblock the standard library parsing.
Resolved Infrastructure
- Dynamic Sized Types (DSTs): Support implemented and stabilized for slices and trait objects within the
alloccontext. - Fat Pointers & Vtables: The vtable generation and fat pointer layout mechanics were successfully refactored and implemented.
- Unsized Coercions: Functional, allowing seamless conversions between thin and fat pointers.
Note
For any missing element (lang item, intrinsic, attribute, etc.), a full implementation is always the primary priority. Even if a full implementation is completely blocked by infrastructure limitations, deciding to register it as a stub requires careful thought and discussion. If something is registered as a stub, you can be certain it is a deliberate and well-considered decision.
Current State
We have successfully established the fundamental infrastructure required to support the alloc crate within gccrs.
Note
The features registered as temporary stubs in the previous section represent architectural boundaries. Rather than waiting for the entire compiler infrastructure to catch up, we utilized these stubs to unblock development and make independent progress. Once those upstream compiler components mature, substituting these stubs with full implementations will be straightforward.
The Test Suite Strategy
Currently, we have an automated test suite for the alloc crate. However, we cannot yet compile the crate end-to-end.
The upstream core test suite is currently capable of compiling up to the AST lowering phase. To keep our work strictly synchronized with the compiler’s current capabilities and avoid upstream panics, the alloc test suite is configured to successfully compile up to the name resolution phase.
The “Mock Core” Architecture
To satisfy alloc’s heavy dependency on core without being blocked by the compiler’s incomplete standard library support, we inject a custom mock core directly into the test suite. This mock core is defined as a submodule and provides just enough structure to pass the required compiler phases.
Here is a high-level representation of how the alloc test suite and the mock core are architected:
gcc/testsuite/rust/alloc <-- alloc testsuite
├── alloc
│ └── src
│ ├── alloc/ ┌───────────────────────────────────┐
│ ├── alloc.rs │152│ . │
│ ├── borrow.rs │153│ . │
│ ├── boxed.rs │154│ │
│ ├── collections/ │155│ #[path = "../../core.rs"] │
│ ├── fmt.rs │156│ pub mod core; <───────────────┼───┐
│ ├── lib.rs ──────────────┤ │ . │ │
│ ├── macros.rs │157│ #[path = "../../prelude.rs"] │ │
│ ├── prelude/ │158│ pub mod gccrs_core_prelude; <─┼───┼────┐
│ ├── raw_vec/ │159│ │ │ │
│ ├── raw_vec.rs │160│ . │ │ │
│ ├── rc/ │161│ . │ │ │
│ ├── rc.rs └───────────────────────────────────┘ │ │
│ ├── slice.rs │ │
│ ├── str.rs │ │
│ ├── string.rs │ │
│ ├── sync/ │ │
│ ├── sync.rs │ │
│ ├── task.rs │ │
│ ├── tests.rs │ │
│ └── vec.rs │ │
├── alloc.exp │ │
├── core.rs ────────────────────────────────────────────────────────────┘ │
└── prelude.rs ──────────────────────────────────────────────────────────────┘
Roadmap & Next Steps
To be entirely candid, the direct, alloc-specific infrastructure is now largely in place. The immediate next steps within the alloc crate itself mostly involve routine maintenance: reviewing the recently merged patches, ironing out edge cases, and fixing any newly discovered bugs.
The true path forward for alloc does not involve writing more alloc-specific code; it requires advancing the upstream core support. Because alloc is fundamentally built on top of core, any structural limitations in core inherently bottleneck alloc.
Therefore, the most impactful contribution to alloc right now is helping the gccrs team complete the missing core infrastructure.
As the compiler’s support for core matures, the future roadmap for alloc will naturally unfold:
- Phasing Out the Mock: The temporary mock core used in our test suite will be gradually removed.
- End-to-End Testing: The
alloctest suite will be updated to compile against the actual standardcorecrate, pushing past the name resolution phase. - Replacing Stubs: The temporary stubs and workarounds we registered to unblock development will be cleanly replaced with their complete backend implementations.
Overview
Note
All development during this GSoC project was directly submitted to the upstream Rust-GCC/gccrs repository on GitHub.
Below is the complete list of contributions.
- gccrs#4556: Add allocator attributes
[Attribute] - gccrs#4557: Add lang item exchange_malloc
[Lang Item] - gccrs#4574: Implement owned_box and box expressions
[Lang Item][Important] - gccrs#4582: Add min_align_of intrinsic
[Intrinsic] - gccrs#4587: Enforce intrinsic signatures during type checking
[Typecheck] - gccrs#4599: Refactor dynamic object fat pointers and vtable generation
[Infrastructure][Important] - gccrs#4620: Add write_bytes and arith_offset intrinsics
[Intrinsic] - gccrs#4660: Fix incorrect impl block selection
[Bug Fix] - gccrs#4672: Add size_of_val and min_align_of_val intrinsics
[Intrinsic] - gccrs#4695: Register rustc_conversion_suggestion
[Attribute] - gccrs#4696: Register needs_allocator
[Attribute] - gccrs#4697: Add assert_zero_valid intrinsic
[Intrinsic] - gccrs#4704: Add range_inclusive_new
[Lang Item] - gccrs#4719: Add unsafe_cell
[Lang Item] - gccrs#4720: Add pin and unpin lang items
[Lang Item] - gccrs#4721: Register lang items for alloc crate
[Lang Item] - gccrs#4722: Support coercions and intrinsics for unsized ADTs
[Infrastructure][Lang Item][Important][Pending Review] - gccrs#4750: Fix ignored self in grouped glob imports
[Bug Fix] - gccrs#4767: Improve box with DST and auto-deref support
[Infrastructure][Important][Pending Review] - gccrs#4693: Add the alloc crate to the testsuite
[Testsuite][Important][Pending Review]
If you prefer to skip the detailed PR breakdowns, you can jump directly to:
- Reproducible Environment with Nix - Learn how to easily build and test these changes.
- Acknowledgements - Concluding thoughts and thanks.
gccrs#4556: Add allocator attrs
Link: Rust-GCC/gccrs#4556
Objective
This PR introduces three new built-in compiler attributes (rustc_std_internal_symbol, rustc_allocator, and rustc_allocator_nounwind) required for alloc crate integration.
Changes Made
Added parsing and handling support for these three attributes in the compiler.
Why is this needed for alloc?
rustc_std_internal_symbol: Prevents name mangling for internal runtime symbols.
rustc_allocator and rustc_allocator_nounwind: Instruct the GCC backend to apply malloc and nothrow.
Test Case
#![allow(unused)]
fn main() {
#[rustc_allocator]
#[rustc_allocator_nounwind]
pub fn foo() -> *mut u8 {
0 as *mut u8
}
#[rustc_std_internal_symbol]
pub fn bar() -> i32 {
0
}
}
gccrs#4557: Add lang item exchange_malloc
Link: Rust-GCC/gccrs#4557
#![allow(unused)]
fn main() {
#[lang = "exchange_malloc"]
unsafe fn exchange_malloc(size: usize, align: usize) -> *mut u8 {
let layout = unsafe { Layout::from_size_align_unchecked(size, align) };
match Global.alloc(layout) {
Ok(ptr) => ptr.as_mut_ptr(),
Err(_) => handle_alloc_error(layout),
}
}
}
Objective
This PR introduces the exchange_malloc lang item to the compiler.
Changes Made
We just need to produce it as a symbol. Then, when we look for it, we need to be able to find it.
Why is this needed for alloc?
This lang item is a strict prerequisite for the owned_box lang item and box expressions.
Test Case
#![allow(unused)]
fn main() {
#[lang = "exchange_malloc"]
unsafe fn _allocate(_size: usize, _align: usize) -> *mut u8 {
0 as *mut u8
}
}
gccrs#4574: Add lang owned box and box expression
Link: Rust-GCC/gccrs#4574
Note
This is important.
Box definition:
#![allow(unused)]
fn main() {
#[lang = "owned_box"]
#[fundamental]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Box<
T: ?Sized,
#[unstable(feature = "allocator_api", issue = "32838")] A: AllocRef = Global,
>(Unique<T>, A);
}
Box expression:
#![allow(unused)]
fn main() {
impl<T> Box<T> {
pub fn new(x: T) -> Self {
box x // box expression looks like this
}
}
}
Objective
This PR implements the owned_box lang item and adds compiler support for box expressions.
Changes Made
- Written
AST -> HIRlowering for box expressions from scratch (including a newHIRclass). - Completed the
HIR -> GENERICbackend translation. - Added helper methods to resolve nested structures within boxes.
- Implemented auto-deref logic for
Box.
Why is this needed for alloc?
Box<T> is the core type for dynamic memory allocation in Rust. Supporting its specific compiler intrinsics and expressions is mandatory for the alloc crate.
Test Case
struct NonCopyStruct {
id: i32,
}
impl NonCopyStruct {
fn get_id(&self) -> i32 {
self.id
}
}
fn main() {
let my_box: Box<NonCopyStruct> = Box::new(NonCopyStruct { id: 42 });
let _moved_val = *my_box;
let my_box2: Box<NonCopyStruct> = Box::new(NonCopyStruct { id: 100 });
let val_id = my_box2.id;
let val_method_id = my_box2.get_id();
println!("id: {val_id}");
println!("get_id: {val_method_id}");
}
The compiler can now correctly handle the instantiation of boxes, move semantics (*my_box), field accesses, and method calls on boxed types using the newly added auto-deref logic.
gccrs#4582: Add min_align_of
Link: Rust-GCC/gccrs#4582
Definition:
#![allow(unused)]
fn main() {
#[rustc_const_stable(feature = "const_min_align_of", since = "1.40.0")]
pub fn min_align_of<T>() -> usize;
}
Objective
This PR implements the min_align_of compiler intrinsic to resolve the minimum alignment of a compile-time sized type.
Changes Made
Added a handler function that leverages the GCC type alignment infrastructure. It calculates the alignment requirement of a given type and returns it as a constant integer expression (size_type_node).
Why is this needed for alloc?
The alloc crate relies heavily on this intrinsic to correctly calculate memory alignment constraints when allocating memory blocks.
Test Case
use std::mem::align_of as min_align_of; // new API is align_of
fn main() {
let align_u16 = min_align_of::<u16>(); // returns 2
let align_u32 = min_align_of::<i32>(); // returns 4
println!("align of u16: {align_u16}");
println!("align of u32: {align_u32}");
}
The intrinsic successfully evaluates the alignments of u16 and i32 at compile-time and returns 2 and 4 respectively.
gccrs#4587: Enforce intrinsic signatures during the type checking
Link: Rust-GCC/gccrs#4587
Objective
This PR enforces signature validation for intrinsic functions during the type-checking phase rather than failing later during code generation.
Changes Made
Added a validation hook that intercepts extern "rust-intrinsic" function declarations. It strictly checks the argument types and argument counts against the expected compiler definitions.
Why is this needed for alloc?
While not strictly required only for alloc, this patch is crucial for development stability. Implementing the alloc crate requires adding numerous new intrinsics; failing early at the type-check phase prevents cryptic backend crashes (ICEs) when an intrinsic signature is mismatched.
Test Case
#![allow(unused)]
fn main() {
extern "rust-intrinsic" {
// this will cause an error -> unrecognized intrinsic function: 'foo'
fn foo();
fn size_of<T, U>() -> usize;
fn offset<T>(dst: usize, offset: isize) -> *const T;
}
}
The compiler now correctly emits user-friendly errors during the frontend type-checking pass instead of letting invalid signatures reach the backend.
gccrs#4599: Refactor dynamic object fat pointers and vtable generation
Link: Rust-GCC/gccrs#4599
Note
This is important
Objective
This PR refactors the memory layout of fat pointers (for dynamic trait objects) and vtable generation to match rustc ABI expectations.
Changes Made
Previously, the compiler incorrectly embedded the entire vtable directly inside the fat pointer as an array, and missed essential trait object metadata.
-
The fat pointer is now strictly constrained to 2 words (data pointer and vtable pointer).
-
The vtable is generated as a separate global static struct.
-
The vtable structure now correctly includes drop_in_place, size, and align fields, followed by trait methods.
-
Added a caching mechanism in the compilation context to prevent duplicate vtable generation and linker conflicts for identical Type-Trait combinations.
Let’s examine the following example (this example is for 64-bit):
# StructX memory
StructX { val: i32, } -- 4 bytes
├────────────────────┤
│ │ <- val (4 bytes)
├────────────────────┤
# Previous fat-pointer memory
&StructX -> &dyn TraitA -- 24 bytes
├────────────────────┤
│ │ <- address of StructX (8 bytes)
├────────────────────┤
│ │ <- address of method do_a (8 bytes)
├────────────────────┤
│ │ <- address of method dont_a (8 bytes)
├────────────────────┤
Note: If TraitA had 10 methods, this fat pointer would occupy 88 bytes. It also lacked size, align, and drop_in_place pointers.
# Current fat-pointer memory
&StructX -> &dyn TraitA -- 16 bytes
├────────────────────┤
│ │ <- address of StructX (8 byte)
├────────────────────┤
│ │ <- address of vtable (8 bytes)
├────────────────────┤
Generated vtable - 40 bytes
├────────────────────┤
│ │ <- address of method drop_in_place (8 byte)
├────────────────────┤
│ │ <- size information (8 byte)
├────────────────────┤
│ │ <- align information (8 byte)
├────────────────────┤
│ │ <- address of method dont_a (8 byte)
├────────────────────┤
│ │ <- address of method dont_a (8 byte)
├────────────────────┤
Note: Regardless of the number of methods in TraitA, the fat pointer size is always 16 bytes. All necessary metadata is successfully preserved in the vtable.
Why is this needed for alloc?
For dynamically sized types (DSTs) and trait objects within alloc to function correctly and efficiently, the fat pointer architecture must exactly match standard Rust behavior.
Test Case
fn print1(label: &str, val: i32) {
let arrow = "─".repeat(36);
println!("{:<7}: {:>2} <{}┘ │", label, val, arrow);
}
fn print2(label: &str, val: i32) {
let arrow = "─".repeat(40);
println!("{:<7}: {:>2} <{}┘", label, val, arrow);
}
trait TraitA {
fn do_a(&self) -> i32;
fn dont_a(&self) -> i32;
}
struct StructX {
val: i32,
}
impl TraitA for StructX {
fn do_a(&self) -> i32 { self.val} // ────────┐
fn dont_a(&self) -> i32 { -self.val } // ────┐ │
} // │ │
// │ │
fn main() { // │ │
let x = StructX { val: 1 }; // self = 1 -> │ │
let dyn_a_x: &dyn TraitA = &x; // │ │
// │ │
print1("dont_a", dyn_a_x.dont_a()); // │ │
print2("do_a", dyn_a_x.do_a()); // │ │
} // │ │
gccrs#4620: Add write_bytes and arith_offset
Link: Rust-GCC/gccrs#4620
Definition:
#![allow(unused)]
fn main() {
#[must_use = "returns a new pointer rather than modifying its argument"]
#[rustc_const_unstable(feature = "const_ptr_offset", issue = "71499")]
pub fn arith_offset<T>(dst: *const T, offset: isize) -> *const T;
}
#![allow(unused)]
fn main() {
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
pub unsafe fn write_bytes<T>(dst: *mut T, val: u8, count: usize) {
extern "rust-intrinsic" {
fn write_bytes<T>(dst: *mut T, val: u8, count: usize);
}
debug_assert!(is_aligned_and_not_null(dst), "attempt to write to unaligned or null pointer");
// SAFETY: the safety contract for `write_bytes` must be upheld by the caller.
unsafe { write_bytes(dst, val, count) }
}
}
Objective
This PR implements arith_offset and write_bytes compiler intrinsics.
Changes Made
write_bytes: Lowered to GCC’s__builtin_memset.arith_offset: Lowered using GCC’sPOINTER_PLUS_EXPR.
Why is this needed for alloc?
The alloc crate relies heavily on these intrinsics for raw memory manipulation. arith_offset is required for pointer arithmetic (calculating offsets for data structures like Vec), and write_bytes is used to quickly set memory regions to a specific value.
Test Case
fn try_arith_offset() -> i32 {
let base_addr: usize = 0;
let ptr = base_addr as *const u64;
let wrap_ptr = ptr.wrapping_offset(-1);
if wrap_ptr as isize == -8_isize {
0
} else {
1
}
}
fn try_write_bytes() -> i32 {
let mut x: u32 = 0;
unsafe {
core::ptr::write_bytes(&mut x as *mut u32, 0xEC_u8, 1);
}
if x == 0xECECECEC_u32 {
0
} else {
1
}
}
fn main() {
println!("arith_offset result: {}", try_arith_offset());
println!("write_bytes result: {}", try_write_bytes());
}
The arith_offset correctly wraps around using two’s complement arithmetic without undefined behavior. write_bytes successfully acts as a memset, modifying the exact byte pattern of the memory block.
gccrs#4660: Fix incorrect impl block selection
Link: Rust-GCC/gccrs#4660
Note
Bug fix (issue #4600).
Objective
Fixes a bug where dynamic dispatch called the wrong methods when multiple types implemented the same trait in the same scope.
Changes Made
During vtable generation (compute_address_for_trait_item), the compiler was previously selecting the first impl block that satisfied the trait predicate, without verifying if that block actually belonged to the target receiver type.
Added a strict type comparison between the impl block’s resolved type and the actual receiver type to ensure the correct associated function address is computed for the vtable.
Why is this needed for alloc?
The alloc crate makes extensive use of dynamic dispatch (dyn Trait), especially for features like the GlobalAlloc trait. If vtable method resolution selects the wrong implementation, it leads to memory corruption or crashes at runtime.
Test Case
trait TraitA {
fn do_a(&self) -> i32;
}
mod mod_a {
use super::TraitA;
pub struct StructX<T> {
pub val: i32,
pub _marker: T,
}
impl TraitA for StructX<i32> {
fn do_a(&self) -> i32 {
self.val * 10
}
}
impl TraitA for StructX<u32> {
fn do_a(&self) -> i32 {
self.val * 1000
}
}
}
mod mod_b {
use super::TraitA;
pub struct StructX<T> {
pub val: i32,
pub _marker: T,
}
impl TraitA for StructX<u32> {
fn do_a(&self) -> i32 {
self.val * 20
}
}
impl TraitA for StructX<i32> {
fn do_a(&self) -> i32 {
self.val * 2000
}
}
}
fn main() {
let a = mod_a::StructX {
val: 10,
_marker: 0_i32,
};
let b = mod_b::StructX {
val: 20,
_marker: 0_u32,
};
let dyn_a = &a as &dyn TraitA;
let dyn_b = &b as &dyn TraitA;
if dyn_a.do_a() == 100 && dyn_b.do_a() == 400 {
println!("ok.");
} else {
println!("wrong!");
}
}
gccrs#4672: Add size_of_val and min_align_of_val intrinsics
Link: Rust-GCC/gccrs#4672
Definition:
#![allow(unused)]
fn main() {
#[rustc_const_unstable(feature = "const_size_of_val", issue = "46571")]
pub fn size_of_val<T: ?Sized>(_: *const T) -> usize;
}
#![allow(unused)]
fn main() {
#[rustc_const_unstable(feature = "const_align_of_val", issue = "46571")]
pub fn min_align_of_val<T: ?Sized>(_: *const T) -> usize;
}
Objective
This PR adds the size_of_val and min_align_of_val intrinsics. Unlike compile-time intrinsics, these evaluate the size and alignment of a value dynamically at runtime.
Changes Made
Implemented the backend function bodies for these intrinsics. If the passed pointer points to a Dynamically Sized Type (DST, like a trait object or slice), the compiler now correctly fetches the size and alignment information directly from the associated vtable or slice metadata at runtime.
Why is this needed for alloc?
alloc functions need to know the exact memory layout to allocate or deallocate memory correctly. These intrinsics allow alloc to safely handle unsized types dynamically.
Test Case
trait TraitA {
fn foo(&self) {}
}
struct StructA {
_a: i32,
_b: i32,
}
impl TraitA for StructA {}
impl TraitA for u8 {}
fn try_size_of_val() -> i32 {
let val_i32: i32 = 32;
let size_i32 = std::mem::size_of_val(&val_i32);
let val_struct = StructA { _a: 1, _b: 2 };
let size_struct = std::mem::size_of_val(&val_struct);
let arr: [i32; 3] = [10, 20, 30];
let val_slice: &[i32] = &arr;
let size_slice = std::mem::size_of_val(val_slice);
let val_str: &str = "gccrs";
let size_str = std::mem::size_of_val(val_str);
let val_dyn_struct = &val_struct as &dyn TraitA;
let size_dyn_struct = std::mem::size_of_val(val_dyn_struct);
let val_u8: u8 = 7;
let size_u8 = std::mem::size_of_val(&val_u8);
let val_dyn_u8 = &val_u8 as &dyn TraitA;
let size_dyn_u8 = std::mem::size_of_val(val_dyn_u8);
if size_i32 != 4 {
1
} else if size_struct != 8 {
2
} else if size_slice != 12 {
3
} else if size_str != 5 {
4
} else if size_dyn_struct != 8 {
5
} else if size_u8 != 1 {
6
} else if size_dyn_u8 != 1 {
7
} else {
0
}
}
fn main() {
println!("size_of_val result: {}", try_size_of_val());
}
trait TraitA {
fn foo(&self) {}
}
struct StructA {
_a: i32,
_b: i16,
}
impl TraitA for StructA {}
impl TraitA for u8 {}
fn try_min_align_of_val() -> i32 {
let val_i32: i32 = 32;
let align_i32 = std::mem::align_of_val(&val_i32);
let val_struct = StructA { _a: 1, _b: 2 };
let align_struct = std::mem::align_of_val(&val_struct);
let arr: [i32; 3] = [10, 20, 30];
let val_slice: &[i32] = &arr;
let align_slice = std::mem::align_of_val(val_slice);
let val_str: &str = "gccrs";
let align_str = std::mem::align_of_val(val_str);
let val_dyn_struct = &val_struct as &dyn TraitA;
let align_dyn_struct = std::mem::align_of_val(val_dyn_struct);
let val_u8: u8 = 7;
let align_u8 = std::mem::align_of_val(&val_u8);
let val_dyn_u8 = &val_u8 as &dyn TraitA;
let align_dyn_u8 = std::mem::align_of_val(val_dyn_u8);
if align_i32 != 4 {
1
} else if align_struct != 4 {
2
} else if align_slice != 4 {
3
} else if align_str != 1 {
4
} else if align_dyn_struct != 4 {
5
} else if align_u8 != 1 {
6
} else if align_dyn_u8 != 1 {
7
} else {
0
}
}
fn main() {
println!("min_align_of_val result: {}", try_min_align_of_val());
}
gccrs#4695: Register rustc_conversion_suggestion
Link: Rust-GCC/gccrs#4695
Objective
This PR registers the rustc_conversion_suggestion attribute into the compiler.
Changes Made
It is currently implemented as a stub. When the compiler encounters this attribute, it simply ignores it and emits a warning rather than throwing a hard parsing error.
Why is this needed for alloc?
The alloc crate utilizes this attribute on standard traits like ToString. To successfully compile alloc, gccrs must be able to parse and gracefully handle this attribute, even if the hint mechanism itself isn’t fully implemented yet.
Test Case
#![allow(unused)]
fn main() {
pub trait ToString {
#[rustc_conversion_suggestion]
#[stable(feature = "rust1", since = "1.0.0")]
fn to_string(&self) -> String;
}
}
gccrs#4696: Register needs_allocator
Link: Rust-GCC/gccrs#4696
Objective
This PR registers the #![needs_allocator] crate-level attribute.
Changes Made
Implemented as a temporary stub that emits a compiler warning. Full handling of needs_allocator requires the global allocator infrastructure and the core::alloc::GlobalAlloc trait. Since gccrs does not yet fully support the core crate, the complete implementation is deferred.
Why is this needed for alloc?
The alloc crate root defines #![needs_allocator] to signal to the compiler that the final binary needs a global allocator linked in. Registering the attribute is the first necessary step to prevent compilation failure when parsing the alloc crate.
Test Case
#![allow(unused)]
#![needs_allocator]
fn main() {
}
gccrs#4697: Add assert_zero_valid
Link: Rust-GCC/gccrs#4697
Objective
This PR introduces the assert_zero_valid intrinsic to the compiler.
Changes Made
Properly implementing this intrinsic requires deep layout engine support to detect types that cannot safely be zero-initialized (e.g., references like &T or NonNull pointers). Since gccrs currently lacks this layout capability, it is implemented as a stub that always returns unit ().
Note: In the future, this intrinsic should inject a runtime panic during codegen if the type does not permit zero-initialization.
Why is this needed for alloc?
Internal structures within alloc (such as Box and Vec) use this intrinsic to ensure memory safety when creating zero-initialized data buffers.
Test Case
fn main() {
unsafe {
let _valid: i32 = std::mem::zeroed();
println!("valid for i32");
// this will abort because std::mem::zeroed uses assert_zero_valid
let _invalid: &i32 = std::mem::zeroed();
}
}
gccrs#4704: Add range_inclusive_new
Link: Rust-GCC/gccrs#4704
Definition:
#![allow(unused)]
fn main() {
#[lang = "RangeInclusive"]
#[doc(alias = "..=")]
#[derive(Clone, PartialEq, Eq, Hash)] // not Copy -- see #27186
#[stable(feature = "inclusive_range", since = "1.26.0")]
pub struct RangeInclusive<Idx> {
pub(crate) start: Idx,
pub(crate) end: Idx,
pub(crate) exhausted: bool,
}
impl<Idx> RangeInclusive<Idx> {
#[lang = "range_inclusive_new"]
#[stable(feature = "inclusive_range_methods", since = "1.27.0")]
#[inline]
#[rustc_promotable]
#[rustc_const_stable(feature = "const_range_new", since = "1.32.0")]
pub const fn new(start: Idx, end: Idx) -> Self {
Self { start, end, exhausted: false }
}
}
}
Objective
This patch introduces the range_inclusive_new lang item to the compiler.
Changes Made
When the compiler encounters an inclusive range expression (..=), it now correctly desugars the operation into a function call targeting this lang item, rather than lowering it directly into a static struct.
As a result, the unnecessary HIR::RangeFromToInclExpr class and its associated visitors were removed across the compiler. Since inclusive ranges are now directly desugared into a CallExpr targeting range_inclusive_new during the AST -> HIR lowering phase, the old HIR node and its codegen/typecheck implementations became dead code and were cleaned up.
Why is this needed for alloc?
The alloc crate relies heavily on inclusive ranges for indexing, slicing, and iteration over heap-allocated collections like Vec and String.
Test Case
#![allow(unused)]
fn main() {
fn foo() {
let _ = 0..=23;
// ^^^^^^ this will lower to this ─────┐
// let _ = RangeInclusive::new(0, 23); <────┘
}
}
gccrs#4719: Add unsafe_cell
Link: Rust-GCC/gccrs#4719
Definition:
#![allow(unused)]
fn main() {
#[lang = "unsafe_cell"]
#[stable(feature = "rust1", since = "1.0.0")]
#[repr(transparent)]
#[repr(no_niche)] // rust-lang/rust#68303.
pub struct UnsafeCell<T: ?Sized> {
value: T,
}
}
Objective
This PR implements the unsafe_cell lang item to the compilers.
Changes Made
Registered the lang item. Since gccrs currently lacks niche-filling optimizations, this patch does not include any changes related to type layout size adjustments. The focus is purely on correctly handling aliasing and mutability semantics at the compiler level.
Why is this needed for alloc?
UnsafeCell<T> is the foundational primitive for interior mutability in Rust. The alloc crate requires it to implement data structures that manage shared state or raw memory buffers, ensuring the compiler’s optimizer does not make incorrect assumptions about memory immutability.
Test Case
#![allow(unused)]
fn main() {
pub fn normal_ref(_a: &i32) {/* ... */}
// ^^^^^^^^ this's gimple will looks like 'const i32 & const _a'
// ^^^^^ ^^^^^
// both pointee and pointer are const <────┴───────────┘
pub fn unsafe_ref(_b: &UnsafeCell<i32>) {/* ... */}
// ^^^^^^^^^^^^^^^^^^^^
// this's gimple will looks like 'struct UnsafeCell<i32> & const _b;
// ^^^^^
// pointer is const, but pointee is NOT const! <──────┘
}
The compiler correctly translates UnsafeCell references to GIMPLE, dropping the const qualifier from the pointee, thereby safely allowing internal mutation.
gccrs#4720: Add lang pin/unpin
Link: Rust-GCC/gccrs#4720
Definition:
#![allow(unused)]
fn main() {
#[stable(feature = "pin", since = "1.33.0")]
#[lang = "pin"]
#[fundamental]
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct Pin<P> {
pointer: P,
}
}
#![allow(unused)]
fn main() {
#[stable(feature = "pin", since = "1.33.0")]
#[rustc_on_unimplemented(
on(_Self = "std::future::Future", note = "consider using `Box::pin`",),
message = "`{Self}` cannot be unpinned"
)]
#[lang = "unpin"]
pub auto trait Unpin {}
}
Objective
This patch adds the pin and unpin lang items to the compiler.
Changes Made
Registered the lang items. Full behavioral testing is currently blocked due to upstream issues #4709 and #4678. Therefore, the validation in this patch specifically targets auto-trait behavior and negative implementations (!Unpin).
Why is this needed for alloc?
Pin and Unpin are essential for safely working with self-referential structures and Futures. The alloc crate provides implementations for pinning heap-allocated objects (e.g., Box::pin), making these lang items a prerequisite.
Test Case
use std::marker::PhantomPinned;
use std::pin::Pin;
struct PinnedStruct {
_marker: PhantomPinned,
}
struct NormalStruct;
fn main() {
let normal = NormalStruct;
let _ = Pin::new(&normal);
println!("valid");
let pinned = PinnedStruct { _marker: PhantomPinned };
// uncomment the line below to see the bound error
// let _ = Pin::new(&pinned);
}
This test demonstrates that the compiler correctly enforces negative trait bounds (!Unpin) when attempting to pin a type that explicitly opts out of it.
gccrs#4721: Register lang items for alloc crate
Link: Rust-GCC/gccrs#4721
Definitions:
#![allow(unused)]
fn main() {
#[cfg(all(bootstrap, not(test)))]
#[stable(feature = "global_alloc", since = "1.28.0")]
#[rustc_allocator_nounwind]
pub fn handle_alloc_error(layout: Layout) -> ! {
extern "Rust" {
#[lang = "oom"]
fn oom_impl(layout: Layout) -> !;
}
unsafe { oom_impl(layout) }
}
}
#![allow(unused)]
fn main() {
#[stable(feature = "alloc_layout", since = "1.28.0")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[lang = "alloc_layout"]
pub struct Layout {
size_: usize,
align_: NonZeroUsize,
}
}
#![allow(unused)]
fn main() {
#[cfg_attr(not(test), lang = "box_free")]
#[inline]
pub(crate) unsafe fn box_free<T: ?Sized, A: AllocRef>(ptr: Unique<T>, alloc: A) {
unsafe {
let size = size_of_val(ptr.as_ref());
let align = min_align_of_val(ptr.as_ref());
let layout = Layout::from_size_align_unchecked(size, align);
alloc.dealloc(ptr.cast().into(), layout)
}
}
}
#![allow(unused)]
fn main() {
#[stable(feature = "drop_in_place", since = "1.8.0")]
#[lang = "drop_in_place"]
pub unsafe fn drop_in_place<T: ?Sized>(to_drop: *mut T) {
// Code here does not matter - this is replaced by the
// real drop glue by the compiler.
// SAFETY: see comment above
unsafe { drop_in_place(to_drop) }
}
}
#![allow(unused)]
fn main() {
#[stable(feature = "maybe_uninit", since = "1.36.0")]
// Lang item so we can wrap other types in it. This is useful for generators.
#[lang = "maybe_uninit"]
#[derive(Copy)]
#[repr(transparent)]
pub union MaybeUninit<T> {
uninit: (),
value: ManuallyDrop<T>,
}
}
#![allow(unused)]
fn main() {
#[stable(feature = "futures_api", since = "1.36.0")]
#[lang = "future_trait"]
pub trait Future {
#[stable(feature = "futures_api", since = "1.36.0")]
type Output;
#[lang = "poll"]
#[stable(feature = "futures_api", since = "1.36.0")]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
}
#![allow(unused)]
fn main() {
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[stable(feature = "futures_api", since = "1.36.0")]
pub enum Poll<T> {
/// Represents that a value is immediately ready.
#[lang = "Ready"]
#[stable(feature = "futures_api", since = "1.36.0")]
Ready(#[stable(feature = "futures_api", since = "1.36.0")] T),
#[lang = "Pending"]
#[stable(feature = "futures_api", since = "1.36.0")]
Pending,
}
}
#![allow(unused)]
fn main() {
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[lang = "generator_state"]
#[unstable(feature = "generator_trait", issue = "43122")]
pub enum GeneratorState<Y, R> {
Yielded(Y),
Complete(R),
}
#[lang = "generator"]
#[unstable(feature = "generator_trait", issue = "43122")]
#[fundamental]
pub trait Generator<R = ()> {
type Return;
fn resume(self: Pin<&mut Self>, arg: R) -> GeneratorState<Self::Yield, Self::Return>;
}
}
Objective
This PR registers several lang items as stubs, acting as a crucial stepping stone to unblock the compilation of the alloc crate.
Changes Made
oom,alloc_layout: Require no specific handling; symbol generation is sufficient.box_free,drop_in_place: Full implementation requires a mature drop infrastructure. Registered as a stub for now.maybe_uninit: Requires layout engine maturity and niche-filling mechanisms. Registered as a stub.future_trait,poll,Ready,Pending: Registered as stubs due to lack of async support.generator,generator_state: Registered as stubs due to lack of generator support.
Note: The Ready and Pending lang items are excluded from the test cases because lang item attributes on enum variants are not currently handled by the compiler (see #4703).
Why is this needed for alloc?
The standard alloc crate contains definitions for memory layout handling, out-of-memory states, dropping mechanics, and foundational async traits (Future, Generator). Providing these stubs satisfies the compiler’s symbol and attribute resolution, allowing the parser and type checker to process the alloc crate without throwing unknown attribute errors.
Test Case
#![allow(unused)]
fn main() {
#[lang = "future_trait"]
pub trait Future {
#[lang = "poll"]
fn poll() {}
}
#[lang = "generator"]
pub trait Generator {}
#[lang = "generator_state"]
pub enum GeneratorState {}
#[lang = "box_free"]
pub fn _box_free() {}
#[repr(transparent)]
pub struct ManuallyDrop<T: ?Sized> {
_value: T,
}
#[lang = "maybe_uninit"]
#[repr(transparent)]
pub union MaybeUninit<T> {
uninit: (),
value: ManuallyDrop<T>,
}
#[lang = "drop_in_place"]
pub unsafe fn drop_in_place<T: ?Sized>(to_drop: *mut T) {}
// ---
#[lang = "alloc_layout"]
pub struct Layout;
#[lang = "oom"]
pub fn _oom() {}
}
gccrs#4722: Support coercions and intrinsics for unsized ADTs
Link: Rust-GCC/gccrs#4722
Note
This is important
Objective
This PR introduces support for Dynamically Sized Types (DST) within Algebraic Data Types (ADTs). It enables basic ADT-to-ADT unsized coercions (e.g., array-to-slice conversions like [T; N] to [T]) and implements the unsize and coerce_unsized lang items.
Changes Made
Previously, the compiler incorrectly embedded DST fields directly into the ADT memory layout, completely breaking unsize coercion rules. With this patch:
-
The compiler now correctly identifies unsized ADTs, preserves their static layout by avoiding direct embedding, and dynamically generates fat pointers (data pointer + metadata) for references to these ADTs.
-
The compiler correctly identifies valid array-to-slice coercions and injects the appropriate
Adjustment::UNSIZEtags. -
Refactored the monolithic
coerce_unsizedfunction into smaller, maintainable helper functions.
To understand the zero-cost thin-to-fat pointer coercion, let’s examine how memory is structured during these conversions:
#![allow(unused)]
fn main() {
let t = [1, 2, 3]; // this is an array '[i32; 3]'
// coercion to unsized slice
let t1 = &t as &[i32]; // &[i32; 3] -> &[i32]
}
t is [i32; 3] - (12 bytes)
├────────────────────┤
│ │ <- t[0] (4 bytes)
├────────────────────┤
│ │ <- t[1] (4 bytes)
├────────────────────┤
│ │ <- t[2] (4 bytes)
├────────────────────┤
&t is &[i32; 3] - (8 bytes) ────────────────────────────────┐
├────────────────────┤ │
│ │ <- address of t[0] (8 bytes) │
├────────────────────┤ │
│
zero-cost thin-to-fat coercion │
t1 is &[i32] - (16 bytes) <─────────────────────────────────┘
├────────────────────┤
│ │ <- address of t[0] (8 bytes)
├────────────────────┤
│ │ <- length information (8 bytes)
├────────────────────┤
#![allow(unused)]
fn main() {
let s = TailStruct { a: 10, tail: t, }; // this is a normal struct
// coercion to unsized ADT
let s1: &TailStruct<[i32]> = &s; // &TailStruct<[i32; 3]> -> &TailStruct<[i32]>
}
s is TailStruct<[i32; 3]> - (16 bytes)
├────────────────────┤
│ │ <- s.a (4 bytes)
├────────────────────┤
│ │ <- (s.tail)[0] (4 bytes)
├────────────────────┤
│ │ <- (s.tail)[1] (4 bytes)
├────────────────────┤
│ │ <- (s.tail)[2] (4 bytes)
├────────────────────┤
&s is &TailStruct<[i32; 3]> - (8 bytes) ────────────────────────────────┐
├────────────────────┤ │
│ │ <- address of s (8 bytes) │
├────────────────────┤ │
zero-cost thin-to-fat coercion │
d is &TailStruct<[i32]> - (16 bytes) <──────────────────────────────────┘
├────────────────────┤
│ │ <- address of s (8 bytes)
├────────────────────┤
│ │ <- length information of s.tail (8 bytes)
├────────────────────┤
#![allow(unused)]
fn main() {
let d = TailStruct { a: 10, tail: 10 } // this is a normal struct
// coercion to unsized ADT
let d1: &TailStruct<dyn TraitA> = &d; // &TailStruct<i32> -> &TailStruct<dyn TraitA>
}
d is TailStruct<i32> - (8 bytes)
├────────────────────┤
│ │ <- d.a (4 bytes)
├────────────────────┤
│ │ <- d.tail (4 bytes)
├────────────────────┤
&d is &TailStruct<i32> - (8 bytes) ───────────────────────────────────────────┐
├────────────────────┤ │
│ │ <- address of d (8 bytes) │
├────────────────────┤ │
zero-cost thin-to-fat coercion │
d1 is &TailStruct<dyn TraitA> - (16 bytes) <──────────────────────────────────┘
├────────────────────┤
│ │ <- address of d (8 bytes)
├────────────────────┤
│ │ <- address of d.tail's vtable (8 bytes)
├────────────────────┤
Note: For the above coercion to work, i32 must implement TraitA.
As you may have noticed, all these coercions happen exclusively through references (or smart pointers). This is because we never actually transform the underlying data.
So, why do we need unsized coercions in the first place?
In Rust, [i32; 3] and [i32; 2] are entirely distinct types. Imagine designing an API that takes an array of integers as input. If you lock the signature to &[i32; 3], you would need to write a different function for every possible array length. Since Rust does not support function overloading, you would end up writing foo2, foo3, and so on. This is because array lengths are strictly compile-time constructs.
This is exactly where Dynamically Sized Types (DSTs) and unsized coercions shine. By defining the function signature to accept a slice &[i32], we create a unified interface. Under the hood, the compiler takes the starting address of the array (a thin pointer), pairs it with the array’s length (the metadata), and passes them together as a fat pointer. We achieve a zero-cost abstraction for all array sizes, simply by attaching size information to a pointer, without ever mutating the actual data in memory.
This philosophy extends directly to trait objects. If you need a function to dynamically handle distinct types (e.g., both [i32] and [u32]), you define their shared behavior via a trait (e.g., TraitA). By setting your function to accept &dyn TraitA, the compiler performs another unsized coercion: this time, it attaches a vtable to the thin pointer instead of a length.
Ultimately, the true power of unsized coercions is not about transforming data structures; it is about abstracting away memory layouts to avoid code duplication, enable dynamic dispatch, and design clean, unified APIs.
Why is this needed for alloc?
Data structures in alloc like Box<[T]>, Rc<dyn Trait>, and Arc<[T]> are built around ADTs wrapping unsized types. Without properly generating fat pointers and preserving the static layout of unsized ADTs, dynamic allocation of slices and trait objects is impossible.
Test Case
pub struct TailStruct<T: ?Sized> {
pub a: i32,
pub tail: T,
}
pub trait TraitA {
fn dummy(&self) -> i32;
}
impl TraitA for i32 {
fn dummy(&self) -> i32 {
0
}
}
impl TraitA for [i32; 3] {
fn dummy(&self) -> i32 {
0
}
}
pub fn sovt(s: &TailStruct<dyn TraitA>) -> usize {
size_of_val(s)
}
pub fn sov1(s: &[i32]) -> usize {
size_of_val(s)
}
pub fn sov2(s: &TailStruct<[i32]>) -> usize {
size_of_val(s)
}
pub fn sov3(s: &TailStruct<TailStruct<[i32]>>) -> usize {
size_of_val(s)
}
fn main() {
let t = [1, 2, 3];
let t1 = &t as &[i32];
let s1 : TailStruct<[i32; 3]> = TailStruct { a: 10, tail: t, };
let s2_tail: TailStruct<[i32; 3]> = TailStruct { a: 10, tail: t, };
let s2 : TailStruct<TailStruct<[i32; 3]>> = TailStruct { a: 20, tail: s2_tail };
let a = sov1(t1);
let b = sov2(&s1);
let c = sov3(&s2);
let d = sov1(&s1.tail);
let e = sov2(&s2.tail);
let s3 = TailStruct {a: 10, tail: 10_i32, };
let s4 = TailStruct { a: 20, tail: t };
let r1: &TailStruct<dyn TraitA> = &s3;
let r2: &TailStruct<dyn TraitA> = &s4;
let f = sovt(r1);
let g = sovt(r2);
let slice_ok = a == 12 && b == 16 && c == 20 && d == a && e == b;
let trait_ok = f == 8 && g == 16;
if slice_ok && trait_ok {
println!("ok.");
} else {
println!("wrong!");
println!("slice_ok: {slice_ok}");
println!("trait_ok: {trait_ok}");
}
}
Note
Also, you can see in The Rust Reference
gccrs#4750: Fix ignored self in grouped glob imports
Link: Rust-GCC/gccrs#4750
Note
Bug fix (issue #4689)
Objective
Fixes a bug in the module resolution system where grouped imports combining self and a glob (e.g., use path::module::{self, *};) would successfully import the inner items via the glob, but silently drop the base module (or enum) itself.
Changes Made
Modified the early name resolution phase. Previously, the self keyword in grouped imports was being overwritten or ignored. This PR ensures that self imports within a group are assigned their own unique NodeId, preventing them from being dropped from the AST during resolution.
Why is this needed for alloc?
The standard library and the alloc crate heavily utilize this specific import syntax for module preludes and re-exports (e.g., importing all variants of an enum while also exposing the enum type itself). Failing to resolve these imports blocks the compilation of the alloc crate root.
Test Case
pub mod collections {
pub enum TryReserveError {
AllocError,
CapacityOverflow,
}
}
pub mod test_working {
// this explicit list was working correctly before the PR.
use super::collections::TryReserveError::{self, AllocError, CapacityOverflow};
fn _test_function() -> TryReserveError {
AllocError
}
}
pub mod test_failing {
use super::collections::TryReserveError::{self, *};
// ^^^^ this was silently ignored
fn _test_function() -> TryReserveError {
// ^^^^^^^^^^^^^^^
// compiler threw an "unknown symbol" error here.
CapacityOverflow
}
}
fn main() {
println!("valid");
}
gccrs#4767: Improve box
Link: Rust-GCC/gccrs#4767
Note
This is important
Objective
This PR introduces full DST support for Box, allowing it to correctly handle unsized types like trait objects and slices. Crucially, it also implements the compiler-level auto-deref mechanism for Box.
Changes Made
Wired up the compiler’s auto-dereference logic to recognize Box<T> similarly to how it recognizes standard references (&T). This ensures that method dispatch, array indexing, and tuple field accesses are transparently forwarded to the underlying data pointer inside the box.
Why is this needed for alloc?
While Box<T> is defined as a standard struct in the alloc crate, it requires compiler magic to be ergonomic. When a developer writes my_box.method() or my_box[0], the compiler must automatically dereference the pointer.
Test Case
Box dispatch from dyn:
trait Animal {
fn get_age(self: Box<Self>) -> i32;
}
struct Dog {
age: i32,
}
impl Animal for Dog {
fn get_age(self: Box<Self>) -> i32 {
self.age
}
}
fn foo () -> i32 {
let dog = Dog { age: 42 };
let _ = dog.age;
let coerced_box: Box<dyn Animal> = Box::new(dog);
let result = coerced_box.get_age();
// ^^^^^^^^^^^^^^^^^^^^^ auto-deref for method call
if result == 42 {
0
} else {
1
}
}
fn main() {
if foo() == 0 {
println!("ok.");
}
}
Box dispatch:
struct X { data: i32 }
trait A {
fn a(&self) -> i32;
}
impl A for X {
fn a(&self) -> i32 {
self.data
}
}
fn foo() -> i32 {
let x = X { data: 44 };
let y = X { data: 22 };
let z : [i32; 3] = [1, 2, 3];
let w : (i32, i32) = (10, 20);
let a : Box<dyn A> = Box::new(x);
let b : Box<X> = Box::new(y);
let c : Box<[i32; 3]> = Box::new(z);
let d : Box<(i32, i32)> = Box::new(w);
let e : Box<[i32]> = Box::new(z);
a.a() - 2 * b.data // 44 - 2 * 22
// ^^^^^ ^^^^^^ ─> field access
// └─> method dispatch(dyn)
+
c[0] + c[1] - c[2] // 1 + 2 - 3
// ^^^^ ─ ^^^^ ─ ^^^^ ─> array index access
+
2 * d.0 - d.1 // 2 * 10 - 20
// ^^^ ─ ^^^ ─> tuple field access
+
e[0] + e[1] - e[2] // 1 + 2 - 3
// ^^^^ ─ ^^^^ ─ ^^^^ ─> slice index access with trait Index
}
fn main() {
if foo() == 0 {
println!("ok.");
}
}
gccrs#4693: Add the alloc crate to the testsuite
Link: Rust-GCC/gccrs#4693
Note
This is important
Objective
This PR introduces the alloc crate to the compiler’s automated test suite. To achieve a clean compilation baseline, a few source-level workarounds were applied to bypass current compiler limitations.
Changes Made
-
Mock Core Integration: Embedded a primitive “mock core” as a submodule directly within the
alloccrate’slib.rsfile. -
Prelude Imports: Injected
use gccrs_core_prelude::*;acrossallocsource files to ensure they can successfully resolve the mock core items. -
Test Runner Configuration: Configured the test suite to compile the crate up to the name resolution phase using the
-frust-compile-until=nameresolutionflag.
Why is this needed for alloc?
Integrating alloc into the automated test suite locks in the progress made. It provides a strict baseline that prevents future compiler modifications from silently breaking alloc compatibility.
Known Restrictions & Future Work
Currently, the alloc test suite must be kept synchronized with the core test suite. Our mock core is minimal but gccrs still lacks complete upstream core support so the alloc crate can only be compiled up to the name resolution phase.
These temporary source-level workarounds and phase limitations will be incrementally removed as gccrs matures and gains full core support.
Reproducible Environment with Nix
If you want to continue the work on the alloc crate, contribute to the project independently, or simply test the changes locally, setting up the GCCRS development environment can be tedious.
To eliminate this friction, I created a Nix flake that provides a fully reproducible and configured workspace. You can inspect the flake source in my fork here:
Quick Start
Clone the repository (Skip if you already have it):
git clone https://github.com/Rust-GCC/gccrs.git
Initialize the environment:
-
Option A (If you use direnv): Run the following command once. It configures gccrs, builds it, and triggers direnv so your environment is automatically loaded whenever you enter the directory.
nix develop "github:nsvke/gccrs/gccrs-nix?dir=contrib/nix" -c "gccrs-setup --use-direnv" -
Option B (Manual approach): If you don’t use direnv, you must manually enter the Nix shell and run the setup script for your first time.
nix develop "github:nsvke/gccrs/gccrs-nix?dir=contrib/nix" -c "gccrs-setup"Note: You will need to run the nix develop command every time you open a new terminal to load the environment. Thanks to Nix caching, subsequent loads are instantaneous
Workspace Utilities
The Nix environment exposes several wrapper scripts designed to make compiler development smoother.
Note: These wrappers automatically resolve your project root and build directories. You can execute them from anywhere within your gccrs-workspace.
gccrs-setup: Configures the GCC build directory with well-thought-out defaults for gccrs development.
gccrs-build: Compiles the compiler. Pass –bear to automatically generate a compile_commands.json for your editor.
gccrs-test: Runs the Rust testsuite. Accepts DejaGnu arguments (e.g., gccrs-test compile.exp or gccrs-test execute.exp=my-test.rs).
gccrs-exec: Wraps the executables in build/gcc/ so you can call them directly (e.g., gccrs-exec crab1 my-test.rs).“
gccrs-git: A wrapper for git that resolves the repository root, allowing you to run git commands from outside the .git directory tree.
Changelog and Commit Tools
gccrs-mklog: Generates the GNU ChangeLog for your staged changes and prints it to stdout.
gccrs-commit-mklog: Generates the ChangeLog and directly injects it into your git commit template, ready for editing.
gccrs-verify: Validates your final commit message format against GCC’s strict commit guidelines before you open a PR.
Note
You can use
gccrs-helpfor learn all usages for all commands.
Extra: You can use all commands with a g prefix instead of the gccrs prefix (e.g., gtest, gbuild).
-
If using direnv: Add
--use-gprefixto your initial setup command (gccrs-setup --use-direnv --use-gprefix). -
If using the manual approach: Append
#gprefixto the flake target when entering the shell (nix develop "github:nsvke/gccrs/gccrs-nix?dir=contrib/nix#gprefix").
Acknowledgements
You have reached the end of this report. Thank you for taking the time to read through my journey.
I would like to express my sincere gratitude to the Google Summer of Code organizers for creating this incredible program, and to the GCC organization administrators for making this collaboration possible.
A very special thank you goes to my mentors, Arthur Cohen and Pierre-Emmanuel Patry. I am deeply grateful for your invaluable guidance, patience, and kindness throughout this entire project. I would also like to extend my thanks to the rest of the gccrs core team for their continuous help and for fostering such a welcoming environment.
I plan to remain a regular contributor to the gccrs project and look forward to continuing my work with this amazing community in the future.
If you have any questions about the project, or if you simply want to connect, please feel free to reach out to me at: [enes@nsvke.com]
If you truly dedicate yourself to something, there’s no reason why it shouldn’t turn out perfectly.
And remember, the world is open source!
Enes