Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 114 additions & 4 deletions compiler/rustc_codegen_llvm/src/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1244,12 +1244,123 @@ fn autocast<'ll>(
}
}

fn parse_integer(string: &mut &[u8]) -> Option<u64> {
let mut number = 0;
let mut position = 0;
while let Some(&digit @ b'0'..=b'9') = string.get(position) {
number = (10 * number) + (digit - b'0') as u64;
position += 1;
}

if position != number.checked_ilog10().unwrap_or(0) as usize + 1 {
return None;
}

*string = &string[position..];
Some(number)
}

fn strip_off_prefix(slice: &mut &[u8], prefix: &[u8]) -> bool {
slice.strip_prefix(prefix).map(|remainder| *slice = remainder).is_some()
}

fn demangle_type_str<'ll>(cx: &CodegenCx<'ll, '_>, slice: &mut &[u8]) -> Option<&'ll Type> {
Some(if strip_off_prefix(slice, b"isVoid") {
cx.type_void()
} else if strip_off_prefix(slice, b"f16") {
cx.type_f16()
} else if strip_off_prefix(slice, b"bf16") {
cx.type_bf16()
} else if strip_off_prefix(slice, b"f32") {
cx.type_f32()
} else if strip_off_prefix(slice, b"f64") {
cx.type_f64()
} else if strip_off_prefix(slice, b"f128") {
cx.type_f128()
} else if strip_off_prefix(slice, b"i") {
let width = parse_integer(slice)?;
cx.type_ix(width)
} else if strip_off_prefix(slice, b"p") {
let address_space = parse_integer(slice)?;
cx.type_ptr_ext(AddressSpace(address_space as u32))
} else if strip_off_prefix(slice, b"v") {
let length = parse_integer(slice)?;
let element_type = demangle_type_str(cx, slice)?;
cx.type_vector(element_type, length)
} else if strip_off_prefix(slice, b"nxv") {
let length = parse_integer(slice)?;
let element_type = demangle_type_str(cx, slice)?;
cx.type_scalable_vector(element_type, length)
} else if strip_off_prefix(slice, b"a") {
let length = parse_integer(slice)?;
let element_type = demangle_type_str(cx, slice)?;
cx.type_array(element_type, length)
} else if strip_off_prefix(slice, b"sl_") {
let mut elements = Vec::new();

loop {
if let Some(remainder) = slice.strip_prefix(b"s")
&& !remainder.starts_with(b"_")
&& !remainder.starts_with(b"l_")
{
*slice = remainder;
break cx.type_struct(&elements, true);
}
elements.push(demangle_type_str(cx, slice)?);
}
} else if strip_off_prefix(slice, b"f_") {
let return_type = demangle_type_str(cx, slice)?;
let mut arguments = Vec::new();

loop {
if let Some(remainder) = slice.strip_prefix(b"f")
&& !remainder.starts_with(b"_")
{
*slice = remainder;
break cx.type_func(&arguments, return_type);
}
if strip_off_prefix(slice, b"varargf") {
break cx.type_variadic_func(&arguments, return_type);
}
arguments.push(demangle_type_str(cx, slice)?);
}
} else {
return None;
})
}

fn parse_type_parameters<'ll, 'tcx>(
cx: &CodegenCx<'ll, 'tcx>,
intrinsic: llvm::Intrinsic,
name: &str,
) -> Option<Vec<&'ll Type>> {
let base_name: &'ll [u8] = intrinsic.base_name();

let slice = &mut name.as_bytes().strip_prefix(base_name).unwrap();

if !intrinsic.is_overloaded() {
return slice.is_empty().then(|| Vec::new());
}

let mut type_params = Vec::new();

while !slice.is_empty() {
if !strip_off_prefix(slice, b".") {
return None;
}

type_params.push(demangle_type_str(cx, slice)?);
}

Some(type_params)
}

fn intrinsic_fn<'ll, 'tcx>(
bx: &Builder<'_, 'll, 'tcx>,
name: &str,
rust_return_ty: &'ll Type,
rust_argument_tys: Vec<&'ll Type>,
instance: ty::Instance<'tcx>,
instance: Instance<'tcx>,
) -> &'ll Value {
let tcx = bx.tcx;

Expand All @@ -1275,10 +1386,9 @@ fn intrinsic_fn<'ll, 'tcx>(
}

if let Some(intrinsic) = intrinsic
&& !intrinsic.is_overloaded()
&& let Some(type_params) = parse_type_parameters(bx.cx, intrinsic, name)
{
// FIXME: also do this for overloaded intrinsics
let llfn = intrinsic.get_declaration(bx.llmod, &[]);
let llfn = intrinsic.get_declaration(bx.llmod, &type_params);
let llvm_fn_ty = bx.get_type_of_global(llfn);

let llvm_return_ty = bx.get_return_type(llvm_fn_ty);
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,10 @@ unsafe extern "C" {
NewFn: &mut Option<&'a Value>,
) -> bool;
pub(crate) fn LLVMRustIsTargetIntrinsic(ID: NonZero<c_uint>) -> bool;
pub(crate) fn LLVMRustIntrinsicGetBaseName(
ID: NonZero<c_uint>,
NameLength: &mut size_t,
) -> *const c_char;

// Operations on parameters
pub(crate) fn LLVMIsAArgument(Val: &Value) -> Option<&Value>;
Expand Down
8 changes: 7 additions & 1 deletion compiler/rustc_codegen_llvm/src/llvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

use std::ffi::{CStr, CString};
use std::num::NonZero;
use std::ptr;
use std::string::FromUtf8Error;
use std::{ptr, slice};

use libc::c_uint;
use rustc_abi::{AddressSpace, Align, Size, WrappingRange};
Expand Down Expand Up @@ -355,6 +355,12 @@ impl Intrinsic {
LLVMGetIntrinsicDeclaration(llmod, self.id, type_params.as_ptr(), type_params.len())
}
}

pub(crate) fn base_name<'ll>(self) -> &'ll [u8] {
let mut length = 0;
let ptr = unsafe { LLVMRustIntrinsicGetBaseName(self.id, &mut length) };
unsafe { slice::from_raw_parts(ptr.cast(), length) }
}
}

/// Safe wrapper for `LLVMSetValueName2` from a byte slice
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
let start = dest.val.llval;
let size = bx.const_usize(dest.layout.size.bytes());

// Use llvm.memset.p0i8.* to initialize all same byte arrays
// Use llvm.memset.p0.* to initialize all same byte arrays
if let Some(int) = bx.cx().const_to_opt_u128(v, false)
&& let bytes = &int.to_le_bytes()[..cg_elem.layout.size.bytes_usize()]
&& let Ok(&byte) = bytes.iter().all_equal_value()
Expand All @@ -151,7 +151,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
return true;
}

// Use llvm.memset.p0i8.* to initialize byte arrays
// Use llvm.memset.p0.* to initialize byte arrays
let v = bx.from_immediate(v);
if bx.cx().val_ty(v) == bx.cx().type_i8() {
bx.memset(start, v, size, dest.val.align, MemFlags::empty());
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,13 @@ extern "C" bool LLVMRustIsTargetIntrinsic(unsigned ID) {
return Intrinsic::isTargetIntrinsic(ID);
}

extern "C" const char *LLVMRustIntrinsicGetBaseName(unsigned ID,
size_t *NameLength) {
auto baseName = Intrinsic::getBaseName(ID);
*NameLength = baseName.size();
return baseName.data();
}

extern "C" LLVMValueRef LLVMRustConstPtrAuth(LLVMValueRef Ptr, uint32_t Key,
uint64_t Disc,
LLVMValueRef AddrDiversity,
Expand Down
6 changes: 3 additions & 3 deletions library/core/src/intrinsics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -953,7 +953,7 @@ pub const unsafe fn slice_get_unchecked<
#[rustc_intrinsic]
pub fn ptr_mask<T>(ptr: *const T, mask: usize) -> *const T;

/// Equivalent to the appropriate `llvm.memcpy.p0i8.0i8.*` intrinsic, with
/// Equivalent to the appropriate `llvm.memcpy.p0.p0.*` intrinsic, with
/// a size of `count` * `size_of::<T>()` and an alignment of `align_of::<T>()`.
///
/// This intrinsic does not have a stable counterpart.
Expand All @@ -967,7 +967,7 @@ pub fn ptr_mask<T>(ptr: *const T, mask: usize) -> *const T;
#[rustc_intrinsic]
#[rustc_nounwind]
pub unsafe fn volatile_copy_nonoverlapping_memory<T>(dst: *mut T, src: *const T, count: usize);
/// Equivalent to the appropriate `llvm.memmove.p0i8.0i8.*` intrinsic, with
/// Equivalent to the appropriate `llvm.memmove.p0.p0.*` intrinsic, with
/// a size of `count * size_of::<T>()` and an alignment of `align_of::<T>()`.
///
/// The volatile parameter is set to `true`, so it will not be optimized out
Expand All @@ -977,7 +977,7 @@ pub unsafe fn volatile_copy_nonoverlapping_memory<T>(dst: *mut T, src: *const T,
#[rustc_intrinsic]
#[rustc_nounwind]
pub unsafe fn volatile_copy_memory<T>(dst: *mut T, src: *const T, count: usize);
/// Equivalent to the appropriate `llvm.memset.p0i8.*` intrinsic, with a
/// Equivalent to the appropriate `llvm.memset.p0.*` intrinsic, with a
/// size of `count * size_of::<T>()` and an alignment of `align_of::<T>()`.
///
/// This intrinsic does not have a stable counterpart.
Expand Down
2 changes: 1 addition & 1 deletion src/tools/rustfmt/tests/target/format_strings/issue-202.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ fn compile_empty_program() {
let expected = "; ModuleID = \'foo\'

; Function Attrs: nounwind
declare void @llvm.memset.p0i8.i32(i8* nocapture, i8, i32, i32, i1) #0
declare void @llvm.memset.p0.i32(i8* nocapture, i8, i32, i32, i1) #0

declare i32 @write(i32, i8*, i32)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fn compile_empty_program() {
let expected = "; ModuleID = \'foo\'

; Function Attrs: nounwind
declare void @llvm.memset.p0i8.i32(i8* nocapture, i8, i32, i32, i1) #0
declare void @llvm.memset.p0.i32(i8* nocapture, i8, i32, i32, i1) #0

declare i32 @write(i32, i8*, i32)

Expand Down
2 changes: 1 addition & 1 deletion src/tools/rustfmt/tests/target/string-lit-custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ fn main() {
let expected = "; ModuleID = \'foo\'

; Function Attrs: nounwind
declare void @llvm.memset.p0i8.i32(i8* nocapture, i8, i32, i32, i1) #0
declare void @llvm.memset.p0.i32(i8* nocapture, i8, i32, i32, i1) #0

declare i32 @write(i32, i8*, i32)

Expand Down
16 changes: 16 additions & 0 deletions tests/codegen-llvm/inject-autocast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,20 @@ pub unsafe fn amx_autocast(m: u16, n: u16, k: u16, a: Tile, b: Tile, c: Tile) ->
foo(m, n, k, a, b, c)
}

// CHECK-LABEL: @overloaded_bf16_autocast
#[no_mangle]
pub unsafe fn overloaded_bf16_autocast(a: i16x8) -> i16x8 {
extern "unadjusted" {
#[link_name = "llvm.sqrt.v8bf16"]
fn foo(a: i16x8) -> i16x8;

@RalfJung RalfJung Aug 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this extremely confusing. What is going on here? The intrinsic actually has type <8 x bfloat> but we pass it an i16x8? Why should we allow such code...?

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main motivation for this entire approach was ability to insert magic type casts for types that Rust does not support.

Of course, this is kind of moot with f16b being introduced in #160859.

(FWIW, I don't think what this PR does is a good idea, but I don't have time to fight it.)

@RalfJung RalfJung Aug 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, this is kind of moot with f16b being introduced in #160859.

Yeah that seems to remove this part of the motivation. OTOH the old referenced PR also mentions

LLVM intrinsics that have types in their signature that can't be accessed from Rust (notable examples are the AMX intrinsics that have the x86amx type, and (almost) all intrinsics that have vectors of i1 types) can't be linked to at all. This is a (major?) roadblock in the AMX and AVX512 support in stdarch.

So the question is, will we have native types for those as well? We might have to, if they are relevant for ABIs we need to implement. But if they are only used for LLVM intrinsics, not for extern "C", then this PR may save us from adding that type. I don't know anything about x86amx, or whether there's a better way to deal with vectors of i1.

Another motivation seems to be dealing with LLVM intrinsics that change their signature -- how is stdarch supposed to import these? But this does works today so it seems the LLVM auto-upgrade for this is enough?

And then there's "finding signatures that are just wrong". But it seems that already landed in #140763.

On the list of downsides we have "keeping the long mangled LLVM names even after LLVM itself doesn't use them any more", if I understood correctly the plan for how to deal with @nikic's work on getting rid of the name mangling in LLVM. That seems like a silly end state and is clearly path-dependent -- if LLVM had removed the mangling 5 years ago, I doubt we'd accept a PR like this to add magic name mangling in the link_name string to indicate the "real" signature. So likewise I don't think we want to be in a state where we are keeping the name mangling alive long after LLVM dropped it.

I also couldn't find an MCP for this change. Should it have one? It's very backend-internal, but OTOH one of our main backend experts disagrees so it may be good to have a bit of a wider discussion. @dianqk seems fine with it.

}

// CHECK: [[A:%[0-9]+]] = bitcast <8 x i16> {{.*}} to <8 x bfloat>
// CHECK: [[B:%[0-9]+]] = call <8 x bfloat> @llvm.sqrt.v8bf16(<8 x bfloat> [[A]])
// CHECK: bitcast <8 x bfloat> [[B]] to <8 x i16>
foo(a)
}

// CHECK: declare { i32, <2 x i64>, <2 x i64>, <2 x i64>, <2 x i64>, <2 x i64>, <2 x i64> } @llvm.x86.encodekey128(i32, <2 x i64>)

// CHECK: declare { <2 x i1>, <2 x i1> } @llvm.x86.avx512.vp2intersect.q.128(<2 x i64>, <2 x i64>)
Expand All @@ -116,3 +130,5 @@ pub unsafe fn amx_autocast(m: u16, n: u16, k: u16, a: Tile, b: Tile, c: Tile) ->
// CHECK: declare x86_amx @llvm.x86.cast.vector.to.tile.v1024i8(<1024 x i8>)

// CHECK: declare <1024 x i8> @llvm.x86.cast.tile.to.vector.v1024i8(x86_amx)

// CHECK: declare <8 x bfloat> @llvm.sqrt.v8bf16(<8 x bfloat>)
2 changes: 1 addition & 1 deletion tests/codegen-llvm/intrinsics/mask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
#[no_mangle]
pub fn mask_ptr(ptr: *const u16, mask: usize) -> *const u16 {
// CHECK: call
// CHECK-SAME: @llvm.ptrmask.{{p0|p0i8}}.[[WORD]](ptr {{%ptr|%1}}, [[WORD]] %mask)
// CHECK-SAME: @llvm.ptrmask.p0.[[WORD]](ptr {{%ptr|%1}}, [[WORD]] %mask)
core::intrinsics::ptr_mask(ptr, mask)
}
4 changes: 2 additions & 2 deletions tests/ui/abi/arm-unadjusted-intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ impl Copy for int8x16x4_t {}
pub unsafe fn vld1q_s8_x4(a: *const i8) -> int8x16x4_t {
#[allow(improper_ctypes)]
extern "unadjusted" {
#[cfg_attr(target_arch = "arm", link_name = "llvm.arm.neon.vld1x4.v16i8.p0i8")]
#[cfg_attr(target_arch = "aarch64", link_name = "llvm.aarch64.neon.ld1x4.v16i8.p0i8")]
#[cfg_attr(target_arch = "arm", link_name = "llvm.arm.neon.vld1x4.v16i8.p0")]
#[cfg_attr(target_arch = "aarch64", link_name = "llvm.aarch64.neon.ld1x4.v16i8.p0")]
fn vld1q_s8_x4_(a: *const i8) -> int8x16x4_t;
}
vld1q_s8_x4_(a)
Expand Down
Loading