diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf1e3a0f..e9fa2141 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,10 @@ jobs: wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - sudo add-apt-repository "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main" sudo apt-get update - sudo apt-get install -y clang-20 clang-tidy-20 + for i in 1 2 3; do + sudo apt-get install -y clang-20 clang-tidy-20 && break + sleep 17 + done if: matrix.compiler == 'clang' - name: Install Clang Format diff --git a/SOM.xcodeproj/project.pbxproj b/SOM.xcodeproj/project.pbxproj index d297e7e1..a33a4a2f 100644 --- a/SOM.xcodeproj/project.pbxproj +++ b/SOM.xcodeproj/project.pbxproj @@ -142,6 +142,9 @@ 0AB80AD42C392B78006B6419 /* Print.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0AB80AD12C392B78006B6419 /* Print.cpp */; }; 0AB80AD82C394806006B6419 /* Globals.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0AB80AD72C394806006B6419 /* Globals.cpp */; }; 0AB80AD92C394806006B6419 /* Globals.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0AB80AD72C394806006B6419 /* Globals.cpp */; }; + 0ACA17172E509E6100FAC36B /* InfIntTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0ACA17162E509E6100FAC36B /* InfIntTests.cpp */; }; + 0AF394592D7CA9860036FDEF /* VMBigInteger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0AF394582D7CA9860036FDEF /* VMBigInteger.cpp */; }; + 0AF3945A2D7CA9860036FDEF /* VMBigInteger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0AF394582D7CA9860036FDEF /* VMBigInteger.cpp */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -252,6 +255,12 @@ 0AB80AD62C3947F7006B6419 /* Globals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Globals.h; sourceTree = ""; }; 0AB80AD72C394806006B6419 /* Globals.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Globals.cpp; sourceTree = ""; }; 0AB80ADA2C39AC27006B6419 /* InterpreterLoop.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InterpreterLoop.h; sourceTree = ""; }; + 0ACA17152E509E6100FAC36B /* InfIntTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = InfIntTests.h; path = unitTests/InfIntTests.h; sourceTree = ""; }; + 0ACA17162E509E6100FAC36B /* InfIntTests.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = InfIntTests.cpp; path = unitTests/InfIntTests.cpp; sourceTree = ""; }; + 0AF394542D7B91990036FDEF /* TaggingTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TaggingTests.h; path = unitTests/TaggingTests.h; sourceTree = ""; }; + 0AF394572D7CA9860036FDEF /* VMBigInteger.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMBigInteger.h; sourceTree = ""; }; + 0AF394582D7CA9860036FDEF /* VMBigInteger.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = VMBigInteger.cpp; sourceTree = ""; }; + 0AF3945C2D7CAAF80036FDEF /* InfInt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InfInt.h; sourceTree = ""; }; 3F5202F10FA6624C00E75857 /* BytecodeGenerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BytecodeGenerator.cpp; sourceTree = ""; }; 3F5202F20FA6624C00E75857 /* BytecodeGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.c.h; path = BytecodeGenerator.h; sourceTree = ""; tabWidth = 4; }; 3F5202F30FA6624C00E75857 /* ClassGenerationContext.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ClassGenerationContext.cpp; sourceTree = ""; }; @@ -448,13 +457,16 @@ 0A67EA7A19ACD44000830E3B /* unittests */ = { isa = PBXGroup; children = ( + 0AF394542D7B91990036FDEF /* TaggingTests.h */, 0A3FABE724F4670D002D4D69 /* BasicInterpreterTests.h */, 0A67EA7019ACD43A00830E3B /* CloneObjectsTest.cpp */, 0A67EA7119ACD43A00830E3B /* main.cpp */, + 0ACA17152E509E6100FAC36B /* InfIntTests.h */, + 0ACA17162E509E6100FAC36B /* InfIntTests.cpp */, 0A67EAA319ACE09700830E3B /* CloneObjectsTest.h */, 0A67EAA519ACE09700830E3B /* WalkObjectsTest.h */, - 0A67EAA619ACE09700830E3B /* WriteBarrierTest.h */, 0A67EA7319ACD43A00830E3B /* WalkObjectsTest.cpp */, + 0A67EAA619ACE09700830E3B /* WriteBarrierTest.h */, 0A67EA7419ACD43A00830E3B /* WriteBarrierTest.cpp */, 0A1C98562C3DD87300735850 /* unitTests/BytecodeGenerationTest.h */, 0A1C98572C3DD88500735850 /* unitTests/BytecodeGenerationTest.cpp */, @@ -466,6 +478,14 @@ name = unittests; sourceTree = ""; }; + 0AF3945D2D7CAAF80036FDEF /* lib */ = { + isa = PBXGroup; + children = ( + 0AF3945C2D7CAAF80036FDEF /* InfInt.h */, + ); + path = lib; + sourceTree = ""; + }; 3F5202720FA661D900E75857 = { isa = PBXGroup; children = ( @@ -487,6 +507,7 @@ 3F5202EF0FA6624C00E75857 /* src */ = { isa = PBXGroup; children = ( + 0AF3945D2D7CAAF80036FDEF /* lib */, 0A67EA7A19ACD44000830E3B /* unittests */, 3F5202F00FA6624C00E75857 /* compiler */, 3F5203000FA6624C00E75857 /* interpreter */, @@ -656,6 +677,8 @@ 3F5203380FA6624C00E75857 /* Signature.h */, 3F52033B0FA6624C00E75857 /* VMArray.cpp */, 3F52033C0FA6624C00E75857 /* VMArray.h */, + 0AF394572D7CA9860036FDEF /* VMBigInteger.h */, + 0AF394582D7CA9860036FDEF /* VMBigInteger.cpp */, 3F52033F0FA6624C00E75857 /* VMBlock.cpp */, 3F5203400FA6624C00E75857 /* VMBlock.h */, 3F5203410FA6624C00E75857 /* VMClass.cpp */, @@ -907,6 +930,7 @@ 0A1887351832C10E00A2CBCA /* MarkSweepCollector.cpp in Sources */, 0A5A7E912C5D45A00011C783 /* VMSafePrimitive.cpp in Sources */, 0A1886FB1832BCF500A2CBCA /* VMBlock.cpp in Sources */, + 0AF3945A2D7CA9860036FDEF /* VMBigInteger.cpp in Sources */, 0A1887391832C12E00A2CBCA /* Timer.cpp in Sources */, 0A1887021832BCFA00A2CBCA /* VMMethod.cpp in Sources */, 0AB80AD32C392B78006B6419 /* Print.cpp in Sources */, @@ -962,6 +986,7 @@ 0A67EA9019ACD83200830E3B /* Lexer.cpp in Sources */, 0AB80AD92C394806006B6419 /* Globals.cpp in Sources */, 0A5A7E9D2C617EE00011C783 /* TestWithParsing.cpp in Sources */, + 0AF394592D7CA9860036FDEF /* VMBigInteger.cpp in Sources */, 0A3A3CA71A5D546D004CB03B /* Object.cpp in Sources */, 0A3A3CB21A5D5476004CB03B /* PrimitiveContainer.cpp in Sources */, 0A1C98582C3DD88500735850 /* unitTests/BytecodeGenerationTest.cpp in Sources */, @@ -996,6 +1021,7 @@ 0AB80AD02C392811006B6419 /* IsValidObject.cpp in Sources */, 0A67EA9519ACD83900830E3B /* Interpreter.cpp in Sources */, 0A67EA8A19ACD74800830E3B /* VMSymbol.cpp in Sources */, + 0ACA17172E509E6100FAC36B /* InfIntTests.cpp in Sources */, 0A67EA7C19ACD74800830E3B /* AbstractObject.cpp in Sources */, 0A67EA8E19ACD83200830E3B /* ClassGenerationContext.cpp in Sources */, 0A3A3CB31A5D5476004CB03B /* PrimitiveLoader.cpp in Sources */, diff --git a/src/compiler/BytecodeGenerator.cpp b/src/compiler/BytecodeGenerator.cpp index 6b59b07e..4ae73f38 100644 --- a/src/compiler/BytecodeGenerator.cpp +++ b/src/compiler/BytecodeGenerator.cpp @@ -164,11 +164,11 @@ void EmitPUSHCONSTANT(MethodGenerationContext& mgenc, const Parser& parser, // we also make sure that we don't miss anything in the else // branch of the class check if (CLASS_OF(cst) == load_ptr(integerClass)) { - if (INT_VAL(cst) == 0LL) { + if (SMALL_INT_VAL(cst) == 0LL) { Emit1(mgenc, BC_PUSH_0, 1); return; } - if (INT_VAL(cst) == 1LL) { + if (SMALL_INT_VAL(cst) == 1LL) { Emit1(mgenc, BC_PUSH_1, 1); return; } diff --git a/src/compiler/Disassembler.cpp b/src/compiler/Disassembler.cpp index e77a45b9..35298d59 100644 --- a/src/compiler/Disassembler.cpp +++ b/src/compiler/Disassembler.cpp @@ -38,6 +38,7 @@ #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" #include "../vmobjects/Signature.h" +#include "../vmobjects/VMBigInteger.h" #include "../vmobjects/VMClass.h" #include "../vmobjects/VMDouble.h" #include "../vmobjects/VMFrame.h" @@ -66,6 +67,10 @@ void Disassembler::dispatch(vm_oop_t o) { DebugPrint("{Block Class object}"); } else if (o == Universe::GetGlobal(SymbolFor("system"))) { DebugPrint("{System}"); + } else if (IS_SMALL_INT(o)) { + DebugPrint("%lld", SMALL_INT_VAL(o)); + } else if (IS_BIG_INT(o)) { + DebugPrint("%s", AS_BIG_INT(o)->embeddedInteger.toString().c_str()); } else { VMClass* c = CLASS_OF(o); if (c == load_ptr(stringClass)) { @@ -73,8 +78,6 @@ void Disassembler::dispatch(vm_oop_t o) { static_cast(o)->GetStdString().c_str()); } else if (c == load_ptr(doubleClass)) { DebugPrint("%g", static_cast(o)->GetEmbeddedDouble()); - } else if (c == load_ptr(integerClass)) { - DebugPrint("%lld", INT_VAL(o)); } else if (c == load_ptr(symbolClass)) { DebugPrint("#%s", static_cast(o)->GetStdString().c_str()); diff --git a/src/compiler/MethodGenerationContext.cpp b/src/compiler/MethodGenerationContext.cpp index 6bb59bcb..73fc6d4d 100644 --- a/src/compiler/MethodGenerationContext.cpp +++ b/src/compiler/MethodGenerationContext.cpp @@ -42,6 +42,7 @@ #include "../vm/Print.h" #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMMethod.h" #include "../vmobjects/VMPrimitive.h" #include "../vmobjects/VMSymbol.h" diff --git a/src/compiler/Parser.cpp b/src/compiler/Parser.cpp index f5d8fe12..d19e555a 100644 --- a/src/compiler/Parser.cpp +++ b/src/compiler/Parser.cpp @@ -44,6 +44,7 @@ #include "../vm/Symbols.h" #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMClass.h" #include "../vmobjects/VMDouble.h" // NOLINT(misc-include-cleaner) it's required to make the types complete #include "../vmobjects/VMMethod.h" @@ -782,7 +783,7 @@ vm_oop_t Parser::negativeDecimal() { } vm_oop_t Parser::literalInteger(bool negateValue) { - vm_oop_t i = ParseInteger(text.c_str(), 10, negateValue); + vm_oop_t i = ParseInteger(text.c_str(), negateValue); expect(Integer); return i; } diff --git a/src/interpreter/Interpreter.cpp b/src/interpreter/Interpreter.cpp index 7cde1395..2c1c5eff 100644 --- a/src/interpreter/Interpreter.cpp +++ b/src/interpreter/Interpreter.cpp @@ -43,6 +43,7 @@ #include "../vmobjects/ObjectFormats.h" #include "../vmobjects/Signature.h" #include "../vmobjects/VMArray.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMBlock.h" #include "../vmobjects/VMClass.h" #include "../vmobjects/VMDouble.h" @@ -1077,8 +1078,8 @@ void Interpreter::doReturnNonLocal() { void Interpreter::doInc() { vm_oop_t val = GetFrame()->Top(); - if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) { - int64_t const result = (int64_t)INT_VAL(val) + 1; + if (IS_SMALL_INT(val)) { + int64_t const result = SMALL_INT_VAL(val) + 1; val = NEW_INT(result); } else if (CLASS_OF(val) == load_ptr(doubleClass)) { double const d = static_cast(val)->GetEmbeddedDouble(); @@ -1093,8 +1094,8 @@ void Interpreter::doInc() { void Interpreter::doDec() { vm_oop_t val = GetFrame()->Top(); - if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) { - int64_t const result = (int64_t)INT_VAL(val) - 1; + if (IS_SMALL_INT(val)) { + int64_t const result = SMALL_INT_VAL(val) - 1; val = NEW_INT(result); } else if (CLASS_OF(val) == load_ptr(doubleClass)) { double const d = static_cast(val)->GetEmbeddedDouble(); @@ -1117,8 +1118,8 @@ void Interpreter::doIncField(uint8_t fieldIndex) { vm_oop_t val = selfObj->GetField(fieldIndex); - if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) { - int64_t const result = (int64_t)INT_VAL(val) + 1; + if (IS_SMALL_INT(val)) { + int64_t const result = SMALL_INT_VAL(val) + 1; val = NEW_INT(result); } else if (CLASS_OF(val) == load_ptr(doubleClass)) { double const d = static_cast(val)->GetEmbeddedDouble(); @@ -1141,8 +1142,8 @@ void Interpreter::doIncFieldPush(uint8_t fieldIndex) { vm_oop_t val = selfObj->GetField(fieldIndex); - if (IS_TAGGED(val) || CLASS_OF(val) == load_ptr(integerClass)) { - int64_t const result = (int64_t)INT_VAL(val) + 1; + if (IS_SMALL_INT(val)) { + int64_t const result = SMALL_INT_VAL(val) + 1; val = NEW_INT(result); } else if (CLASS_OF(val) == load_ptr(doubleClass)) { double const d = static_cast(val)->GetEmbeddedDouble(); @@ -1159,14 +1160,11 @@ bool Interpreter::checkIsGreater() { vm_oop_t top = GetFrame()->Top(); vm_oop_t top2 = GetFrame()->Top2(); - if ((IS_TAGGED(top) || CLASS_OF(top) == load_ptr(integerClass)) && - (IS_TAGGED(top2) || CLASS_OF(top2) == load_ptr(integerClass))) { - return INT_VAL(top) > INT_VAL(top2); + if (IS_SMALL_INT(top) && IS_SMALL_INT(top2)) { + return SMALL_INT_VAL(top) > SMALL_INT_VAL(top2); } - if ((CLASS_OF(top) == load_ptr(doubleClass)) && - (CLASS_OF(top2) == load_ptr(doubleClass))) { - return static_cast(top)->GetEmbeddedDouble() > - static_cast(top2)->GetEmbeddedDouble(); + if (IS_DOUBLE(top) && IS_DOUBLE(top2)) { + return AS_DOUBLE(top) > AS_DOUBLE(top2); } return false; diff --git a/src/lib/InfInt.h b/src/lib/InfInt.h new file mode 100644 index 00000000..6c78bd95 --- /dev/null +++ b/src/lib/InfInt.h @@ -0,0 +1,1126 @@ +/* + * InfInt - Arbitrary-Precision Integer Arithmetic Library + * Copyright (C) 2013 Sercan Tutar + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * + * USAGE: + * It is pretty straight forward to use the library. Just create an instance + * of InfInt class and start using it. + * + * Useful methods: + * intSqrt: integer square root operation + * digitAt: returns digit at index + * numberOfDigits: returns number of digits + * size: returns size in bytes + * toString: converts it to a string + * + * There are also conversion methods which allow conversion to primitive + * types: toLong, toLongLong, toUnsignedLong, + * toUnsignedLongLong. + * + * You may define INFINT_USE_EXCEPTIONS and library methods will start raising + * InfIntException in case of error instead of writing error messages using + * std::cerr. + * + * See ReadMe.txt for more info. + * + * + * No overflows, happy programmers! + * + */ + +#ifndef INFINT_H_ +#define INFINT_H_ + +#include +#include +#include +#include +#include +#include + +#ifndef LONG_LONG_MAX + #define LONG_LONG_MIN LLONG_MIN + #define LONG_LONG_MAX LLONG_MAX + #define ULONG_LONG_MAX ULLONG_MAX +#endif + +#ifdef INFINT_USE_EXCEPTIONS + #include +#endif + +typedef int ELEM_TYPE; +typedef int64_t PRODUCT_TYPE; +static const ELEM_TYPE BASE = 1000000000; +static const ELEM_TYPE UPPER_BOUND = 999999999; +static const ELEM_TYPE DIGIT_COUNT = 9; +static const int powersOfTen[] = {1, 10, 100, 1000, 10000, + 100000, 1000000, 10000000, 100000000}; + +#ifdef INFINT_USE_EXCEPTIONS +class InfIntException : public std::exception { +public: + InfIntException(const std::string& txt) throw(); + ~InfIntException() throw(); + const char* what() const throw(); + +private: + std::string txt; +}; + +inline InfIntException::InfIntException(const std::string& txt) throw() + : std::exception(), txt(txt) {} + +inline InfIntException::~InfIntException() throw() {} + +inline const char* InfIntException::what() const throw() { + return txt.c_str(); +} +#endif + +inline static div_t my_div(int num, int denom) { + div_t result; + result.quot = num / denom; + result.rem = num - denom * result.quot; + return result; +} + +inline static lldiv_t my_lldiv(int64_t num, int64_t denom) { + lldiv_t result; + result.quot = num / denom; + result.rem = num - denom * result.quot; + return result; +} + +class InfInt { + friend std::ostream& operator<<(std::ostream& s, const InfInt& n); + friend std::istream& operator>>(std::istream& s, InfInt& n); + +public: + /* constructors */ + InfInt(); // Positive Zero Constructor + explicit InfInt(const char* c); + explicit InfInt(const std::string& s); + explicit InfInt(int64_t l); + InfInt(const InfInt& l) = default; + + /* assignment operators */ + InfInt& operator=(const char* c); + InfInt& operator=(const std::string& s); + InfInt& operator=(int l); + InfInt& operator=(int64_t l); + InfInt& operator=(unsigned int l); + InfInt& operator=(uint64_t l); + InfInt& operator=(const InfInt& l); + + /* unary increment/decrement operators */ + InfInt& operator++(); + InfInt& operator--(); + InfInt operator++(int); + InfInt operator--(int); + + /* operational assignments */ + InfInt& operator+=(const InfInt& rhs); + InfInt& operator-=(const InfInt& rhs); + InfInt& operator*=(const InfInt& rhs); + InfInt& operator/=(const InfInt& rhs); // throw + InfInt& operator%=(const InfInt& rhs); // throw + InfInt& operator*=(ELEM_TYPE rhs); + + /* operations */ + InfInt operator-() const; + InfInt operator+(const InfInt& rhs) const; + InfInt operator-(const InfInt& rhs) const; + InfInt operator*(const InfInt& rhs) const; + InfInt operator/(const InfInt& rhs) const; // throw + InfInt operator%(const InfInt& rhs) const; // throw + InfInt operator*(ELEM_TYPE rhs) const; + InfInt operator<<(int64_t rhs) const; + + /* relational operations */ + bool operator==(const InfInt& rhs) const; + bool operator!=(const InfInt& rhs) const; + bool operator<(const InfInt& rhs) const; + bool operator<=(const InfInt& rhs) const; + bool operator>(const InfInt& rhs) const; + bool operator>=(const InfInt& rhs) const; + + /* basic properties */ + [[nodiscard]] bool isZero() const; + + /* integer square root */ + [[nodiscard]] InfInt intSqrt() const; // throw + + /* digit operations */ + [[nodiscard]] char digitAt(size_t i) const; // throw + [[nodiscard]] size_t numberOfDigits() const; + + /* size in bytes */ + [[nodiscard]] size_t size() const; + + /* string conversion */ + [[nodiscard]] std::string toString() const; + + /* conversion to primitive types */ + [[nodiscard]] int64_t toLong() const; // throw + [[nodiscard]] int64_t toLongLong() const; // throw + [[nodiscard]] uint64_t toUnsignedLong() const; // throw + [[nodiscard]] uint64_t toUnsignedLongLong() const; // throw + + [[nodiscard]] int truncateToInt() const; + [[nodiscard]] int64_t truncateToInt64() const; + [[nodiscard]] int64_t toLongLongForHash() const; + + [[nodiscard]] double toDouble() const; + +private: + static ELEM_TYPE dInR(const InfInt& R, const InfInt& D); + static void multiplyByDigit(ELEM_TYPE factor, std::vector& val); + + void correct(bool justCheckLeadingZeros = false, bool hasValidSign = false); + void fromString(const std::string& s); + void optimizeSqrtSearchBounds(InfInt& lo, InfInt& hi) const; + void truncateToBase(); + bool equalizeSigns(); + void removeLeadingZeros(); + + std::vector val; // number with base FACTOR + bool pos{}; // true if number is positive +}; + +// Positive Zero Constructor +inline InfInt::InfInt() : pos(true) { + // PROFINY_SCOPE + val.push_back((ELEM_TYPE)0); +} + +inline InfInt::InfInt(const char* c) { + // PROFINY_SCOPE + fromString(c); +} + +inline InfInt::InfInt(const std::string& s) { + // PROFINY_SCOPE + fromString(s); +} + +inline InfInt::InfInt(int64_t l) : pos(l >= 0) { + // PROFINY_SCOPE + bool subtractOne = false; + if (l == INT64_MIN) { + subtractOne = true; + ++l; + } + + if (!pos) { + l = -l; + } + do { + lldiv_t dt = my_lldiv(l, BASE); + val.push_back((ELEM_TYPE)dt.rem); + l = dt.quot; + } while (l > 0); + + if (subtractOne) { + --*this; + } +} + +inline InfInt& InfInt::operator=(const char* c) { + // PROFINY_SCOPE + fromString(c); + return *this; +} + +inline InfInt& InfInt::operator=(const std::string& s) { + // PROFINY_SCOPE + fromString(s); + return *this; +} + +inline InfInt& InfInt::operator=(int l) { + // PROFINY_SCOPE + bool subtractOne = false; + if (l == INT_MIN) { + subtractOne = true; + ++l; + } + + pos = l >= 0; + val.clear(); + if (!pos) { + l = -l; + } + do { + div_t dt = my_div(l, BASE); + val.push_back((ELEM_TYPE)dt.rem); + l = dt.quot; + } while (l > 0); + + // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + return subtractOne ? --*this : *this; +} + +inline InfInt& InfInt::operator=(int64_t l) { + // PROFINY_SCOPE + bool subtractOne = false; + if (l == INT64_MIN) { + subtractOne = true; + ++l; + } + + pos = l >= 0; + val.clear(); + if (!pos) { + l = -l; + } + do { + lldiv_t dt = my_lldiv(l, BASE); + val.push_back((ELEM_TYPE)dt.rem); + l = dt.quot; + } while (l > 0); + + // NOLINTNEXTLINE(cppcoreguidelines-c-copy-assignment-signature,misc-unconventional-assign-operator) + return subtractOne ? --*this : *this; +} + +inline InfInt& InfInt::operator=(unsigned int l) { + // PROFINY_SCOPE + pos = true; + val.clear(); + do { + val.push_back((ELEM_TYPE)(l % BASE)); + l = l / BASE; + } while (l > 0); + return *this; +} + +inline InfInt& InfInt::operator=(uint64_t l) { + // PROFINY_SCOPE + pos = true; + val.clear(); + do { + val.push_back((ELEM_TYPE)(l % BASE)); + l = l / BASE; + } while (l > 0); + return *this; +} + +inline InfInt& InfInt::operator=(const InfInt& l) = default; + +inline InfInt& InfInt::operator++() { + // PROFINY_SCOPE + val[0] += (pos ? 1 : -1); + this->correct(false, true); + return *this; +} + +inline InfInt& InfInt::operator--() { + // PROFINY_SCOPE + val[0] -= (pos ? 1 : -1); + this->correct(false, true); + return *this; +} + +inline InfInt InfInt::operator++(int) { + // PROFINY_SCOPE + InfInt const result = *this; + val[0] += (pos ? 1 : -1); + this->correct(false, true); + return result; +} + +inline InfInt InfInt::operator--(int) { + // PROFINY_SCOPE + InfInt const result = *this; + val[0] -= (pos ? 1 : -1); + this->correct(false, true); + return result; +} + +inline InfInt& InfInt::operator+=(const InfInt& rhs) { + // PROFINY_SCOPE + if (rhs.val.size() > val.size()) { + val.resize(rhs.val.size(), 0); + } + for (size_t i = 0; i < val.size(); ++i) { + val[i] = + (pos ? val[i] : -val[i]) + + // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator) + (i < rhs.val.size() ? (rhs.pos ? rhs.val[i] : -rhs.val[i]) : 0); + } + correct(); + return *this; +} + +inline InfInt& InfInt::operator-=(const InfInt& rhs) { + // PROFINY_SCOPE + if (rhs.val.size() > val.size()) { + val.resize(rhs.val.size(), 0); + } + for (size_t i = 0; i < val.size(); ++i) { + val[i] = + (pos ? val[i] : -val[i]) - + // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator) + (i < rhs.val.size() ? (rhs.pos ? rhs.val[i] : -rhs.val[i]) : 0); + } + correct(); + return *this; +} + +inline InfInt& InfInt::operator*=(const InfInt& rhs) { + // PROFINY_SCOPE + // TODO(original): optimize (do not use operator*) + *this = *this * rhs; + return *this; +} + +inline InfInt& InfInt::operator/=(const InfInt& rhs) { + // PROFINY_SCOPE + if (rhs.isZero()) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("division by zero"); +#else + std::cerr << "Division by zero!" << '\n'; + return *this; +#endif + } + InfInt R; + InfInt const D = (rhs.pos ? rhs : -rhs); + InfInt N = (pos ? *this : -*this); + bool const oldpos = pos; + std::fill(val.begin(), val.end(), 0); + for (int i = (int)N.val.size() - 1; i >= 0; --i) { + R.val.insert(R.val.begin(), N.val[i]); + R.correct(true); + ELEM_TYPE const cnt = dInR(R, D); + R -= D * cnt; + val[i] += cnt; + } + correct(); + pos = (val.size() == 1 && val[0] == 0) ? true : (oldpos == rhs.pos); + return *this; +} + +inline InfInt& InfInt::operator%=(const InfInt& rhs) { + // PROFINY_SCOPE + // TODO(original): optimize (do not use operator%) + *this = *this % rhs; + return *this; + // if (rhs == 0) + // { + // #ifdef INFINT_USE_EXCEPTIONS + // throw InfIntException("division by zero"); + // #else + // std::cerr << "Division by zero!" << std::endl; + // return *this; + // #endif + // } + // InfInt D = (rhs.pos ? rhs : -rhs), N = (pos ? *this : -*this); + // bool oldpos = pos; + // val.clear(); + // for (int i = (int) N.val.size() - 1; i >= 0; --i) + // { + // val.insert(val.begin(), N.val[i]); + // correct(true); + // *this -= D * dInR(*this, D); + // } + // correct(); + // pos = (val.size() == 1 && val[0] == 0) ? true : oldpos; + // return *this; +} + +inline InfInt& InfInt::operator*=(ELEM_TYPE rhs) { + // PROFINY_SCOPE + ELEM_TYPE const factor = rhs < 0 ? -rhs : rhs; + bool const oldpos = pos; + multiplyByDigit(factor, val); + correct(); + pos = (val.size() == 1 && val[0] == 0) ? true : (oldpos == (rhs >= 0)); + return *this; +} + +inline InfInt InfInt::operator-() const { + // PROFINY_SCOPE + InfInt result = *this; + result.pos = !pos; + return result; +} + +inline InfInt InfInt::operator+(const InfInt& rhs) const { + // PROFINY_SCOPE + InfInt result; + result.val.resize(val.size() > rhs.val.size() ? val.size() : rhs.val.size(), + 0); + for (size_t i = 0; i < val.size() || i < rhs.val.size(); ++i) { + result.val[i] = + // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator) + (i < val.size() ? (pos ? val[i] : -val[i]) : 0) + + // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator) + (i < rhs.val.size() ? (rhs.pos ? rhs.val[i] : -rhs.val[i]) : 0); + } + result.correct(); + return result; +} + +inline InfInt InfInt::operator-(const InfInt& rhs) const { + // PROFINY_SCOPE + InfInt result; + result.val.resize(val.size() > rhs.val.size() ? val.size() : rhs.val.size(), + 0); + for (size_t i = 0; i < val.size() || i < rhs.val.size(); ++i) { + result.val[i] = + // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator) + (i < val.size() ? (pos ? val[i] : -val[i]) : 0) - + // NOLINTNEXTLINE(readability-avoid-nested-conditional-operator) + (i < rhs.val.size() ? (rhs.pos ? rhs.val[i] : -rhs.val[i]) : 0); + } + result.correct(); + return result; +} + +inline InfInt InfInt::operator*(const InfInt& rhs) const { + // PROFINY_SCOPE + InfInt result; + result.val.resize(val.size() + rhs.val.size(), 0); + PRODUCT_TYPE carry = 0; + size_t digit = 0; + for (;; ++digit) { + lldiv_t const dt = my_lldiv(carry, BASE); + carry = dt.quot; + result.val[digit] = (ELEM_TYPE)dt.rem; + + bool found = false; + for (size_t i = digit < rhs.val.size() ? 0 : digit - rhs.val.size() + 1; + i < val.size() && i <= digit; + ++i) { + PRODUCT_TYPE pval = + result.val[digit] + (val[i] * (PRODUCT_TYPE)rhs.val[digit - i]); + if (pval >= BASE || pval <= -BASE) { + lldiv_t const dt = my_lldiv(pval, BASE); + carry += dt.quot; + pval = dt.rem; + } + result.val[digit] = (ELEM_TYPE)pval; + found = true; + } + if (!found) { + break; + } + } + for (; carry > 0; ++digit) { + lldiv_t const dt = my_lldiv(carry, BASE); + result.val[digit] = (ELEM_TYPE)dt.rem; + carry = dt.quot; + } + result.correct(); + result.pos = (result.val.size() == 1 && result.val[0] == 0) + ? true + : (pos == rhs.pos); + return result; +} + +inline InfInt InfInt::operator<<(const int64_t rhs) const { + const InfInt two(2ULL); + InfInt result = *this; + for (int64_t i = 0; i < rhs; i += 1) { + result *= two; + } + return result; +} + +inline InfInt InfInt::operator/(const InfInt& rhs) const { + // PROFINY_SCOPE + if (rhs.isZero()) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("division by zero"); +#else + std::cerr << "Division by zero!" << '\n'; + return {}; +#endif + } + InfInt Q; + InfInt R; + InfInt const D = (rhs.pos ? rhs : -rhs); + InfInt N = (pos ? *this : -*this); + Q.val.resize(N.val.size(), 0); + for (int i = (int)N.val.size() - 1; i >= 0; --i) { + R.val.insert(R.val.begin(), N.val[i]); + R.correct(true); + ELEM_TYPE const cnt = dInR(R, D); + R -= D * cnt; + Q.val[i] += cnt; + } + Q.correct(); + Q.pos = (Q.val.size() == 1 && Q.val[0] == 0) ? true : (pos == rhs.pos); + return Q; +} + +inline InfInt InfInt::operator%(const InfInt& rhs) const { + // PROFINY_SCOPE + if (rhs.isZero()) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("division by zero"); +#else + std::cerr << "Division by zero!" << '\n'; + return {}; +#endif + } + InfInt R; + InfInt const D = (rhs.pos ? rhs : -rhs); + InfInt N = (pos ? *this : -*this); + for (int i = (int)N.val.size() - 1; i >= 0; --i) { + R.val.insert(R.val.begin(), N.val[i]); + R.correct(true); + R -= D * dInR(R, D); + } + R.correct(); + R.pos = (R.val.size() == 1 && R.val[0] == 0) ? true : pos; + return R; +} + +inline InfInt InfInt::operator*(ELEM_TYPE rhs) const { + // PROFINY_SCOPE + InfInt result = *this; + ELEM_TYPE const factor = rhs < 0 ? -rhs : rhs; + multiplyByDigit(factor, result.val); + result.correct(); + result.pos = (result.val.size() == 1 && result.val[0] == 0) + ? true + : (pos == (rhs >= 0)); + return result; +} + +inline bool InfInt::operator==(const InfInt& rhs) const { + // PROFINY_SCOPE + if (pos != rhs.pos || val.size() != rhs.val.size()) { + return false; + } + for (int i = (int)val.size() - 1; i >= 0; --i) { + if (val[i] != rhs.val[i]) { + return false; + } + } + return true; +} + +inline bool InfInt::operator!=(const InfInt& rhs) const { + // PROFINY_SCOPE + if (pos != rhs.pos || val.size() != rhs.val.size()) { + return true; + } + for (int i = (int)val.size() - 1; i >= 0; --i) { + if (val[i] != rhs.val[i]) { + return true; + } + } + return false; +} + +inline bool InfInt::operator<(const InfInt& rhs) const { + // PROFINY_SCOPE + if (pos && !rhs.pos) { + return false; + } + if (!pos && rhs.pos) { + return true; + } + if (val.size() > rhs.val.size()) { + return !pos; + } + if (val.size() < rhs.val.size()) { + return pos; + } + for (int i = (int)val.size() - 1; i >= 0; --i) { + if (val[i] < rhs.val[i]) { + return pos; + } + if (val[i] > rhs.val[i]) { + return !pos; + } + } + return false; +} + +inline bool InfInt::operator<=(const InfInt& rhs) const { + // PROFINY_SCOPE + if (pos && !rhs.pos) { + return false; + } + if (!pos && rhs.pos) { + return true; + } + if (val.size() > rhs.val.size()) { + return !pos; + } + if (val.size() < rhs.val.size()) { + return pos; + } + for (int i = (int)val.size() - 1; i >= 0; --i) { + if (val[i] < rhs.val[i]) { + return pos; + } + if (val[i] > rhs.val[i]) { + return !pos; + } + } + return true; +} + +inline bool InfInt::operator>(const InfInt& rhs) const { + // PROFINY_SCOPE + if (pos && !rhs.pos) { + return true; + } + if (!pos && rhs.pos) { + return false; + } + if (val.size() > rhs.val.size()) { + return pos; + } + if (val.size() < rhs.val.size()) { + return !pos; + } + for (int i = (int)val.size() - 1; i >= 0; --i) { + if (val[i] < rhs.val[i]) { + return !pos; + } + if (val[i] > rhs.val[i]) { + return pos; + } + } + return false; +} + +inline bool InfInt::operator>=(const InfInt& rhs) const { + // PROFINY_SCOPE + if (pos && !rhs.pos) { + return true; + } + if (!pos && rhs.pos) { + return false; + } + if (val.size() > rhs.val.size()) { + return pos; + } + if (val.size() < rhs.val.size()) { + return !pos; + } + for (int i = (int)val.size() - 1; i >= 0; --i) { + if (val[i] < rhs.val[i]) { + return !pos; + } + if (val[i] > rhs.val[i]) { + return pos; + } + } + return true; +} + +inline void InfInt::optimizeSqrtSearchBounds(InfInt& lo, InfInt& hi) const { + // PROFINY_SCOPE + InfInt hdn(1ULL); + for (int i = (int)this->numberOfDigits() / 2; i >= 2; --i) { + hdn *= 10; + } + if (lo < hdn) { + lo = hdn; + } + hdn *= 100; + if (hi > hdn) { + hi = hdn; + } +} + +inline bool InfInt::isZero() const { + return val.size() == 1 && val[0] == 0; +} + +inline InfInt InfInt::intSqrt() const { + // PROFINY_SCOPE + if (*this <= InfInt()) { // TODO(smarr): replace by a more specific check +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("intSqrt called for non-positive integer"); +#else + std::cerr << "intSqrt called for non-positive integer: " << *this + << '\n'; + return {}; +#endif + } + InfInt hi = *this / InfInt(2ULL) + InfInt(1ULL); + InfInt lo = InfInt(); + InfInt mid; + InfInt mid2; + optimizeSqrtSearchBounds(lo, hi); + do { + mid = (hi + lo) / InfInt(2ULL); // 8 factor + mid2 = mid * mid; // 1 factor + if (mid2 == *this) { + lo = mid; + break; + } + if (mid2 < *this) { + lo = mid; + } else { + hi = mid; + } + } while (lo < hi - InfInt(1ULL) && mid2 != *this); + return lo; +} + +inline char InfInt::digitAt(size_t i) const { + // PROFINY_SCOPE + if (numberOfDigits() <= i) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("invalid digit index"); +#else + std::cerr << "Invalid digit index: " << i << '\n'; + return -1; +#endif + } + return static_cast( + (val[i / DIGIT_COUNT] / powersOfTen[i % DIGIT_COUNT]) % 10); +} + +inline size_t InfInt::numberOfDigits() const { + // PROFINY_SCOPE + auto lastVal = val.back(); + size_t const baseDigits = (val.size() - 1) * DIGIT_COUNT; + if (lastVal > 99999999) { + return baseDigits + 9; + } + if (lastVal > 9999999) { + return baseDigits + 8; + } + if (lastVal > 999999) { + return baseDigits + 7; + } + if (lastVal > 99999) { + return baseDigits + 6; + } + if (lastVal > 9999) { + return baseDigits + 5; + } + if (lastVal > 999) { + return baseDigits + 4; + } + if (lastVal > 99) { + return baseDigits + 3; + } + if (lastVal > 9) { + return baseDigits + 2; + } + return baseDigits + 1; +} + +inline std::string InfInt::toString() const { + // PROFINY_SCOPE + std::ostringstream oss; + oss << *this; + return oss.str(); +} + +inline size_t InfInt::size() const { + // PROFINY_SCOPE + return (val.size() * sizeof(ELEM_TYPE)) + sizeof(bool); +} + +inline int InfInt::truncateToInt() const { + int result = 0; + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return pos ? result : -result; +} + +inline int64_t InfInt::toLong() const { + // PROFINY_SCOPE + if (*this > InfInt(INT64_MAX) || *this < InfInt(INT64_MIN)) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("out of bounds"); +#else + std::cerr << "Out of LONG bounds: " << *this << '\n'; +#endif + } + int64_t result = 0; + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return pos ? result : -result; +} + +inline int64_t InfInt::truncateToInt64() const { + // PROFINY_SCOPE + int64_t result = 0; + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return pos ? result : -result; +} + +inline int64_t InfInt::toLongLong() const { + // PROFINY_SCOPE + if (*this > InfInt(INT64_MAX) || *this < InfInt(INT64_MIN)) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("out of bounds"); +#else + std::cerr << "Out of LLONG bounds: " << *this << '\n'; +#endif + } + int64_t result = 0; + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return pos ? result : -result; +} + +inline int64_t InfInt::toLongLongForHash() const { + int64_t result = 0; + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return pos ? result : -result; +} + +inline double InfInt::toDouble() const { + double result = 0; + + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return pos ? result : -result; +} + +inline uint64_t InfInt::toUnsignedLong() const { + // PROFINY_SCOPE + if (!pos || *this > InfInt(UINT64_MAX)) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("out of bounds"); +#else + std::cerr << "Out of ULONG bounds: " << *this << '\n'; +#endif + } + uint64_t result = 0; + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return result; +} + +inline uint64_t InfInt::toUnsignedLongLong() const { + // PROFINY_SCOPE + if (!pos || *this > InfInt(UINT64_MAX)) { +#ifdef INFINT_USE_EXCEPTIONS + throw InfIntException("out of bounds"); +#else + std::cerr << "Out of ULLONG bounds: " << *this << '\n'; +#endif + } + uint64_t result = 0; + for (int i = (int)val.size() - 1; i >= 0; --i) { + result = result * BASE + val[i]; + } + return result; +} + +inline void InfInt::truncateToBase() { + // PROFINY_SCOPE + for (size_t i = 0; i < val.size(); ++i) // truncate each + { + if (val[i] >= BASE || val[i] <= -BASE) { + div_t const dt = my_div(val[i], BASE); + val[i] = dt.rem; + if (i + 1 >= val.size()) { + val.push_back(dt.quot); + } else { + val[i + 1] += dt.quot; + } + } + } +} + +inline bool InfInt::equalizeSigns() { + // PROFINY_SCOPE + bool isPositive = true; + int i = (int)((val.size())) - 1; + for (; i >= 0; --i) { + if (val[i] != 0) { + isPositive = val[i--] > 0; + break; + } + } + + if (isPositive) { + for (; i >= 0; --i) { + if (val[i] < 0) { + int k = 0; + int index = i + 1; + for (; (size_t)(index) < val.size() && val[index] == 0; + ++k, ++index) { + ; // count adjacent zeros on left + } + // if ((size_t)(index) < val.size() && val[index] > 0) + { // number on the left is positive + val[index] -= 1; + val[i] += BASE; + for (; k > 0; --k) { + val[i + k] = UPPER_BOUND; + } + } + } + } + } else { + for (; i >= 0; --i) { + if (val[i] > 0) { + int k = 0; + int index = i + 1; + for (; (size_t)(index) < val.size() && val[index] == 0; + ++k, ++index) { + ; // count adjacent zeros on right + } + // if ((size_t)(index) < val.size() && val[index] < 0) + { // number on the left is negative + val[index] += 1; + val[i] -= BASE; + for (; k > 0; --k) { + val[i + k] = -UPPER_BOUND; + } + } + } + } + } + + return isPositive; +} + +inline void InfInt::removeLeadingZeros() { + // PROFINY_SCOPE + for (int i = (int)(val.size()) - 1; i > 0; --i) // remove leading 0's + { + if (val[i] != 0) { + return; + } + val.erase(val.begin() + i); + } +} + +inline void InfInt::correct(bool justCheckLeadingZeros, bool hasValidSign) { + // PROFINY_SCOPE + if (!justCheckLeadingZeros) { + truncateToBase(); + + if (equalizeSigns()) { + pos = ((val.size() == 1 && val[0] == 0) || !hasValidSign) ? true + : pos; + } else { + pos = hasValidSign ? !pos : false; + for (int& i : val) { + i = abs(i); + } + } + } + + removeLeadingZeros(); +} + +inline void InfInt::fromString(const std::string& s) { + // PROFINY_SCOPE + pos = true; + val.clear(); + val.reserve((s.size() / DIGIT_COUNT) + 1); + int i = (int)s.size() - DIGIT_COUNT; + for (; i >= 0; i -= DIGIT_COUNT) { + // NOLINTNEXTLINE(cert-err34-c) + val.push_back(atoi(s.substr(i, DIGIT_COUNT).c_str())); + } + if (i > -DIGIT_COUNT) { + std::string ss = s.substr(0, i + DIGIT_COUNT); + if (ss.size() == 1 && ss[0] == '-') { + pos = false; + } else { + // NOLINTNEXTLINE(cert-err34-c) + val.push_back(atoi(ss.c_str())); + } + } + if (val.back() < 0) { + val.back() = -val.back(); + pos = false; + } + correct(true); +} + +inline ELEM_TYPE InfInt::dInR(const InfInt& R, const InfInt& D) { + // PROFINY_SCOPE + ELEM_TYPE min = 0; + ELEM_TYPE max = UPPER_BOUND; + while (max > min) { + ELEM_TYPE avg = max + min; + div_t const dt = my_div(avg, 2); + avg = (dt.rem != 0) ? (dt.quot + 1) : dt.quot; + InfInt const prod = D * avg; + if (R == prod) { + return avg; + } + if (R > prod) { + min = avg; + } else { + max = avg - 1; + } + } + return min; +} + +inline void InfInt::multiplyByDigit(ELEM_TYPE factor, + std::vector& val) { + // PROFINY_SCOPE + ELEM_TYPE carry = 0; + for (int& i : val) { + PRODUCT_TYPE pval = (i * (PRODUCT_TYPE)factor) + carry; + if (pval >= BASE || pval <= -BASE) { + lldiv_t const dt = my_lldiv(pval, BASE); + carry = (ELEM_TYPE)dt.quot; + pval = dt.rem; + } else { + carry = 0; + } + i = (ELEM_TYPE)pval; + } + if (carry > 0) { + val.push_back(carry); + } +} + +/**************************************************************/ +/******************** NON-MEMBER OPERATORS ********************/ +/**************************************************************/ + +inline std::istream& operator>>(std::istream& s, InfInt& n) { + // PROFINY_SCOPE + std::string str; + s >> str; + n.fromString(str); + return s; +} + +inline std::ostream& operator<<(std::ostream& s, const InfInt& n) { + // PROFINY_SCOPE + if (!n.pos) { + s << '-'; + } + bool first = true; + for (int i = (int)n.val.size() - 1; i >= 0; --i) { + if (first) { + s << n.val[i]; + first = false; + } else { + s << std::setfill('0') << std::setw(DIGIT_COUNT) << n.val[i]; + } + } + return s; +} + +#endif diff --git a/src/misc/ParseInteger.cpp b/src/misc/ParseInteger.cpp index 53175f14..3bd80484 100644 --- a/src/misc/ParseInteger.cpp +++ b/src/misc/ParseInteger.cpp @@ -7,13 +7,14 @@ #include "../vm/Universe.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/ObjectFormats.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) -vm_oop_t ParseInteger(const char* str, int base, bool negateValue) { +vm_oop_t ParseInteger(const char* str, bool negateValue) { errno = 0; char* pEnd{}; - const int64_t i = std::strtoll(str, &pEnd, base); + const int64_t i = std::strtoll(str, &pEnd, 10); if (str == pEnd) { // did not parse anything @@ -22,8 +23,7 @@ vm_oop_t ParseInteger(const char* str, int base, bool negateValue) { const bool rangeError = errno == ERANGE; if (rangeError) { - // TODO(smarr): try a big int library - return NEW_INT(0); + return Universe::NewBigIntegerFromStr(str, negateValue); } // the normal case @@ -33,6 +33,6 @@ vm_oop_t ParseInteger(const char* str, int base, bool negateValue) { return NEW_INT(i); } -vm_oop_t ParseInteger(std::string& str, int base, bool negateValue) { - return ParseInteger(str.c_str(), base, negateValue); +vm_oop_t ParseInteger(std::string& str, bool negateValue) { + return ParseInteger(str.c_str(), negateValue); } diff --git a/src/misc/ParseInteger.h b/src/misc/ParseInteger.h index 0db42f1b..81e76527 100644 --- a/src/misc/ParseInteger.h +++ b/src/misc/ParseInteger.h @@ -5,5 +5,5 @@ #include "../vmobjects/ObjectFormats.h" #include "defs.h" -vm_oop_t ParseInteger(const char* str, int base, bool negateValue); -vm_oop_t ParseInteger(std::string& str, int base, bool negateValue); +vm_oop_t ParseInteger(const char* str, bool negateValue); +vm_oop_t ParseInteger(std::string& str, bool negateValue); diff --git a/src/primitives/Array.cpp b/src/primitives/Array.cpp index b8e5f65b..c4643577 100644 --- a/src/primitives/Array.cpp +++ b/src/primitives/Array.cpp @@ -31,16 +31,17 @@ #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" #include "../vmobjects/VMArray.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMFrame.h" static vm_oop_t arrAt(vm_oop_t leftObj, vm_oop_t idx) { auto* self = static_cast(leftObj); - return self->GetIndexableField(INT_VAL(idx) - 1); + return self->GetIndexableField(SMALL_INT_VAL(idx) - 1); } static vm_oop_t arrAtPut(vm_oop_t rcvr, vm_oop_t index, vm_oop_t value) { auto* self = static_cast(rcvr); - int64_t const i = INT_VAL(index); + int64_t const i = SMALL_INT_VAL(index); self->SetIndexableField(i - 1, value); return rcvr; } @@ -51,7 +52,7 @@ static vm_oop_t arrLength(vm_oop_t leftObj) { } static vm_oop_t arrNew(vm_oop_t /*unused*/, vm_oop_t arg) { - int64_t const size = INT_VAL(arg); + int64_t const size = SMALL_INT_VAL(arg); return Universe::NewArray(size); } diff --git a/src/primitives/Double.cpp b/src/primitives/Double.cpp index 3d0eef04..a5efa715 100644 --- a/src/primitives/Double.cpp +++ b/src/primitives/Double.cpp @@ -36,29 +36,29 @@ #include "../vm/Print.h" #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" +#include "../vmobjects/VMBigInteger.h" #include "../vmobjects/VMDouble.h" #include "../vmobjects/VMFrame.h" -#include "../vmobjects/VMInteger.h" #include "../vmobjects/VMString.h" /* * This function coerces any right-hand parameter to a double, regardless of its * true nature. This is to make sure that all Double operations return Doubles. */ -double coerceDouble(vm_oop_t x) { - if (IS_TAGGED(x)) { - return (double)INT_VAL(x); +static double coerceDouble(vm_oop_t x) { + if (IS_SMALL_INT(x)) { + return (double)SMALL_INT_VAL(x); } - VMClass* cl = ((AbstractVMObject*)x)->GetClass(); - if (cl == load_ptr(doubleClass)) { - return static_cast(x)->GetEmbeddedDouble(); + if (IS_DOUBLE(x)) { + return AS_DOUBLE(x); } - if (cl == load_ptr(integerClass)) { - return (double)static_cast(x)->GetEmbeddedInteger(); + + if (IS_BIG_INT(x)) { + return AS_BIG_INT(x)->embeddedInteger.toDouble(); } - ErrorExit("Attempt to apply Double operation to non-number."); + ErrorExit("Attempt to apply Double operation to non-number."); return 0.0F; } diff --git a/src/primitives/Double.h b/src/primitives/Double.h index 7b6a8eb4..130a6144 100644 --- a/src/primitives/Double.h +++ b/src/primitives/Double.h @@ -34,5 +34,4 @@ class _Double : public PrimitiveContainer { _Double(); }; -double coerceDouble(vm_oop_t x); vm_oop_t dblPercent(vm_oop_t leftPtr, vm_oop_t rightObj); diff --git a/src/primitives/Integer.cpp b/src/primitives/Integer.cpp index 03b569ae..cf56d0b2 100644 --- a/src/primitives/Integer.cpp +++ b/src/primitives/Integer.cpp @@ -34,225 +34,528 @@ #include #include +#include "../lib/InfInt.h" #include "../misc/ParseInteger.h" +#include "../misc/defs.h" #include "../vm/Globals.h" +#include "../vm/Print.h" #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" -#include "../vmobjects/VMDouble.h" +#include "../vmobjects/VMBigInteger.h" +#include "../vmobjects/VMDouble.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMFrame.h" #include "../vmobjects/VMString.h" -#include "Double.h" // // arithmetic operations // -#define doDoubleOpIfNeeded(leftInt, rightObj, op) \ - { \ - VMClass* cl = CLASS_OF(rightObj); \ - if (cl == load_ptr(doubleClass)) { \ - double const leftDbl = (double)(leftInt); \ - double const rightDbl = coerceDouble(rightObj); \ - return Universe::NewDouble(leftDbl op rightDbl); \ - } \ +#define doSmallIntWithDoubleOp(leftInt, rightObj, op) \ + { \ + double const leftDbl = (double)(leftInt); \ + double const rightDbl = AS_DOUBLE(rightObj); \ + return Universe::NewDouble(leftDbl op rightDbl); \ } static vm_oop_t intPlus(vm_oop_t leftObj, vm_oop_t rightObj) { assert(CLASS_OF(leftObj) == load_ptr(integerClass) && "The receiver should always be an int"); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + + if (IS_SMALL_INT(rightObj)) { + int64_t const right = SMALL_INT_VAL(rightObj); + int64_t result = 0; + if (unlikely(__builtin_add_overflow(left, right, &result))) { + InfInt const l(left); + InfInt const r(right); + return Universe::NewBigInteger(l + r); + } + return NEW_INT(result); + } - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, +); + if (IS_DOUBLE(rightObj)) { + doSmallIntWithDoubleOp(left, rightObj, +); + } - int64_t const result = left + INT_VAL(rightObj); - return NEW_INT(result); + assert(IS_BIG_INT(rightObj) && "assume we're having a big int now"); + return AS_BIG_INT(rightObj)->Add(left); + } + + assert(IS_BIG_INT(leftObj) && "assume rcvr is a big int now"); + return AS_BIG_INT(leftObj)->Add(rightObj); } static vm_oop_t intBitwiseXor(vm_oop_t leftObj, vm_oop_t rightObj) { - // NOLINTNEXTLINE(hicpp-signed-bitwise) - int64_t const result = INT_VAL(leftObj) ^ INT_VAL(rightObj); - return NEW_INT(result); + if (IS_SMALL_INT(leftObj) && IS_SMALL_INT(rightObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + + // NOLINTNEXTLINE(hicpp-signed-bitwise) + int64_t const result = left ^ SMALL_INT_VAL(rightObj); + return NEW_INT(result); + } + + ErrorExit("#xor: not supported on non-small-int arguments"); } static vm_oop_t intLeftShift(vm_oop_t leftObj, vm_oop_t rightObj) { - // NOLINTNEXTLINE(hicpp-signed-bitwise) - int64_t const result = INT_VAL(leftObj) << INT_VAL(rightObj); - return NEW_INT(result); + if (IS_SMALL_INT(leftObj) && IS_SMALL_INT(rightObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + int64_t const right = SMALL_INT_VAL(rightObj); + + // NOLINTNEXTLINE(misc-include-cleaner) + auto const numberOfLeadingZeros = __builtin_clzll((uint64_t)left); + + if (64 - numberOfLeadingZeros + right > 63) { + return Universe::NewBigInteger(InfInt(left) << right); + } + + // NOLINTNEXTLINE(hicpp-signed-bitwise) + int64_t const result = left << right; + return NEW_INT(result); + } + + ErrorExit("#<< not supported on non-small-int arguments"); } static vm_oop_t intUnsignedRightShift(vm_oop_t leftObj, vm_oop_t rightObj) { - // NOLINTNEXTLINE(hicpp-signed-bitwise) - int64_t const result = INT_VAL(leftObj) >> INT_VAL(rightObj); - return NEW_INT(result); + if (IS_SMALL_INT(leftObj) && IS_SMALL_INT(rightObj)) { + uint64_t const left = SMALL_INT_VAL(leftObj); + uint64_t const right = SMALL_INT_VAL(rightObj); + + // NOLINTNEXTLINE(hicpp-signed-bitwise) + auto const result = static_cast(left >> right); + return NEW_INT(result); + } + + ErrorExit("#<< not supported on non-small-int arguments"); } static vm_oop_t intMinus(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, -); + assert(CLASS_OF(leftObj) == load_ptr(integerClass) && + "The receiver should always be an int"); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + + if (IS_SMALL_INT(rightObj)) { + int64_t const right = SMALL_INT_VAL(rightObj); + int64_t result = 0; + if (unlikely(__builtin_sub_overflow(left, right, &result))) { + InfInt const l(left); + InfInt const r(right); + return Universe::NewBigInteger(l - r); + } + return NEW_INT(result); + } - int64_t const result = left - INT_VAL(rightObj); - return NEW_INT(result); + if (IS_DOUBLE(rightObj)) { + doSmallIntWithDoubleOp(left, rightObj, -); + } + + assert(IS_BIG_INT(rightObj) && "assume we're having a big int now"); + return AS_BIG_INT(rightObj)->SubtractFrom(left); + } + + assert(IS_BIG_INT(leftObj) && "assume rcvr is a big int now"); + return AS_BIG_INT(leftObj)->Subtract(rightObj); } static vm_oop_t intStar(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, *); + assert(CLASS_OF(leftObj) == load_ptr(integerClass) && + "The receiver should always be an int"); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + + if (IS_SMALL_INT(rightObj)) { + int64_t const right = SMALL_INT_VAL(rightObj); + int64_t result = 0; + if (unlikely(__builtin_mul_overflow(left, right, &result))) { + InfInt const l(left); + InfInt const r(right); + return Universe::NewBigInteger(l * r); + } + return NEW_INT(result); + } - int64_t const result = left * INT_VAL(rightObj); - return NEW_INT(result); + if (IS_DOUBLE(rightObj)) { + doSmallIntWithDoubleOp(left, rightObj, *); + } + + assert(IS_BIG_INT(rightObj) && "assume we're having a big int now"); + return AS_BIG_INT(rightObj)->Multiply(left); + } + + assert(IS_BIG_INT(leftObj) && "assume rcvr is a big int now"); + return AS_BIG_INT(leftObj)->Multiply(rightObj); } static vm_oop_t intSlashslash(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, /); + double left = NAN; + if (IS_SMALL_INT(leftObj)) { + left = (double)SMALL_INT_VAL(leftObj); + } else { + assert(IS_BIG_INT(leftObj) && "should be a big integer now"); + left = AS_BIG_INT(leftObj)->embeddedInteger.toDouble(); + } + + double right = NAN; + if (IS_SMALL_INT(rightObj)) { + right = (double)SMALL_INT_VAL(rightObj); + } else if (IS_DOUBLE(rightObj)) { + right = AS_DOUBLE(rightObj); + } else { + assert(IS_BIG_INT(leftObj) && "should be a big integer now"); + right = AS_BIG_INT(leftObj)->embeddedInteger.toDouble(); + } - double const result = (double)left / (double)INT_VAL(rightObj); + double const result = left / right; return Universe::NewDouble(result); } static vm_oop_t intSlash(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - int64_t right = 0; + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); - VMClass* cl = CLASS_OF(rightObj); - if (cl == load_ptr(doubleClass)) { - right = (int64_t)((VMDouble*)rightObj)->GetEmbeddedDouble(); - } else { - right = INT_VAL(rightObj); + if (IS_SMALL_INT(rightObj)) { + int64_t const right = SMALL_INT_VAL(rightObj); + int64_t const result = left / right; + return NEW_INT(result); + } + + if (IS_DOUBLE(rightObj)) { + double const right = AS_DOUBLE(rightObj); + auto const result = (int64_t)(static_cast(left) / right); + return NEW_INT(result); + } + + assert(IS_BIG_INT(rightObj) && "expected to be a big int now"); + return AS_BIG_INT(rightObj)->DivisionFrom(left); } - int64_t const result = left / right; - return NEW_INT(result); + assert(IS_BIG_INT(leftObj)); + return AS_BIG_INT(leftObj)->DivideBy(rightObj); } static vm_oop_t intPercent(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const l = INT_VAL(leftObj); - int64_t r = 0; + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); - VMClass* cl = CLASS_OF(rightObj); - if (cl == load_ptr(doubleClass)) { - r = (int64_t)((VMDouble*)rightObj)->GetEmbeddedDouble(); - } else { - r = INT_VAL(rightObj); - } + if (IS_SMALL_INT(rightObj)) { + int64_t const right = SMALL_INT_VAL(rightObj); + int64_t result = left % right; - int64_t result = l % r; + if ((result != 0) && ((result < 0) != (right < 0))) { + result += right; + } + return NEW_INT(result); + } - if ((result != 0) && ((result < 0) != (r < 0))) { - result += r; + if (IS_DOUBLE(rightObj)) { + auto const right = (int64_t)AS_DOUBLE(rightObj); + int64_t result = left % right; + + if ((result != 0) && ((result < 0) != (right < 0))) { + result += right; + } + return NEW_INT(result); + } + + assert(IS_BIG_INT(rightObj) && "expected to be a big int now"); + return AS_BIG_INT(rightObj)->ModuloFrom(left); } - return NEW_INT(result); + assert(IS_BIG_INT(leftObj)); + return AS_BIG_INT(leftObj)->Modulo(rightObj); } static vm_oop_t intRem(vm_oop_t leftObj, vm_oop_t rightObj) { - VMClass* cl = CLASS_OF(rightObj); - if (cl == load_ptr(doubleClass)) { - return dblPercent(leftObj, rightObj); - } + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); - auto const l = INT_VAL(leftObj); - auto const r = INT_VAL(rightObj); + if (IS_SMALL_INT(rightObj)) { + int64_t const right = SMALL_INT_VAL(rightObj); + int64_t const result = left - ((left / right) * right); - int64_t const result = l - ((l / r) * r); + return NEW_INT(result); + } - return NEW_INT(result); + ErrorExit("not yet implemented #rem: small-int and something else"); + } + + ErrorExit("not yet implemented #rem: big int and something else"); } static vm_oop_t intAnd(vm_oop_t leftObj, vm_oop_t rightObj) { - // NOLINTNEXTLINE(hicpp-signed-bitwise) - int64_t const result = INT_VAL(leftObj) & INT_VAL(rightObj); - return NEW_INT(result); + if (IS_SMALL_INT(leftObj) && IS_SMALL_INT(rightObj)) { + // NOLINTNEXTLINE(hicpp-signed-bitwise) + int64_t const result = SMALL_INT_VAL(leftObj) & SMALL_INT_VAL(rightObj); + return NEW_INT(result); + } + + if (IS_BIG_INT(leftObj) && IS_SMALL_INT(rightObj)) { + VMBigInteger* left = AS_BIG_INT(leftObj); + int64_t const l = left->embeddedInteger.truncateToInt64(); + // NOLINTNEXTLINE(hicpp-signed-bitwise) + return NEW_INT(l & SMALL_INT_VAL(rightObj)); + } + + ErrorExit("#& not supported on non-small-int arguments"); } static vm_oop_t intEqual(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); - if (IS_TAGGED(rightObj) || CLASS_OF(rightObj) == load_ptr(integerClass)) { - if (left == INT_VAL(rightObj)) { - return load_ptr(trueObject); + if (IS_SMALL_INT(rightObj)) { + int64_t const right = SMALL_INT_VAL(rightObj); + return left == right ? load_ptr(trueObject) : load_ptr(falseObject); } - } else if (CLASS_OF(rightObj) == load_ptr(doubleClass)) { - if ((double)left == ((VMDouble*)rightObj)->GetEmbeddedDouble()) { - return load_ptr(trueObject); + + if (IS_DOUBLE(rightObj)) { + auto const leftDouble = (double)left; + double const right = AS_DOUBLE(rightObj); + return leftDouble == right ? load_ptr(trueObject) + : load_ptr(falseObject); } + + return load_ptr(falseObject); + } + + assert(IS_BIG_INT(leftObj)); + if (!IS_BIG_INT(rightObj)) { + return load_ptr(falseObject); } - return load_ptr(falseObject); + return AS_BIG_INT(leftObj)->IsEqual(AS_BIG_INT(rightObj)); } static vm_oop_t intEqualEqual(vm_oop_t leftObj, vm_oop_t rightObj) { - if (IS_TAGGED(rightObj) || CLASS_OF(rightObj) == load_ptr(integerClass)) { - if (INT_VAL(leftObj) == INT_VAL(rightObj)) { - return load_ptr(trueObject); + if (IS_SMALL_INT(leftObj)) { + if (IS_SMALL_INT(rightObj)) { + return SMALL_INT_VAL(leftObj) == SMALL_INT_VAL(rightObj) + ? load_ptr(trueObject) + : load_ptr(falseObject); } + return load_ptr(falseObject); + } + + assert(IS_BIG_INT(leftObj)); + if (!IS_BIG_INT(rightObj)) { + return load_ptr(falseObject); } - return load_ptr(falseObject); + return AS_BIG_INT(leftObj)->IsEqual(AS_BIG_INT(rightObj)); } -static vm_oop_t intLowerthan(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, <); +inline static bool lowerThan(vm_oop_t leftObj, vm_oop_t rightObj) { + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + if (IS_SMALL_INT(rightObj)) { + return left < SMALL_INT_VAL(rightObj); + } + + if (IS_DOUBLE(rightObj)) { + double const right = AS_DOUBLE(rightObj); + return static_cast(left) < right; + } - if (left < INT_VAL(rightObj)) { - return load_ptr(trueObject); + assert(IS_BIG_INT(rightObj)); + return AS_BIG_INT(rightObj)->embeddedInteger >= InfInt(left); } - return load_ptr(falseObject); + + assert(IS_BIG_INT(leftObj) && "assume big int"); + VMBigInteger* left = AS_BIG_INT(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return left->embeddedInteger < InfInt(SMALL_INT_VAL(rightObj)); + } + + if (IS_DOUBLE(rightObj)) { + ErrorExit("#< on big int not supported with double"); + // return (left->embeddedInteger < AS_DOUBLE(rightObj)) ? + // load_ptr(trueObject) : load_ptr(falseObject); + } + + assert(IS_BIG_INT(rightObj) && "assume big int"); + return left->embeddedInteger < AS_BIG_INT(rightObj)->embeddedInteger; +} + +static vm_oop_t intLowerthan(vm_oop_t leftObj, vm_oop_t rightObj) { + return lowerThan(leftObj, rightObj) ? load_ptr(trueObject) + : load_ptr(falseObject); } static vm_oop_t intLowerThanEqual(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, <=); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return left <= SMALL_INT_VAL(rightObj) ? load_ptr(trueObject) + : load_ptr(falseObject); + } - if (left <= INT_VAL(rightObj)) { - return load_ptr(trueObject); + if (IS_DOUBLE(rightObj)) { + double const right = AS_DOUBLE(rightObj); + return static_cast(left) <= right ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + assert(IS_BIG_INT(rightObj)); + return AS_BIG_INT(rightObj)->embeddedInteger > InfInt(left) + ? load_ptr(trueObject) + : load_ptr(falseObject); } - return load_ptr(falseObject); + + assert(IS_BIG_INT(leftObj) && "assume big int"); + VMBigInteger* left = AS_BIG_INT(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return (left->embeddedInteger <= InfInt(SMALL_INT_VAL(rightObj))) + ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + if (IS_DOUBLE(rightObj)) { + ErrorExit("#< on big int not supported with double"); + // return (left->embeddedInteger <= AS_DOUBLE(rightObj)) ? + // load_ptr(trueObject) : load_ptr(falseObject); + } + + assert(IS_BIG_INT(rightObj) && "assume big int"); + return (left->embeddedInteger <= AS_BIG_INT(rightObj)->embeddedInteger) + ? load_ptr(trueObject) + : load_ptr(falseObject); } static vm_oop_t intGreaterThan(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, >); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); - if (left > INT_VAL(rightObj)) { - return load_ptr(trueObject); + if (IS_SMALL_INT(rightObj)) { + return left > SMALL_INT_VAL(rightObj) ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + if (IS_DOUBLE(rightObj)) { + double const right = AS_DOUBLE(rightObj); + return static_cast(left) > right ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + assert(IS_BIG_INT(rightObj)); + return AS_BIG_INT(rightObj)->embeddedInteger < InfInt(left) + ? load_ptr(trueObject) + : load_ptr(falseObject); } - return load_ptr(falseObject); + + assert(IS_BIG_INT(leftObj) && "assume big int"); + VMBigInteger* left = AS_BIG_INT(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return (left->embeddedInteger > InfInt(SMALL_INT_VAL(rightObj))) + ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + if (IS_DOUBLE(rightObj)) { + ErrorExit("#< on big int not supported with double"); + // return (left->embeddedInteger > AS_DOUBLE(rightObj)) ? + // load_ptr(trueObject) : load_ptr(falseObject); + } + + assert(IS_BIG_INT(rightObj) && "assume big int"); + return (left->embeddedInteger > AS_BIG_INT(rightObj)->embeddedInteger) + ? load_ptr(trueObject) + : load_ptr(falseObject); } static vm_oop_t intGreaterThanEqual(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, >=); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return left >= SMALL_INT_VAL(rightObj) ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + if (IS_DOUBLE(rightObj)) { + double const right = AS_DOUBLE(rightObj); + return static_cast(left) >= right ? load_ptr(trueObject) + : load_ptr(falseObject); + } - if (left >= INT_VAL(rightObj)) { - return load_ptr(trueObject); + assert(IS_BIG_INT(rightObj)); + return AS_BIG_INT(rightObj)->embeddedInteger < InfInt(left) + ? load_ptr(trueObject) + : load_ptr(falseObject); } - return load_ptr(falseObject); + + assert(IS_BIG_INT(leftObj) && "assume big int"); + VMBigInteger* left = AS_BIG_INT(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return (left->embeddedInteger >= InfInt(SMALL_INT_VAL(rightObj))) + ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + if (IS_DOUBLE(rightObj)) { + ErrorExit("#< on big int not supported with double"); + // return (left->embeddedInteger >= AS_DOUBLE(rightObj)) ? + // load_ptr(trueObject) : load_ptr(falseObject); + } + + assert(IS_BIG_INT(rightObj) && "assume big int"); + return (left->embeddedInteger >= AS_BIG_INT(rightObj)->embeddedInteger) + ? load_ptr(trueObject) + : load_ptr(falseObject); } static vm_oop_t intAsString(vm_oop_t self) { - int64_t const integer = INT_VAL(self); - ostringstream Str; - Str << integer; - return Universe::NewString(Str.str()); + if (IS_SMALL_INT(self)) { + int64_t const integer = SMALL_INT_VAL(self); + ostringstream Str; + Str << integer; + return Universe::NewString(Str.str()); + } + + assert(IS_BIG_INT(self) && "assume big int"); + return Universe::NewString(AS_BIG_INT(self)->embeddedInteger.toString()); } static vm_oop_t intAsDouble(vm_oop_t self) { - int64_t const integer = INT_VAL(self); - return Universe::NewDouble((double)integer); + double value = NAN; + if (IS_SMALL_INT(self)) { + value = (double)SMALL_INT_VAL(self); + } else { + assert(IS_BIG_INT(self) && "assume big int"); + value = AS_BIG_INT(self)->embeddedInteger.toDouble(); + } + return Universe::NewDouble(value); } static vm_oop_t intAs32BitSigned(vm_oop_t self) { - int64_t const integer = INT_VAL(self); - return NEW_INT((int64_t)(int32_t)integer); + int32_t value = 0; + if (IS_SMALL_INT(self)) { + value = (int32_t)SMALL_INT_VAL(self); + } else { + assert(IS_BIG_INT(self) && "assume big int"); + value = AS_BIG_INT(self)->embeddedInteger.truncateToInt(); + } + return NEW_INT((int64_t)value); } static vm_oop_t intAs32BitUnsigned(vm_oop_t self) { - int64_t const integer = INT_VAL(self); - return NEW_INT((int64_t)(uint32_t)integer); + uint32_t value = 0; + if (IS_SMALL_INT(self)) { + value = (uint32_t)SMALL_INT_VAL(self); + } else { + assert(IS_BIG_INT(self) && "assume big int"); + value = (uint32_t)AS_BIG_INT(self)->embeddedInteger.truncateToInt(); + } + return NEW_INT((int64_t)value); } static vm_oop_t intSqrt(vm_oop_t self) { - double const result = sqrt((double)INT_VAL(self)); + double const result = sqrt((double)SMALL_INT_VAL(self)); if (result == rint(result)) { return NEW_INT((int64_t)result); @@ -262,67 +565,85 @@ static vm_oop_t intSqrt(vm_oop_t self) { static vm_oop_t intAtRandom(vm_oop_t self) { int64_t const result = - INT_VAL(self) * + SMALL_INT_VAL(self) * rand(); // NOLINT(clang-analyzer-security.insecureAPI.rand) return NEW_INT(result); } static vm_oop_t intAbs(vm_oop_t self) { - int64_t const result = INT_VAL(self); - if (result < 0) { - return NEW_INT(-result); + if (IS_SMALL_INT(self)) { + int64_t const result = SMALL_INT_VAL(self); + if (result < 0) { + return NEW_INT(-result); + } + return self; } - return self; + + assert(IS_BIG_INT(self) && "assume big int"); + VMBigInteger* s = AS_BIG_INT(self); + return s->Negate(); } static vm_oop_t intMin(vm_oop_t self, vm_oop_t arg) { - int64_t const result = INT_VAL(self); - - VMClass* cl = CLASS_OF(arg); - if (cl == load_ptr(doubleClass)) { - if ((double)result < ((VMDouble*)arg)->GetEmbeddedDouble()) { - return self; - } - return arg; - } - - return (result < INT_VAL(arg)) ? self : arg; + return lowerThan(self, arg) ? self : arg; } static vm_oop_t intMax(vm_oop_t self, vm_oop_t arg) { - int64_t const result = INT_VAL(self); - - VMClass* cl = CLASS_OF(arg); - if (cl == load_ptr(doubleClass)) { - if ((double)result > ((VMDouble*)arg)->GetEmbeddedDouble()) { - return self; - } - return arg; - } - - return (result > INT_VAL(arg)) ? self : arg; + return lowerThan(self, arg) ? arg : self; } static vm_oop_t intFromString(vm_oop_t /*unused*/, vm_oop_t right) { auto* self = (VMString*)right; std::string str = self->GetStdString(); - return ParseInteger(str, 10, false); + return ParseInteger(str, false); } static vm_oop_t intUnequal(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - doDoubleOpIfNeeded(left, rightObj, !=); + if (IS_SMALL_INT(leftObj)) { + int64_t const left = SMALL_INT_VAL(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return left != SMALL_INT_VAL(rightObj) ? load_ptr(trueObject) + : load_ptr(falseObject); + } - if (left != INT_VAL(rightObj)) { - return load_ptr(trueObject); + if (IS_DOUBLE(rightObj)) { + double const right = AS_DOUBLE(rightObj); + return static_cast(left) != right ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + assert(IS_BIG_INT(rightObj)); + return AS_BIG_INT(rightObj)->embeddedInteger != InfInt(left) + ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + assert(IS_BIG_INT(leftObj) && "assume big int"); + VMBigInteger* left = AS_BIG_INT(leftObj); + + if (IS_SMALL_INT(rightObj)) { + return (left->embeddedInteger != InfInt(SMALL_INT_VAL(rightObj))) + ? load_ptr(trueObject) + : load_ptr(falseObject); + } + + if (IS_DOUBLE(rightObj)) { + ErrorExit("#< on big int not supported with double"); + // return (left->embeddedInteger >= AS_DOUBLE(rightObj)) ? + // load_ptr(trueObject) : load_ptr(falseObject); } - return load_ptr(falseObject); + + assert(IS_BIG_INT(rightObj) && "assume big int"); + return (left->embeddedInteger != AS_BIG_INT(rightObj)->embeddedInteger) + ? load_ptr(trueObject) + : load_ptr(falseObject); } static vm_oop_t intRange(vm_oop_t leftObj, vm_oop_t rightObj) { - int64_t const left = INT_VAL(leftObj); - int64_t const right = INT_VAL(rightObj); + int64_t const left = SMALL_INT_VAL(leftObj); + int64_t const right = SMALL_INT_VAL(rightObj); int64_t const numInteger = right - left + 1; VMArray* arr = Universe::NewArray(numInteger); diff --git a/src/primitives/Object.cpp b/src/primitives/Object.cpp index 99c0243e..4f4e8adb 100644 --- a/src/primitives/Object.cpp +++ b/src/primitives/Object.cpp @@ -33,6 +33,7 @@ #include "../vm/Universe.h" // NOLINT(misc-include-cleaner) it's required to make the types complete #include "../vmobjects/ObjectFormats.h" #include "../vmobjects/VMArray.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMClass.h" // NOLINT(misc-include-cleaner) it's required to make the types complete #include "../vmobjects/VMFrame.h" #include "../vmobjects/VMInvokable.h" @@ -116,12 +117,12 @@ static void objPerformWithArgumentsInSuperclass(VMFrame* frame) { } static vm_oop_t objInstVarAt(vm_oop_t self, vm_oop_t idx) { - int64_t const field_idx = INT_VAL(idx) - 1; + int64_t const field_idx = SMALL_INT_VAL(idx) - 1; return static_cast(self)->GetField(field_idx); } static vm_oop_t objInstVarAtPut(vm_oop_t self, vm_oop_t idx, vm_oop_t value) { - size_t const field_idx = INT_VAL(idx) - 1; + size_t const field_idx = SMALL_INT_VAL(idx) - 1; static_cast(self)->SetField(field_idx, value); return self; } diff --git a/src/primitives/String.cpp b/src/primitives/String.cpp index e7118d00..73287c2a 100644 --- a/src/primitives/String.cpp +++ b/src/primitives/String.cpp @@ -37,6 +37,7 @@ #include "../vm/Symbols.h" #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMFrame.h" #include "../vmobjects/VMString.h" #include "../vmobjects/VMSymbol.h" // NOLINT(misc-include-cleaner) it's required to make the types complete @@ -107,8 +108,8 @@ static vm_oop_t strPrimSubstringFromTo(vm_oop_t rcvr, auto* self = static_cast(rcvr); std::string const str = self->GetStdString(); - int64_t const s = INT_VAL(start) - 1; - int64_t const e = INT_VAL(end) - 1; + int64_t const s = SMALL_INT_VAL(start) - 1; + int64_t const e = SMALL_INT_VAL(end) - 1; std::string const result = str.substr(s, e - s + 1); return Universe::NewString(result); @@ -116,7 +117,7 @@ static vm_oop_t strPrimSubstringFromTo(vm_oop_t rcvr, static vm_oop_t strCharAt(vm_oop_t rcvr, vm_oop_t indexPtr) { auto* self = static_cast(rcvr); - int64_t const index = INT_VAL(indexPtr) - 1; + int64_t const index = SMALL_INT_VAL(indexPtr) - 1; if (unlikely(index < 0 || (size_t)index >= self->GetStringLength())) { return Universe::NewString("Error - index out of bounds"); diff --git a/src/primitives/System.cpp b/src/primitives/System.cpp index da316ea5..dc9f55df 100644 --- a/src/primitives/System.cpp +++ b/src/primitives/System.cpp @@ -38,6 +38,7 @@ #include "../vm/Print.h" #include "../vm/Universe.h" #include "../vmobjects/ObjectFormats.h" +#include "../vmobjects/VMBigInteger.h" // NOLINT(misc-include-cleaner) #include "../vmobjects/VMClass.h" #include "../vmobjects/VMFrame.h" #include "../vmobjects/VMString.h" @@ -87,7 +88,7 @@ static vm_oop_t sysLoad_(vm_oop_t /*unused*/, vm_oop_t rightObj) { } static vm_oop_t sysExit_(vm_oop_t /*unused*/, vm_oop_t err) { - int64_t const err_no = INT_VAL(err); + int64_t const err_no = SMALL_INT_VAL(err); Quit((int32_t)err_no); } diff --git a/src/unitTests/BasicInterpreterTests.h b/src/unitTests/BasicInterpreterTests.h index e3963315..1ccfb3f3 100644 --- a/src/unitTests/BasicInterpreterTests.h +++ b/src/unitTests/BasicInterpreterTests.h @@ -158,7 +158,7 @@ class BasicInterpreterTests : public CPPUNIT_NS::TestFixture { static void assertEqualsSOMValue(vm_oop_t actualResult, TestData& data) { if (data.type == INTEGER) { auto const expected = (int64_t)(intptr_t)data.expectedResult; - int64_t const actual = INT_VAL(actualResult); + int64_t const actual = SMALL_INT_VAL(actualResult); CPPUNIT_ASSERT_EQUAL(expected, actual); return; } diff --git a/src/unitTests/InfIntTests.cpp b/src/unitTests/InfIntTests.cpp new file mode 100644 index 00000000..bb656efb --- /dev/null +++ b/src/unitTests/InfIntTests.cpp @@ -0,0 +1,45 @@ +#include "InfIntTests.h" + +#include +#include + +#include "../lib/InfInt.h" + +void InfIntTest::testBasicNumbers() { + InfInt const zero(int64_t(0LL)); + CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zero.toLongLong()); + + InfInt const one(int64_t(1LL)); + CPPUNIT_ASSERT_EQUAL(int64_t(1LL), one.toLongLong()); + + InfInt const a500(int64_t(500LL)); + CPPUNIT_ASSERT_EQUAL(int64_t(500LL), a500.toLongLong()); + + InfInt const a32bitNum(int64_t(3221258751LL)); + CPPUNIT_ASSERT_EQUAL(int64_t(3221258751LL), a32bitNum.toLongLong()); + + InfInt const a48bitNum(int64_t(211109453791743LL)); + CPPUNIT_ASSERT_EQUAL(int64_t(211109453791743LL), a48bitNum.toLongLong()); + + InfInt const a63bitNum(int64_t(8070661641701720575LL)); + CPPUNIT_ASSERT_EQUAL(int64_t(8070661641701720575LL), + a63bitNum.toLongLong()); +} + +void InfIntTest::testIsZero() { + InfInt const zero{}; + CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zero.toLongLong()); + CPPUNIT_ASSERT(zero.isZero()); + + InfInt const zeroInt64(int64_t(0LL)); + CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zeroInt64.toLongLong()); + CPPUNIT_ASSERT(zeroInt64.isZero()); + + InfInt const zeroStr("0"); + CPPUNIT_ASSERT_EQUAL(int64_t(0LL), zeroStr.toLongLong()); + CPPUNIT_ASSERT(zeroStr.isZero()); + + InfInt const negZeroStr("-0"); + CPPUNIT_ASSERT_EQUAL(int64_t(0LL), negZeroStr.toLongLong()); + CPPUNIT_ASSERT(negZeroStr.isZero()); +} diff --git a/src/unitTests/InfIntTests.h b/src/unitTests/InfIntTests.h new file mode 100644 index 00000000..b276b4d6 --- /dev/null +++ b/src/unitTests/InfIntTests.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include "../vmobjects/VMArray.h" + +using namespace std; + +class InfIntTest : public CPPUNIT_NS::TestCase { + CPPUNIT_TEST_SUITE(InfIntTest); // NOLINT(misc-const-correctness) + CPPUNIT_TEST(testBasicNumbers); + CPPUNIT_TEST(testIsZero); + CPPUNIT_TEST_SUITE_END(); + +public: + inline void setUp() override {} + inline void tearDown() override {} + +private: + static void testBasicNumbers(); + static void testIsZero(); +}; diff --git a/src/unitTests/main.cpp b/src/unitTests/main.cpp index 6b9a90b8..a4e5be6d 100644 --- a/src/unitTests/main.cpp +++ b/src/unitTests/main.cpp @@ -21,6 +21,7 @@ #include "BasicInterpreterTests.h" #include "BytecodeGenerationTest.h" #include "CloneObjectsTest.h" +#include "InfIntTests.h" #include "TrivialMethodTest.h" #include "WalkObjectsTest.h" @@ -28,6 +29,7 @@ #include "WriteBarrierTest.h" #endif +CPPUNIT_TEST_SUITE_REGISTRATION(InfIntTest); CPPUNIT_TEST_SUITE_REGISTRATION(WalkObjectsTest); CPPUNIT_TEST_SUITE_REGISTRATION(CloneObjectsTest); #if GC_TYPE == GENERATIONAL diff --git a/src/vm/IsValidObject.cpp b/src/vm/IsValidObject.cpp index bcf1885b..61e85def 100644 --- a/src/vm/IsValidObject.cpp +++ b/src/vm/IsValidObject.cpp @@ -9,6 +9,7 @@ #include "../vmobjects/AbstractObject.h" #include "../vmobjects/ObjectFormats.h" #include "../vmobjects/VMArray.h" +#include "../vmobjects/VMBigInteger.h" #include "../vmobjects/VMBlock.h" #include "../vmobjects/VMClass.h" // NOLINT(misc-include-cleaner) it's required to make the types complete #include "../vmobjects/VMDouble.h" @@ -30,6 +31,7 @@ static void* vt_double; static void* vt_eval_primitive; static void* vt_frame; static void* vt_integer; +static void* vt_big_integer; static void* vt_method; static void* vt_object; static void* vt_primitive; @@ -69,10 +71,11 @@ bool IsValidObject(vm_oop_t obj) { void* vt = *(void**)obj; bool b = vt == vt_array || vt == vt_block || vt == vt_class || vt == vt_double || vt == vt_eval_primitive || vt == vt_frame || - vt == vt_integer || vt == vt_method || vt == vt_object || - vt == vt_primitive || vt == vt_safe_un_primitive || - vt == vt_safe_bin_primitive || vt == vt_safe_ter_primitive || - vt == vt_string || vt == vt_symbol || vt == vt_literal_return || + vt == vt_integer || vt == vt_big_integer || vt == vt_method || + vt == vt_object || vt == vt_primitive || + vt == vt_safe_un_primitive || vt == vt_safe_bin_primitive || + vt == vt_safe_ter_primitive || vt == vt_string || + vt == vt_symbol || vt == vt_literal_return || vt == vt_global_return || vt == vt_getter || vt == vt_setter; if (!b) { assert(b && "Expected vtable to be one of the known ones."); @@ -106,6 +109,7 @@ void set_vt_to_null() { vt_eval_primitive = nullptr; vt_frame = nullptr; vt_integer = nullptr; + vt_big_integer = nullptr; vt_method = nullptr; vt_object = nullptr; vt_primitive = nullptr; @@ -129,6 +133,16 @@ bool IsVMInteger(vm_oop_t obj) { return get_vtable(AS_OBJ(obj)) == vt_integer; } +bool IsVMDouble(vm_oop_t obj) { + assert(vt_double != nullptr); + return get_vtable(AS_OBJ(obj)) == vt_double; +} + +bool IsVMBigInteger(vm_oop_t obj) { + assert(vt_big_integer != nullptr); + return get_vtable(AS_OBJ(obj)) == vt_big_integer; +} + bool IsVMMethod(vm_oop_t obj) { assert(vt_method != nullptr); return get_vtable(AS_OBJ(obj)) == vt_method; @@ -184,6 +198,9 @@ void obtain_vtables_of_known_classes(VMSymbol* someValidSymbol) { auto* i = new (GetHeap(), 0) VMInteger(0); vt_integer = get_vtable(i); + auto* bi = new (GetHeap(), 0) VMBigInteger("0", false); + vt_big_integer = get_vtable(bi); + auto* mth = new (GetHeap(), 0) VMMethod(nullptr, 0, 0, 0, 0, nullptr, nullptr); vt_method = get_vtable(mth); diff --git a/src/vm/IsValidObject.h b/src/vm/IsValidObject.h index 84030daf..f3943a24 100644 --- a/src/vm/IsValidObject.h +++ b/src/vm/IsValidObject.h @@ -6,6 +6,8 @@ bool IsValidObject(vm_oop_t obj); bool IsVMInteger(vm_oop_t obj); +bool IsVMBigInteger(vm_oop_t obj); +bool IsVMDouble(vm_oop_t obj); bool IsVMMethod(vm_oop_t obj); bool IsVMSymbol(vm_oop_t obj); bool IsLiteralReturn(vm_oop_t obj); diff --git a/src/vm/Universe.cpp b/src/vm/Universe.cpp index 7df6503a..7df3d55e 100644 --- a/src/vm/Universe.cpp +++ b/src/vm/Universe.cpp @@ -35,17 +35,20 @@ #include #include #include +#include #include #include "../compiler/Disassembler.h" #include "../compiler/LexicalScope.h" #include "../compiler/SourcecodeCompiler.h" #include "../interpreter/bytecodes.h" +#include "../lib/InfInt.h" #include "../memory/Heap.h" #include "../misc/defs.h" #include "../vmobjects/IntegerBox.h" #include "../vmobjects/ObjectFormats.h" #include "../vmobjects/VMArray.h" +#include "../vmobjects/VMBigInteger.h" #include "../vmobjects/VMBlock.h" #include "../vmobjects/VMClass.h" #include "../vmobjects/VMDouble.h" @@ -787,6 +790,19 @@ VMInteger* Universe::NewInteger(int64_t value) { return new (GetHeap(), 0) VMInteger(value); } +VMBigInteger* Universe::NewBigInteger(InfInt&& value) { + return new (GetHeap(), 0) VMBigInteger(std::move(value)); +} + +VMBigInteger* Universe::NewBigIntegerFromStr(const char* value, + bool negateValue) { + return new (GetHeap(), 0) VMBigInteger(value, negateValue); +} + +VMBigInteger* Universe::NewBigIntegerFromInt(int64_t value) { + return new (GetHeap(), 0) VMBigInteger(value); +} + VMClass* Universe::NewMetaclassClass() { auto* result = new (GetHeap(), 0) VMClass; auto* mclass = new (GetHeap(), 0) VMClass; diff --git a/src/vm/Universe.h b/src/vm/Universe.h index 9b289f91..d59fb7f2 100644 --- a/src/vm/Universe.h +++ b/src/vm/Universe.h @@ -30,6 +30,7 @@ #include #include "../interpreter/Interpreter.h" +#include "../lib/InfInt.h" #include "../memory/Heap.h" #include "../misc/Timer.h" #include "../misc/defs.h" @@ -81,6 +82,10 @@ class Universe { static VMObject* NewInstance(VMClass* /*classOfInstance*/); static VMObject* NewInstanceWithoutFields(); static VMInteger* NewInteger(int64_t /*value*/); + static VMBigInteger* NewBigInteger(InfInt&& /*value*/); + static VMBigInteger* NewBigIntegerFromInt(int64_t /*value*/); + static VMBigInteger* NewBigIntegerFromStr(const char* /*value*/, + bool /* negateValue */); static void WalkGlobals(walk_heap_fn /*walk*/); static VMDouble* NewDouble(double /*value*/); static VMClass* NewMetaclassClass(); diff --git a/src/vmobjects/ObjectFormats.h b/src/vmobjects/ObjectFormats.h index 9038985b..0aaa77bc 100644 --- a/src/vmobjects/ObjectFormats.h +++ b/src/vmobjects/ObjectFormats.h @@ -28,6 +28,13 @@ #include "../misc/defs.h" // some MACROS for integer tagging +/** + * The second-highest bit of a 64bit integer is not useable, because we need it + * for tagging at the other end. The highest bit is the sign, which we want/need + * and can use though. + */ +#define VMTAGGED_INT_OUT_OUF_RANGE_BITS 0x4000'0000'0000'0000LL + /** * max value for tagged integers * 01111111 11111111 ... 11111111 1111111X @@ -41,34 +48,59 @@ */ #define VMTAGGEDINTEGER_MIN (-0x4000000000000000LL) +#ifdef __GNUC__ + #define VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(X) \ + ((X) >= VMTAGGEDINTEGER_MIN && (X) <= VMTAGGEDINTEGER_MAX) +#else +__attribute__((always_inline)) inline bool VMTAGGED_INTEGER_WITHIN_RANGE_CHECK( + int64_t X) { + int64_t overflow_check_result; + + __builtin_add_overflow(X, VMTAGGED_INT_OUT_OUF_RANGE_BITS, + &overflow_check_result); + return overflow_check_result >= 0; +} +#endif + #if ADDITIONAL_ALLOCATION - #define TAG_INTEGER(X) \ - (((X) >= VMTAGGEDINTEGER_MIN && (X) <= VMTAGGEDINTEGER_MAX && \ - Universe::NewInteger(0)) \ - ? ((vm_oop_t)(((X) << 1U) | 1U)) \ - : (Universe::NewInteger(X))) + #define TAG_INTEGER(X) \ + ((VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(X) && Universe::NewInteger(0)) \ + ? ((vm_oop_t)(((X) << 1U) | 1U)) \ + : (Universe::NewBigIntegerFromInt(X))) #else - #define TAG_INTEGER(X) \ - (((X) >= VMTAGGEDINTEGER_MIN && (X) <= VMTAGGEDINTEGER_MAX) \ - ? ((vm_oop_t)((((uintptr_t)(X)) << 1U) | 1U)) \ - : (Universe::NewInteger(X))) + #define TAG_INTEGER(X) \ + ((VMTAGGED_INTEGER_WITHIN_RANGE_CHECK(X)) \ + ? ((vm_oop_t)((((uintptr_t)(X)) << 1U) | 1U)) \ + : (Universe::NewBigIntegerFromInt(X))) #endif #if USE_TAGGING #define INT_VAL(X) \ (IS_TAGGED(X) ? ((int64_t)(X) >> 1U) /* NOLINT (hicpp-signed-bitwise) */ \ : (((VMInteger*)(X))->GetEmbeddedInteger())) + #define SMALL_INT_VAL(X) \ + ((int64_t)(X) >> 1U) /* NOLINT (hicpp-signed-bitwise) */ #define NEW_INT(X) (TAG_INTEGER((X))) #define IS_TAGGED(X) ((bool)((uintptr_t)(X) & 1U)) + #define IS_SMALL_INT(X) IS_TAGGED(X) + #define IS_BIG_INT(X) IsVMBigInteger(X) + #define AS_BIG_INT(X) (static_cast(X)) + #define IS_DOUBLE(X) IsVMDouble(X) + #define AS_DOUBLE(X) (static_cast(X)->GetEmbeddedDouble()) #define CLASS_OF(X) \ (IS_TAGGED(X) ? load_ptr(integerClass) \ : ((AbstractVMObject*)(X))->GetClass()) #define AS_OBJ(X) \ (IS_TAGGED(X) ? GlobalBox::IntegerBox() : ((AbstractVMObject*)(X))) #else - #define INT_VAL(X) (static_cast(X)->GetEmbeddedInteger()) + #define SMALL_INT_VAL(X) (static_cast(X)->GetEmbeddedInteger()) #define NEW_INT(X) (Universe::NewInteger(X)) #define IS_TAGGED(X) false + #define IS_SMALL_INT(X) IsVMInteger(X) + #define IS_BIG_INT(X) IsVMBigInteger(X) + #define AS_BIG_INT(X) (static_cast(X)) + #define IS_DOUBLE(X) IsVMDouble(X) + #define AS_DOUBLE(X) (static_cast(X)->GetEmbeddedDouble()) #define CLASS_OF(X) (AS_OBJ(X)->GetClass()) #define AS_OBJ(X) ((AbstractVMObject*)(X)) #endif @@ -81,6 +113,7 @@ class VMClass; class VMDouble; class VMEvaluationPrimitive; class VMFrame; +class VMBigInteger; class VMInteger; class VMInvokable; class VMMethod; @@ -147,6 +180,7 @@ class GCArray : public GCObject { public: typedef VMArray class GCBlock : public GCObject { public: typedef VMBlock Loaded; }; class GCDouble : public GCAbstractObject { public: typedef VMDouble Loaded; }; class GCInteger : public GCAbstractObject { public: typedef VMInteger Loaded; }; +class GCBigInteger : public GCAbstractObject { public: typedef VMBigInteger Loaded; }; class GCInvokable : public GCAbstractObject { public: typedef VMInvokable Loaded; }; class GCMethod : public GCInvokable { public: typedef VMMethod Loaded; }; class GCPrimitive : public GCInvokable { public: typedef VMPrimitive Loaded; }; diff --git a/src/vmobjects/VMBigInteger.cpp b/src/vmobjects/VMBigInteger.cpp new file mode 100644 index 00000000..a5013f02 --- /dev/null +++ b/src/vmobjects/VMBigInteger.cpp @@ -0,0 +1,172 @@ +#include "VMBigInteger.h" + +#include +#include +#include +#include + +#include "../memory/Heap.h" +#include "../misc/defs.h" +#include "../vm/Globals.h" +#include "../vm/Universe.h" +#include "../vmobjects/VMDouble.h" // NOLINT(misc-include-cleaner) +#include "ObjectFormats.h" +#include "VMClass.h" + +VMBigInteger* VMBigInteger::CloneForMovingGC() const { + return new (GetHeap(), 0 ALLOC_MATURE) VMBigInteger(*this); +} + +VMClass* VMBigInteger::GetClass() const { + return load_ptr(integerClass); +} + +std::string VMBigInteger::AsDebugString() const { + return "Integer(" + embeddedInteger.toString() + ")"; +} + +#define INVALID_INT_MARKER 9002002002002002002LL + +void VMBigInteger::MarkObjectAsInvalid() { + embeddedInteger = InfInt(INVALID_INT_MARKER); +} + +bool VMBigInteger::IsMarkedInvalid() const { + return embeddedInteger == InfInt(INVALID_INT_MARKER); +} + +vm_oop_t VMBigInteger::Add(int64_t value) const { + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger + InfInt(value)); +} + +vm_oop_t VMBigInteger::Add(vm_oop_t value) const { + if (IS_SMALL_INT(value)) { + return Add(SMALL_INT_VAL(value)); + } + + if (IS_DOUBLE(value)) { + double const left = embeddedInteger.toDouble(); + double const right = AS_DOUBLE(value); + return Universe::NewDouble(left + right); + } + + assert(IS_BIG_INT(value) && "assume rcvr is a big int now"); + + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger + AS_BIG_INT(value)->embeddedInteger); +} + +vm_oop_t VMBigInteger::SubtractFrom(int64_t value) const { + InfInt const left = InfInt(value); + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) VMBigInteger(left - embeddedInteger); +} + +vm_oop_t VMBigInteger::Subtract(vm_oop_t value) const { + if (IS_SMALL_INT(value)) { + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger - InfInt(SMALL_INT_VAL(value))); + } + + if (IS_DOUBLE(value)) { + double const left = embeddedInteger.toDouble(); + double const right = AS_DOUBLE(value); + return Universe::NewDouble(left - right); + } + + assert(IS_BIG_INT(value) && "assume rcvr is a big int now"); + + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger - AS_BIG_INT(value)->embeddedInteger); +} + +vm_oop_t VMBigInteger::Multiply(int64_t value) const { + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger * InfInt(value)); +} + +vm_oop_t VMBigInteger::Multiply(vm_oop_t value) const { + if (IS_SMALL_INT(value)) { + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger * InfInt(SMALL_INT_VAL(value))); + } + + if (IS_DOUBLE(value)) { + double const left = embeddedInteger.toDouble(); + double const right = AS_DOUBLE(value); + return Universe::NewDouble(left * right); + } + + assert(IS_BIG_INT(value) && "assume rcvr is a big int now"); + + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger * AS_BIG_INT(value)->embeddedInteger); +} + +vm_oop_t VMBigInteger::DivisionFrom(int64_t value) const { + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(InfInt(value) / embeddedInteger); +} + +vm_oop_t VMBigInteger::DivideBy(vm_oop_t value) const { + if (IS_SMALL_INT(value)) { + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger / InfInt(SMALL_INT_VAL(value))); + } + + if (IS_DOUBLE(value)) { + double const left = embeddedInteger.toDouble(); + double const right = AS_DOUBLE(value); + return Universe::NewDouble(left / right); + } + + assert(IS_BIG_INT(value) && "assume rcvr is a big int now"); + + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger / AS_BIG_INT(value)->embeddedInteger); +} + +vm_oop_t VMBigInteger::ModuloFrom(int64_t value) const { + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(InfInt(value) % embeddedInteger); +} + +vm_oop_t VMBigInteger::Modulo(vm_oop_t value) const { + if (IS_SMALL_INT(value)) { + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger % InfInt(SMALL_INT_VAL(value))); + } + + if (IS_DOUBLE(value)) { + double const left = embeddedInteger.toDouble(); + double const right = AS_DOUBLE(value); + return Universe::NewDouble(std::fmod(left, right)); + } + + assert(IS_BIG_INT(value) && "assume rcvr is a big int now"); + + // TODO(smarr): try to fit into SmallInt + return new (GetHeap(), 0) + VMBigInteger(embeddedInteger % AS_BIG_INT(value)->embeddedInteger); +} + +vm_oop_t VMBigInteger::Negate() { + if (embeddedInteger < InfInt()) { + return new (GetHeap(), 0) VMBigInteger(-embeddedInteger); + } + return const_cast(this); +} + +vm_oop_t VMBigInteger::IsEqual(VMBigInteger* o) const { + return embeddedInteger == o->embeddedInteger ? load_ptr(trueObject) + : load_ptr(falseObject); +} diff --git a/src/vmobjects/VMBigInteger.h b/src/vmobjects/VMBigInteger.h new file mode 100644 index 00000000..09ff1a76 --- /dev/null +++ b/src/vmobjects/VMBigInteger.h @@ -0,0 +1,62 @@ +#pragma once + +#include "../lib/InfInt.h" +#include "../misc/defs.h" +#include "AbstractObject.h" + +class VMBigInteger : public AbstractVMObject { +public: + typedef GCBigInteger Stored; + + explicit VMBigInteger(const char* value, bool negate) + : embeddedInteger(negate ? -InfInt(value) : InfInt(value)) {} + explicit VMBigInteger(int64_t value) : embeddedInteger(InfInt(value)) {} + explicit VMBigInteger(const InfInt&& value) : embeddedInteger(value) {} + + ~VMBigInteger() override = default; + + [[nodiscard]] inline const InfInt* GetEmbeddedInteger() const { + return &embeddedInteger; + } + + [[nodiscard]] VMBigInteger* CloneForMovingGC() const override; + [[nodiscard]] VMClass* GetClass() const override; + + [[nodiscard]] inline size_t GetObjectSize() const override { + return sizeof(VMBigInteger); + } + + [[nodiscard]] inline int64_t GetHash() const override { + return embeddedInteger.toLongLong(); + } + + void MarkObjectAsInvalid() override; + [[nodiscard]] bool IsMarkedInvalid() const override; + + [[nodiscard]] std::string AsDebugString() const override; + + /* primitive operations */ + [[nodiscard]] vm_oop_t Add(int64_t /*value*/) const; + vm_oop_t Add(vm_oop_t /*value*/) const; + + [[nodiscard]] vm_oop_t SubtractFrom(int64_t /*value*/) const; + vm_oop_t Subtract(vm_oop_t /*value*/) const; + + [[nodiscard]] vm_oop_t Multiply(int64_t /*value*/) const; + vm_oop_t Multiply(vm_oop_t /*value*/) const; + + [[nodiscard]] vm_oop_t DivisionFrom(int64_t /*value*/) const; + vm_oop_t DivideBy(vm_oop_t /*value*/) const; + + [[nodiscard]] vm_oop_t ModuloFrom(int64_t /*value*/) const; + vm_oop_t Modulo(vm_oop_t /*value*/) const; + + [[nodiscard]] vm_oop_t Negate(); + + vm_oop_t IsEqual(VMBigInteger* /*o*/) const; + + /* fields */ + make_testable(public); + + InfInt embeddedInteger; +};