Skip to content

Commit 6b807e1

Browse files
authored
Make sure we don't allocate VMBigInteger objects for values fitting into the small int range (#75)
This is the follow up to #74 and makes sure that we don't have VMBigInteger objects that could just be tagged ints. The PR also fixes a typing issue with the relevant macros and has a few other minor improvements.
2 parents b54e07f + ec9afb5 commit 6b807e1

File tree

9 files changed

+148
-47
lines changed

9 files changed

+148
-47
lines changed

src/lib/InfInt.h

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
* toString: converts it to a string
2020
*
2121
* There are also conversion methods which allow conversion to primitive
22-
* types: toLong, toLongLong, toUnsignedLong,
22+
* types: toInt64, toUnsignedLong,
2323
* toUnsignedLongLong.
2424
*
2525
* You may define INFINT_USE_EXCEPTIONS and library methods will start raising
@@ -151,6 +151,7 @@ class InfInt {
151151

152152
/* basic properties */
153153
[[nodiscard]] bool isZero() const;
154+
[[nodiscard]] bool isWithinSmallIntRange() const;
154155

155156
/* integer square root */
156157
[[nodiscard]] InfInt intSqrt() const; // throw
@@ -166,8 +167,7 @@ class InfInt {
166167
[[nodiscard]] std::string toString() const;
167168

168169
/* conversion to primitive types */
169-
[[nodiscard]] int64_t toLong() const; // throw
170-
[[nodiscard]] int64_t toLongLong() const; // throw
170+
[[nodiscard]] int64_t toInt64() const; // throw
171171
[[nodiscard]] uint64_t toUnsignedLong() const; // throw
172172
[[nodiscard]] uint64_t toUnsignedLongLong() const; // throw
173173

@@ -740,6 +740,81 @@ inline bool InfInt::isZero() const {
740740
return val.size() == 1 && val[0] == 0;
741741
}
742742

743+
inline bool InfInt::isWithinSmallIntRange() const {
744+
// this is implemented based on the <= and >= operators
745+
// and the known encoding of VMTAGGEDINTEGER_MAX and VMTAGGEDINTEGER_MIN as
746+
// an InfInt
747+
if (pos) {
748+
// if the value is positive, we only need to check it against
749+
// VMTAGGEDINTEGER_MAX
750+
751+
// if it has more than 3 parts, we now it's out of range,
752+
// but if it has less, then we know it's within range.
753+
size_t const size = val.size();
754+
if (size > 3) {
755+
return false;
756+
}
757+
if (size < 3) {
758+
return true;
759+
}
760+
761+
// now we look at the individual parts
762+
// starting at the most significant
763+
if (val[2] < 4) {
764+
return true;
765+
}
766+
if (val[2] > 4) {
767+
return false;
768+
}
769+
770+
if (val[1] < 611686018) {
771+
return true;
772+
}
773+
if (val[1] > 611686018) {
774+
return false;
775+
}
776+
777+
if (val[0] <= 427387903) {
778+
return true;
779+
}
780+
return false;
781+
}
782+
783+
// if the value is negative, we only need to check it against
784+
// VMTAGGEDINTEGER_MIN
785+
786+
// if it has more than 3 parts, we now it's out of range,
787+
// but if it has less, then we know it's within range.
788+
size_t const size = val.size();
789+
if (size > 3) {
790+
return false;
791+
}
792+
if (size < 3) {
793+
return true;
794+
}
795+
796+
// now we look at the individual parts
797+
// starting at the most significant
798+
if (val[2] < 4) {
799+
return true;
800+
}
801+
if (val[2] > 4) {
802+
return false;
803+
}
804+
805+
if (val[1] < 611686018) {
806+
return true;
807+
}
808+
if (val[1] > 611686018) {
809+
return false;
810+
}
811+
812+
if (val[0] <= 427387904) {
813+
return true;
814+
}
815+
return false;
816+
}
817+
743818
inline InfInt InfInt::intSqrt() const {
744819
// PROFINY_SCOPE
745820
if (*this <= InfInt()) { // TODO(smarr): replace by a more specific check
@@ -837,7 +912,7 @@ inline int InfInt::truncateToInt() const {
837912
return pos ? result : -result;
838913
}
839914

840-
inline int64_t InfInt::toLong() const {
915+
inline int64_t InfInt::toInt64() const {
841916
// PROFINY_SCOPE
842917
if (*this > InfInt(INT64_MAX) || *this < InfInt(INT64_MIN)) {
843918
#ifdef INFINT_USE_EXCEPTIONS
@@ -862,22 +937,6 @@ inline int64_t InfInt::truncateToInt64() const {
862937
return pos ? result : -result;
863938
}
864939

865-
inline int64_t InfInt::toLongLong() const {
866-
// PROFINY_SCOPE
867-
if (*this > InfInt(INT64_MAX) || *this < InfInt(INT64_MIN)) {
868-
#ifdef INFINT_USE_EXCEPTIONS
869-
throw InfIntException("out of bounds");
870-
#else
871-
std::cerr << "Out of LLONG bounds: " << *this << '\n';
872-
#endif
873-
}
874-
int64_t result = 0;
875-
for (int i = (int)val.size() - 1; i >= 0; --i) {
876-
result = result * BASE + val[i];
877-
}
878-
return pos ? result : -result;
879-
}
880-
881940
inline int64_t InfInt::toLongLongForHash() const {
882941
int64_t result = 0;
883942
for (int i = (int)val.size() - 1; i >= 0; --i) {

src/primitives/Integer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ static vm_oop_t intPlus(vm_oop_t leftObj, vm_oop_t rightObj) {
6969
if (unlikely(__builtin_add_overflow(left, right, &result))) {
7070
InfInt const l(left);
7171
InfInt const r(right);
72-
return Universe::NewBigInteger(l + r);
72+
return Universe::NewInt(l + r);
7373
}
7474
return NEW_INT(result);
7575
}
@@ -107,7 +107,7 @@ static vm_oop_t intLeftShift(vm_oop_t leftObj, vm_oop_t rightObj) {
107107
auto const numberOfLeadingZeros = __builtin_clzll((uint64_t)left);
108108

109109
if (64 - numberOfLeadingZeros + right > 63) {
110-
return Universe::NewBigInteger(InfInt(left) << right);
110+
return Universe::NewInt(InfInt(left) << right);
111111
}
112112

113113
// NOLINTNEXTLINE(hicpp-signed-bitwise)
@@ -143,7 +143,7 @@ static vm_oop_t intMinus(vm_oop_t leftObj, vm_oop_t rightObj) {
143143
if (unlikely(__builtin_sub_overflow(left, right, &result))) {
144144
InfInt const l(left);
145145
InfInt const r(right);
146-
return Universe::NewBigInteger(l - r);
146+
return Universe::NewInt(l - r);
147147
}
148148
return NEW_INT(result);
149149
}
@@ -172,7 +172,7 @@ static vm_oop_t intStar(vm_oop_t leftObj, vm_oop_t rightObj) {
172172
if (unlikely(__builtin_mul_overflow(left, right, &result))) {
173173
InfInt const l(left);
174174
InfInt const r(right);
175-
return Universe::NewBigInteger(l * r);
175+
return Universe::NewInt(l * r);
176176
}
177177
return NEW_INT(result);
178178
}

src/unitTests/InfIntTests.cpp

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,72 @@
44
#include <cstdint>
55

66
#include "../lib/InfInt.h"
7+
#include "../vmobjects/ObjectFormats.h"
78

89
void InfIntTest::testBasicNumbers() {
910
InfInt const zero(int64_t(0LL));
10-
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zero.toLongLong());
11+
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zero.toInt64());
1112

1213
InfInt const one(int64_t(1LL));
13-
CPPUNIT_ASSERT_EQUAL(int64_t(1LL), one.toLongLong());
14+
CPPUNIT_ASSERT_EQUAL(int64_t(1LL), one.toInt64());
1415

1516
InfInt const a500(int64_t(500LL));
16-
CPPUNIT_ASSERT_EQUAL(int64_t(500LL), a500.toLongLong());
17+
CPPUNIT_ASSERT_EQUAL(int64_t(500LL), a500.toInt64());
1718

1819
InfInt const a32bitNum(int64_t(3221258751LL));
19-
CPPUNIT_ASSERT_EQUAL(int64_t(3221258751LL), a32bitNum.toLongLong());
20+
CPPUNIT_ASSERT_EQUAL(int64_t(3221258751LL), a32bitNum.toInt64());
2021

2122
InfInt const a48bitNum(int64_t(211109453791743LL));
22-
CPPUNIT_ASSERT_EQUAL(int64_t(211109453791743LL), a48bitNum.toLongLong());
23+
CPPUNIT_ASSERT_EQUAL(int64_t(211109453791743LL), a48bitNum.toInt64());
2324

2425
InfInt const a63bitNum(int64_t(8070661641701720575LL));
25-
CPPUNIT_ASSERT_EQUAL(int64_t(8070661641701720575LL),
26-
a63bitNum.toLongLong());
26+
CPPUNIT_ASSERT_EQUAL(int64_t(8070661641701720575LL), a63bitNum.toInt64());
2727
}
2828

2929
void InfIntTest::testIsZero() {
3030
InfInt const zero{};
31-
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zero.toLongLong());
31+
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zero.toInt64());
3232
CPPUNIT_ASSERT(zero.isZero());
3333

3434
InfInt const zeroInt64(int64_t(0LL));
35-
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zeroInt64.toLongLong());
35+
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zeroInt64.toInt64());
3636
CPPUNIT_ASSERT(zeroInt64.isZero());
3737

3838
InfInt const zeroStr("0");
39-
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zeroStr.toLongLong());
39+
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zeroStr.toInt64());
4040
CPPUNIT_ASSERT(zeroStr.isZero());
4141

4242
InfInt const negZeroStr("-0");
43-
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), negZeroStr.toLongLong());
43+
CPPUNIT_ASSERT_EQUAL(int64_t(0LL), negZeroStr.toInt64());
4444
CPPUNIT_ASSERT(negZeroStr.isZero());
4545
}
46+
47+
void InfIntTest::testIsWithinSmallIntRange() {
48+
InfInt const smallIntMax(VMTAGGEDINTEGER_MAX);
49+
CPPUNIT_ASSERT_EQUAL(int64_t(VMTAGGEDINTEGER_MAX), smallIntMax.toInt64());
50+
CPPUNIT_ASSERT(smallIntMax.isWithinSmallIntRange());
51+
52+
InfInt const smallIntMin(VMTAGGEDINTEGER_MIN);
53+
CPPUNIT_ASSERT_EQUAL(int64_t(VMTAGGEDINTEGER_MIN), smallIntMin.toInt64());
54+
CPPUNIT_ASSERT(smallIntMin.isWithinSmallIntRange());
55+
56+
InfInt const smallIntMaxPlusOne(VMTAGGEDINTEGER_MAX + 1LL);
57+
CPPUNIT_ASSERT_EQUAL(int64_t(VMTAGGEDINTEGER_MAX + 1LL),
58+
smallIntMaxPlusOne.toInt64());
59+
CPPUNIT_ASSERT(!smallIntMaxPlusOne.isWithinSmallIntRange());
60+
61+
InfInt const smallIntMinMinusOne(VMTAGGEDINTEGER_MIN - 1LL);
62+
CPPUNIT_ASSERT_EQUAL(int64_t(VMTAGGEDINTEGER_MIN - 1LL),
63+
smallIntMinMinusOne.toInt64());
64+
CPPUNIT_ASSERT(!smallIntMinMinusOne.isWithinSmallIntRange());
65+
66+
InfInt const smallIntMaxMinusOne(VMTAGGEDINTEGER_MAX - 1LL);
67+
CPPUNIT_ASSERT_EQUAL(int64_t(VMTAGGEDINTEGER_MAX - 1LL),
68+
smallIntMaxMinusOne.toInt64());
69+
CPPUNIT_ASSERT(smallIntMaxMinusOne.isWithinSmallIntRange());
70+
71+
InfInt const smallIntMinPlusOne(VMTAGGEDINTEGER_MIN + 1LL);
72+
CPPUNIT_ASSERT_EQUAL(int64_t(VMTAGGEDINTEGER_MIN + 1LL),
73+
smallIntMinPlusOne.toInt64());
74+
CPPUNIT_ASSERT(smallIntMinPlusOne.isWithinSmallIntRange());
75+
}

src/unitTests/InfIntTests.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class InfIntTest : public CPPUNIT_NS::TestCase {
1010
CPPUNIT_TEST_SUITE(InfIntTest); // NOLINT(misc-const-correctness)
1111
CPPUNIT_TEST(testBasicNumbers);
1212
CPPUNIT_TEST(testIsZero);
13+
CPPUNIT_TEST(testIsWithinSmallIntRange);
1314
CPPUNIT_TEST_SUITE_END();
1415

1516
public:
@@ -19,4 +20,5 @@ class InfIntTest : public CPPUNIT_NS::TestCase {
1920
private:
2021
static void testBasicNumbers();
2122
static void testIsZero();
23+
static void testIsWithinSmallIntRange();
2224
};

src/vm/IsValidObject.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) {
198198
auto* i = new (GetHeap<HEAP_CLS>(), 0) VMInteger(0);
199199
vt_integer = get_vtable(i);
200200

201-
auto* bi = new (GetHeap<HEAP_CLS>(), 0) VMBigInteger("0", false);
201+
auto* bi =
202+
new (GetHeap<HEAP_CLS>(), 0) VMBigInteger("4611686018427387904", false);
202203
vt_big_integer = get_vtable(bi);
203204

204205
auto* mth = new (GetHeap<HEAP_CLS>(), 0)

src/vm/Universe.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,10 @@ VMInteger* Universe::NewInteger(int64_t value) {
790790
return new (GetHeap<HEAP_CLS>(), 0) VMInteger(value);
791791
}
792792

793-
VMBigInteger* Universe::NewBigInteger(InfInt&& value) {
793+
vm_oop_t Universe::NewInt(InfInt&& value) {
794+
if (value.isWithinSmallIntRange()) {
795+
return NEW_INT(value.toInt64());
796+
}
794797
return new (GetHeap<HEAP_CLS>(), 0) VMBigInteger(std::move(value));
795798
}
796799

src/vm/Universe.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ class Universe {
8282
static VMObject* NewInstance(VMClass* /*classOfInstance*/);
8383
static VMObject* NewInstanceWithoutFields();
8484
static VMInteger* NewInteger(int64_t /*value*/);
85-
static VMBigInteger* NewBigInteger(InfInt&& /*value*/);
85+
86+
static vm_oop_t NewInt(InfInt&& /*value*/);
87+
8688
static VMBigInteger* NewBigIntegerFromInt(int64_t /*value*/);
8789
static VMBigInteger* NewBigIntegerFromStr(const char* /*value*/,
8890
bool /* negateValue */);

src/vmobjects/ObjectFormats.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@
5050

5151
#ifdef __GNUC__
5252
#define VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(X) \
53-
((X) >= VMTAGGEDINTEGER_MIN && (X) <= VMTAGGEDINTEGER_MAX)
53+
((int64_t(X) >= VMTAGGEDINTEGER_MIN) && \
54+
(int64_t(X) <= VMTAGGEDINTEGER_MAX))
5455
#else
5556
__attribute__((always_inline)) inline bool VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(
5657
int64_t X) {
@@ -65,7 +66,7 @@ __attribute__((always_inline)) inline bool VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(
6566
#if ADDITIONAL_ALLOCATION
6667
#define TAG_INTEGER(X) \
6768
((VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(X) && Universe::NewInteger(0)) \
68-
? ((vm_oop_t)(((X) << 1U) | 1U)) \
69+
? ((vm_oop_t)((((uintptr_t)(X)) << 1U) | 1U)) \
6970
: (Universe::NewBigIntegerFromInt(X)))
7071
#else
7172
#define TAG_INTEGER(X) \
@@ -75,9 +76,6 @@ __attribute__((always_inline)) inline bool VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(
7576
#endif
7677

7778
#if USE_TAGGING
78-
#define INT_VAL(X) \
79-
(IS_TAGGED(X) ? ((int64_t)(X) >> 1U) /* NOLINT (hicpp-signed-bitwise) */ \
80-
: (((VMInteger*)(X))->GetEmbeddedInteger()))
8179
#define SMALL_INT_VAL(X) \
8280
((int64_t)(X) >> 1U) /* NOLINT (hicpp-signed-bitwise) */
8381
#define NEW_INT(X) (TAG_INTEGER((X)))

src/vmobjects/VMBigInteger.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@ class VMBigInteger : public AbstractVMObject {
99
typedef GCBigInteger Stored;
1010

1111
explicit VMBigInteger(const char* value, bool negate)
12-
: embeddedInteger(negate ? -InfInt(value) : InfInt(value)) {}
13-
explicit VMBigInteger(int64_t value) : embeddedInteger(InfInt(value)) {}
14-
explicit VMBigInteger(const InfInt&& value) : embeddedInteger(value) {}
12+
: embeddedInteger(negate ? -InfInt(value) : InfInt(value)) {
13+
assert(!embeddedInteger.isWithinSmallIntRange());
14+
}
15+
explicit VMBigInteger(int64_t value) : embeddedInteger(InfInt(value)) {
16+
assert(!embeddedInteger.isWithinSmallIntRange());
17+
}
18+
explicit VMBigInteger(const InfInt&& value) : embeddedInteger(value) {
19+
assert(!embeddedInteger.isWithinSmallIntRange());
20+
}
1521

1622
~VMBigInteger() override = default;
1723

@@ -27,7 +33,7 @@ class VMBigInteger : public AbstractVMObject {
2733
}
2834

2935
[[nodiscard]] inline int64_t GetHash() const override {
30-
return embeddedInteger.toLongLong();
36+
return embeddedInteger.toInt64();
3137
}
3238

3339
void MarkObjectAsInvalid() override;

0 commit comments

Comments
 (0)