Closed Bug 1290579 Opened 8 years ago Closed 7 years ago

Float32 RadixSort ignores typed array byte offsets

Categories

(Core :: JavaScript: Standard Library, defect)

defect
Not set
normal

Tracking

()

RESOLVED FIXED
mozilla59
Tracking Status
firefox59 --- fixed

People

(Reporter: anba, Assigned: anba)

References

Details

Attachments

(2 files, 1 obsolete file)

Test case:
---
ab = new ArrayBuffer(257 * Float32Array.BYTES_PER_ELEMENT);
ta = new Float32Array(ab, Float32Array.BYTES_PER_ELEMENT, 256);
ta2 = new Float32Array(ab, 0, 1);

ta2[0] = 1;

ta.sort();

print(ta2[0]);
---

Expected: Prints "1"
Actual: Prints "0"
Blocks: 1291005
Andre: I have a fix that, by exposing TypedArray_byteOffsetGetter in SelfHosting.cpp, gets the byteOffset which can then be used to construct the underlying Int32Array object in RadixSort.

```
let byteOffset = 0;
byteOffset = callFunction(std_TypedArray_byteOffset, array);
view = new Int32Array(buffer, byteOffset, len);
```

As always, I can't comment on the performance issues that might arise from this approach, but all the TypedArray tests pass atleast. Your thoughts?
Yes, that approach looks good to me. :-)
It might be preferable to use UnsafeGetInt32FromReservedSlot(array, JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT).  That should optimize/inline better than calling a getter function that I don't believe we inline right now.  We *would* have to be careful, tho, about being sure |array| was a typed array, and never possibly a *wrapped* typed array from another global.  Something to be cautious about.

Feel free to ask more questions here or (somewhat preferably, if you can be on at a time coinciding with approximate working hours in the US -- but either's fine) in #jsapi on Mozilla's IRC servers if you have them.
(In reply to Jeff Walden [:Waldo] (remove +bmo to email) from comment #3)
> We *would* have to be careful, tho, about being sure |array| was a typed array,
> and never possibly a *wrapped* typed array from another global.

It's possible for |array| to be a wrapped typed array, TypedArraySort doesn't use CallTypedArrayMethodIfWrapped to unwrap itself.
Thank you Jeff and Andre for your inputs. This is my first time with these APIs so please bear with my naivete.

Would something like this work? All TypedArray tests pass and everything seems to work:

```
// Find the offset of the buffer based on whether array is typed or not
 let offset = 0;
 var isTypedArray = IsObject(array) && IsTypedArray(array);
 if (isTypedArray) {
    offset = UnsafeGetInt32FromReservedSlot(array, 2 /*JS_TYPEDARRAYLAYOUT_BYTEOFFSET_SLOT*/);
 } else {
    offset = callFunction(CallTypedArrayMethodIfWrapped, array, "TypedArrayByteOffset");
 }
view = new Int32Array(buffer, offset, len);
```

Note that we do find out if the object is a TypedArray or not in TypedArray.js, so we can pass that information to the RadixSort method, but that would just increase the number of arguments so I am not too sure about that approach.
Also, I think I am unable to hit the 'else' condition with any existing test.
This test asserts when the patch is applied:
---
var ab = new ArrayBuffer(257 * Float32Array.BYTES_PER_ELEMENT);
var ta = new Float32Array(ab, Float32Array.BYTES_PER_ELEMENT, 256);
var ta2 = new Float32Array(ab, 0, 1);

ta2[0] = 1;

// Use the sort() method of a different global.
newGlobal().Float32Array.prototype.sort.call(ta);

print(ta2[0]);
---

Assertion error:
---
Assertion failure: args.length() == 1, at /home/andre/git/mozilla-central/js/src/vm/SelfHosting.cpp:1126
Segmentation fault (core dumped)
---

Fortunately it's quite simple to fix the error. This line:
---
offset = callFunction(CallTypedArrayMethodIfWrapped, array, "TypedArrayByteOffset");
---

needs to be changed to:
---
offset = callFunction(CallTypedArrayMethodIfWrapped, array, array, "TypedArrayByteOffset");
---

The first |array| parameter is needed to select the correct global, and the second |array| parameter is the argument to TypedArrayByteOffset. (http://hg.mozilla.org/mozilla-central/file/644b3de5d7a1/js/src/builtin/TypedArray.js#l57 is another example of this invocation pattern.)
Thank you Andre for the correction. I knew I was missing something, I just didn't fully understand that API. Please review the patch and redirect to the proper authority, if necessary.
Comment on attachment 8789610 [details]
Bug 1290579 - Float32 RadixSort ignores typed array byte offsets

https://reviewboard.mozilla.org/r/77764/#review76970

::: js/src/builtin/Sorting.js:122
(Diff revision 2)
>          }
>  
>          // Verify that the buffer is non-null
>          assert(buffer !== null, "Attached data buffer should be reified when array length is >= 128.");
>  
> -        view = new Int32Array(buffer);
> +        // Find the offset of the buffer based on whether array is typed or not

I think I prefer this version
```js
// |array| is either a typed array or a cross-compartment wrapper for one.
let offset;
if (IsTypedArray(array)) {
    offset = TypedArrayByteOffset(array);
} else {
    offset = callFunction(CallTypedArrayMethodIfWrapped, array, array, "TypedArrayByteOffset");
}
```

We can skip the `IsObject(array)` call because `array` should always be a typed array or wrapped typed array. If that's not the case, it means something is horribly broken. ;-)

Maybe we should document this by adding
```js
assert(IsPossiblyWrappedTypedArray(array), "typed arrays must be passed to RadixSort");
```
as the first statement in RadixSort.

::: js/src/tests/ecma_6/TypedArray/sort_byteoffset.js:17
(Diff revision 2)
> +    Int32Array,
> +    Uint32Array,
> +    Float32Array,
> +    Float64Array ];
> +
> +for (var ctor of constructors) {

Can you duplicate this loop to test the cross-compartment case? (Using `newGlobal().Float32Array.prototype.sort.call(ta);` instead of `ta.sort()`.)
Attachment #8789610 - Flags: review?(jwalden+bmo)
I think this looks good, but I'll let Waldo decide. :-)
Comment on attachment 8789610 [details]
Bug 1290579 - Float32 RadixSort ignores typed array byte offsets

https://reviewboard.mozilla.org/r/77764/#review77036

Looks good, just one mini-fix and I can stamp this.

::: js/src/builtin/Sorting.js:125
(Diff revision 2)
>          assert(buffer !== null, "Attached data buffer should be reified when array length is >= 128.");
>  
> -        view = new Int32Array(buffer);
> +        // Find the offset of the buffer based on whether array is typed or not
> +        let offset = 0;
> +        var isTypedArray = IsObject(array) && IsTypedArray(array);
> +        if (isTypedArray) {

Yeah, no need for IsObject.  At that point, you should just make it a ternary expression:

    let offset = IsTypedArray(array)
                 ? TypedArrayByteOffset(array)
                 : callFunction(CallTypedArrayMethodIfWrapped, array, array,
                                "TypedArrayByteOffset");
Attachment #8789610 - Flags: review?(jwalden+bmo) → review-
Comment on attachment 8789610 [details]
Bug 1290579 - Float32 RadixSort ignores typed array byte offsets

https://reviewboard.mozilla.org/r/77764/#review77168
Attachment #8789610 - Flags: review?(jwalden+bmo) → review+
Attached patch bug1290579.patchSplinter Review
Updated Sumit Tiwari's patch to apply cleanly on inbound. Also added the assertions mentioned in comment #10 and changed the test case to include tests with cross-compartment wrapped typed arrays per comment #10. Carrying r+ from Waldo.
Assignee: nobody → andrebargull
Attachment #8789610 - Attachment is obsolete: true
Status: NEW → ASSIGNED
Attachment #8930443 - Flags: review+
Pushed by ryanvm@gmail.com:
https://hg.mozilla.org/integration/mozilla-inbound/rev/e9fc43e54c99
Float32 RadixSort ignores typed array byte offsets. r=Waldo
Keywords: checkin-needed
Hi :jonco, 

this patch for self-hosted JS code changed some GC timings, resulting in a test failure for the testGCRootsRemoved test in testGCHooks.cpp. Adding AutoLeaveZeal made the test failure go away, but I don't know if that's the correct approach for this test. Can you take a look?


JS_GC_ZEAL=IncrementalMultipleSlices gdb --args dist/bin/jsapi-tests testGCRootsRemoved
---
testGCRootsRemoved
Assertion failure: !isIncrementalGCInProgress(), at /home/andre/hg/mozilla-inbound/js/src/jsgc.cpp:7634

Thread 1 "jsapi-tests" received signal SIGSEGV, Segmentation fault.
0x0000000000ca43ad in js::gc::GCRuntime::startDebugGC (this=0x7ffff5f16748, gckind=GC_SHRINK, budget=...) at /home/andre/hg/mozilla-inbound/js/src/jsgc.cpp:7634
7634        MOZ_ASSERT(!isIncrementalGCInProgress());
---


Stack trace:
---
#0  0x0000000000ca43ad in js::gc::GCRuntime::startDebugGC (this=0x7ffff5f16748, gckind=GC_SHRINK, budget=...) at /home/andre/hg/mozilla-inbound/js/src/jsgc.cpp:7634
#1  0x000000000049cc5c in cls_testGCRootsRemoved::run (this=0x28d07e0 <cls_testGCRootsRemoved_instance>, global=...) at /home/andre/hg/mozilla-inbound/js/src/jsapi-tests/testGCHooks.cpp:81
#2  0x0000000000563f29 in main (argc=2, argv=0x7fffffffd8b8) at /home/andre/hg/mozilla-inbound/js/src/jsapi-tests/tests.cpp:132
---
Flags: needinfo?(andrebargull) → needinfo?(jcoppeard)
(In reply to André Bargull [:anba] from comment #19)
> Adding
> AutoLeaveZeal made the test failure go away, but I don't know if that's the
> correct approach for this test. 

Yes, you can add AutoLeaveZeal at the start of the testGCRootsRemoved test.  The test tries to start a GC, and this fails if zeal has already started a GC.
Flags: needinfo?(jcoppeard)
(In reply to Jon Coppeard (:jonco) from comment #20)
> (In reply to André Bargull [:anba] from comment #19)
> > Adding
> > AutoLeaveZeal made the test failure go away, but I don't know if that's the
> > correct approach for this test. 
> 
> Yes, you can add AutoLeaveZeal at the start of the testGCRootsRemoved test. 
> The test tries to start a GC, and this fails if zeal has already started a
> GC.

Great! I've attached a patch with this change.
Attachment #8931662 - Flags: review?(jcoppeard)
Attachment #8931662 - Flags: review?(jcoppeard) → review+
Pushed by archaeopteryx@coole-files.de:
https://hg.mozilla.org/integration/mozilla-inbound/rev/81ee4253265c
Float32 RadixSort ignores typed array byte offsets. r=Waldo
https://hg.mozilla.org/integration/mozilla-inbound/rev/adff34fe23b4
Part 2: Add AutoZeal to GC test to avoid starting GC too early. r=jonco
Keywords: checkin-needed
https://hg.mozilla.org/mozilla-central/rev/81ee4253265c
https://hg.mozilla.org/mozilla-central/rev/adff34fe23b4
Status: ASSIGNED → RESOLVED
Closed: 7 years ago
Resolution: --- → FIXED
Target Milestone: --- → mozilla59
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: