@@ -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, ¶m_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
753898const 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
0 commit comments