Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BigInt: reallocate space, digit_t, increment, decrement #5788

Merged
merged 1 commit into from
Oct 23, 2018

Conversation

duongnhn
Copy link
Contributor

@duongnhn duongnhn commented Oct 17, 2018

In this PR, we implement

  • reallocate space for BigInt if necessary -> make it arbitrarily-precision.
  • make use of configuration with digit_t
  • implement increment/decrement operators
  • native tests for methods in Javascript BigInt class

I defer "assign along with inc/dec" y=x++ and y=++x to a future PR

@@ -61,3 +61,6 @@ namespace Js
{
typedef uint32 LocalFunctionId;
};

// digit_t represents a digit in bigint underline
typedef uintptr_t digit_t;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

define a digit of BigInt

body: function () {
var x = 123n;
assert.isTrue(x == 123n);
x++;
Copy link
Contributor

@sethbrenith sethbrenith Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could also capture the result of these expressions, to verify prefix versus postfix behavior. Something like this:

        assert.isTrue(x == 123n);
        var y = x++;
        assert.isTrue(x == 124n);
        assert.isTrue(y == 123n);
        y = ++x;
        assert.isTrue(x == 125n);
        assert.isTrue(y == 125n);

(Certainly not necessary in each test case, and if this is already adequately covered by test262 feel free to ignore.)

Copy link
Contributor Author

@duongnhn duongnhn Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's is what I did in assign_by_value.js

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't think I explained very well what I was hoping to see. I thought it might be nice to have a test that uses the result of the increment expression itself, like an assignment or passing it to a method. I see a lot of x++; statements on their own, but never a y = x++ or a f(x++).


In reply to: 226483957 [](ancestors = 226483957)

Copy link
Contributor Author

@duongnhn duongnhn Oct 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got you but I don't think this PR can address assignment with pre/post inc/dec. This is in my TODO lists

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expect that if you start AbsoluteIncrement and AbsoluteDecrement with cloning the existing BigInt, then everything else should work out correctly.


In reply to: 226508075 [](ancestors = 226508075)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sethbrenith I think it is more to that (tried, didn't work). Also, when we do y=x++, the existing flow do convert x to number which can not apply directly to bigint.

@@ -0,0 +1,29 @@
//-------------------------------------------------------------------------------------------------------
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did this file come back? Maybe a merge issue? These shouldn't be necessary anymore because you defined VarIsImpl.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will delete. this branch is created a long back :)

@@ -10627,7 +10627,7 @@ using namespace Js;
Var JavascriptOperators::ToNumber(Var aRight, ScriptContext* scriptContext)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the abstract operation ToNumber as defined in the spec? If so it should throw a TypeError if called on a BigInt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@@ -994,6 +994,10 @@ using namespace Js;
: PatchGetMethod ;
value = (!Root && !Method) ? PatchGetValue(functionBody, inlineCache, inlineCacheIndex, object, propertyId, thisObject) :
PatchGet(functionBody, inlineCache, inlineCacheIndex, object, propertyId);
if (VarIs<JavascriptBigInt>(value))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised that this type would need special handling here. If you override the instance method CloneToScriptContext inherited from RecyclableObject, does it maybe just work?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

roll it back and defer to a future PR

bigintNew->m_isNegative = pbi->m_isNegative;
bigintNew->m_digits = bigintNew->m_reservedDigitsInit;
bigintNew->Resize(bigintNew->m_length);
for (digit_t i = 0; i < bigintNew->m_length; i++)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could use memcpy for this step (the compiler might be clever enough to emit something similar based on this loop, I'm not sure)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@@ -72,6 +72,19 @@ var tests = [
assert.isFalse(x > y);
}
},
{
name: "Out of 64 bit range",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have some tests with ridiculously huge numbers so they exercise your dynamic allocation code. You can use strings, the .repeat method, and eval to avoid typing the numbers in directly: just generate a string with a big literal and eval it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

static const uint32 MaxStringLength = 100; // Max String length
static const uint32 MaxDigitLength = 20; // Max Digit length
Field(uint32) m_reservedDigitsInit[MaxDigitLength]; // pre-defined space to store array
static const digit_t InitDigitLength = 20; // Max Digit length
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is rather a lot of space to allocate by default. BigInt is pretty new and not a whole lot of code uses it yet, but I would guess that the majority of BigInts fit within 2 digits.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let it be 2 then 👍

length += length;//double size
if (m_digits == m_reservedDigitsInit)
{
if ((SIZE_MAX / sizeof(digit_t) < length) || (NULL == (digits = (digit_t *)malloc(length * sizeof(digit_t)))))
Copy link
Contributor

@sethbrenith sethbrenith Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should RecyclerNewArrayLeaf rather than malloc/realloc. Then the recycler can manage cleaning up the memory without having to do complicated finalization logic for this class. "Leaf" means the allocation doesn't contain any pointers to other recycler objects, so the recycler doesn't waste time scanning it or risk misinterpreting some plain data as a pointer and keeping stuff alive when it shouldn't.

Also, I'd prefer to have the allocation and assignment in its own statement rather than hidden out halfway through a long conditional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed


JavascriptBigInt * JavascriptBigInt::AbsoluteIncrement(JavascriptBigInt * pbi)
{
JavascriptBigInt* result = pbi;
Copy link
Contributor

@sethbrenith sethbrenith Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make a new one at this point? I expect that in-place changes might end up being useful as a performance optimization, but BigInt is meant to be logically immutable. Consider the following:

x = 123n;
y = x;
++x;
print(y); // still 123

The ++x means "make a new BigInt literal that is one bigger than x and store it in x", but anybody with pointers to the old one needs to still have the old value.

Copy link
Contributor Author

@duongnhn duongnhn Oct 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, I will make a new one before passing into increment/decrement function

if (0 < m_length)
js_memcpy_s(digits, length * sizeof(digit_t), m_digits, m_length * sizeof(digit_t));
}
else if (NULL == (digits = (digit_t *)realloc(m_digits, length * sizeof(digit_t))))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a thought: this business of reallocating and copying is great for all kinds of places where you don't know ahead of time how much storage you need. However, it seems to me like we always have a pretty good idea of the upper bound on the number of digits we need to build a BigInt:

  • Addition, subtraction: Length of the longer input plus 1
  • Multiplication: Sum of the count of digits in each input
  • Parsing: Directly related to the input string length, which I think the scanner already gives us (we'd have to peek at the beginning for "0x" etc to figure out the radix).

Anyhow, not a blocker, just a thought.

Copy link
Contributor Author

@duongnhn duongnhn Oct 18, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is a great thought. Since reallocate operation is costly then it would save us a lot. This Resize function would be called after we examine the need like your suggesstion.


Var JavascriptBigInt::Decrement(Var aRight)
{
AssertMsg(VarIs<JavascriptBigInt>(aRight), "BigInt Increment");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decrement

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, VarTo already asserts this, so feel free to remove if you like.


In reply to: 226474297 [](ancestors = 226474297)

// + [AH*BH] // rHigh
// = [R1 R2 R3 R4] // high = [R1 R2], low = [R3 R4]
//
#ifdef TARGET_64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can avoid the #if stuff by using sizeof. These expressions are compile-time constant so the compiler should do the right thing and emit numbers directly.

digit_t kHalfDigitBits = sizeof(digit_t) * 4;
digit_t kHalfDigitMask = (1 << kHalfDigitBits) - 1;

Copy link
Contributor Author

@duongnhn duongnhn Oct 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somehow this does not work, failed on native tests

Copy link
Contributor Author

@duongnhn duongnhn Oct 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add one cast to the 2nd line and it seems to work fine

digit_t kHalfDigitBits = sizeof(digit_t) * 4;
digit_t kHalfDigitMask = ((digit_t)1 << kHalfDigitBits) - 1;

// return low(a*b) and out high
digit_t JavascriptBigInt::MulDigit(digit_t a, digit_t b, digit_t* resultHigh)
{
// Multiply is performed in half chuck favor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good to have a portable implementation of this, but we might find that perf is improved by using platform-specific implementations. For example, on x64 when compiling with MSVC, we could use _mul128.

Copy link
Contributor Author

@duongnhn duongnhn Oct 19, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will use _umul128 for x64 but cannot find something like_umul64 on win32

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it fail on Linux though so I revert back to first implementation. I think we can improve it later if needed

@@ -8615,6 +8615,10 @@ namespace Js

Var InterpreterStackFrame::OP_Ld_A(Var aValue)
{
if (VarIs<JavascriptBigInt>(aValue))
{
return JavascriptBigInt::CloneToScriptContext(aValue, this->GetScriptContext());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to another comment I made elsewhere, this makes me kind of uncomfortable. Here's an operation that was working consistently for every type (even Proxy!) and now it needs special handling for BigInt? I feel like this is a workaround for a more fundamental issue somewhere else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right. I will defer it to a future PR when I find a better approach

@duongnhn duongnhn force-pushed the user/duongn/bigint_size_t branch from 2a82748 to 7192fb9 Compare October 18, 2018 23:09
@@ -299,7 +299,7 @@ using namespace Js;
Var JavascriptOperators::ToNumberInPlace(Var aRight, ScriptContext* scriptContext, JavascriptNumber* result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too, if this is the spec operation ToNumber, it should throw on BigInt.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove

Field(uint32) m_length; // length
Field(digit_t*) m_digits; // digits
Field(digit_t) m_length; // length
Field(digit_t) m_maxLength; // max length without resize
Field(bool) m_isNegative;

// TODO: BigInt should be arbitrary-precision, need to resize space and add tagged BigInt
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could remove this TODO since you're doing it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will remove

return false;
}

digits = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), digit_t, length);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could use GetScriptContext()->GetRecycler() to avoid passing in the ScriptContext. Ideally the only case we would need to pass the script context is in CloneToScriptContext, which is explicitly requesting a new copy of the object in some other script context (not the script context it was created from).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do


inline BOOL isNegative() { return m_isNegative; }

static JavascriptBigInt * Create(const char16 * content, charcount_t cchUseLength, bool isNegative, ScriptContext * scriptContext);
static JavascriptBigInt * CloneToScriptContext(Var aValue, ScriptContext* scriptContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please change this signature to RecyclableObject * CloneToScriptContext(ScriptContext* requestContext) override so it overrides the method inherited from RecyclableObject? There aren't any tests for that case yet, but we'll need it eventually.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

x++;
assert.isTrue(x == 124n);
assert.isTrue(y == 123n);
y = x;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm glad to see this test working again :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, thanks to your suggestion 👍

static bool IsZero(JavascriptBigInt * pbi);
static JavascriptBigInt * AbsoluteIncrement(JavascriptBigInt * pbi);
static JavascriptBigInt * AbsoluteDecrement(JavascriptBigInt * pbi);
static JavascriptBigInt * Increment(JavascriptBigInt * aValue);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these four methods return void to make it obvious they are an in-place update? Otherwise somebody glancing at the code might have a hard time understanding the subtle but important difference between Increment(JavascriptBigInt*) and Increment(Var).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

Copy link
Contributor

@sethbrenith sethbrenith left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@duongnhn duongnhn force-pushed the user/duongn/bigint_size_t branch 2 times, most recently from 9f8f2b6 to fcb7e88 Compare October 22, 2018 16:14
change to digit_t

add nativetest for add,sub,mul with digit
@duongnhn duongnhn force-pushed the user/duongn/bigint_size_t branch from fcb7e88 to 88ce20f Compare October 22, 2018 16:24
@duongnhn
Copy link
Contributor Author

@dotnet-bot test this please

duongnhn added a commit to duongnhn/ChakraCore that referenced this pull request Oct 22, 2018
… increment, decrement

Merge pull request chakra-core#5788 from duongnhn:user/duongn/bigint_size_t

In this PR, we implement
- reallocate space for BigInt if necessary -> make it arbitrarily-precision.
- make use of configuration with digit_t
- implement increment/decrement operators
- native tests for methods in Javascript BigInt class

I defer "assign along with inc/dec" `y=x++` and `y=++x` to a future PR
@chakrabot chakrabot merged commit 88ce20f into chakra-core:master Oct 23, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants