compiler-rt: Add udivmodei5 to builtins and add bitint library

According to the RFC [0], this review contains the compiler-rt parts of large integer divison for _BitInt.

It adds the functions
```
/// Computes the unsigned division of a / b for two large integers
/// composed of n significant words.
/// Writes the quotient to quo and the remainder to rem.
///
/// \param quo The quotient represented by n words. Must be non-null.
/// \param rem The remainder represented by n words. Must be non-null.
/// \param a The dividend represented by n + 1 words. Must be non-null.
/// \param b The divisor represented by n words. Must be non-null.

/// \note The word order is in host endianness.
/// \note Might modify a and b.
/// \note The storage of 'a' needs to hold n + 1 elements because some
///       implementations need extra scratch space in the most significant word.
///       The value of that word is ignored.
COMPILER_RT_ABI void __udivmodei5(su_int *quo, su_int *rem, su_int *a,
                                  su_int *b, unsigned int n);

/// Computes the signed division of a / b.
/// See __udivmodei5 for details.
COMPILER_RT_ABI void __divmodei5(su_int *quo, su_int *rem, su_int *a, su_int *b,
                                 unsigned int words);
```
into builtins.
In addition it introduces a new "bitint" library containing only those new functions,
which is meant as a way to provide those when using libgcc as runtime.

[0] https://discourse.llvm.org/t/rfc-add-support-for-division-of-large-bitint-builtins-selectiondag-globalisel-clang/60329

Differential Revision: https://reviews.llvm.org/D120327
This commit is contained in:
Matthias Gehre 2022-02-22 15:09:54 +00:00
parent 36d3efea15
commit bf2dc4b376
14 changed files with 21160 additions and 0 deletions

View file

@ -34,6 +34,8 @@ include(CompilerRTUtils)
option(COMPILER_RT_BUILD_BUILTINS "Build builtins" ON)
mark_as_advanced(COMPILER_RT_BUILD_BUILTINS)
option(COMPILER_RT_BUILD_BITINT "Build bitint" ON)
mark_as_advanced(COMPILER_RT_BUILD_BITINT)
option(COMPILER_RT_BUILD_CRT "Build crtbegin.o/crtend.o" ON)
mark_as_advanced(COMPILER_RT_BUILD_CRT)
option(COMPILER_RT_CRT_USE_EH_FRAME_REGISTRY "Use eh_frame in crtbegin.o/crtend.o" ON)

View file

@ -65,6 +65,9 @@ set(ALL_BUILTIN_SUPPORTED_ARCH
${RISCV32} ${RISCV64} ${SPARC} ${SPARCV9}
${WASM32} ${WASM64} ${VE})
set(ALL_BITINT_SUPPORTED_ARCH
${ALL_BUILTIN_SUPPORTED_ARCH})
include(CompilerRTUtils)
include(CompilerRTDarwinUtils)
@ -195,6 +198,7 @@ if(APPLE)
endforeach()
list_intersect(BUILTIN_SUPPORTED_ARCH ALL_BUILTIN_SUPPORTED_ARCH COMPILER_RT_SUPPORTED_ARCH)
list_intersect(BITINT_SUPPORTED_ARCH ALL_BITINT_SUPPORTED_ARCH COMPILER_RT_SUPPORTED_ARCH)
else()
# If we're not building the builtins standalone, just rely on the tests in
@ -206,6 +210,8 @@ else()
# Architectures supported by compiler-rt libraries.
filter_available_targets(BUILTIN_SUPPORTED_ARCH
${ALL_BUILTIN_SUPPORTED_ARCH})
filter_available_targets(BITINT_SUPPORTED_ARCH
${ALL_BITINT_SUPPORTED_ARCH})
endif()
message(STATUS "Builtin supported architectures: ${BUILTIN_SUPPORTED_ARCH}")

View file

@ -17,6 +17,10 @@ if(COMPILER_RT_BUILD_BUILTINS)
add_subdirectory(builtins)
endif()
if(COMPILER_RT_BUILD_BITINT)
add_subdirectory(bitint)
endif()
if(COMPILER_RT_BUILD_CRT)
add_subdirectory(crt)
endif()

View file

@ -0,0 +1,11 @@
add_compiler_rt_component(bitint)
include(builtin-config-ix)
add_compiler_rt_runtime(clang_rt.bitint
STATIC
ARCHS ${BITINT_SUPPORTED_ARCH}
SOURCES ../builtins/udivmodei5.c
CFLAGS ${SANITIZER_COMMON_CFLAGS} -DIS_BITINT_LIBRARY
PARENT_TARGET bitint)

View file

@ -174,6 +174,7 @@ set(GENERIC_SOURCES
udivmoddi4.c
udivmodsi4.c
udivmodti4.c
udivmodei5.c
udivsi3.c
udivti3.c
umoddi3.c

View file

@ -110,6 +110,29 @@ COMPILER_RT_ABI su_int __udivsi3(su_int n, su_int d);
COMPILER_RT_ABI su_int __udivmodsi4(su_int a, su_int b, su_int *rem);
COMPILER_RT_ABI du_int __udivmoddi4(du_int a, du_int b, du_int *rem);
/// Computes the unsigned division of a / b for two large integers
/// composed of n significant words.
/// Writes the quotient to quo and the remainder to rem.
///
/// \param quo The quotient represented by n words. Must be non-null.
/// \param rem The remainder represented by n words. Must be non-null.
/// \param a The dividend represented by n + 1 words. Must be non-null.
/// \param b The divisor represented by n words. Must be non-null.
/// \note The word order is in host endianness.
/// \note Might modify a and b.
/// \note The storage of 'a' needs to hold n + 1 elements because some
/// implementations need extra scratch space in the most significant word.
/// The value of that word is ignored.
COMPILER_RT_ABI void __udivmodei5(su_int *quo, su_int *rem, su_int *a,
su_int *b, unsigned int n);
/// Computes the signed division of a / b.
/// See __udivmodei5 for details.
COMPILER_RT_ABI void __divmodei5(su_int *quo, su_int *rem, su_int *a, su_int *b,
unsigned int words);
#ifdef CRT_HAS_128BIT
COMPILER_RT_ABI int __clzti2(ti_int a);
COMPILER_RT_ABI tu_int __udivmodti4(tu_int a, tu_int b, tu_int *rem);

View file

@ -0,0 +1,173 @@
//===-- udivmodei5.c - Implement _udivmodei5-------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements _udivmodei5 for the compiler_rt library.
//
//===----------------------------------------------------------------------===//
#include "int_lib.h"
// When this is built into the bitint library, provide symbols with
// weak linkage to allow gracefully upgrading once libgcc contains those
// symbols.
#ifdef IS_BITINT_LIBRARY
#define WEAK_IF_BITINT_LIBRARY __attribute__((weak))
#else
#define WEAK_IF_BITINT_LIBRARY
#endif
static const int WORD_SIZE_IN_BITS = sizeof(su_int) * CHAR_BIT;
/// A mask with the most significant bit set.
static const su_int WORD_MSB = (su_int)1 << (WORD_SIZE_IN_BITS - 1);
// Define an index such that a[WORD_IDX(0)] is the least significant word
// and a[WORD_IDX(words-1)] is the most significant word.
#if _YUGA_LITTLE_ENDIAN
#define WORD_IDX(X, words) (X)
#else
#define WORD_IDX(X) ((words) - (X))
#endif
static bool has_msb_set(su_int a) { return (a & WORD_MSB) != 0; }
static bool is_neg(const uint32_t *V, unsigned words) {
return has_msb_set(V[WORD_IDX(words - 1, words)]);
}
/// dst = ~dst
static void complement(su_int *dst, unsigned words) {
for (unsigned i = 0; i < words; i++)
dst[i] = ~dst[i];
}
/// dst += src
static void add_part(su_int *dst, su_int src, unsigned words) {
for (unsigned i = 0; i < words; ++i) {
dst[WORD_IDX(i, words)] += src;
if (dst[WORD_IDX(i, words)] >= src)
return; // No carry
src = 1;
}
}
/// dst += 1
static void increment(su_int *dst, unsigned words) { add_part(dst, 1u, words); }
/// dst = -dst
static void negate(su_int *dst, unsigned words) {
complement(dst, words);
increment(dst, words);
}
/// a -= b
/// \pre a >= b
static void subtract(su_int *a, const su_int *b, unsigned words) {
su_int carry = 0;
for (unsigned i = 0; i < words; ++i) {
su_int dst = 0;
carry = __builtin_sub_overflow(a[WORD_IDX(i, words)], carry, &dst);
carry += __builtin_sub_overflow(dst, b[WORD_IDX(i, words)],
&a[WORD_IDX(i, words)]);
}
}
/// a = 0
static void set_zero(su_int *a, unsigned words) {
for (unsigned i = 0; i < words; ++i)
a[i] = 0;
}
/// a > b: return +1
/// a < b: return -1
/// a == b: return 0
static int ucompare(const su_int *a, const su_int *b, unsigned int words) {
for (int i = words - 1; i >= 0; --i) {
if (a[WORD_IDX(i, words)] != b[WORD_IDX(i, words)])
return a[WORD_IDX(i, words)] > b[WORD_IDX(i, words)] ? 1 : -1;
}
return 0;
}
/// Performs a logic left shift of one bit of 'a'.
static void left_shift_1(su_int *a, unsigned words) {
for (int i = words - 1; i >= 0; --i) {
a[WORD_IDX(i, words)] <<= 1;
if (i == 0)
continue;
if (has_msb_set(a[WORD_IDX(i - 1, words)]))
a[WORD_IDX(i, words)] |= 1;
}
}
/// Set the least signitificant bit of a to 'a'.
/// \pre The least signitificant bit of 'a' is zero.
static void set_bit_0(su_int *a, unsigned words, unsigned v) {
a[WORD_IDX(0, words)] |= v;
}
/// Sets the n-th bit of 'a' to 1.
/// \pre The n-th bit of 'a' is zero.
static void set_bit(su_int *a, unsigned words, unsigned n) {
unsigned word = n / WORD_SIZE_IN_BITS;
unsigned bit = n % WORD_SIZE_IN_BITS;
a[WORD_IDX(word, words)] |= (su_int)1 << bit;
}
/// Returns the n-th bit of 'a'.
static unsigned get_bit(const su_int *a, unsigned words, unsigned n) {
unsigned word = n / WORD_SIZE_IN_BITS;
unsigned bit = n % WORD_SIZE_IN_BITS;
su_int mask = (su_int)1 << bit;
return !!(a[WORD_IDX(word, words)] & mask);
}
/// Unsigned divison quo = a / b, rem = a % b
WEAK_IF_BITINT_LIBRARY
COMPILER_RT_ABI void __udivmodei5(su_int *quo, su_int *rem, su_int *a,
su_int *b, unsigned int words) {
set_zero(quo, words);
set_zero(rem, words);
unsigned bits = words * WORD_SIZE_IN_BITS;
for (int i = bits - 1; i >= 0; --i) {
left_shift_1(rem, words); // rem <<= 1;
set_bit_0(rem, words, get_bit(a, words, i)); // rem(bit 0) = a(bit i);
if (ucompare(rem, b, words) >= 0) { // if (rem >= b)
subtract(rem, b, words); // rem -= b;
set_bit(quo, words, i); // quo(bit i) = 1;
}
}
}
/// Signed divison quo = a / b, rem = a % b
WEAK_IF_BITINT_LIBRARY
COMPILER_RT_ABI void __divmodei5(su_int *quo, su_int *rem, su_int *a, su_int *b,
unsigned int words) {
int asign = is_neg(a, words);
int bsign = is_neg(b, words);
if (asign) {
if (bsign) {
negate(a, words);
negate(b, words);
__udivmodei5(quo, rem, a, b, words);
} else {
negate(a, words);
__udivmodei5(quo, rem, a, b, words);
negate(quo, words);
}
negate(rem, words);
} else if (bsign) {
negate(b, words);
__udivmodei5(quo, rem, a, b, words);
negate(quo, words);
} else {
__udivmodei5(quo, rem, a, b, words);
}
}

View file

@ -60,6 +60,9 @@ if(COMPILER_RT_CAN_EXECUTE_TESTS)
if(COMPILER_RT_BUILD_BUILTINS)
add_subdirectory(builtins)
endif()
if(COMPILER_RT_BUILD_BITINT)
add_subdirectory(bitint)
endif()
if(COMPILER_RT_BUILD_SANITIZERS)
compiler_rt_test_runtime(interception)

View file

@ -0,0 +1,23 @@
set(BITINT_LIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
set(BITINT_TEST_DEPS ${SANITIZER_COMMON_LIT_TEST_DEPS} bitint)
set(BITINT_TESTSUITES)
include(builtin-config-ix)
foreach(arch ${BITINT_SUPPORTED_ARCH})
set(BITINT_TEST_TARGET_ARCH ${arch})
string(TOLOWER "-${arch}" BITINT_TEST_CONFIG_SUFFIX)
get_test_cc_for_arch(${arch} BITINT_TEST_TARGET_CC BITINT_TEST_TARGET_CFLAGS)
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/Unit/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/Unit/${arch}/lit.site.cfg.py
)
list(APPEND BITINT_TESTSUITES ${CMAKE_CURRENT_BINARY_DIR}/Unit/${arch})
endforeach()
add_lit_testsuite(check-bitint "Running the Bitint tests"
${BITINT_TESTSUITES}
DEPENDS ${BITINT_TEST_DEPS})
set_target_properties(check-bitint PROPERTIES FOLDER "Compiler-RT Misc")

View file

@ -0,0 +1,17 @@
// RUN: %clang_bitint %s %libbitint -o %t && %run %t
// Ensure that libclang_rt.bitint exports the right symbols
#include "int_lib.h"
#define WORDS 4
int main(int argc, char *argv[]) {
unsigned int quo[WORDS], rem[WORDS];
// 'a' needs to have an extra word, see documentation of __udivmodei5.
unsigned int a[WORDS+1] = {0, 0, 0, 1, 0};
unsigned int b[WORDS] = {0, 0, 0, 1};
__udivmodei5(quo, rem, a, b, WORDS);
__divmodei5(quo, rem, a, b, WORDS);
return 0;
}

View file

@ -0,0 +1,43 @@
# -*- Python -*-
import os
# Setup config name.
config.name = 'bitint' + config.name_suffix
config.test_source_root = os.path.dirname(__file__)
# Test suffixes.
config.suffixes = ['.c', '.cc', '.cpp', '.m', '.mm']
base_lib = os.path.join(config.compiler_rt_libdir, "libclang_rt.bitint%s.a"
% config.target_suffix)
config.substitutions.append( ("%libbitint ", base_lib + ' -lc ') )
def build_invocation(compile_flags):
return " ".join([config.clang] + compile_flags) + " "
def get_required_attr(config, attr_name):
attr_value = getattr(config, attr_name, None)
if attr_value == None:
lit_config.fatal(
"No attribute %r in test configuration! You may need to run "
"tests from your build directory or add this attribute "
"to lit.site.cfg.py " % attr_name)
return attr_value
builtins_source_dir = os.path.join(
get_required_attr(config, "compiler_rt_src_root"), "lib", "builtins")
if sys.platform in ['win32'] and execute_external:
# Don't pass dosish path separator to msys bash.exe.
builtins_source_dir = builtins_source_dir.replace('\\', '/')
clang_bitint_static_cflags = (['-g',
'-fno-builtin',
'-nodefaultlibs',
'-I', builtins_source_dir,
config.target_cflags])
config.substitutions.append( ("%clang_bitint ", \
build_invocation(clang_bitint_static_cflags)))

View file

@ -0,0 +1,14 @@
@LIT_SITE_CFG_IN_HEADER@
config.name_suffix = "@BITINT_TEST_CONFIG_SUFFIX@"
config.target_cflags = "@BITINT_TEST_TARGET_CFLAGS@"
config.target_arch = "@BITINT_TEST_TARGET_ARCH@"
# Load common config for all compiler-rt lit tests.
lit_config.load_config(config, "@COMPILER_RT_BINARY_DIR@/test/lit.common.configured")
# Load tool-specific config that would do the real work.
lit_config.load_config(config, "@BITINT_LIT_SOURCE_DIR@/Unit/lit.cfg.py")

View file

@ -0,0 +1,143 @@
// RUN: %clang_builtins %s %librt -o %t && %run %t
#include "int_lib.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// bigx = sext x
static void sext(int x, unsigned int* bigx, unsigned words) {
char sign = (x < 0) ? 0xFF : 0;
memset(bigx, sign, words*sizeof(su_int));
#ifdef _YUGA_LITTLE_ENDIAN
(void)words;
memcpy(bigx, &x, sizeof(x));
#else
memcpy(bigx + (sizeof(su_int)*words - sizeof(x)), &x, sizeof(x));
#endif
}
// x = trunc bigx
static void trunc(unsigned int* bigx, int *x, unsigned words) {
#ifdef _YUGA_LITTLE_ENDIAN
(void)words;
memcpy(x, bigx, sizeof(*x));
#else
memcpy(x, bigx + (sizeof(su_int)*words - sizeof(x)), sizeof(x));
#endif
}
int test__divmodei5(int a, int b, int expected_quo, int expected_rem,
unsigned words, unsigned int *a_scratch,
unsigned int *b_scratch, unsigned int *quo_scratch,
unsigned int *rem_scratch) {
// sign-extend a and b
// 'a' needs to have an extra word, see documentation of __udivmodei5.
sext(a, a_scratch, words+1);
sext(b, b_scratch, words);
memset(quo_scratch, 0, words * sizeof(su_int));
memset(rem_scratch, 0, words * sizeof(su_int));
__divmodei5(quo_scratch, rem_scratch, a_scratch, b_scratch, words);
su_int sign_q = (expected_quo < 0) ? (su_int)-1 : 0;
su_int sign_r = (expected_rem < 0) ? (su_int)-1 : 0;
int q, r;
trunc(quo_scratch, &q, words);
trunc(rem_scratch, &r, words);
if (q != expected_quo) {
printf("error in __divmodei5: %d / %d = %d, expected %d\n", a, b, q,
expected_quo);
return 1;
}
if (r != expected_rem) {
printf("error in __divmodei5: %d mod %d = %d, expected %d\n", a, b, r,
expected_rem);
return 1;
}
/// Expect upper bits of result to be sign-extended
#ifdef _YUGA_LITTLE_ENDIAN
for(unsigned i= sizeof(q); i < words; ++i) {
#else
for(unsigned i= words - 1; i >= sizeof(q); --i) {
#endif
if (quo_scratch[i] != sign_q) {
printf("error in __divmodei5: %d / %d = %d, R = %d, words=%d expected "
"quo_scratch[%d] == %d but got %d\n",
a, b, q, r, words, i, sign_q, quo_scratch[i]);
return 1;
}
if (rem_scratch[i] != sign_r) {
printf("error in __divmodei5: %d / %d = %d, R = %d, words=%d expected "
"rem_scratch[%d] == %d but got %d\n",
a, b, q, r, words, i, sign_r, rem_scratch[i]);
return 1;
}
}
return 0;
}
// Multiples of sizeof(int) are allowed for _divmodei5.
int words[] = {1, 2, 4, 5, 6, 8};
int main() {
for (unsigned iwords = 0; iwords < sizeof(words) / sizeof(words[0]); ++iwords) {
int nwords = words[iwords];
// 'a' needs to have an extra word, see documentation of __udivmodei5.
unsigned int *a_scratch = malloc((nwords + 1) * sizeof(su_int));
unsigned int *b_scratch = malloc(nwords * sizeof(su_int));
unsigned int *quo_scratch = malloc(nwords * sizeof(su_int));
unsigned int *rem_scratch = malloc(nwords * sizeof(su_int));
if (test__divmodei5(0, 1, 0, 0, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(0, -1, 0, 0, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(1, 1, 1, 0, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(2, 1, 2, 0, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(2, -1, -2, 0, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(-2, 1, -2, 0, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(-2, -1, 2, 0, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(7, 5, 1, 2, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(-7, 5, -1, -2, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(19, 5, 3, 4, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(19, -5, -3, 4, nwords, a_scratch, b_scratch, quo_scratch,
rem_scratch))
return 1;
if (test__divmodei5(0x80000000, 8, 0xf0000000, 0, nwords, a_scratch,
b_scratch, quo_scratch, rem_scratch))
return 1;
if (test__divmodei5(0x80000007, 8, 0xf0000001, -1, nwords, a_scratch,
b_scratch, quo_scratch, rem_scratch))
return 1;
}
return 0;
}

File diff suppressed because it is too large Load diff