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
4 changes: 3 additions & 1 deletion gcc/rust/backend/rust-compile-intrinsic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ static const std::map<std::string, handlers::HandlerBuilder> generic_intrinsics
{IValue::CATCH_UNWIND, handlers::try_handler (true)},
{IValue::DISCRIMINANT_VALUE, handlers::discriminant_value},
{IValue::VARIANT_COUNT, handlers::variant_count},
{IValue::BSWAP, handlers::bswap_handler}};
{IValue::BSWAP, handlers::bswap_handler},
{IValue::CTLZ, handlers::ctlz_handler},
{IValue::CTLZ_NONZERO, handlers::ctlz_nonzero_handler}};

Intrinsics::Intrinsics (Context *ctx) : ctx (ctx) {}

Expand Down
157 changes: 157 additions & 0 deletions gcc/rust/backend/rust-intrinsic-handlers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,151 @@ atomic_load (Context *ctx, TyTy::FnType *fntype, int ordering)
return fndecl;
}

// Shared inner implementation for ctlz and ctlz_nonzero.
//
// nonzero=false → ctlz: ctlz(0) is well-defined in Rust and must return
// bit_size, but __builtin_clz*(0) is undefined behaviour in C, so an
// explicit arg==0 guard is emitted.
//
// nonzero=true → ctlz_nonzero: the caller guarantees arg != 0 (passing 0
// is immediate UB in Rust), so the zero guard is omitted entirely.
static tree
ctlz_handler (Context *ctx, TyTy::FnType *fntype, bool nonzero)
{
rust_assert (fntype->get_params ().size () == 1);

tree lookup = NULL_TREE;
if (check_for_cached_intrinsic (ctx, fntype, &lookup))
return lookup;

auto fndecl = compile_intrinsic_function (ctx, fntype);

std::vector<Bvariable *> param_vars;
compile_fn_params (ctx, fntype, fndecl, &param_vars);

auto arg_param = param_vars.at (0);
if (!Backend::function_set_parameters (fndecl, param_vars))
return error_mark_node;

rust_assert (fntype->get_num_substitutions () == 1);
auto *monomorphized_type
= fntype->get_substs ().at (0).get_param_ty ()->resolve ();
if (!check_for_basic_integer_type ("ctlz", fntype->get_locus (),
monomorphized_type))
return error_mark_node;

enter_intrinsic_block (ctx, fndecl);

// BUILTIN ctlz FN BODY BEGIN
auto locus = fntype->get_locus ();
auto arg_expr = Backend::var_expression (arg_param, locus);
tree arg_type = TREE_TYPE (arg_expr);
unsigned bit_size = TYPE_PRECISION (arg_type);

// Convert signed types to their same-width unsigned equivalent before
// widening. Without this, widening a signed type sign-extends it.
// For example, i8(-1) = 0xFF widened to u32 gives 0xFFFFFFFF, so
// __builtin_clz(0xFFFFFFFF) = 0, then 0 - diff(24) = -24.
// Converting to u8 first gives 0xFF → 0x000000FF (zero-extended), so
// __builtin_clz(0x000000FF) = 24, then 24 - 24 = 0.
tree unsigned_type
= !TYPE_UNSIGNED (arg_type) ? unsigned_type_for (arg_type) : arg_type;
tree unsigned_arg = fold_convert (unsigned_type, arg_expr);

// Pick the narrowest GCC clz builtin whose operand type is wide enough to
// hold bit_size bits. diff records how many extra leading zeros the builtin
// will count due to the width difference and is subtracted from the result.
//
// Example: ctlz(1u8) bit_size=8, int_prec=32, diff=24.
// __builtin_clz(1u) returns 31 (counts from bit 31 down to bit 0).
// 31 - 24 = 7, which is the correct answer for an 8-bit value.
//
// TODO: 128-bit integers are not yet handled.
unsigned int_prec = TYPE_PRECISION (unsigned_type_node);
unsigned long_prec = TYPE_PRECISION (long_unsigned_type_node);
unsigned longlong_prec = TYPE_PRECISION (long_long_unsigned_type_node);

const char *builtin_name = nullptr;
tree cast_type = NULL_TREE;
int diff = 0;

if (bit_size <= int_prec)
{
// Fits in unsigned int: covers 8/16/32-bit integers on most targets.
builtin_name = "__builtin_clz";
cast_type = unsigned_type_node;
diff = static_cast<int> (int_prec - bit_size);
}
else if (bit_size <= long_prec)
{
// Fits in unsigned long but not unsigned int.
builtin_name = "__builtin_clzl";
cast_type = long_unsigned_type_node;
diff = static_cast<int> (long_prec - bit_size);
}
else if (bit_size <= longlong_prec)
{
// Fits in unsigned long long but not unsigned long.
builtin_name = "__builtin_clzll";
cast_type = long_long_unsigned_type_node;
diff = static_cast<int> (longlong_prec - bit_size);
}
else
{
rust_sorry_at (locus, "ctlz for %u-bit integers is not yet implemented",
bit_size);
return error_mark_node;
}

// Widen the unsigned arg to the chosen builtin's operand type, call it,
// then subtract the padding bits. diff == 0 means the Rust type exactly
// matches the builtin's operand width, so the subtraction is skipped.
tree call_arg = fold_convert (cast_type, unsigned_arg);

tree builtin_decl = error_mark_node;
BuiltinsContext::get ().lookup_simple_builtin (builtin_name, &builtin_decl);
rust_assert (builtin_decl != error_mark_node);

tree builtin_fn = build_fold_addr_expr_loc (locus, builtin_decl);
tree clz_expr
= Backend::call_expression (builtin_fn, {call_arg}, nullptr, locus);

if (diff > 0)
{
tree diff_cst = build_int_cst (integer_type_node, diff);
clz_expr
= fold_build2 (MINUS_EXPR, integer_type_node, clz_expr, diff_cst);
}

clz_expr = fold_convert (uint32_type_node, clz_expr);

tree final_expr;
if (!nonzero)
{
// ctlz(0) must return bit_size per the Rust reference.
// We cannot pass 0 to __builtin_clz* (UB), so emit:
// arg == 0 ? bit_size : clz_expr
tree zero = build_int_cst (arg_type, 0);
tree cmp = fold_build2 (EQ_EXPR, boolean_type_node, arg_expr, zero);
tree width_cst = build_int_cst (uint32_type_node, bit_size);
final_expr
= fold_build3 (COND_EXPR, uint32_type_node, cmp, width_cst, clz_expr);
}
else
{
// ctlz_nonzero: arg != 0 is guaranteed by the caller, no guard needed.
final_expr = clz_expr;
}

tree result = fold_convert (TREE_TYPE (DECL_RESULT (fndecl)), final_expr);
auto return_stmt = Backend::return_statement (fndecl, result, locus);
ctx->add_statement (return_stmt);
// BUILTIN ctlz FN BODY END

finalize_intrinsic_block (ctx, fndecl);
return fndecl;
}

} // namespace inner

const HandlerBuilder
Expand Down Expand Up @@ -1530,6 +1675,18 @@ bswap_handler (Context *ctx, TyTy::FnType *fntype)
return fndecl;
}

tree
ctlz_handler (Context *ctx, TyTy::FnType *fntype)
{
return inner::ctlz_handler (ctx, fntype, false);
}

tree
ctlz_nonzero_handler (Context *ctx, TyTy::FnType *fntype)
{
return inner::ctlz_handler (ctx, fntype, true);
}

} // namespace handlers
} // namespace Compile
} // namespace Rust
2 changes: 2 additions & 0 deletions gcc/rust/backend/rust-intrinsic-handlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ tree assume (Context *ctx, TyTy::FnType *fntype);
tree discriminant_value (Context *ctx, TyTy::FnType *fntype);
tree variant_count (Context *ctx, TyTy::FnType *fntype);
tree bswap_handler (Context *ctx, TyTy::FnType *fntype);
tree ctlz_handler (Context *ctx, TyTy::FnType *fntype);
tree ctlz_nonzero_handler (Context *ctx, TyTy::FnType *fntype);

tree prefetch_data (Context *ctx, TyTy::FnType *fntype, Prefetch kind);

Expand Down
6 changes: 5 additions & 1 deletion gcc/rust/lex/rust-lex.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2271,7 +2271,11 @@ Lexer::parse_non_decimal_int_literal (location_t loc, IsDigitFunc is_digit_func,
}

// convert value to decimal representation
long dec_num = std::strtol (existent_str.c_str (), nullptr, base);
// Use strtoull so that values like 0xFFFFFFFF are handled correctly on
// 32-bit hosts where |long| is only 32 bits and strtol would clamp to
// LONG_MAX (0x7FFFFFFF) instead of preserving the full unsigned value.
unsigned long long dec_num
= std::strtoull (existent_str.c_str (), nullptr, base);

existent_str = std::to_string (dec_num);

Expand Down
1 change: 1 addition & 0 deletions gcc/rust/util/rust-intrinsic-values.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class Intrinsics
static constexpr auto &CALLER_LOCATION = "caller_location";
static constexpr auto &CTPOP = "ctpop";
static constexpr auto &CTLZ = "ctlz";
static constexpr auto &CTLZ_NONZERO = "ctlz_nonzero";
static constexpr auto &CTTZ = "cttz";
static constexpr auto &BSWAP = "bswap";
static constexpr auto &BITREVERSE = "bitreverse";
Expand Down
17 changes: 17 additions & 0 deletions gcc/testsuite/rust/compile/ctlz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// { dg-do compile }
#![feature(intrinsics, lang_items, no_core)]
#![no_core]

#[lang = "sized"]
pub trait Sized {}

#[lang = "copy"]
pub trait Copy {}

extern "rust-intrinsic" {
pub fn ctlz<T>(x: T) -> u32; // { dg-error "ctlz intrinsics can only be used with basic integer types .got 'bool'." }
}

fn main() {
let _ = ctlz(true);
}
19 changes: 19 additions & 0 deletions gcc/testsuite/rust/compile/ctlz_nonzero.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// { dg-do compile }
#![feature(intrinsics, lang_items, no_core)]
#![no_core]

#[lang = "sized"]
pub trait Sized {}

#[lang = "copy"]
pub trait Copy {}

extern "rust-intrinsic" {
pub fn ctlz_nonzero<T>(x: T) -> u32; // { dg-error "ctlz intrinsics can only be used with basic integer types .got 'bool'." }
}

fn main() {
unsafe {
let _ = ctlz_nonzero(true);
}
}
56 changes: 56 additions & 0 deletions gcc/testsuite/rust/execute/torture/ctlz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#![feature(no_core)]
#![no_core]
#![feature(intrinsics)]
#![feature(lang_items)]

#[lang = "sized"]
pub trait Sized {}

extern "rust-intrinsic" {
pub fn ctlz<T>(x: T) -> u32;
pub fn abort() -> !;
}

fn main() -> i32 {
if ctlz(0u8) != 8 {
abort();
}
if ctlz(1u8) != 7 {
abort();
}
if ctlz(255u8) != 0 {
abort();
}

if ctlz(0u16) != 16 {
abort();
}
if ctlz(1u16) != 15 {
abort();
}
if ctlz(0xFFFFu16) != 0 {
abort();
}

if ctlz(0u32) != 32 {
abort();
}
if ctlz(1u32) != 31 {
abort();
}
if ctlz(0xFFFFFFFFu32) != 0 {
abort();
}

if ctlz(0u64) != 64 {
abort();
}
if ctlz(1u64) != 63 {
abort();
}
if ctlz(!0u64) != 0 {
abort();
}

0
}
28 changes: 28 additions & 0 deletions gcc/testsuite/rust/execute/torture/ctlz_i16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![feature(no_core)]
#![no_core]
#![feature(intrinsics)]
#![feature(lang_items)]

#[lang = "sized"]
pub trait Sized {}

extern "rust-intrinsic" {
pub fn ctlz<T>(x: T) -> u32;
pub fn abort() -> !;
}

fn main() -> i32 {
if ctlz(0i16) != 16 {
abort();
}
// 1i16 = 0x0001: 15 leading zeros
if ctlz(1i16) != 15 {
abort();
}
// -1i16 = 0xFFFF: 0 leading zeros
if ctlz(-1i16) != 0 {
abort();
}

0
}
28 changes: 28 additions & 0 deletions gcc/testsuite/rust/execute/torture/ctlz_i32.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![feature(no_core)]
#![no_core]
#![feature(intrinsics)]
#![feature(lang_items)]

#[lang = "sized"]
pub trait Sized {}

extern "rust-intrinsic" {
pub fn ctlz<T>(x: T) -> u32;
pub fn abort() -> !;
}

fn main() -> i32 {
if ctlz(0i32) != 32 {
abort();
}
// 1i32 = 0x00000001: 31 leading zeros
if ctlz(1i32) != 31 {
abort();
}
// -1i32 = 0xFFFFFFFF: 0 leading zeros
if ctlz(-1i32) != 0 {
abort();
}

0
}
28 changes: 28 additions & 0 deletions gcc/testsuite/rust/execute/torture/ctlz_i64.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![feature(no_core)]
#![no_core]
#![feature(intrinsics)]
#![feature(lang_items)]

#[lang = "sized"]
pub trait Sized {}

extern "rust-intrinsic" {
pub fn ctlz<T>(x: T) -> u32;
pub fn abort() -> !;
}

fn main() -> i32 {
if ctlz(0i64) != 64 {
abort();
}
// 1i64 = 0x0000000000000001: 63 leading zeros
if ctlz(1i64) != 63 {
abort();
}
// -1i64 = 0xFFFFFFFFFFFFFFFF: 0 leading zeros
if ctlz(-1i64) != 0 {
abort();
}

0
}
Loading
Loading