aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/qemu/int128.h25
-rw-r--r--tests/Makefile6
-rw-r--r--tests/test-int128.c212
3 files changed, 234 insertions, 9 deletions
diff --git a/include/qemu/int128.h b/include/qemu/int128.h
index bfe7678a04..9ed47aafd3 100644
--- a/include/qemu/int128.h
+++ b/include/qemu/int128.h
@@ -1,6 +1,10 @@
#ifndef INT128_H
#define INT128_H
+#include <assert.h>
+#include <stdint.h>
+#include <stdbool.h>
+
typedef struct Int128 Int128;
struct Int128 {
@@ -55,21 +59,26 @@ static inline Int128 int128_rshift(Int128 a, int n)
static inline Int128 int128_add(Int128 a, Int128 b)
{
- Int128 r = { a.lo + b.lo, a.hi + b.hi };
- r.hi += (r.lo < a.lo) || (r.lo < b.lo);
- return r;
+ uint64_t lo = a.lo + b.lo;
+
+ /* a.lo <= a.lo + b.lo < a.lo + k (k is the base, 2^64). Hence,
+ * a.lo + b.lo >= k implies 0 <= lo = a.lo + b.lo - k < a.lo.
+ * Similarly, a.lo + b.lo < k implies a.lo <= lo = a.lo + b.lo < k.
+ *
+ * So the carry is lo < a.lo.
+ */
+ return (Int128) { lo, (uint64_t)a.hi + b.hi + (lo < a.lo) };
}
static inline Int128 int128_neg(Int128 a)
{
- a.lo = ~a.lo;
- a.hi = ~a.hi;
- return int128_add(a, int128_one());
+ uint64_t lo = -a.lo;
+ return (Int128) { lo, ~(uint64_t)a.hi + !lo };
}
static inline Int128 int128_sub(Int128 a, Int128 b)
{
- return int128_add(a, int128_neg(b));
+ return (Int128){ a.lo - b.lo, a.hi - b.hi - (a.lo < b.lo) };
}
static inline bool int128_nonneg(Int128 a)
@@ -89,7 +98,7 @@ static inline bool int128_ne(Int128 a, Int128 b)
static inline bool int128_ge(Int128 a, Int128 b)
{
- return int128_nonneg(int128_sub(a, b));
+ return a.hi > b.hi || (a.hi == b.hi && a.lo >= b.lo);
}
static inline bool int128_lt(Int128 a, Int128 b)
diff --git a/tests/Makefile b/tests/Makefile
index c107489f44..279d5f8307 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -44,6 +44,9 @@ check-unit-y += tests/test-cutils$(EXESUF)
gcov-files-test-cutils-y += util/cutils.c
check-unit-y += tests/test-mul64$(EXESUF)
gcov-files-test-mul64-y = util/host-utils.c
+check-unit-y += tests/test-int128$(EXESUF)
+# all code tested by test-int128 is inside int128.h
+gcov-files-test-int128-y =
check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
@@ -75,7 +78,7 @@ test-obj-y = tests/check-qint.o tests/check-qstring.o tests/check-qdict.o \
tests/test-string-input-visitor.o tests/test-qmp-output-visitor.o \
tests/test-qmp-input-visitor.o tests/test-qmp-input-strict.o \
tests/test-qmp-commands.o tests/test-visitor-serialization.o \
- tests/test-x86-cpuid.o tests/test-mul64.o
+ tests/test-x86-cpuid.o tests/test-mul64.o tests/test-int128.o
test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o
@@ -98,6 +101,7 @@ tests/test-hbitmap$(EXESUF): tests/test-hbitmap.o libqemuutil.a libqemustub.a
tests/test-x86-cpuid$(EXESUF): tests/test-x86-cpuid.o
tests/test-xbzrle$(EXESUF): tests/test-xbzrle.o xbzrle.o page_cache.o libqemuutil.a
tests/test-cutils$(EXESUF): tests/test-cutils.o util/cutils.o
+tests/test-int128$(EXESUF): tests/test-int128.o
tests/test-qapi-types.c tests/test-qapi-types.h :\
$(SRC_PATH)/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py
diff --git a/tests/test-int128.c b/tests/test-int128.c
new file mode 100644
index 0000000000..5aca032e0c
--- /dev/null
+++ b/tests/test-int128.c
@@ -0,0 +1,212 @@
+/*
+ * Test Int128 arithmetic
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include <glib.h>
+#include <stdio.h>
+#include "qemu/int128.h"
+#include "qemu/osdep.h"
+
+static uint32_t tests[8] = {
+ 0x00000000, 0x00000001, 0x7FFFFFFE, 0x7FFFFFFF,
+ 0x80000000, 0x80000001, 0xFFFFFFFE, 0xFFFFFFFF,
+};
+
+#define LOW 3ULL
+#define HIGH (1ULL << 63)
+#define MIDDLE (-1ULL & ~LOW & ~HIGH)
+
+static uint64_t expand16(unsigned x)
+{
+ return (x & LOW) | ((x & 4) ? MIDDLE : 0) | (x & 0x8000 ? HIGH : 0);
+}
+
+static Int128 expand(uint32_t x)
+{
+ uint64_t l, h;
+ l = expand16(x & 65535);
+ h = expand16(x >> 16);
+ return (Int128) {l, h};
+};
+
+static void test_and(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ Int128 a = expand(tests[i]);
+ Int128 b = expand(tests[j]);
+ Int128 r = expand(tests[i] & tests[j]);
+ Int128 s = int128_and(a, b);
+ g_assert_cmpuint(r.lo, ==, s.lo);
+ g_assert_cmpuint(r.hi, ==, s.hi);
+ }
+ }
+}
+
+static void test_add(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ Int128 a = expand(tests[i]);
+ Int128 b = expand(tests[j]);
+ Int128 r = expand(tests[i] + tests[j]);
+ Int128 s = int128_add(a, b);
+ g_assert_cmpuint(r.lo, ==, s.lo);
+ g_assert_cmpuint(r.hi, ==, s.hi);
+ }
+ }
+}
+
+static void test_sub(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ Int128 a = expand(tests[i]);
+ Int128 b = expand(tests[j]);
+ Int128 r = expand(tests[i] - tests[j]);
+ Int128 s = int128_sub(a, b);
+ g_assert_cmpuint(r.lo, ==, s.lo);
+ g_assert_cmpuint(r.hi, ==, s.hi);
+ }
+ }
+}
+
+static void test_neg(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ Int128 a = expand(tests[i]);
+ Int128 r = expand(-tests[i]);
+ Int128 s = int128_neg(a);
+ g_assert_cmpuint(r.lo, ==, s.lo);
+ g_assert_cmpuint(r.hi, ==, s.hi);
+ }
+}
+
+static void test_nz(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ Int128 a = expand(tests[i]);
+ g_assert_cmpuint(int128_nz(a), ==, tests[i] != 0);
+ }
+ }
+}
+
+static void test_le(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ /* Signed comparison */
+ int32_t a = (int32_t) tests[i];
+ int32_t b = (int32_t) tests[j];
+ g_assert_cmpuint(int128_le(expand(a), expand(b)), ==, a <= b);
+ }
+ }
+}
+
+static void test_lt(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ /* Signed comparison */
+ int32_t a = (int32_t) tests[i];
+ int32_t b = (int32_t) tests[j];
+ g_assert_cmpuint(int128_lt(expand(a), expand(b)), ==, a < b);
+ }
+ }
+}
+
+static void test_ge(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ /* Signed comparison */
+ int32_t a = (int32_t) tests[i];
+ int32_t b = (int32_t) tests[j];
+ g_assert_cmpuint(int128_ge(expand(a), expand(b)), ==, a >= b);
+ }
+ }
+}
+
+static void test_gt(void)
+{
+ int i, j;
+
+ for (i = 0; i < ARRAY_SIZE(tests); ++i) {
+ for (j = 0; j < ARRAY_SIZE(tests); ++j) {
+ /* Signed comparison */
+ int32_t a = (int32_t) tests[i];
+ int32_t b = (int32_t) tests[j];
+ g_assert_cmpuint(int128_gt(expand(a), expand(b)), ==, a > b);
+ }
+ }
+}
+
+/* Make sure to test undefined behavior at runtime! */
+
+static void __attribute__((__noinline__, __noclone__))
+test_rshift_one(uint32_t x, int n, uint64_t h, uint64_t l)
+{
+ Int128 a = expand(x);
+ Int128 r = int128_rshift(a, n);
+ g_assert_cmpuint(r.lo, ==, l);
+ g_assert_cmpuint(r.hi, ==, h);
+}
+
+static void test_rshift(void)
+{
+ test_rshift_one(0x00010000U, 64, 0x0000000000000000ULL, 0x0000000000000001ULL);
+ test_rshift_one(0x80010000U, 64, 0xFFFFFFFFFFFFFFFFULL, 0x8000000000000001ULL);
+ test_rshift_one(0x7FFE0000U, 64, 0x0000000000000000ULL, 0x7FFFFFFFFFFFFFFEULL);
+ test_rshift_one(0xFFFE0000U, 64, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFFEULL);
+ test_rshift_one(0x00010000U, 60, 0x0000000000000000ULL, 0x0000000000000010ULL);
+ test_rshift_one(0x80010000U, 60, 0xFFFFFFFFFFFFFFF8ULL, 0x0000000000000010ULL);
+ test_rshift_one(0x00018000U, 60, 0x0000000000000000ULL, 0x0000000000000018ULL);
+ test_rshift_one(0x80018000U, 60, 0xFFFFFFFFFFFFFFF8ULL, 0x0000000000000018ULL);
+ test_rshift_one(0x7FFE0000U, 60, 0x0000000000000007ULL, 0xFFFFFFFFFFFFFFE0ULL);
+ test_rshift_one(0xFFFE0000U, 60, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFE0ULL);
+ test_rshift_one(0x7FFE8000U, 60, 0x0000000000000007ULL, 0xFFFFFFFFFFFFFFE8ULL);
+ test_rshift_one(0xFFFE8000U, 60, 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFFFFFFFE8ULL);
+ test_rshift_one(0x00018000U, 0, 0x0000000000000001ULL, 0x8000000000000000ULL);
+ test_rshift_one(0x80018000U, 0, 0x8000000000000001ULL, 0x8000000000000000ULL);
+ test_rshift_one(0x7FFE0000U, 0, 0x7FFFFFFFFFFFFFFEULL, 0x0000000000000000ULL);
+ test_rshift_one(0xFFFE0000U, 0, 0xFFFFFFFFFFFFFFFEULL, 0x0000000000000000ULL);
+ test_rshift_one(0x7FFE8000U, 0, 0x7FFFFFFFFFFFFFFEULL, 0x8000000000000000ULL);
+ test_rshift_one(0xFFFE8000U, 0, 0xFFFFFFFFFFFFFFFEULL, 0x8000000000000000ULL);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ g_test_add_func("/int128/int128_and", test_and);
+ g_test_add_func("/int128/int128_add", test_add);
+ g_test_add_func("/int128/int128_sub", test_sub);
+ g_test_add_func("/int128/int128_neg", test_neg);
+ g_test_add_func("/int128/int128_nz", test_nz);
+ g_test_add_func("/int128/int128_le", test_le);
+ g_test_add_func("/int128/int128_lt", test_lt);
+ g_test_add_func("/int128/int128_ge", test_ge);
+ g_test_add_func("/int128/int128_gt", test_gt);
+ g_test_add_func("/int128/int128_rshift", test_rshift);
+ return g_test_run();
+}