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

jsrt: JsObject Delete/Get/Has/OwnP../Set Property #3875

Merged
merged 2 commits into from
Nov 6, 2017

Conversation

obastemur
Copy link
Collaborator

AcmeAIR LTO gain (3%)

New JSRT property interface that API user can use without going through convert-to-property-id process.

See #3790 for details.

Also added a note;

TODO: Can we make PropertyString && LiteralStringWithPropertyStringPtr
share the string buffer?

Once this PR is merged, there will be an additional PR on node-chakracore end to benefit this new interface.

///
template <typename AllocatorFunction>
HRESULT NarrowStringToWide(_In_ AllocatorFunction allocator,_In_ LPCSTR sourceString, size_t sourceCount, _Out_ LPWSTR* destStringPtr, _Out_ size_t* destCount, size_t* allocateCount = nullptr)
inline HRESULT NarrowStringToWideNoAlloc(_In_ LPCSTR sourceString, size_t sourceCount, _Out_ LPWSTR destString, _Out_ size_t* destCount)
Copy link
Contributor

Choose a reason for hiding this comment

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

For the no-alloc case, you should pass in the size of destString so we can check for and avoid overflows.

AssertMsg(scriptContext->GetThreadContext()->IsScriptActive(), "Caller is expected to be under ContextAPIWrapper!");
PERFORM_JSRT_TTD_RECORD_ACTION(scriptContext, RecordJsRTGetProperty, propertyRecord, object);

*value = Js::JavascriptOperators::GetPropertyNoCache(object, propertyRecord->GetPropertyId(), scriptContext);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why GetPropertyNoCache?

Copy link
Collaborator Author

@obastemur obastemur Oct 4, 2017

Choose a reason for hiding this comment

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

We know the type and we don't have cache info ptr. So, don't waste time checking the type again and doing cache related stuff.

@@ -1367,102 +1367,199 @@ CHAKRA_API JsPreventExtension(_In_ JsValueRef object)
});
}

static JsErrorCode InternalGetPropertyRecord(Js::ScriptContext * scriptContext, JsValueRef key, _Out_ const Js::PropertyRecord ** propertyRecord)
{
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 deliberately weaker than being able to compute x[y], e.g. var y = 2, var x = [1,2,3]; x[y]? That is we deliberately do not support getting property records for numbers, null, true, false, undefined, or arbitrary objects which get converted to a string?

Copy link
Collaborator Author

@obastemur obastemur Oct 4, 2017

Choose a reason for hiding this comment

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

We have an interface for number JsGetIndexed... (also works similarly) for the time being.. Once / if we ever drop that interface, we may combine them here. I was designing in a way that they are handled in the same place. Then I realized, consumer may have a better chance to know property type hence no point wasting it here.

@obastemur obastemur force-pushed the desert_cerodon branch 4 times, most recently from c8c22f6 to 3a9a41d Compare October 5, 2017 11:57
@obastemur
Copy link
Collaborator Author

@dotnet-bot test Ubuntu shared_ubuntu_linux_debug please

@@ -9,18 +9,30 @@ namespace Js
class LiteralStringWithPropertyStringPtr : public LiteralString
{
private:
// TODO: Can we make PropertyString && LiteralStringWithPropertyStringPtr share the string buffer?
PropertyString * propertyString;
Copy link
Contributor

Choose a reason for hiding this comment

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

@MikeHolman do you know why this isn't a Field? The CI seems to be complaining about it now, and I'm inclined to agree with it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, it should be one.

propertyRecord = propertyString->GetPropertyRecord();
}

propertyString = str->GetPropertyStringLazy();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not also use GetPropertyStringLazy in OP_GetElementI?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

? sorry not sure I understood.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also looks like JavascriptConversion::ToPropertyKey could have some of it's code dealing with LiteralStringWithPropertyStringPtr replaced with a call to GetPropertyStringLazy

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

JavascriptConversion::ToPropertyKey and similar places good idea. Could you please clarify -> Why not also use GetPropertyStringLazy in OP_GetElementI ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I was looking for where you had used GetPropertyStringLazy, and I saw some other places that GetPropertyString was used but you had not changed it to use GetPropertyStringLazy; is there a reason for that, or should we also update the other locations? Specifically for OP_GetElementI I was referring to https://github.com/Microsoft/ChakraCore/blob/master/lib/Runtime/Language/JavascriptOperators.cpp#L3697

if (allocateCount != nullptr) *allocateCount = cbDestString;

*destStringPtr = destString;
HRESULT result = NarrowStringToWideNoAlloc(sourceString, sourceCount, destString, sourceCount, destCount);
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that the destBufferCount parameter here should be sourceCount+1 to allow for the null

@obastemur
Copy link
Collaborator Author

/cc @mrkmarron are you happy with the TTD changes?

/cc @liminzhu comments on new JSRT?


public:
PropertyString * GetPropertyString() const;
PropertyString * GetPropertyStringLazy(); // Get if it's there, otherwise bring it in.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this name is confusing to me. i think we could come up with something better, maybe GetOrAddPropertyString?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'm not strong on naming and what you suggest sounds good.

@@ -9,18 +9,30 @@ namespace Js
class LiteralStringWithPropertyStringPtr : public LiteralString
{
private:
PropertyString * propertyString;
// TODO: Can we make PropertyString && LiteralStringWithPropertyStringPtr share the string buffer?
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 can just overwrite the buffer on this once we populate the PropertyString field (and then PropertyString, LiteralStringWithPropertyStringPtr, and PropertyRecord will all be sharing same buffer)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Indeed, this is a trivial TODO. However, I would like to keep it separate. (That's why TODO) BTW; the issue is still open and it also mentions this improvement.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done.

switch (charCount)
{
case 0:
return (LiteralStringWithPropertyStringPtr*) library->GetEmptyString();
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 have GetEmptyString return LiteralStringWithPropertyStringPtr* so we can avoid this cast? otherwise someone could change the emptyString to some other type of string, and compiler won't complain

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I could put an assert on the returning type (VT check)

Copy link
Contributor

Choose a reason for hiding this comment

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

Sure that would work, but I think it is strictly better to have more type information represented at compile time rather than relying on run time asserts.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Aren't we going to cast this type eventually? (library-GetEmptyString)

@MikeHolman
Copy link
Contributor

MikeHolman commented Oct 5, 2017

Just FYI I have some changes in master that this will conflict with (refactoring, nothing major).

Also, any reason this is going in 1.7? Seems odd to be doing perf work in a release branch.

@liminzhu
Copy link
Collaborator

liminzhu commented Oct 5, 2017

We should have an equivalent for JsHasOwnProperty for completeness. Also, do those APIs work with symbols?

@obastemur
Copy link
Collaborator Author

Also, any reason this is going in 1.7? Seems odd to be doing perf work in a release branch.

and stressful..

@digitalinfinity ?

@mrkmarron
Copy link
Contributor

TTD parts look good to me. I am not sure if they have been run through the TTD top NPM tests yet which will test this API more thoroughly.

@obastemur
Copy link
Collaborator Author

obastemur commented Oct 30, 2017

self note for the PR;

  • JavascriptOperators::GetElementIHelper (etc.) could use PropertyString || LiteralStringWithPropertyStringPtr to identify the type. done

  • JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty done

@obastemur obastemur force-pushed the desert_cerodon branch 2 times, most recently from e651dc7 to fea3125 Compare November 1, 2017 01:27
@obastemur
Copy link
Collaborator Author

Made some additional improvements. Will measure the perf separately but need review on this.

/cc @MikeHolman @MSLaguana

return E_OUTOFMEMORY;
}

WCHAR* destString = (WCHAR*)allocator(cbDestString);
if (destString == nullptr)
if (destString == nullptr || sourceCount >= destBufferCount)
Copy link
Contributor

Choose a reason for hiding this comment

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

If destString == nullptr then we shouldn't be writing to it at all; perhaps put that check first?

Copy link
Contributor

Choose a reason for hiding this comment

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

Also isn't sourceCount >= destBufferCount legal? E.g. if the input narrow string has more 3-byte utf8 codepoints than 1-bye utf8 codeopints, the utf16 representation can be shorter than the input.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I didn't understand your first comment.

Second comment; -> sourceCount represents the length of char16 buffer. Allocator is supposed to allocate equal or more (prior to calculating exact correspondence). So we can convert using the fast path below.

Copy link
Contributor

Choose a reason for hiding this comment

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

This condition is saying if (destString == nullptr || ...) { destString[0] = 0} which will (try to) write to the null pointer. Also the previous condition will write to destString without checking whether it is null. We should either be checking if destString == nullptr up front and exiting with an error without writing to it, or we should be relying on SAL to make sure that we never pass in a null pointer I think.

Regarding the second point; Requiring that sourceCount >= destBufferCount is safe, but it may lead to excess memory usage. Prior to this PR the over-estimating allocations are (I believe) always temporary, and the string is copied to a more accurately sized buffer for long-term storage, or freed once the variable goes out of scope. The new usage that you're adding in recycler-allocates a (potentially) over-estimated amount and hands that off to to be stored in a LiteralString.

Perhaps it is worth being clear about what the contract of this function is? Are you supposed to supply a buffer with destBufferCount == sourceCount, and then the caller is responsible for adjusting to destCount when the call returns? Is the caller supposed to supply a buffer with size at least destCount which is initially unknown, and it is an error if destBufferCount < destCount?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Second part; Yes, the idea is/was, instead of double pass, allocate ASCII amount of memory. OR we will endup having double pass for all the strings.

@@ -142,14 +134,44 @@ namespace utf8
// instead of replacing them with the "replacement" chracter. Pass a flag to our
// decoder to require such behavior
utf8::DecodeUnitsIntoAndNullTerminateNoAdvance(remDestString, remSourceString, (LPCUTF8) sourceString + cbSourceString, DecodeOptions::doAllowInvalidWCHARs);
Copy link
Contributor

Choose a reason for hiding this comment

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

The assert just before here that cchDestString <= sourceCount should be cchDestString <= destBufferCount I believe (maybe strictly less to allow for the null terminator), and if this function is exposed outside of our code then I think it should be an if(...) { return E_OUTOFMEMORY;} and not an assert.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not sure.. This PR didn't make any logical changes within the Utf8Helper stuff. Just split an existing interface for reuse purposes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added if (cchDestString > sourceCount)

@obastemur obastemur force-pushed the desert_cerodon branch 7 times, most recently from 79487c9 to cfaa57b Compare November 3, 2017 20:44
{
return CheckPrototypesForAccessorOrNonWritablePropertyCore<JavascriptString*, true, false>(instance, propertyNameString, setterValue, flags, info, scriptContext);
}
PropertyString * propertyString = nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

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

This variable isn't used and that's causing some compile errors on windows.

@obastemur
Copy link
Collaborator Author

Made some additional changes, looks like FindPropertyRecord was also taking the slow path all the time. Will measure the perf separately.

@@ -431,10 +431,16 @@ namespace JSON

if (value == nullptr)
{
Js::PropertyString* propertyString = Js::PropertyString::TryFromVar(key);
if (propertyString)
Js::JavascriptString * propertyKey = key;
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 going to conflict with another JSON change that went in earlier today, might want to rebase your change and reapply if necessary

AcmeAIR LTO gain (3%)

New JSRT property interface that API user can use without going through
convert-to-property-id process.

See # 3790 for details.

Also added a TODO note;
```
TODO: Can we make PropertyString && LiteralStringWithPropertyStringPtr
share the string buffer?
```

Once this PR is merged, there will be an additional PR on
node-chakracore end to benefit this new interface.
@chakrabot chakrabot merged commit fc6240e into chakra-core:master Nov 6, 2017
chakrabot pushed a commit that referenced this pull request Nov 6, 2017
…perty

Merge pull request #3875 from obastemur:desert_cerodon

AcmeAIR LTO gain (3%)

New JSRT property interface that API user can use without going through convert-to-property-id process.

See #3790 for details.

Also added a note;
```
TODO: Can we make PropertyString && LiteralStringWithPropertyStringPtr
share the string buffer?
```

Once this PR is merged, there will be an additional PR on node-chakracore end to benefit this new interface.
chakrabot pushed a commit that referenced this pull request Nov 23, 2017
Merge pull request #4123 from obastemur:lesscmp

See issue details #4110

- Fix unnecessary string comparison with PropertyRecord/Equal (acme-air hits often)
- Improve BuiltInPropertyRecord string comparison (acme-air hits 3 times per request)

This PR needs #3875 to be merged first.

Results from micro-`test/benchmark` are flat.
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.

7 participants