Stage 1 Draft / September 17, 2024

JavaScript Structs

Structs, Shared Structs, Unsafe Blocks, and Synchronization Primitives

We extend the JS language with fixed-layout objects both for unshared and shared uses, unsafe blocks to syntactically limit the scope of where racy accesses may be performed, and high-level synchronization primitive APIs. This spec draft is organized by logical feature.

1 Structs

Structs are fixed-layout objects. They are constructed with the integrity level sealed, and have all declared fields initialized before the instance is made available to user code. They may only extend other structs. Their instance methods are non-generic, and throw TypeError exceptions when the this value is not either an instance of the struct declaration within which the method was declared, or a subclass of that struct declaration.

1.1 Syntax

StructDeclaration[Yield, Await, Default] : struct [no LineTerminator here] BindingIdentifier[?Yield, ?Await] StructTail[?Yield, ?Await] [+Default] struct [no LineTerminator here] StructTail[?Yield, ?Await] StructExpression[Yield, Await] : struct [no LineTerminator here] BindingIdentifier[?Yield, ?Await]opt StructTail[?Yield, ?Await] StructTail[Yield, Await] : ClassHeritage[?Yield, ?Await]opt { StructBody[?Yield, ?Await]opt } StructBody[Yield, Await] : ClassElementList[?Yield, ?Await] Note

A struct definition is always strict mode code.

1.1.1 Static Semantics: Early Errors

StructBody : ClassElementList

1.1.2 DefineStructField ( receiver, fieldRecord )

The abstract operation DefineStructField takes arguments receiver (an Object) and fieldRecord (a ClassFieldDefinition Record) and returns unused. It performs the following steps when called:

  1. Let fieldName be fieldRecord.[[Name]].
  2. If fieldName is a Private Name, then
    1. Perform ! PrivateFieldAdd(receiver, fieldName, undefined).
  3. Else,
    1. Assert: fieldName is a property key.
    2. Perform ! DefinePropertyOrThrow(receiver, fieldName, PropertyDescriptor { [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }).
  4. Return unused.

1.1.3 InitializeStructInstanceFieldsAndBrand ( receiver, constructor )

The abstract operation InitializeStructInstanceFieldsAndBrand takes arguments receiver (an Object) and constructor (an ECMAScript function object) and returns unused. It performs the following steps when called:

  1. If constructor.[[ConstructorKind]] is derived, then
    1. Let parent be ! constructor.[[GetPrototypeOf]]().
    2. Perform InitializeStructInstanceFieldsAndBrand(receiver, parent).
  2. If constructor has a [[StructBrand]] internal slot, then
    1. Prepend constructor.[[StructBrand]] to receiver.[[StructBrands]].
    2. NOTE: Shared Struct constructors do not have a [[StructBrand]] internal slot because per-Realm prototypes is currently an open design question and are not included in this draft. Without per-Realm prototypes, Shared Structs cannot have methods, and there are no users of Shared Struct brands.
  3. Let methods be the value of constructor.[[PrivateMethods]].
  4. For each PrivateElement method of methods, do
    1. Perform ! PrivateMethodOrAccessorAdd(receiver, method).
  5. Let fields be the value of constructor.[[Fields]].
  6. For each element fieldRecord of fields, do
    1. Perform DefineStructField(receiver, fieldRecord).
  7. Return unused.

1.1.4 RunFieldInitializer ( receiver, fieldRecord )

The abstract operation RunFieldInitializer takes arguments receiver (an Object) and fieldRecord (a ClassFieldDefinition Record) and returns either a normal completion containing unused or a throw completion. It performs the following steps when called:

  1. Let fieldName be fieldRecord.[[Name]].
  2. Let initializer be fieldRecord.[[Initializer]].
  3. If initializer is not empty, then
    1. Let initValue be ? Call(initializer, receiver).
    2. If fieldName is a Private Name, then
      1. Perform ? PrivateSet(receiver, fieldName, initValue).
    3. Else,
      1. Assert: fieldName is a property key.
      2. Perform ? DefinePropertyOrThrow(receiver, fieldName, PropertyDescriptor { [[Value]]: initValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }).
  4. Return unused.

1.1.5 RunStructInstanceFieldInitializers ( receiver, constructor )

The abstract operation RunStructInstanceFieldInitializers takes arguments receiver (an Object) and constructor (an ECMAScript function object) and returns either a normal completion containing unused or a throw completion. It performs the following steps when called:

  1. If constructor.[[ConstructorKind]] is derived, then
    1. Let parent be ! constructor.[[GetPrototypeOf]]().
    2. Perform ? RunStructInstanceFieldInitializers(receiver, parent).
  2. Let fields be the value of constructor.[[Fields]].
  3. For each element fieldRecord of fields, do
    1. Perform ? RunFieldInitializer(receiver, fieldRecord).
  4. Return unused.

1.1.6 Runtime Semantics: StructDefinitionEvaluation

The syntax-directed operation StructDefinitionEvaluation takes arguments structBinding (a String or undefined) and structName (a property key) and returns either a normal completion containing a function object or an abrupt completion. It is defined piecewise over the following productions:

StructTail : ClassHeritageopt { StructBodyopt }
  1. Let env be the LexicalEnvironment of the running execution context.
  2. Let structEnv be NewDeclarativeEnvironment(env).
  3. If structBinding is not undefined, then
    1. Perform ! structEnv.CreateImmutableBinding(structBinding, true).
  4. Let outerPrivateEnvironment be the running execution context's PrivateEnvironment.
  5. Let classPrivateEnvironment be NewPrivateEnvironment(outerPrivateEnvironment).
  6. If StructBody is present, then
    1. For each String dn of the PrivateBoundIdentifiers of StructBody, do
      1. If classPrivateEnvironment.[[Names]] contains a Private Name pn such that pn.[[Description]] is dn, then
        1. Assert: This is only possible for getter/setter pairs.
      2. Else,
        1. Let name be a new Private Name whose [[Description]] is dn.
        2. Append name to classPrivateEnvironment.[[Names]].
  7. If ClassHeritage is not present, then
    1. Let protoParent be %Object.prototype%.
    2. Let constructorParent be %Function.prototype%.
  8. Else,
    1. Set the running execution context's LexicalEnvironment to structEnv.
    2. NOTE: The running execution context's PrivateEnvironment is outerPrivateEnvironment when evaluating ClassHeritage.
    3. Let superclassRef be Completion(Evaluation of ClassHeritage).
    4. Set the running execution context's LexicalEnvironment to env.
    5. Let superclass be ? GetValue(? superclassRef).
    6. If superclass is null, then
      1. Let protoParent be null.
      2. Let constructorParent be %Function.prototype%.
    7. Else if superclass does not have a [[IsStructConstructor]] internal slot, then
      1. Throw a TypeError exception.
    8. Else,
      1. Let protoParent be ? Get(superclass, "prototype").
      2. If protoParent is not an Object and protoParent is not null, throw a TypeError exception.
      3. Let constructorParent be superclass.
  9. Let proto be OrdinaryObjectCreate(protoParent, « [[StructBrand]] »).
  10. Let structSerial be the value of GlobalStructSerial.
  11. Set proto.[[StructBrand]] to structSerial.
  12. Set GlobalStructSerial to GlobalStructSerial + 1.
  13. NOTE: GlobalStructSerial is a monotonically increasing integer that is globally available. It is shared by all realms. Prior to the evaluation of any ECMAScript code, it is initialized to 0.
  14. NOTE: Structs have one-shot construction, with the user-defined "constructor" method performing post-construction initialization. By the time ECMAScript code has access to a struct instance, it already has all of its declared fields as own properties.
  15. Set the running execution context's LexicalEnvironment to structEnv.
  16. Set the running execution context's PrivateEnvironment to classPrivateEnvironment.
  17. If StructBody is not present, let initializerParseNode be empty.
  18. Else, let initializerParseNode be ConstructorMethod of StructBody.
  19. If initializerParseNode is empty, then
    1. Let initializer be empty.
  20. Else,
    1. Let initializerInfo be ? DefineMethod of initializerParseNode with arguments proto and constructorParent.
    2. Let initializer be initializerInfo.[[Closure]].
    3. Perform SetFunctionName(initializer, structName).
  21. Let constructor be a new Abstract Closure with no parameters that captures initializer and structSerial and performs the following steps when called:
    1. Let args be the List of arguments that was passed to this function by [[Call]] or [[Construct]].
    2. Let F be the active function object.
    3. If NewTarget is not F, throw a TypeError exception.
    4. Let result be ? OrdinaryCreateFromConstructor(NewTarget, "%Object.prototype%", « [[StructBrands]] »).
    5. Set result.[[StructBrands]] to « structSerial ».
    6. Perform InitializeStructInstanceFieldsAndBrand(result, F).
    7. Perform ! result.[[PreventExtensions]]().
    8. Assert: ! TestIntegrityLevel(result, sealed) is true.
    9. Perform ? RunStructInstanceFieldInitializers(result, F).
    10. If initializer is not empty, then
      1. Perform ? Call(initializer, result).
    11. Return result.
  22. Let F be CreateBuiltinFunction(constructor, 0, structName, « [[ConstructorKind]], [[SourceText]], [[StructBrand]], [[StructInitializer]], [[IsStructConstructor]] », the current Realm Record, constructorParent).
  23. Perform MakeConstructor(F, false, proto).
  24. If ClassHeritage is present, set F.[[ConstructorKind]] to derived.
  25. Set F.[[StructInitializer]] to initializer.
  26. Set F.[[StructBrand]] to structSerial.
  27. Set F.[[IsStructConstructor]] to true.
  28. If StructBody is not present, let elements be a new empty List.
  29. Else, let elements be NonConstructorElements of StructBody.
  30. Let instancePrivateMethods be a new empty List.
  31. Let staticPrivateMethods be a new empty List.
  32. Let instanceFields be a new empty List.
  33. Let staticElements be a new empty List.
  34. For each ClassElement e of elements, do
    1. If IsStatic of e is false, then
      1. Let element be Completion(ClassElementEvaluation of e with argument proto).
    2. Else,
      1. Let element be Completion(ClassElementEvaluation of e with argument F).
    3. If element is an abrupt completion, then
      1. Set the running execution context's LexicalEnvironment to env.
      2. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
      3. Return ? element.
    4. Set element to ! element.
    5. If element is a PrivateElement, then
      1. Assert: element.[[Kind]] is either method or accessor.
      2. If IsStatic of e is false, let container be instancePrivateMethods.
      3. Else, let container be staticPrivateMethods.
      4. If container contains a PrivateElement pe such that pe.[[Key]] is element.[[Key]], then
        1. Assert: element.[[Kind]] and pe.[[Kind]] are both accessor.
        2. If element.[[Get]] is undefined, then
          1. Let combined be PrivateElement { [[Key]]: element.[[Key]], [[Kind]]: accessor, [[Get]]: pe.[[Get]], [[Set]]: element.[[Set]] }.
        3. Else,
          1. Let combined be PrivateElement { [[Key]]: element.[[Key]], [[Kind]]: accessor, [[Get]]: element.[[Get]], [[Set]]: pe.[[Set]] }.
        4. Replace pe in container with combined.
      5. Else,
        1. Append element to container.
    6. Else if element is a ClassFieldDefinition Record, then
      1. If IsStatic of e is false, append element to instanceFields.
      2. Else, append element to staticElements.
    7. Else if element is a ClassStaticBlockDefinition Record, then
      1. Append element to staticElements.
  35. Set the running execution context's LexicalEnvironment to env.
  36. If structBinding is not undefined, then
    1. Perform ! structEnv.InitializeBinding(structBinding, F).
  37. Set F.[[PrivateMethods]] to instancePrivateMethods.
  38. Set F.[[Fields]] to instanceFields.
  39. For each PrivateElement method of staticPrivateMethods, do
    1. Perform ! PrivateMethodOrAccessorAdd(F, method).
  40. For each element elementRecord of staticElements, do
    1. If elementRecord is a ClassFieldDefinition Record, then
      1. Let result be Completion(DefineField(F, elementRecord)).
    2. Else,
      1. Assert: elementRecord is a ClassStaticBlockDefinition Record.
      2. Let result be Completion(Call(elementRecord.[[BodyFunction]], F)).
    3. If result is an abrupt completion, then
      1. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
      2. Return ? result.
  41. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
  42. Perform ! SetIntegrityLevel(proto, sealed).
  43. Return F.

1.1.7 Runtime Semantics: BindingStructDeclarationEvaluation

The syntax-directed operation BindingStructDeclarationEvaluation takes no arguments and returns either a normal completion containing a function object or an abrupt completion. It is defined piecewise over the following productions:

StructDeclaration : struct BindingIdentifier StructTail
  1. Let structName be the StringValue of BindingIdentifier.
  2. Let value be ? StructDefinitionEvaluation of StructTail with arguments structName and structName.
  3. Set value.[[SourceText]] to the source text matched by StructDeclaration.
  4. Let env be the running execution context's LexicalEnvironment.
  5. Perform ? InitializeBoundName(structName, value, env).
  6. Return value.
StructDeclaration : struct StructTail
  1. Let value be ? StructDefinitionEvaluation of StructTail with arguments undefined and "default".
  2. Set value.[[SourceText]] to the source text matched by StructDeclaration.
  3. Return value.

1.1.8 Runtime Semantics: Evaluation

StructDeclaration : struct BindingIdentifier StructTail
  1. Perform ? BindingStructDeclarationEvaluation of this StructDeclaration.
  2. Return empty.
StructExpression : struct StructTail
  1. Let value be ? StructDefinitionEvaluation of StructTail with arguments undefined and "".
  2. Set value.[[SourceText]] to the source text matched by StructExpression.
  3. Return value.
StructExpression : struct BindingIdentifier StructTail
  1. Let structName be StringValue of BindingIdentifier.
  2. Let value be ? StructDefinitionEvaluation of StructTail with arguments structName and structName.
  3. Set value.[[SourceText]] to the source text matched by StructExpression.
  4. Return value.

1.2 Struct Method Exotic Objects

A struct method exotic object is an exotic object that wraps another method. A struct method exotic object is callable (it has a [[Call]] internal method). Calling a struct method exotic object checks if the this value is a struct instance constructed by the same struct declaration that defined the method, then in calls its wrapped method.

An object is a struct method exotic object if its [[Call]] internal method uses the following implementation, and its other essential internal methods use the definitions found in 10.1. These methods are installed in StructMethodCreate.

Struct method exotic objects do not have the internal slots of ECMAScript function objects listed in Table 30. Instead they have the internal slots listed in Table 1, in addition to [[Prototype]] and [[Extensible]].

Table 1: Internal Slots of Struct Method Exotic Objects
Internal Slot Type Description
[[BoundTargetMethod]] a callable Object The wrapped method object.

1.2.1 [[Call]] ( thisArgument, argumentsList )

The [[Call]] internal method of a struct method exotic object F takes arguments thisArgument (an ECMAScript language value) and argumentsList (a List of ECMAScript language values) and returns either a normal completion containing an ECMAScript language value or a throw completion. It performs the following steps when called:

  1. Let target be F.[[BoundTargetMethod]].
  2. Let homeObject be target.[[HomeObject]].
  3. Assert: homeObject is not undefined.
  4. Assert: homeObject has a [[StructBrand]] internal slot.
  5. If thisArgument is not an Object, throw a TypeError exception.
  6. If thisArgument does not have a [[StructBrands]] internal slot, throw a TypeError exception.
  7. If thisArgument.[[StructBrands]] does not contain homeObject.[[StructBrand]], throw a TypeError exception.
  8. Return ? Call(target, thisArgument, argumentsList_).

1.2.2 StructMethodCreate ( targetMethod )

The abstract operation StructMethodCreate takes argument targetMethod (a function object) and returns either a normal completion containing a function object or a throw completion. It is used to specify the creation of new struct method exotic objects. It performs the following steps when called:

  1. Let proto be ? targetMethod.[[GetPrototypeOf]]().
  2. Let internalSlotsList be the list-concatenation of « [[Prototype]], [[Extensible]] » and the internal slots listed in Table 1.
  3. Let obj be MakeBasicObject(internalSlotsList).
  4. Set obj.[[Prototype]] to proto.
  5. Set obj.[[Call]] as described in 1.2.1.
  6. Set obj.[[BoundTargetMethod]] to targetMethod.
  7. Return obj.

1.3 Changes to ECMAScript Language: Expressions

1.3.1 Runtime Semantics: Evaluation

SuperCall : super Arguments
  1. Let newTarget be GetNewTarget().
  2. Assert: newTarget is an Object.
  3. Let func be GetSuperConstructor().
  4. Let argList be ? ArgumentListEvaluation of Arguments.
  5. If IsConstructor(func) is false, throw a TypeError exception.
  6. If func has a [[StructInitializer]] internal slot, then
    1. If func.[[StructInitializer]] is not empty, then
      1. Let envRec be GetThisEnvironment().
      2. Let thisValue be envRec.GetThisBinding().
      3. Return ? Call(func.[[StructInitializer]], thisValue).
    2. Else,
      1. Return undefined.
  7. Let result be ? Construct(func, argList, newTarget).
  8. Let thisER be GetThisEnvironment().
  9. Perform ? thisER.BindThisValue(result).
  10. Let F be thisER.[[FunctionObject]].
  11. Assert: F is an ECMAScript function object.
  12. Perform ? InitializeInstanceElements(result, F).
  13. Return result.

1.4 Changes to ECMAScript Language: Functions and Classes

1.4.1 Runtime Semantics: DefineMethod

The syntax-directed operation DefineMethod takes argument object (an Object) and optional argument functionPrototype (an Object) and returns either a normal completion containing a Record with fields [[Key]] (a property key) and [[Closure]] (an ECMAScript function object) or an abrupt completion. It is defined piecewise over the following productions:

MethodDefinition : ClassElementName ( UniqueFormalParameters ) { FunctionBody }
  1. Let propKey be ? Evaluation of ClassElementName.
  2. Let env be the running execution context's LexicalEnvironment.
  3. Let privateEnv be the running execution context's PrivateEnvironment.
  4. If functionPrototype is present, then
    1. Let prototype be functionPrototype.
  5. Else,
    1. Let prototype be %Function.prototype%.
  6. Let sourceText be the source text matched by MethodDefinition.
  7. Let closure be OrdinaryFunctionCreate(prototype, sourceText, UniqueFormalParameters, FunctionBody, non-lexical-this, env, privateEnv).
  8. Perform MakeMethod(closure, object).
  9. If object has a [[StructBrand]] internal slot, then
    1. NOTE: Struct instance methods' home object have a [[StructBrand]] internal slot.
    2. Set closure to ? StructMethodCreate(closure).
  10. Return the Record { [[Key]]: propKey, [[Closure]]: closure }.

1.5 Changes to Modules

ExportDeclaration : export ExportFromClause FromClause ; export NamedExports ; export VariableStatement[~Yield, +Await] export Declaration[~Yield, +Await] export default HoistableDeclaration[~Yield, +Await, +Default] export default ClassDeclaration[~Yield, +Await, +Default] export default StructDeclaration[~Yield, +Await, +Default] export default [lookahead ∉ { function, async [no LineTerminator here] function, class }] AssignmentExpression[+In, ~Yield, +Await] ;

1.5.1 Runtime Semantics: Evaluation

ExportDeclaration : export default StructDeclaration
  1. Let value be ? BindingStructDeclarationEvaluation of StructDeclaration.
  2. Let structName be the sole element of the BoundNames of StructDeclaration.
  3. If structName is "*default*", then
    1. Let env be the running execution context's LexicalEnvironment.
    2. Perform ? InitializeBoundName("*default*", value, env).
  4. Return empty.

2 Shared Structs

2.1 Shared Struct Exotic Objects

Shared Structs are fixed-layout exotic objects that can be shared across agents and be accessed in parallel from multiple agents. They are like structs with more restricted behaviour so as to be possible to be shared across agents. They cannot contain methods or private fields. Their fields can only hold primitives or other shared values. Accessing their fields is unordered by default and is governed by the memory model. Such accesses can be made sequentially consistent by using newly overloaded Atomics methods.

An object is a Shared Struct if its [[GetOwnProperty]], [[DefineOwnProperty]], [[HasProperty]], [[Get]], [[Set]], and [[Delete]] internal methods use the definitions in this section, and its other essential internal methods use the definitions found in 10.1.

2.1.1 Critical Section for Shared Struct Creation

2.1.1.1 EnterSharedStructCreationCriticalSection ( )

The abstract operation EnterSharedStructCreationCriticalSection takes no arguments and returns unused. It performs the following steps when called:

  1. Assert: The surrounding agent is not in the critical section for Shared Struct creation.
  2. Wait until no agent is in the critical section for Shared Struct creation, then enter the critical section for Shared Struct creation (without allowing any other agent to enter).
  3. Return unused.

2.1.1.2 LeaveSharedStructCreationCriticalSection ( )

The abstract operation LeaveSharedStructCreationCriticalSection takes no arguments and returns unused. It performs the following steps when called:

  1. Assert: The surrounding agent is in the critical section for Shared Struct creation.
  2. Leave the critical section for Shared Struct creation.
  3. Return unused.
Note

This critical section is a specification semantic prescription of the memory model to prohibit the nondeterministic read in ReadSharedStructField from manifesting Shared Struct values that are partially initialized.

This critical section does not provide any ordering guarantees.

In implementations, this critical section is not needed. Implementations must not allow out-of-thin-air reads.

2.1.2 ReadSharedStructField ( struct, field, order )

The abstract operation ReadSharedStructField takes arguments struct (a Shared Struct), field (a property key), and order (seq-cst or unordered) and returns an ECMAScript language value. It performs the following steps when called:

  1. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
  2. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
  3. Let storage be SharedStructStorage { [[Struct]]: struct, [[Field]]: field }.
  4. Perform EnterSharedStructCreationCriticalSection().
  5. Let rawLanguageValue be a nondeterministically chosen ECMAScript language value such that CanBeSharedAcrossAgents(rawLanguageValue) is true.
  6. Perform LeaveSharedStructCreationCriticalSection().
  7. NOTE: In implementations, rawLanguageValue is the result of a non-atomic or atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency.
  8. Let readEvent be ReadSharedMemory { [[Order]]: order, [[NoTear]]: true, [[Storage]]: storage } to eventsRecord.[[EventList]].
  9. Append readEvent to eventsRecord.[[EventList]].
  10. NOTE: Shared struct field accesses can never tear.
  11. Append Chosen Value Record { [[Event]]: readEvent, [[ChosenValue]]: rawLanguageValue } to execution.[[ChosenValues]].
  12. Return rawLanguageValue.

2.1.3 WriteSharedStructField ( struct, field, value, order )

The abstract operation WriteSharedStructField takes arguments struct (a Shared Struct), field (a property key), value (an ECMAScript language value), and order (seq-cst, unordered, or init) and returns unused. It performs the following steps when called:

  1. Assert: CanBeSharedAcrossAgents(value) is true.
  2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
  3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
  4. Let storage be SharedStructStorage { [[Struct]]: struct, [[Field]]: field }.
  5. Append WriteSharedMemory { [[Order]]: order, [[NoTear]]: true, [[Storage]]: storage, [[Payload]]: value } to eventsRecord.[[EventList]].
  6. NOTE: Shared struct field accesses can never tear.
  7. Return unused.

2.1.4 [[GetOwnProperty]] ( P )

The [[GetOwnProperty]] internal method of a Shared Struct O takes argument P (a property key) and returns a normal completion containing either a Property Descriptor or undefined. It performs the following steps when called:

  1. If O does not have an own property with key P, return undefined.
  2. Let D be a newly created Property Descriptor with no fields.
  3. Let X be O's own property whose key is P.
  4. Assert: X is a data property.
  5. Set D.[[Value]] to ReadSharedStructField(O, P, unordered).
  6. Set D.[[Writable]] to false.
  7. Set D.[[Enumerable]] to true.
  8. Set D.[[Configurable]] to false.
  9. Return D.

2.1.5 [[DefineOwnProperty]] ( P, Desc )

The [[DefineOwnProperty]] internal method of a Shared Struct O takes arguments P (a property key) and Desc (a Property Descriptor) and returns either a normal completion containing a Boolean or a throw completion. It performs the following steps when called:

  1. Assert: ! TestIntegrityLevel(O, sealed) is true.
  2. Let current be ! O.[[GetOwnProperty]](P).
  3. If current is undefined, return false.
  4. Assert: IsDataDescriptor(current) is true.
  5. Assert: current.[[Enumerable]] is true.
  6. Assert: current.[[Configurable]] is false.
  7. Assert: current.[[Writable]] is true.
  8. If Desc has a [[Enumerable]] field and Desc.[[Enumerable]] is false, return false.
  9. If Desc has a [[Configurable]] field and Desc.[[Configurable]] is true, return false.
  10. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, return false.
  11. If Desc has a [[Value]] field, then
    1. If CanBeSharedAcrossAgents(Desc.[[Value]]) is false, throw a TypeError exception.
    2. Perform WriteSharedStructField(O, P, Desc.[[Value]], unordered).
  12. Return true.

2.1.6 [[HasProperty]] ( P )

The [[HasProperty]] internal method of a Shared Struct O takes argument P (a property key) and returns a normal completion containing a Boolean. It performs the following steps when called:

  1. If O does not have an own property with key P, return false.
  2. NOTE: [[GetOwnPropertyDescriptor]] is not used to avoid an unnecessary ReadSharedMemory event.
  3. Return true.

2.1.7 [[Get]] ( P, Receiver )

The [[Get]] internal method of a shared Struct O takes arguments P (a property key) and Receiver (an ECMAScript language value) and returns a normal completion containing an ECMAScript language value. It performs the following steps when called:

  1. Let ownDesc be ! O.[[GetOwnProperty]](P).
  2. If ownDesc is undefined, return undefined.
  3. Return ownDesc.[[Value]].

2.1.8 [[Set]] ( P, V, Receiver )

The [[Set]] internal method of a Shared Struct O takes arguments P (a property key), V (an ECMAScript language value), and Receiver (an ECMAScript language value) and returns either a normal completion containing a Boolean or a throw completion. It performs the following steps when called:

  1. If O does not have an own property with key P, return false.
  2. NOTE: [[GetOwnPropertyDescriptor]] is not used to avoid an unnecessary ReadSharedMemory event.
  3. Let desc be PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: false }.
  4. Return ? O.[[DefineOwnProperty]](P, desc).

2.1.9 [[Delete]] ( P )

The [[Delete]] internal method of a Shared Struct O takes argument P (a property key) and returns a normal completion containing false. It performs the following steps when called:

  1. Return false.

2.2 Syntax

StructDeclaration[Yield, Await, Default] : shared [no LineTerminator here] struct [no LineTerminator here] BindingIdentifier[?Yield, ?Await] SharedStructTail[?Yield, ?Await] [+Default] shared [no LineTerminator here] struct [no LineTerminator here] SharedStructTail[?Yield, ?Await] StructExpression[Yield, Await] : shared [no LineTerminator here] struct [no LineTerminator here] BindingIdentifier[?Yield, ?Await]opt SharedStructTail[?Yield, ?Await] SharedStructTail[Yield, Await] : ClassHeritage[?Yield, ?Await]opt { SharedStructBody[?Yield, ?Await]opt } SharedStructBody[Yield, Await] : ClassElementList[?Yield, ?Await] Note

A shared struct definition is always strict mode code.

2.2.1 Static Semantics: ContainsInstancePrivateIdentifier

The syntax-directed operation ContainsInstancePrivateIdentifier takes no arguments and returns a Boolean. It is defined piecewise over the following productions:

ClassElementList : ClassElement
  1. Return ContainsInstancePrivateIdentifier of ClassElement.
ClassElementList : ClassElementList ClassElement
  1. If ContainsInstancePrivateIdentifier of ClassElementList, return true.
  2. Return ContainsInstancePrivateIdentifier of ClassElement.
ClassElement : FieldDefinition ;
  1. Return FieldDefinition Contains PrivateIdentifier.
ClassElement : MethodDefinition
  1. Return MethodDefinition Contains PrivateIdentifier.
ClassElement : static FieldDefinition ; static MethodDefinition ClassStaticBlock ;
  1. Return false.

2.2.2 Static Semantics: ContainsInstanceMethod

The syntax-directed operation ContainsInstanceMethod takes no arguments and returns a Boolean. It is defined piecewise over the following productions:

ClassElementList : ClassElement
  1. Return ContainsInstanceMethod of ClassElement.
ClassElementList : ClassElementList ClassElement
  1. If ContainsInstanceMethod of ClassElementList, return true.
  2. Return ContainsInstanceMethod of ClassElement.
ClassElement : MethodDefinition
  1. If ClassElementKind of ClassElement is constructor-method, return false.
  2. Return true.
ClassElement : FieldDefinition ; static FieldDefinition ; static MethodDefinition ClassStaticBlock ;
  1. Return false.

2.2.3 Static Semantics: Early Errors

SharedStructBody : ClassElementList Note

EDITOR'S NOTE: Per-Realm prototypes, which is currently an open design question and not included in this draft, will allow methods.

2.2.4 CanBeSharedAcrossAgents ( val )

The abstract operation CanBeSharedAcrossAgents takes argument val (an ECMAScript language value) and returns a Boolean.

Returns whether ECMAScript language language values can be shared across agents. Primitives can always be shared.

It performs the following steps when called:

  1. If val is undefined, return true.
  2. If val is null, return true.
  3. If val is a String, return true.
  4. If val is a Boolean, return true.
  5. If val is a Number, return true.
  6. If val is a BigInt, return true.
  7. If val is a Symbol, return true.
  8. Assert: val is an Object.
  9. If val is a Shared Struct exotic object, return true.
  10. If val is a Shared Array exotic object, return true.
  11. Return false.

2.2.5 DefineSharedStructField ( receiver, fieldRecord )

The abstract operation DefineSharedStructField takes arguments receiver (a Shared Struct) and fieldRecord (a ClassFieldDefinition Record) and returns unused. It performs the following steps when called:

  1. Assert: The surrounding agent is in the critical section for Shared Struct creation.
  2. Let fieldName be fieldRecord.[[Name]].
  3. Assert: fieldName is a property key.
  4. Create an own data property named fieldName of object receiver whose [[Value]] is undefined, [[Writable]] is true, [[Enumerable]] is true, and [[Configurable]] is false.
  5. Perform WriteSharedStructField(receiver, fieldName, undefined, init).
  6. Return unused.

2.2.6 Runtime Semantics: SharedStructDefinitionEvaluation

The syntax-directed operation SharedStructDefinitionEvaluation takes arguments structBinding (a String or undefined) and structName (a property key) and returns either a normal completion containing a function object or an abrupt completion. It is defined piecewise over the following productions:

SharedStructTail : ClassHeritageopt { SharedStructBodyopt }
  1. Let env be the LexicalEnvironment of the running execution context.
  2. Let structEnv be NewDeclarativeEnvironment(env).
  3. If structBinding is not undefined, then
    1. Perform ! structEnv.CreateImmutableBinding(structBinding, true).
  4. Let outerPrivateEnvironment be the running execution context's PrivateEnvironment.
  5. Let classPrivateEnvironment be NewPrivateEnvironment(outerPrivateEnvironment).
  6. If SharedStructBody is present, then
    1. For each String dn of the PrivateBoundIdentifiers of SharedStructBody, do
      1. If classPrivateEnvironment.[[Names]] contains a Private Name pn such that pn.[[Description]] is dn, then
        1. Assert: This is only possible for getter/setter pairs.
      2. Else,
        1. Let name be a new Private Name whose [[Description]] is dn.
        2. Append name to classPrivateEnvironment.[[Names]].
  7. If ClassHeritage is not present, then
    1. Let constructorParent be %Function.prototype%.
  8. Else,
    1. Set the running execution context's LexicalEnvironment to structEnv.
    2. Let superclassRef be Completion(Evaluation of ClassHeritage).
    3. Set the running execution context's LexicalEnvironment to env.
    4. Let superclass be ? GetValue(? superclassRef).
    5. If superclass is null, then
      1. Let constructorParent be %Function.prototype%.
    6. Else if superclass does not have a [[IsSharedStructConstructor]] internal slot, then
      1. Throw a TypeError exception.
    7. Else,
      1. Let constructorParent be superclass.
  9. Let proto be null.
  10. NOTE: Per-Realm prototypes, which is currently an open design question and not included in this draft, will allow prototypes.
  11. NOTE: Shared Structs have one-shot construction, with the user-defined "constructor" method performing post-construction initialization. By the time ECMAScript code has access to a Shared Struct instance, it already has all of its declared fields as own properties.
  12. Set the running execution context's LexicalEnvironment to structEnv.
  13. Set the running execution context's PrivateEnvironment to classPrivateEnvironment.
  14. If SharedStructBody is not present, let initializerParseNode be empty.
  15. Else, let initializerParseNode be ConstructorMethod of SharedStructBody.
  16. If initializerParseNode is empty, then
    1. Let initializer be empty.
  17. Else,
    1. Let initializerInfo be ? DefineMethod of initializerParseNode with arguments proto and constructorParent.
    2. Let initializer be initializerInfo.[[Closure]].
    3. Perform SetFunctionName(initializer, structName).
  18. Let constructor be a new Abstract Closure with no parameters that captures initializer and performs the following steps when called:
    1. Let args be the List of arguments that was passed to this function by [[Call]] or [[Construct]].
    2. Let F be the active function object.
    3. If NewTarget is not F, throw a TypeError exception.
    4. Perform EnterSharedStructCreationCriticalSection().
    5. Let result be OrdinaryObjectCreate(null, « »).
    6. Set result.[[GetOwnProperty]] as specified in 2.1.4.
    7. Set result.[[DefineOwnProperty]] as specified in 2.1.5.
    8. Set result.[[HasProperty]] as specified in 2.1.6.
    9. Set result.[[Get]] as specified in 2.1.7.
    10. Set result.[[Set]] as specified in 2.1.8.
    11. Set result.[[Delete]] as specified in 2.1.9.
    12. Perform InitializeStructInstanceFieldsAndBrand(result, F).
    13. Perform ! result.[[PreventExtensions]]().
    14. Perform LeaveSharedStructCreationCriticalSection().
    15. Assert: ! TestIntegrityLevel(result, sealed) is true.
    16. Perform ? RunStructInstanceFieldInitializers(result, F).
    17. If initializer is not empty, then
      1. Perform ? Call(initializer, result).
    18. Return result.
  19. Let F be CreateBuiltinFunction(constructor, 0, structName, « [[ConstructorKind]], [[SourceText]], [[StructInitializer]], [[IsSharedStructConstructor]] », the current Realm Record, constructorParent).
  20. Perform MakeConstructor(F, false, proto).
  21. If ClassHeritage is present, set F.[[ConstructorKind]] to derived.
  22. Set F.[[StructInitializer]] to initializer.
  23. Set F.[[IsSharedStructConstructor]] to true.
  24. If StructBody is not present, let elements be a new empty List.
  25. Else, let elements be NonConstructorElements of SharedStructBody.
  26. Let staticPrivateMethods be a new empty List.
  27. Let instanceFields be a new empty List.
  28. Let staticElements be a new empty List.
  29. For each ClassElement e of elements, do
    1. If IsStatic of e is false, then
      1. Let element be Completion(ClassElementEvaluation of e with argument proto).
    2. Else,
      1. Let element be Completion(ClassElementEvaluation of e with argument F).
    3. If element is an abrupt completion, then
      1. Set the running execution context's LexicalEnvironment to env.
      2. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
      3. Return ? element.
    4. Set element to ! element.
    5. If element is a PrivateElement, then
      1. Assert: element.[[Kind]] is either method or accessor.
      2. Assert: IsStatic of e is true.
      3. Let container be staticPrivateMethods.
      4. If container contains a PrivateElement pe such that pe.[[Key]] is element.[[Key]], then
        1. Assert: element.[[Kind]] and pe.[[Kind]] are both accessor.
        2. If element.[[Get]] is undefined, then
          1. Let combined be PrivateElement { [[Key]]: element.[[Key]], [[Kind]]: accessor, [[Get]]: pe.[[Get]], [[Set]]: element.[[Set]] }.
        3. Else,
          1. Let combined be PrivateElement { [[Key]]: element.[[Key]], [[Kind]]: accessor, [[Get]]: element.[[Get]], [[Set]]: pe.[[Set]] }.
        4. Replace pe in container with combined.
      5. Else,
        1. Append element to container.
    6. Else if element is a ClassFieldDefinition Record, then
      1. If IsStatic of e is false, append element to instanceFields.
      2. Else, append element to staticElements.
    7. Else if element is a ClassStaticBlockDefinition Record, then
      1. Append element to staticElements.
  30. Set the running execution context's LexicalEnvironment to env.
  31. If structBinding is not undefined, then
    1. Perform ! structEnv.InitializeBinding(structBinding, F).
  32. Set F.[[Fields]] to instanceFields.
  33. For each PrivateElement method of staticPrivateMethods, do
    1. Perform ! PrivateMethodOrAccessorAdd(F, method).
  34. For each element elementRecord of staticElements, do
    1. If elementRecord is a ClassFieldDefinition Record, then
      1. Let result be Completion(DefineField(F, elementRecord)).
    2. Else,
      1. Assert: elementRecord is a ClassStaticBlockDefinition Record.
      2. Let result be Completion(Call(elementRecord.[[BodyFunction]], F)).
    3. If result is an abrupt completion, then
      1. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
      2. Return ? result.
  35. Set the running execution context's PrivateEnvironment to outerPrivateEnvironment.
  36. Perform ! SetIntegrityLevel(F, sealed).
  37. Return F.

2.2.7 Runtime Semantics: BindingStructDeclarationEvaluation

StructDeclaration : shared struct BindingIdentifier SharedStructTail
  1. Let structName be the StringValue of BindingIdentifier.
  2. Let value be ? SharedStructDefinitionEvaluation of StructTail with arguments structName and structName.
  3. Set value.[[SourceText]] to the source text matched by StructDeclaration.
  4. Let env be the running execution context's LexicalEnvironment.
  5. Perform ? InitializeBoundName(structName, value, env).
  6. Return value.
StructDeclaration : shared struct SharedStructTail
  1. Let value be ? SharedStructDefinitionEvaluation of SharedStructTail with arguments undefined and "default".
  2. Set value.[[SourceText]] to the source text matched by StructDeclaration.
  3. Return value.

2.2.8 Runtime Semantics: Evaluation

StructDeclaration : shared struct BindingIdentifier SharedStructTail
  1. Perform ? BindingStructDeclarationEvaluation of this StructDeclaration.
  2. Return empty.
StructExpression : shared struct SharedStructTail
  1. Let value be ? SharedStructDefinitionEvaluation of SharedStructTail with arguments undefined and "".
  2. Set value.[[SourceText]] to the source text matched by StructExpression.
  3. Return value.
StructExpression : shared struct BindingIdentifier SharedStructTail
  1. Let structName be StringValue of BindingIdentifier.
  2. Let value be ? SharedStructDefinitionEvaluation of SharedStructTail with arguments structName and structName.
  3. Set value.[[SourceText]] to the source text matched by StructExpression.
  4. Return value.

2.3 Changes to the Atomics Object

2.3.1 AtomicCompareExchangeInSharedStruct ( struct, field, expectedValue, replacementValue )

The abstract operation AtomicCompareExchangeInSharedStruct takes arguments struct (a Shared Struct), field (a property key), expectedValue (an ECMAScript language value), and replacementValue (an ECMAScript language value) and returns an ECMAScript language value. It performs the following steps when called:

  1. Assert: CanBeSharedAcrossAgents(replacementValue) is true.
  2. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
  3. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
  4. Perform EnterSharedStructCreationCriticalSection().
  5. Let rawLanguageValue be a nondeterministically chosen ECMAScript language value such that CanBeSharedAcrossAgents(rawLanguageValue) is true.
  6. Perform LeaveSharedStructCreationCriticalSection().
  7. NOTE: In implementations, rawLanguageValue is the result of a non-atomic or atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency.
  8. NOTE: The comparison of the expected value and the read value is performed outside of the read-modify-write modification function to avoid needlessly strong synchronization when the expected value is not equal to the read value.
  9. Let storage be SharedStructStorage { [[Struct]]: struct, [[Field]]: field }.
  10. If SameValue(expectedValue, replacementValue) is true, then
    1. Let second be a new read-modify-write modification function with parameters (oldValue, newValue) that captures nothing and performs the following steps atomically when called:
      1. Return newValue.
    2. Let event be ReadModifyWriteSharedMemory { [[Order]]: seq-cst, [[NoTear]]: true, [[Storage]]: storage, [[Payload]]: replacementValue, [[ModifyOp]]: second }.
  11. Else,
    1. Let event be ReadSharedMemory { [[Order]]: seq-cst, [[NoTear]]: true, [[Storage]]: storage }.
  12. Append event to eventsRecord.[[EventList]].
  13. Append Chosen Value Record { [[Event]]: event, [[ChosenValue]]: rawLanguageValue } to execution.[[ChosenValues]].
  14. Return rawLanguageValue.

2.3.2 AtomicReadModifyWriteInSharedStruct ( struct, field, value, op )

The abstract operation AtomicReadModifyWriteInSharedStruct takes arguments struct (a Shared Struct), field (an ECMAScript language value), value (an ECMAScript language value), and op (a read-modify-write modification function) and returns either a normal completion containing an ECMAScript language value, or a throw completion. It performs the following steps when called:

  1. If field is not a property key, throw a TypeError exception.
  2. If CanBeSharedAcrossAgents(value) is false, throw a TypeError exception.
  3. If struct does not have an own property with key field, throw a RangeError exception.
  4. Let execution be the [[CandidateExecution]] field of the surrounding agent's Agent Record.
  5. Let eventsRecord be the Agent Events Record of execution.[[EventsRecords]] whose [[AgentSignifier]] is AgentSignifier().
  6. Perform EnterSharedStructCreationCriticalSection().
  7. Let rawLanguageValue be a nondeterministically chosen ECMAScript language value such that CanBeSharedAcrossAgents(rawLanguageValue) is true.
  8. Perform LeaveSharedStructCreationCriticalSection().
  9. NOTE: In implementations, rawLanguageValue is the result of a non-atomic or atomic read instruction on the underlying hardware. The nondeterminism is a semantic prescription of the memory model to describe observable behaviour of hardware with weak consistency.
  10. Let storage be SharedStructStorage { [[Struct]]: struct, [[Field]]: field }.
  11. Let rmwEvent be ReadModifyWriteSharedMemory { [[Order]]: seq-cst, [[NoTear]]: true, [[Storage]]: storage, [[Payload]]: rawLanguageValue, [[ModifyOp]]: op }.
  12. Append rmwEvent to eventsRecord.[[EventList]].
  13. Append Chosen Value Record { [[Event]]: rmwEvent, [[ChosenValue]]: rawLanguageValue } to execution.[[ChosenValues]].
  14. Return rawLanguageValue.

2.3.3 Atomics.compareExchange ( typedArraytypedArrayOrStruct, indexindexOrField, expectedValue, replacementValue )

This function performs the following steps when called:

  1. If typedArrayOrStruct is a Shared Struct, then
    1. If indexOrField is not a property key, throw a TypeError exception.
    2. If CanBeSharedAcrossAgents(replacementValue) is false, throw a TypeError exception.
    3. If typedArrayOrStruct does not have an own property with key indexOrField, throw a RangeError exception.
    4. Return AtomicCompareExchangeInSharedStruct(typedArrayOrStruct, indexOrField, expectedValue, replacementValue).
  2. Let typedArray be typedArrayOrStruct.
  3. Let index be indexOrField.
  4. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
  5. Let buffer be typedArray.[[ViewedArrayBuffer]].
  6. Let block be buffer.[[ArrayBufferData]].
  7. If typedArray.[[ContentType]] is bigint, then
    1. Let expected be ? ToBigInt(expectedValue).
    2. Let replacement be ? ToBigInt(replacementValue).
  8. Else,
    1. Let expected be 𝔽(? ToIntegerOrInfinity(expectedValue)).
    2. Let replacement be 𝔽(? ToIntegerOrInfinity(replacementValue)).
  9. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
  10. Let elementType be TypedArrayElementType(typedArray).
  11. Let elementSize be TypedArrayElementSize(typedArray).
  12. Let isLittleEndian be the value of the [[LittleEndian]] field of the surrounding agent's Agent Record.
  13. Let expectedBytes be NumericToRawBytes(elementType, expected, isLittleEndian).
  14. Let replacementBytes be NumericToRawBytes(elementType, replacement, isLittleEndian).
  15. If IsSharedArrayBuffer(buffer) is true, then
    1. Let rawBytesRead be AtomicCompareExchangeInSharedBlock(block, byteIndexInBuffer, elementSize, expectedBytes, replacementBytes).
  16. Else,
    1. Let rawBytesRead be a List of length elementSize whose elements are the sequence of elementSize bytes starting with block[byteIndexInBuffer].
    2. If ByteListEqual(rawBytesRead, expectedBytes) is true, then
      1. Store the individual bytes of replacementBytes into block, starting at block[byteIndexInBuffer].
  17. Return RawBytesToNumeric(elementType, rawBytesRead, isLittleEndian).

2.3.4 Atomics.exchange ( typedArraytypedArrayOrStruct, indexindexOrField, value )

This function performs the following steps when called:

  1. Let second be a new read-modify-write modification function with parameters (oldBytesoldValue, newBytesnewValue) that captures nothing and performs the following steps atomically when called:
    1. Return newBytesnewValue.
  2. If _typedArrayOrStruct is a Shared Struct, then
    1. Return ? AtomicReadModifyWriteInSharedStruct(typedArrayOrStruct, indexOrField, value, second).
  3. Let typedArray be typedArrayOrStruct.
  4. Let index be indexOrField.
  5. Return ? AtomicReadModifyWrite(typedArray, index, value, second).

2.3.5 Atomics.load ( typedArraytypedArrayOrStruct, indexindexOrField )

This function performs the following steps when called:

  1. If typedArrayOrStruct is a Shared Struct, then
    1. If indexOrField is not a property key, throw a TypeError exception.
    2. If typedArrayOrStruct does not have an own property with key indexOrField, throw a RangeError exception.
    3. Return ReadSharedStructField(typedArrayOrStruct, indexOrField, seq-cst).
  2. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
  3. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
  4. Let buffer be typedArray.[[ViewedArrayBuffer]].
  5. Let elementType be TypedArrayElementType(typedArray).
  6. Return GetValueFromBuffer(buffer, byteIndexInBuffer, elementType, true, seq-cst).

2.3.6 Atomics.store ( typedArraytypedArrayOrStruct, indexindexOrField, value )

This function performs the following steps when called:

  1. If typedArrayOrStruct is a Shared Struct, then
    1. If indexOrField is not a property key, throw a TypeError exception.
    2. If CanBeSharedAcrossAgents(value) is false, throw a TypeError exception.
    3. If typedArrayOrStruct does not have an own property with key indexOrField, throw a RangeError exception.
    4. Perform WriteSharedStructField(typedArrayOrStruct, indexOrField, value, seq-cst).
    5. Return value.
  2. Let byteIndexInBuffer be ? ValidateAtomicAccessOnIntegerTypedArray(typedArray, index).
  3. If typedArray.[[ContentType]] is bigint, let v be ? ToBigInt(value).
  4. Otherwise, let v be 𝔽(? ToIntegerOrInfinity(value)).
  5. Perform ? RevalidateAtomicAccess(typedArray, byteIndexInBuffer).
  6. Let buffer be typedArray.[[ViewedArrayBuffer]].
  7. Let elementType be TypedArrayElementType(typedArray).
  8. Perform SetValueInBuffer(buffer, byteIndexInBuffer, elementType, v, true, seq-cst).
  9. Return v.

2.4 Changes to the Memory Model

2.4.1 Memory Model Fundamentals

Shared memory accesses (reads and writes) are divided into two groups, atomic accesses and data accesses, defined below. Atomic accesses are sequentially consistent, i.e., there is a strict total ordering of events agreed upon by all agents in an agent cluster. Non-atomic accesses do not have a strict total ordering agreed upon by all agents, i.e., unordered.

Note

No orderings weaker than sequentially consistent and stronger than unordered, such as release-acquire, are supported.

A Shared Memory Storage Record is either a SharedBlockStorage or SharedStructStorage Record.

Table 2: SharedBlockStorage Fields
Field Name Value Meaning
[[Block]] a Shared Data Block The block the event operates on.
[[ByteIndex]] a non-negative integer The byte address of the access in [[Block]].
[[ElementSize]] a non-negative integer The size of the access.
Table 3: SharedStructStorage Fields
Field Name Value Meaning
[[Struct]] a Shared Struct The shared struct the event operates on.
[[Field]] a property key The field that is accessed in [[Struct]].

A Shared Data Block event is either a ReadSharedMemory, WriteSharedMemory, or ReadModifyWriteSharedMemory Record.

Table 4: ReadSharedMemory Event Fields
Field Name Value Meaning
[[Order]] seq-cst or unordered The weakest ordering guaranteed by the memory model for the event.
[[NoTear]] a Boolean Whether this event is allowed to read from multiple write events with equal range as this event.
[[Block]] a Shared Data Block The block the event operates on.
[[ByteIndex]] a non-negative integer The byte address of the read in [[Block]].
[[ElementSize]] a non-negative integer The size of the read.
[[Storage]] a Shared Memory Storage Record The storage of memory that is read.
Table 5: WriteSharedMemory Event Fields
Field Name Value Meaning
[[Order]] seq-cst, unordered, or init The weakest ordering guaranteed by the memory model for the event.
[[NoTear]] a Boolean Whether this event is allowed to be read from multiple read events with equal range as this event.
[[Block]] a Shared Data Block The block the event operates on.
[[ByteIndex]] a non-negative integer The byte address of the write in [[Block]].
[[ElementSize]] a non-negative integer The size of the write.
[[Storage]] a Shared Memory Storage Record The storage of memory that is written.
[[Payload]] a List of byte values The List of byte values to be read by other events.
Table 6: ReadModifyWriteSharedMemory Event Fields
Field Name Value Meaning
[[Order]] seq-cst Read-modify-write events are always sequentially consistent.
[[NoTear]] true Read-modify-write events cannot tear.
[[Block]] a Shared Data Block The block the event operates on.
[[ByteIndex]] a non-negative integer The byte address of the read-modify-write in [[Block]].
[[ElementSize]] a non-negative integer The size of the read-modify-write.
[[Storage]] a Shared Memory Storage Record The storage of memory of the read-modify-write.
[[Payload]] a List of byte values The List of byte values to be passed to [[ModifyOp]].
[[ModifyOp]] a read-modify-write modification function An abstract closure that returns a modified List of byte values from a read List of byte values and [[Payload]].

These events are introduced by abstract operations or by methods on the Atomics object.

Some operations may also introduce Synchronize events. A Synchronize event has no fields, and exists purely to directly constrain the permitted orderings of other events.

In addition to Shared Data Block and Synchronize events, there are host-specific events.

If the [[Storage]] field of a ReadSharedMemory, WriteSharedMemory, or ReadModifyWriteSharedMemory event is a SharedBlockStorage, then Llet theits range of a ReadSharedMemory, WriteSharedMemory, or ReadModifyWriteSharedMemory event be the Set of contiguous integers from its [[Storage]].[[ByteIndex]] to [[Storage]].[[ByteIndex]] + [[Storage]].[[ElementSize]] - 1. Two events' ranges are equal when the events have a SharedBlockStorage in their [[Storage]] field, have the same [[Storage]].[[Block]], and the ranges are element-wise equal. Two events' ranges are overlapping when the events have the same [[Storage]].[[Block]], the ranges are not equal and their intersection is non-empty. Two events' ranges are disjoint when the events do not both have a SharedBlockStorage in their [[Storage]] field, do not have the same [[Storage]].[[Block]], or their ranges are neither equal nor overlapping.

If the [[Storage]] field of a ReadSharedMemory, WriteSharedMemory, or ReadModifyWriteSharedMemory event is a SharedStructStorage, then let its range be the value of the [[Storage]] field. Two events' ranges are equal when the events have a SharedStructStorage in their [[Storage]] field, have the same [[Storage]].[[Struct]] and the same [[Storage]].[[Field]]. Two events' ranges that both have a SharedStructStorage in their [[Storage]] field are never overlapping. Two events' ranges are disjoint when the events do not both have a SharedStructStorage in their [[Storage]] Field, or do not have the same [[Storage]].[[Struct]] or the same [[Storage]].[[Field]].

For brevity, the refactoring of the memory model relations to use SharedStructStorage and the modified definition of event ranges is omitted.

3 Shared Array Object

Shared Arrays are a special case of Shared Structs with array indexed properties and an immutable "length" own property. Since they are Shared Structs, their layout, i.e. their length, is fixed at creation time.

3.1 The SharedArray Constructor

The SharedArray constructor:

  • is %SharedArray%.
  • is the initial value of the "SharedArray" property of the global object.
  • creates and initializes a new Shared Array when called as a constructor.
  • is not intended to be called as a function and will throw an exception when called in that manner.
  • is a function whose behaviour differs based upon the number and types of its arguments.

3.1.1 SharedArrayCreate ( length )

The abstract operation SharedArrayCreate takes argument length (an non-negative integer) and returns a Shared Array. It is used to specify the creation of new Shared Arrays. It performs the following steps when called:

  1. Assert: length ≤ 232 - 1.
  2. Perform EnterSharedStructCreationCriticalSection().
  3. Let A be OrdinaryObjectCreate(null, « »).
  4. Set A.[[GetOwnProperty]] as specified in 2.1.4.
  5. Set A.[[DefineOwnProperty]] as specified in 2.1.5.
  6. Set A.[[HasProperty]] as specified in 2.1.6.
  7. Set A.[[Get]] as specified in 2.1.7.
  8. Set A.[[Set]] as specified in 2.1.8.
  9. Set A.[[Delete]] as specified in 2.1.9.
  10. Let k be 0.
  11. Repeat, while k < length,
    1. Let Pk be ! ToString(𝔽(k)).
    2. Create an own data property named Pk of object A whose [[Value]] is undefined, [[Writable]] is true, [[Enumerable]] is true, and [[Configurable]] is false.
    3. Perform WriteSharedStructField(A, Pk, undefined, init).
  12. Create an own data property named "length" of object A whose [[Value]] is 𝔽(length), [[Writable]] is false, [[Enumerable]] is false, and [[Configurable]] is false.
  13. Perform WriteSharedStructField(A, "length", 𝔽(length), init).
  14. Perform ! A.[[PreventExtensions]]().
  15. Perform LeaveSharedStructCreationCriticalSection().
  16. Assert: ! TestIntegrityLevel(A, sealed) is true.
  17. Return A.

3.1.2 SharedArray ( ...values )

This function performs the following steps when called:

  1. If NewTarget is undefined, throw a TypeError exception.
  2. EDITOR'S NOTE: Per-Realm prototypes, which is currently an open design question and not included in this draft, will give Shared Arrays a per-Realm prototype with built-in methods.
  3. Let numberOfArgs be the number of elements in values.
  4. If numberOfArgs = 0, then
    1. Return SharedArrayCreate(0).
  5. Else if numberOfArgs = 1, then
    1. Let len be values[0].
    2. If len is not an integral Number, throw a TypeError exception.
    3. If len < 0, throw a RangeError exception.
    4. Let lenReal be (len).
    5. If lenReal > 232 - 1, throw a RangeError exception.
    6. Return SharedArrayCreate(lenReal).
  6. Else,
    1. Assert: numberOfArgs ≥ 2.
    2. Let array be SharedArrayCreate(numberOfArgs).
    3. Let k be 0.
    4. Repeat, while k < numberOfArgs,
      1. Let Pk be ! ToString(𝔽(k)).
      2. Let itemK be values[k].
      3. Perform ! Set(array, Pk, itemK, true).
      4. Set k to k + 1.
    5. Return array.

A Copyright & Software License

Copyright Notice

© 2024 Shu-yu Guo

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.