Platform/Performance:IndexGetterSetter

From MozillaWiki
Jump to: navigation, search

IndexGetter/Setter support is bug 499201.

Since writing that, I have come to think we might want a slightly different API. In particular, the ideal thing from the POV of content is if we have two things the JS engine asks us:

  1. Is this index in range? (possibly skipped for objects that map all integers; presumably needed for resolve to work right)
  2. What is the value at this index?

If the js engine can use a magic answer to #2 to skip the check in #1, so much the better.

We may want to draw a distinction between objects whose index range is immutable and those for which it can change. Not sure if that would be useful to optimize access to the former.

Semantics

I'll call an object's normal properties "real" and these new properties "virtual".

From the perspective of JS code, there should be almost no difference between virtual properties and real ones.

  • n in obj, obj.hasOwnProperty(n),Object.getOwnPropertyDescriptor(obj, n), and even Object.getOwnPropertyNames(obj) should treat virtual properties exactly the same as real ones.
  • obj[n], obj[n] = val, obj[n]++, and so on should behave exactly as you'd expect, given the property descriptor produced by Object.getOwnPropertyDescriptor.
  • So should JSAPI functions generally. For example, JS_GetElement must get virtual properties just as it would get real ones. (C/C++ code might end up needing JSAPI functions that distinguish the two, but the existing functions shouldn't.)

So what is the difference?

Unlike real properties, virtual properties can come and go without anything happening in the JS engine.

Also: a virtual property never has a stored value (that is, a slot).

Operations that create a property, such as assignment to a nonexisting property and Object.defineProperty, will continue to create real properties, at least for now. (It could change someday. If we ever want to use virtual properties for array elements, we would want a[a.length] = 1 to create a new virtual property, not a real one.)

An object's virtual properties should be shadowed by its real properties, so that if you set a real property obj[1] while obj has no virtual elements, and later it gains virtual elements numbered 0-5, obj[1] is the real property and the virtual one is completely undetectable.

We must not allow Object.defineProperty(obj, n, desc), or anything else, to shadow or overwrite an existing non-configurable virtual own property obj[n]. This means either we decree that virtual properties must be configurable (non-JSPROP_PERMANENT); or we check for virtual properties before defining new properties.

If we do decide to allow JSPROP_PERMANENT (non-configurable) virtual properties, then whoever writes the hooks would be responsible for making sure that such a property never disappears or changes its getter/setter/attrs.

Basic virtual property API

The goal here is to provide JSClass and JSExtendedClass hooks to offer virtual properties as a feature of native objects.

Every property-related operation on a native object starts with a property lookup. We can skip the lookup in certain cases; but in general to hack virtual properties into the interpreter, the only absolutely necessary step is to make the lookup (js_LookupPropertyWithFlags) work how we want.

Caveats:

  • Currently, doing a lookupProperty on a native object always returns a real property that is in the object's JSScope. We must fix whatever is depending on this invariant; callers must be prepared for the possibility that the property is virtual, and thus isn't in the JSScope.
  • In particular, we must not cache virtual properties in the property cache, since they can come and go without modifying the object's shape. (This is a good thing! Not changing shape on nodelist[i] access will help DOM tracing in some common cases.)

Then: js_LookupPropertyWithFlags needs to return something when a virtual element is found. The new behavior will work a lot like the "new resolve hook" in that it participates in the lookup process. So we need to add a hook there, or just another variation on the existing resolve hook. This should be enough to get all the desired functionality baseline working.

// If a virtual property obj[id] exists, return
// JS_ResolveVirtualProperty(cx, getter, setter, attrs).
//
// If obj[id] does not exist, just return true.
//
// On error/exception, return false.
//
typedef JSBool (*JSVirtualResolveOp)(JSContext *cx, JSObject *obj, jsid id);

// Just like JSCLASS_NEW_RESOLVE, but even newer.
#define JSCLASS_VIRTUAL_RESOLVE  (1<<whatever)

An object that has this new hook would have to implement the new enumerate hook in order to get for (prop in obj) to work correctly. Resolve hooks and enumerate hooks have always been related like this; if you want a resolve hook you also need an enumerate hook. (Independently, bug 518663 calls for a new hook to support Object.getOwnPropertyNames. When that exists, the "virtual resolve" hook will be related to that in a similar way.)

Array-like objects. Many objects, such as arrays and node lists, will only have virtual elements: there are no virtual properties with non-numeric names. This is an important special case for performance, since it means that we can skip the virtual-property hooks for method lookups that always find properties inherited from the prototype, as in arr.indexOf(val). We can distinguish such classes either with an extra JSCLASS flag or by having separate hooks for virtual elements and other virtual properties.

Accelerating virtual property operations

The new hook proposed above is a variation on the existing JSClass::resolve hook. This won't be very fast: the hook has to build a whole JSScopeProperty each time it's called; Then the interpreter has to examine it in order to figure out how to get or set the property, or whatever else it wants to do.

So we can have extra hooks to accelerate individual operations on virtual properties, optimizing away all the fiddling about with JSScopeProperty structs. We can have any number of these, really. For example:

struct JSExtendedClass {
    ...

    // This hook accelerates `obj[id]`; it stores *vp = JSVAL_HOLE
    // if obj has no own virtual property with the given id.
    JSBool (*getOwnVirtualProperty)(JSContext *cx, JSObject *obj, jsid id, jsval *vp);

    // This hook accelerates `obj[id] = val`; it sets *foundp = JS_FALSE
    // if obj has no own virtual property with the given id.
    JSBool (*setOwnVirtualProperty)(JSContext *cx, JSObject *obj, jsid id, jsval val,
                                    JSBool *foundp);

    // This hook accelerates `id in obj`, storing the result in *foundp.
    // If the result is false, the interpreter will keep searching along
    // the prototype chain.
    JSBool (*hasOwnVirtualProperty)(JSContext *cx, JSObject *obj, jsid id,
                                    JSBool *foundp);

    ...and so on...
};

I hope the API contract implied by these is self-evident. Anybody implementing one of these hooks would have to promise that their hook is a pure, transparent optimization of what would happen if the interpreter just did the slow thing, using the lookup hook and the JSScopeProperty.

Tracing JIT support

With the above change we will already stay on trace for more cases, including the most common idiom:

   for (var i = 0; i < list.length; i++)
       f(list[i]);

Here are some more things we should consider doing:

  • We should also change the tracer to call the accelerated hooks on trace. That would be a bit faster, and it would cover cases like storageObj.field that we probably aren't tracing now.
  • In the above cases, we have to guard that the desired virtual property exists at run time. More subtly, we may need to worry about cases where we have to guard that a particular virtual property does not exist. For example, consider namedNodeList.remove(x). We have to check that there's no virtual property coincidentally named "remove", because that would be an own property and would therefore shadow the remove method on namedNodeList's prototype. The tracer would have to emit code that (a) calls the hasVirtualProperty hook; (b) checks for errors; (c) guards that the property was not found; (d) emits whatever other code is necessary to complete the property access. Of course in many cases only numeric virtual properties exist (IndexGetter/Setter vs NameGetter/Setter). In those cases this concern is a non-issue; we can detect them by a JSClass flag or having separate hooks for Index vs Name.
  • Add extra hooks with names like traceGetProperty, traceSetProperty, traceHasProperty, which the trace recorder can call at trace time, allowing the object itself to emit the necessary guards and code. This could produce very fast code for e.g. DOM node list iteration. Maybe even avoid any virtual method calls. I'm not sure this is really a good idea though. It would require some fairly serious refactoring in jstracer.

Further tasks

  • Make typed arrays use this feature.
  • Make regular arrays use this feature.
  • Make DOM array-like objects use this feature.