Invariants

or, "The Zen of SpiderMonkey".

A native object must never become non-native. (One reason for this is that the object may have watchpoints set; the watchpoint machinery assumes that all objects with watched properties are native. There may be other reasons that cover more cases, but nobody can remember one.)

All JSObjects and heap-allocated JSStrings must be 8-byte-aligned. (The jsval encoding depends on this.)

The JSStackFrame::down chain never forms a cycle. (It's a stack. But note that a stack frame is not necessarily newer than the next stack frame down, thanks to generators!)

An object's scope chain (found by chasing JSObject::fslots[JSSLOT_PARENT]) never forms a cycle. (We can probably loop forever if that happens. JS_SetParent can violate this, if the application is really that dumb, but generally every object is newer than its __parent__.)

The tracejit must not trace into a function whose scope chain ends in a different global object. (If it is a script function, global names accessed in that function would refer to a different global object. Even if the function is native, there is serious trouble: js_NewObject with null parent argument calculates the parent from cx->fp->scopeChain, which can be stale if we're on trace.)

The chain of properties starting at any JSShape and chasing JSShape::parent never forms a cycle and does not contain any duplicate JSScopeProperty::slot values other than -1. (A cycle would be very silly and could cause infloops. The same slot being allocated to more than one property would be a problem for obvious reasons.)

All JSShapes in dictionary-mode objects have the IN_DICTIONARY flag set. All JSShapes in property trees have it cleared.

If an object is inextensible, its dslots will never again change. (We don't bother locking when accessing slots of a sealed object. The locking is going away regardless.)

Compartments

Suppose obj = JS_GetScopeChain(cx) is not null. Then cx->compartment == obj->compartment().

When a new object is created, it is automatically created in cx->compartment, but its parent and prototype are often determined by examining the scope chain. The object and its proto and parent must be in the same compartment. So it is utterly crucially important that this invariant always be true if objects are being created. The public API for compartment-hopping, JSAutoEnterCompartment, and the internal API, js::AutoCompartment, both make sure the invariant is maintained.

However, there is another internal API, js::SwitchToCompartment, that lets you break this invariant, and of course in XPConnect we use that from time to time when we know we aren't going to be creating any new objects (other than global objects, which have no parent or prototype) or doing anything that might call back into native code that could create objects. We do this in order to save some CPU cycles (in other words, for no good reason whatsoever).

If !JS_IsRunning(cx) && cx->globalObject == NULL, then cx->compartment == cx->runtime->defaultCompartment.

While executing a script, cx->compartment == script->compartment. But this is true only so long as we are actually in the interpreter or JIT code. A JSNative or other callback may move cx to another compartment, as long as it returns cx to the script's compartment before returning.

A given trace-jit trace stays within a single compartment (indeed, a single global object) end-to-end.

Lifetime invariants

In some places, pointers to JSObjects and JSStrings must refer to live heap objects, but this is not a hard fast rule, especially for strings. Some JSStrings are allocated on the stack for quick operations. Some commonly used strings are allocated statically; see JSString::isStatic().

Most JSContext pointers must point to live contexts, but JSTitle::ownercx may point to one that has been destroyed! So code must check js_ValidContextPointer(ownercx) before dereferencing it.

Shape invariants

The shape guarantees hold whenever the property cache is enabled.

Also, we never change the shape of the global object on trace. (Here "the global object" refers to the object at the end of the scope chain of the Function object we're executing.)

Requests

Many functions require a request. That is, they take a parameter cx of type JSContext *, and require that cx is in a request on the current thread. See JS_THREADSAFE.

"Are we in a request on cx?", where cx is any variable of type JSContext *, is a static yes for most lines of code where such a variable exists. Occasionally it's a static no; other times we don't care.

Almost all JSAPI callbacks provide a request; that is, when we call a callback with a cx argument, we know statically that we must be in a request on cx there.

Locks

"Are we holding the runtime-wide GC lock?" is a static yes or no for almost every line of code.

A general rule about the state of all threads at a given time: either exactly one thread is "in GC" and no threads are in requests; or no thread is doing GC, in which case any number of threads may be in requests; or the GC lock is held.

A thread that holds the GC lock never does anything that blocks.

A thread that is in a request never does anything that blocks.

There are the usual invariants regarding locks: we do not reenter them (it would be nice to check this as there might be an exception or two); we do not wait on a condition variable unless the corresponding lock is held.

There are the usual invariants regarding various fields: they are protected by certain locks or more complex locking schemes. In particular, native objects' fields are protected by property locking (below); and several things are protected by the request model, such that there may be either one writer (in GC) or many readers (in requests).

No JSNative or other object-related callback ever runs at the same time as a finalizer for that object.

Property locks

Each thread may have a lock on at most one property at a time. (Nesting them would risk deadlock. JS_SetWatchPoint violates this rule.) Whether a property is locked, and which one, is static information for almost every line of code. The locking scheme is described under JSObjectOps.dropProperty. (Note that the locking scheme applies to all objects and talks about properties being locked. As implemented for native objects, the locking is not really that fine-grained, but that is a transparent optimization as long as we follow the rules.)

A thread holding a property lock never leaves or suspends the current request.

With a few exceptions (known to brendan and probably jst and mrbkap), we never call a JSAPI callback with a property locked. (That would risk deadlock too.)

Name instructions

The first operand to a JSOP_SETNAME instruction is always produced by a preceding JSOP_BINDNAME instruction. (Taken together, ECMA 262-3 ยง11.13.1 and ECMA 262-3 ยง10.1.4 specify that in an assignment such as x = f(), the name lookup for x occurs before f is called. JSOP_BINDNAME performs this lookup.)

The rules below (not exactly invariants) govern the bytecode emitted for NameExpressions.

Background: The fastest instructions for NameExpressions are fat opcodes that combine a load with additional operations, as in JSOP_INCLOCAL or JSOP_GETLOCALPROP. Failing that, JSOP_{GET,CALL,SET}LOCAL and JSOP_{GET,CALL,SET}ARG are the fastest, followed by JSOP_{GET,CALL}UPVAR, JSOP_{GET,CALL}DSLOT, JSOP_{GET,CALL,SET}GVAR, and lastly JSOP_{,CALL,SET}NAME.

If it cannot be statically proven that a name always refers to a specific variable (meaning either a parameter or a variable introduced by var/let/function/const) in the program, then a NAME op must be emitted. (It would result in a bug where the wrong variable is used. JavaScript is only mostly lexically scoped. Some NameExpressions might refer to a variable or global; or might at runtime turn out to reference another object property, due to with, or a variable that isn't in the source code at all but was injected into a local scope by eval. These cases can be detected statically by looking for with and eval "nearby" in the parse tree.)

If a nested function contains a NameExpression that refers to a variable in an enclosing scope which the function can outlive (i.e. the function can be called after control exits that enclosing scope) then UPVAR instructions cannot be used for that NameExpression. (Wrong results or a potentially exploitable crash. The upvar ops depend on a per-context display of currently active stack frames. Once the enclosing stack frame is removed from the stack, and thus from the display, the upvar lookup will no longer work correctly and can crash or read off the end of a different stack frame.)