-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
BigInt: reallocate space, digit_t, increment, decrement #5788
Conversation
@@ -61,3 +61,6 @@ namespace Js | |||
{ | |||
typedef uint32 LocalFunctionId; | |||
}; | |||
|
|||
// digit_t represents a digit in bigint underline | |||
typedef uintptr_t digit_t; |
There was a problem hiding this comment.
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++; |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 @@ | |||
//------------------------------------------------------------------------------------------------------- |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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++) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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))))) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)))) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decrement
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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;
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
2a82748
to
7192fb9
Compare
@@ -299,7 +299,7 @@ using namespace Js; | |||
Var JavascriptOperators::ToNumberInPlace(Var aRight, ScriptContext* scriptContext, JavascriptNumber* result) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
9f8f2b6
to
fcb7e88
Compare
change to digit_t add nativetest for add,sub,mul with digit
fcb7e88
to
88ce20f
Compare
@dotnet-bot test this please |
… 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
In this PR, we implement
I defer "assign along with inc/dec"
y=x++
andy=++x
to a future PR