GCC Rust Weekly Status Report 58

Thanks again to Open Source Security, inc and Embecosm for their ongoing support for this project.

Milestone Progress

We continued to make progress on many fronts last week. The incremental refactor to coercion sites is key as the goal here not only ensures our implementation conforms to Rustc, it will eventually improve our location information for diagnostics on type errors and clean up a lot of code. We have also made a lot of progress in bugs, in general, this week by closing out existing issues that have already been fixed and reviewing and merging outstanding fixes.

Our GSoC project into constexpr is progressing nicely (code is not merged into main yet), with many control flow mechanisms such as conditionals and loops starting to work. We will need to start working on a cleanup effort to make the code mergeable back to main soon.

One missing piece of our GitHub CI is that we are not running our test suite for the 32-bit configuration, so we have regressed on 32-bit systems because of a fix that went into our transmute implementation. We will add the -m32 flags to our testing phase in CI to try and catch these issues as part of pull requests.

In other news, our integration tests of the Blake3 and libcore SIP hasher have made quite a lot of progress. The blake3 test case in particular, is only missing for loop support and some minor issues in parsing range syntax to close the topic out now.

As for our GCC patches, version two of the patches is still a work in progress as it takes time to build up the patches to ensure each one is buildable, but splitting each pass up here looks like a good solution for review purposes.

Completed Activities

  • Fix bug in recurisve macro expansion PR1429
  • Make builtin macro expansion more conformant to rustc PR1430
  • Fix bad transmute of aggregate types PR1433
  • Incremental refactor for conformant coercion sites pt1 PR1431
  • Update type hasher to stop bad converions during codegen PR1435
  • Array index access does not need to unsize to a slice PR1437
  • Add test case for calling builtin macro when it does not exist PR1442
  • Simplify testcase PR1438

Contributors this week

Overall Task Status

CategoryLast WeekThis WeekDelta
TODO160157-3
In Progress2929
Completed420430+10
GitHub Issues

Test Cases

CategoryLast WeekThis WeekDelta
Passing65316607+76
Failed
XFAIL5151
XPASS
make check-rust

Bugs

CategoryLast WeekThis WeekDelta
TODO5554-1
In Progress1314+1
Completed178185+7
GitHub Bugs

Milestone Progress

MilestoneLast WeekThis WeekDeltaStart DateCompletion DateTarget
Data Structures 1 – Core100%100%30th Nov 202027th Jan 202129th Jan 2021
Control Flow 1 – Core100%100%28th Jan 202110th Feb 202126th Feb 2021
Data Structures 2 – Generics100%100%11th Feb 202114th May 202128th May 2021
Data Structures 3 – Traits100%100%20th May 202117th Sept 202127th Aug 2021
Control Flow 2 – Pattern Matching100%100%20th Sept 20219th Dec 202129th Nov 2021
Macros and cfg expansion100100%1st Dec 202131st Mar 202228th Mar 2022
Imports and Visibility100%100%29th Mar 202213th Jul 202227th May 2022
Const Generics45%50%+5%30th May 202217th Oct 2022
Intrinsics0%0%6th Sept 202214th Nov 2022
GitHub Milestones

Risks

RiskImpact (1-3)Likelihood (0-10)Risk (I * L)Mitigation
Rust Language Changes2714Target specific Rustc version for first go
Missing GCC 13 upstream window166Merge in GCC 14 and be proactive about reviews

Planned Activities

  • Prepare gcc patches v2
  • Continue work on const evaluation

Detailed changelog

Deref coercions

We have started an incremental refactor to cleanup how our type system works. The refactor here is focused on coercion sites firstly so that we become more conformant to Rustc. So for example now we can support deref coercions which can look pretty strange from a language perspective, here we are “borrowing foo” which actually ends up producing a deref call to unbox the generic structure foo. This same autoderef cycle already occurs in method resolution but is also supported in coercion sites.

extern "C" {
    fn printf(s: *const i8, ...);
}

struct Foo<T>(T);
impl<T> core::ops::Deref for Foo<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn main() {
    let foo: Foo<i32> = Foo(123);
    let bar: &i32 = &foo;

    unsafe {
        let a = "%i\n\0";
        let b = a as *const str;
        let c = b as *const i8;

        printf(c, *bar);
    }
}

see: https://godbolt.org/z/qPz6G84hd

Array index does not need to unsize

When working through some complex test cases where we define the libcore code for slice creation and access, then add in normal array index operations there was an issue that gccrs always produced an unsize coercion for arrays to a slice in order to do array index access. This is completly unnecessary but could be technically valid rust code since it is valid to unsize an array to a slice. It does however miss GCC -Warray-bounds checking at compile time and add in unnessecary overhead in non optimized builds.

mod intrinsics {
    extern "rust-intrinsic" {
        pub fn offset<T>(ptr: *const T, count: isize) -> *const T;
    }
}

mod mem {
    extern "rust-intrinsic" {
        fn size_of<T>() -> usize;
    }
}

extern "C" {
    fn printf(s: *const i8, ...);
}

struct FatPtr<T> {
    data: *const T,
    len: usize,
}

pub union Repr<T> {
    rust: *const [T],
    rust_mut: *mut [T],
    raw: FatPtr<T>,
}

pub enum Option<T> {
    None,
    Some(T),
}

#[lang = "Range"]
pub struct Range<Idx> {
    pub start: Idx,
    pub end: Idx,
}

#[lang = "const_slice_ptr"]
impl<T> *const [T] {
    pub const fn len(self) -> usize {
        let a = unsafe { Repr { rust: self }.raw };
        a.len
    }

    pub const fn as_ptr(self) -> *const T {
        self as *const T
    }
}

#[lang = "const_ptr"]
impl<T> *const T {
    pub const unsafe fn offset(self, count: isize) -> *const T {
        unsafe { intrinsics::offset(self, count) }
    }

    pub const unsafe fn add(self, count: usize) -> Self {
        unsafe { self.offset(count as isize) }
    }

    pub const fn as_ptr(self) -> *const T {
        self as *const T
    }
}

const fn slice_from_raw_parts<T>(data: *const T, len: usize) -> *const [T] {
    unsafe {
        Repr {
            raw: FatPtr { data, len },
        }
        .rust
    }
}

#[lang = "index"]
trait Index<Idx> {
    type Output;

    fn index(&self, index: Idx) -> &Self::Output;
}

impl<T> [T] {
    pub const fn is_empty(&self) -> bool {
        self.len() == 0
    }

    pub const fn len(&self) -> usize {
        unsafe { Repr { rust: self }.raw.len }
    }
}

pub unsafe trait SliceIndex<T> {
    type Output;

    fn get(self, slice: &T) -> Option<&Self::Output>;

    unsafe fn get_unchecked(self, slice: *const T) -> *const Self::Output;

    fn index(self, slice: &T) -> &Self::Output;
}

unsafe impl<T> SliceIndex<[T]> for usize {
    type Output = T;

    fn get(self, slice: &[T]) -> Option<&T> {
        unsafe { Option::Some(&*self.get_unchecked(slice)) }
    }

    unsafe fn get_unchecked(self, slice: *const [T]) -> *const T {
        // SAFETY: the caller guarantees that `slice` is not dangling, so it
        // cannot be longer than `isize::MAX`. They also guarantee that
        // `self` is in bounds of `slice` so `self` cannot overflow an `isize`,
        // so the call to `add` is safe.
        unsafe { slice.as_ptr().add(self) }
    }

    fn index(self, slice: &[T]) -> &T {
        unsafe { &*self.get_unchecked(slice) }
    }
}

unsafe impl<T> SliceIndex<[T]> for Range<usize> {
    type Output = [T];

    fn get(self, slice: &[T]) -> Option<&[T]> {
        if self.start > self.end || self.end > slice.len() {
            Option::None
        } else {
            unsafe { Option::Some(&*self.get_unchecked(slice)) }
        }
    }

    unsafe fn get_unchecked(self, slice: *const [T]) -> *const [T] {
        unsafe {
            let a: *const T = slice.as_ptr();
            let b: *const T = a.add(self.start);
            slice_from_raw_parts(b, self.end - self.start)
        }
    }

    fn index(self, slice: &[T]) -> &[T] {
        unsafe { &*self.get_unchecked(slice) }
    }
}

impl<T, I> Index<I> for [T]
where
    I: SliceIndex<[T]>,
{
    type Output = I::Output;

    fn index(&self, index: I) -> &I::Output {
        unsafe {
            let a = "slice-index\n\0";
            let b = a as *const str;
            let c = b as *const i8;

            printf(c);
        }

        index.index(self)
    }
}

fn main() -> i32 {
    let a = [1, 2, 3, 4, 5];
    let b = a[1];

    b - 2
}

see: https://godbolt.org/z/q3rEdjr1e

Integration tests update

We have a hit a break through we our blake3 integration test, so that most of the code now compiles. The remaining issues we have our missing for-loop support and some minor bugs in our range syntax to finish this off. For loop’s sound simple but they actualy depend on a tremendous amount of libcore code, but the benifit here is that once you support for loops you implicitly support iterators from libcore because a for loop is syntactic sugar for calling IntoIterator and calling next with a check on the result on whether to break or not. If you are interested in what this means, try compiling an empty for loop on compiler explorer for Rustc and see how much code is produced when optimizations are turned off to see what we mean.

see:

Leave a Reply

Your email address will not be published.