[Webkit-unassigned] [Bug 120112] Typed Arrays have no public facing API

bugzilla-daemon at webkit.org bugzilla-daemon at webkit.org
Tue Oct 13 12:29:19 PDT 2015


https://bugs.webkit.org/show_bug.cgi?id=120112

--- Comment #28 from Filip Pizlo <fpizlo at apple.com> ---
(In reply to comment #22)
> So, I have now changed the phrasing in my implementation as suggested by
> Geoffrey Garen:
> 
> JS_EXPORT JSTypedArrayType JSObjectGetTypedArrayType(JSContextRef ctx,
> JSObjectRef object);
> JS_EXPORT JSObjectRef JSObjectMakeTypedArray(JSContextRef ctx,
> JSTypedArrayType arrayType, size_t numElements);
> JS_EXPORT void * JSObjectGetTypedArrayDataPtr(JSContextRef ctx, JSObjectRef
> object, size_t* byteLength);
> 
> See:
> https://github.com/phoboslab/JavaScriptCore-iOS/commit/
> ac4a76470f5adfa592846466f2f8a84d868c053f
> 
> While this follows the JSC naming scheme, it also makes the API a bit more
> cumbersome to use: now, every time you want to get the data pointer or type
> of a typed array, you first have to check if the JSValueRef you got in your
> function call is a JSObjectRef (using JSValueIsObject()). Allowing for
> JSValues to be passed into these functions (i.e.
> "JSValueGetTypedArrayDataPtr") would be much more convenient, but also not
> reflect the truth that a Typed Array is a JSObject, not a JSValue.

Yeah.  Personally, I would not object to JSValueGetTypedArrayType in addition to JSObjectGetTypedArrayType, particularly since JSValueGetTypedArrayType has a very obvious spec and could even be implemented in terms of the other APIs.

> 
> 
> To answer a few of the questions here:
> 
> > [JSObjectMakeTypedArray] What happens when arrayType is kJSTypedArrayTypeNone? or kJSTypedArrayTypeArrayBuffer? 
> In case of kJSTypedArrayTypeNone the function returns NULL. In case of
> kJSTypedArrayTypeArrayBuffer the function returns an ArrayBuffer with the
> specified number of elements (i.e. bytes), similar to JS ArrayBuffer
> constructor (new ArrayBuffer(length)).
> 
> 
> > Can you clarify what the caller must do to ensure that the returned pointer continues to point to valid memory? 
> My assumption was that a call to JSValueProtect() on the Typed Array would
> also ensure the pointer remains valid, but I'm not really sure if that's
> actually the case. Maybe Filip Pizlo can chime in: when/how exactly is the
> underlying buffer "pinned"?

The underlying buffer can be moved by the GC.  This will happen even if you called JSValueProtect().

Currently internally in JSC the underlying buffer will be pinned (i.e. it won't move) so long as the typed array object is marked (JSValueProtect() ensures this) and so long as one of two other things happens:

1) The GC sees a pointer into that backing store on the stack.  This is transient, in the sense that if during some GC we pin the backing store, we may still move it in a future GC if in that future GC there are no longer any pointers into that backing store on the stack.
2) We pin the backing store by using the typed array's slowDownAndWasteMemory() API.  This is persistent, in the sense that if we ever do this then the typed array backing store will never move again.

It's not clear to me that we want to expose API that requires us to set either of these two things in stone.  Both of these are implementation details that we might want to change.  We may prohibit having backing store pointers on the stack at all as part of the concurrent GC work.  It's not super obvious that the fact that we have an inefficient method of making the backing immovable (slowDownAndWasteMemory()) is a good idea; we introduced it because it was sort of the most natural thing for the DOM at the time, but I'm not sure that we want an API that essentially mandates this.

That said, you could rely on either (1) or (2) and say that so long as you've JSValueProtect()'d the object and your pointer to the backing store is on the stack, then your backing store pointer is valid.  This is dangerous because this would force us to have a good definition of what it means for a pointer to an array to be on the stack.  Does an interior pointer count?  Does an offset pointer count?  What if the compiler sees that you wrote a loop over the backing store and as an optimization, it drops the pointer to the base of the array and instead has some crazy offset pointer that facilitates some exotic loop transformation - in that case its not at all clear that our GC would recognize the pointer that the compiler ended up using as being a pointer to that backing store.  These are tough questions and I don't want an API to have to answer.

Relying on either (1) or (2) is also weird because in all other JSC APIs, you don't have to JSValueProtect() an object if you're only referencing it from the stack.  So, if we introduced an API that mandating JSValueProtect()'ing a typed array object whenever you got its backing store, then this would be both error-prone and surprising.  Just imagine how you would explain this in the documentation for JSValueProtect().  That documentation currently implies that there is no need to JSValueProtect() if your stuff is on the stack.  I would anticipate that lots of people would not call JSValueProtect() in this case, and would only discover the error by way of heisencrashes.

So, for these reasons, it's worthwhile to have a slightly more verbose API that requires you to "release" the backing store.

> 
> If JSValueProtect() indeed already ensures the pointer remains valid, I
> don't think we need another set of functions to retain/release the data
> pointer. Imho it's obvious that if the JSObject is GC'ed, the pointer will
> become invalid. If you want to hold on to the pointer, hold on to the
> JSObject.

See above.  JSValueProtect() is not currently used for protecting values that are only referenced from the stack, and it's not clear that this is enough to guarantee that the GC won't move the backing store even if the typed array object is protected.

> 
> 
> > Why does getting the data pointer require a byte length?
> I found that if you want to obtain the data pointer of a Typed Array, you
> almost always also need to get the byte length of that data. Note that the
> pointer to the size_t you pass in, is only used for output! Having two
> separate API calls for this seemed wasteful. If you don't need the byte
> length, simply pass NULL instead of a pointer to a size_t:
> void * ptr = JSObjectGetTypedArrayDataPtr(ctx, object, NULL);
> 
> 
> > Why do we use byte length for access but element count for creation?
> JSObjectGetTypedArrayDataPtr() returns a pointer to raw data. If we were to
> return the element count instead of the byte length, you would always need
> to call JSObjectGetTypedArrayType() as well, to figure out how many bytes
> you could access. Using the byte length here greatly simplifies stuff like
> memcpy() to/from own buffers for API users.
> 
> Likewise, we use an element count for JSObjectMakeTypedArray() because 
> 1) you know the type of the Typed Array you want to create anyway
> 2) usually you know the number of elements you want to store when creating a
> Typed Array through the API (this is different from being passed a Typed
> Array from JS Land!)
> 3) using a byte length here would be error prone: what if I try to create a
> Uint32Array with a byte length of 5?
> 
> 
> > How do you create a typed array with a data pointer? Is the input a void*?
> I don't think we need an API for this. Just let JSC handle the data
> allocation and then obtain a pointer to it.
> 
> JSObjectRef array = JSObjectMakeTypedArray(ctx, kJSTypedArrayTypeUint8Array,
> 1024);
> void *dataPtr = JSObjectGetTypedArrayDataPtr(ctx, array, NULL);
> memcpy(dataPtr, myOwnData, 1024);
> 
> Imho it's not worth to complicate the API to avoid a short memcpy() for
> handing over data to JS Land.

-- 
You are receiving this mail because:
You are the assignee for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.webkit.org/pipermail/webkit-unassigned/attachments/20151013/ad4f6765/attachment-0001.html>


More information about the webkit-unassigned mailing list