Skip to content

Commit 349fba1

Browse files
committed
intrinsic: Add ctlz and ctlz_nonzero intrinsics
This commit introduces the `ctlz` and `ctlz_nonzero` intrinsics for counting leading zeros in integer types. The implementation includes handlers for both intrinsics, which validate input types and utilize GCC built-ins for the actual computation. Addresses: #658 gcc/rust/ChangeLog: * backend/rust-compile-intrinsic.cc: Register ctlz and ctlz_nonzero intrinsics. * backend/rust-intrinsic-handlers.cc (ctlz_handler): Implement ctlz_handler. (ctlz_nonzero_handler): Implement ctlz_nonzero_handler. * backend/rust-intrinsic-handlers.h (ctlz_handler): Add decl for ctlz_handler. (ctlz_nonzero_handler): Add decl for ctlz_nonzero_handler. * util/rust-intrinsic-values.h: map ctlz_nonzero to its name. gcc/testsuite/ChangeLog: * rust/compile/ctlz.rs: New test. * rust/compile/ctlz_nonzero.rs: New test. * rust/execute/torture/ctlz.rs: New test. * rust/execute/torture/ctlz_nonzero.rs: New test. Signed-off-by: Mohamed Ali <mohmedali1462005@gmail.com>
1 parent 13cfa5d commit 349fba1

File tree

8 files changed

+301
-1
lines changed

8 files changed

+301
-1
lines changed

gcc/rust/backend/rust-compile-intrinsic.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ static const std::map<std::string, handlers::HandlerBuilder> generic_intrinsics
6868
{IValue::CATCH_UNWIND, handlers::try_handler (true)},
6969
{IValue::DISCRIMINANT_VALUE, handlers::discriminant_value},
7070
{IValue::VARIANT_COUNT, handlers::variant_count},
71-
{IValue::BSWAP, handlers::bswap_handler}};
71+
{IValue::BSWAP, handlers::bswap_handler},
72+
{IValue::CTLZ, handlers::ctlz_handler},
73+
{IValue::CTLZ_NONZERO, handlers::ctlz_nonzero_handler}};
7274

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

gcc/rust/backend/rust-intrinsic-handlers.cc

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -748,6 +748,151 @@ atomic_load (Context *ctx, TyTy::FnType *fntype, int ordering)
748748
return fndecl;
749749
}
750750

751+
// Shared inner implementation for ctlz and ctlz_nonzero.
752+
//
753+
// nonzero=false → ctlz: ctlz(0) is well-defined in Rust and must return
754+
// bit_size, but __builtin_clz*(0) is undefined behaviour in C, so an
755+
// explicit arg==0 guard is emitted.
756+
//
757+
// nonzero=true → ctlz_nonzero: the caller guarantees arg != 0 (passing 0
758+
// is immediate UB in Rust), so the zero guard is omitted entirely.
759+
static tree
760+
ctlz_handler (Context *ctx, TyTy::FnType *fntype, bool nonzero)
761+
{
762+
rust_assert (fntype->get_params ().size () == 1);
763+
764+
tree lookup = NULL_TREE;
765+
if (check_for_cached_intrinsic (ctx, fntype, &lookup))
766+
return lookup;
767+
768+
auto fndecl = compile_intrinsic_function (ctx, fntype);
769+
770+
std::vector<Bvariable *> param_vars;
771+
compile_fn_params (ctx, fntype, fndecl, &param_vars);
772+
773+
auto arg_param = param_vars.at (0);
774+
if (!Backend::function_set_parameters (fndecl, param_vars))
775+
return error_mark_node;
776+
777+
rust_assert (fntype->get_num_substitutions () == 1);
778+
auto *monomorphized_type
779+
= fntype->get_substs ().at (0).get_param_ty ()->resolve ();
780+
if (!check_for_basic_integer_type ("ctlz", fntype->get_locus (),
781+
monomorphized_type))
782+
return error_mark_node;
783+
784+
enter_intrinsic_block (ctx, fndecl);
785+
786+
// BUILTIN ctlz FN BODY BEGIN
787+
auto locus = fntype->get_locus ();
788+
auto arg_expr = Backend::var_expression (arg_param, locus);
789+
tree arg_type = TREE_TYPE (arg_expr);
790+
unsigned bit_size = TYPE_PRECISION (arg_type);
791+
792+
// Convert signed types to their same-width unsigned equivalent before
793+
// widening. Without this, widening a signed type sign-extends it.
794+
// For example, i8(-1) = 0xFF widened to u32 gives 0xFFFFFFFF, so
795+
// __builtin_clz(0xFFFFFFFF) = 0, then 0 - diff(24) = -24.
796+
// Converting to u8 first gives 0xFF → 0x000000FF (zero-extended), so
797+
// __builtin_clz(0x000000FF) = 24, then 24 - 24 = 0.
798+
tree unsigned_type
799+
= !TYPE_UNSIGNED (arg_type) ? unsigned_type_for (arg_type) : arg_type;
800+
tree unsigned_arg = fold_convert (unsigned_type, arg_expr);
801+
802+
// Pick the narrowest GCC clz builtin whose operand type is wide enough to
803+
// hold bit_size bits. diff records how many extra leading zeros the builtin
804+
// will count due to the width difference and is subtracted from the result.
805+
//
806+
// Example: ctlz(1u8) bit_size=8, int_prec=32, diff=24.
807+
// __builtin_clz(1u) returns 31 (counts from bit 31 down to bit 0).
808+
// 31 - 24 = 7, which is the correct answer for an 8-bit value.
809+
//
810+
// TODO: 128-bit integers are not yet handled.
811+
unsigned int_prec = TYPE_PRECISION (unsigned_type_node);
812+
unsigned long_prec = TYPE_PRECISION (long_unsigned_type_node);
813+
unsigned longlong_prec = TYPE_PRECISION (long_long_unsigned_type_node);
814+
815+
const char *builtin_name = nullptr;
816+
tree cast_type = NULL_TREE;
817+
int diff = 0;
818+
819+
if (bit_size <= int_prec)
820+
{
821+
// Fits in unsigned int: covers 8/16/32-bit integers on most targets.
822+
builtin_name = "__builtin_clz";
823+
cast_type = unsigned_type_node;
824+
diff = static_cast<int> (int_prec - bit_size);
825+
}
826+
else if (bit_size <= long_prec)
827+
{
828+
// Fits in unsigned long but not unsigned int.
829+
builtin_name = "__builtin_clzl";
830+
cast_type = long_unsigned_type_node;
831+
diff = static_cast<int> (long_prec - bit_size);
832+
}
833+
else if (bit_size <= longlong_prec)
834+
{
835+
// Fits in unsigned long long but not unsigned long.
836+
builtin_name = "__builtin_clzll";
837+
cast_type = long_long_unsigned_type_node;
838+
diff = static_cast<int> (longlong_prec - bit_size);
839+
}
840+
else
841+
{
842+
rust_sorry_at (locus, "ctlz for %u-bit integers is not yet implemented",
843+
bit_size);
844+
return error_mark_node;
845+
}
846+
847+
// Widen the unsigned arg to the chosen builtin's operand type, call it,
848+
// then subtract the padding bits. diff == 0 means the Rust type exactly
849+
// matches the builtin's operand width, so the subtraction is skipped.
850+
tree call_arg = fold_convert (cast_type, unsigned_arg);
851+
852+
tree builtin_decl = error_mark_node;
853+
BuiltinsContext::get ().lookup_simple_builtin (builtin_name, &builtin_decl);
854+
rust_assert (builtin_decl != error_mark_node);
855+
856+
tree builtin_fn = build_fold_addr_expr_loc (locus, builtin_decl);
857+
tree clz_expr
858+
= Backend::call_expression (builtin_fn, {call_arg}, nullptr, locus);
859+
860+
if (diff > 0)
861+
{
862+
tree diff_cst = build_int_cst (integer_type_node, diff);
863+
clz_expr
864+
= fold_build2 (MINUS_EXPR, integer_type_node, clz_expr, diff_cst);
865+
}
866+
867+
clz_expr = fold_convert (uint32_type_node, clz_expr);
868+
869+
tree final_expr;
870+
if (!nonzero)
871+
{
872+
// ctlz(0) must return bit_size per the Rust reference.
873+
// We cannot pass 0 to __builtin_clz* (UB), so emit:
874+
// arg == 0 ? bit_size : clz_expr
875+
tree zero = build_int_cst (arg_type, 0);
876+
tree cmp = fold_build2 (EQ_EXPR, boolean_type_node, arg_expr, zero);
877+
tree width_cst = build_int_cst (uint32_type_node, bit_size);
878+
final_expr
879+
= fold_build3 (COND_EXPR, uint32_type_node, cmp, width_cst, clz_expr);
880+
}
881+
else
882+
{
883+
// ctlz_nonzero: arg != 0 is guaranteed by the caller, no guard needed.
884+
final_expr = clz_expr;
885+
}
886+
887+
tree result = fold_convert (TREE_TYPE (DECL_RESULT (fndecl)), final_expr);
888+
auto return_stmt = Backend::return_statement (fndecl, result, locus);
889+
ctx->add_statement (return_stmt);
890+
// BUILTIN ctlz FN BODY END
891+
892+
finalize_intrinsic_block (ctx, fndecl);
893+
return fndecl;
894+
}
895+
751896
} // namespace inner
752897

753898
const HandlerBuilder
@@ -1530,6 +1675,18 @@ bswap_handler (Context *ctx, TyTy::FnType *fntype)
15301675
return fndecl;
15311676
}
15321677

1678+
tree
1679+
ctlz_handler (Context *ctx, TyTy::FnType *fntype)
1680+
{
1681+
return inner::ctlz_handler (ctx, fntype, false);
1682+
}
1683+
1684+
tree
1685+
ctlz_nonzero_handler (Context *ctx, TyTy::FnType *fntype)
1686+
{
1687+
return inner::ctlz_handler (ctx, fntype, true);
1688+
}
1689+
15331690
} // namespace handlers
15341691
} // namespace Compile
15351692
} // namespace Rust

gcc/rust/backend/rust-intrinsic-handlers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ tree assume (Context *ctx, TyTy::FnType *fntype);
6565
tree discriminant_value (Context *ctx, TyTy::FnType *fntype);
6666
tree variant_count (Context *ctx, TyTy::FnType *fntype);
6767
tree bswap_handler (Context *ctx, TyTy::FnType *fntype);
68+
tree ctlz_handler (Context *ctx, TyTy::FnType *fntype);
69+
tree ctlz_nonzero_handler (Context *ctx, TyTy::FnType *fntype);
6870

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

gcc/rust/util/rust-intrinsic-values.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class Intrinsics
8181
static constexpr auto &CALLER_LOCATION = "caller_location";
8282
static constexpr auto &CTPOP = "ctpop";
8383
static constexpr auto &CTLZ = "ctlz";
84+
static constexpr auto &CTLZ_NONZERO = "ctlz_nonzero";
8485
static constexpr auto &CTTZ = "cttz";
8586
static constexpr auto &BSWAP = "bswap";
8687
static constexpr auto &BITREVERSE = "bitreverse";

gcc/testsuite/rust/compile/ctlz.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// { dg-do compile }
2+
#![feature(intrinsics, lang_items, no_core)]
3+
#![no_core]
4+
5+
#[lang = "sized"]
6+
pub trait Sized {}
7+
8+
#[lang = "copy"]
9+
pub trait Copy {}
10+
11+
extern "rust-intrinsic" {
12+
pub fn ctlz<T>(x: T) -> u32; // { dg-error "ctlz intrinsics can only be used with basic integer types .got 'bool'." }
13+
}
14+
15+
fn main() {
16+
let _ = ctlz(true);
17+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// { dg-do compile }
2+
#![feature(intrinsics, lang_items, no_core)]
3+
#![no_core]
4+
5+
#[lang = "sized"]
6+
pub trait Sized {}
7+
8+
#[lang = "copy"]
9+
pub trait Copy {}
10+
11+
extern "rust-intrinsic" {
12+
pub fn ctlz_nonzero<T>(x: T) -> u32; // { dg-error "ctlz intrinsics can only be used with basic integer types .got 'bool'." }
13+
}
14+
15+
fn main() {
16+
unsafe {
17+
let _ = ctlz_nonzero(true);
18+
}
19+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#![feature(no_core)]
2+
#![no_core]
3+
#![feature(intrinsics)]
4+
#![feature(lang_items)]
5+
6+
#[lang = "sized"]
7+
pub trait Sized {}
8+
9+
extern "rust-intrinsic" {
10+
pub fn ctlz<T>(x: T) -> u32;
11+
pub fn abort() -> !;
12+
}
13+
14+
fn main() -> i32 {
15+
if ctlz(0u8) != 8 {
16+
abort();
17+
}
18+
if ctlz(1u8) != 7 {
19+
abort();
20+
}
21+
if ctlz(255u8) != 0 {
22+
abort();
23+
}
24+
25+
if ctlz(0u16) != 16 {
26+
abort();
27+
}
28+
if ctlz(1u16) != 15 {
29+
abort();
30+
}
31+
if ctlz(0xFFFFu16) != 0 {
32+
abort();
33+
}
34+
35+
if ctlz(0u32) != 32 {
36+
abort();
37+
}
38+
if ctlz(1u32) != 31 {
39+
abort();
40+
}
41+
if ctlz(0xFFFFFFFFu32) != 0 {
42+
abort();
43+
}
44+
45+
if ctlz(0u64) != 64 {
46+
abort();
47+
}
48+
if ctlz(1u64) != 63 {
49+
abort();
50+
}
51+
if ctlz(!0u64) != 0 {
52+
abort();
53+
}
54+
55+
0
56+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#![feature(no_core)]
2+
#![no_core]
3+
#![feature(intrinsics)]
4+
#![feature(lang_items)]
5+
6+
#[lang = "sized"]
7+
pub trait Sized {}
8+
9+
extern "rust-intrinsic" {
10+
pub fn ctlz_nonzero<T>(x: T) -> u32;
11+
pub fn abort() -> !;
12+
}
13+
14+
fn main() -> i32 {
15+
unsafe {
16+
if ctlz_nonzero(1u8) != 7 {
17+
abort();
18+
}
19+
if ctlz_nonzero(255u8) != 0 {
20+
abort();
21+
}
22+
23+
if ctlz_nonzero(1u16) != 15 {
24+
abort();
25+
}
26+
if ctlz_nonzero(0xFFFFu16) != 0 {
27+
abort();
28+
}
29+
30+
if ctlz_nonzero(1u32) != 31 {
31+
abort();
32+
}
33+
if ctlz_nonzero(0xFFFFFFFFu32) != 0 {
34+
abort();
35+
}
36+
37+
if ctlz_nonzero(1u64) != 63 {
38+
abort();
39+
}
40+
if ctlz_nonzero(!0u64) != 0 {
41+
abort();
42+
}
43+
}
44+
45+
0
46+
}

0 commit comments

Comments
 (0)