29 메모리 모델

메모리 일관성 모델, 또는 메모리 모델은 SharedArrayBuffer를 기반으로 하는 TypedArray 인스턴스에 접근하거나 Atomics 객체의 메서드를 통해 발생하는 Shared Data Block 이벤트의 가능한 순서를 지정한다. 프로그램에 데이터 레이스(아래에서 정의됨)가 없을 때, 이벤트의 순서는 순차적으로 일관된 것처럼, 즉 각 agent의 동작이 interleaving된 것처럼 보인다. 프로그램에 데이터 레이스가 있으면, 공유 메모리 연산은 순차적으로 일관되지 않게 보일 수 있다. 예를 들어, 프로그램은 인과성을 위반하는 동작 및 다른 놀라운 현상을 보일 수 있다. 이러한 놀라운 현상은 컴파일러 변환과 CPU 설계(예: out-of-order 실행 및 speculation)에서 비롯된다. 메모리 모델은 프로그램이 순차적으로 일관된 동작을 보이는 정확한 조건과 데이터 레이스에서 읽을 수 있는 가능한 값을 모두 정의한다. 즉, undefined behaviour는 없다.

메모리 모델은 평가 중 SharedArrayBuffer의 추상 연산 또는 Atomics 객체의 메서드에 의해 도입되는 Memory 이벤트에 대한 관계적 제약으로 정의된다.

Note

이 절은 SharedArrayBuffer에 대한 추상 연산에 의해 도입되는 Memory 이벤트에 관한 공리적 모델을 제공한다. 이 모델은 이 명세의 나머지 부분과 달리 알고리즘적으로 표현할 수 없다는 점을 강조할 필요가 있다. 추상 연산에 의한 이벤트의 비결정적 도입은 ECMAScript 평가의 조작적 의미론과 메모리 모델의 공리적 의미론 사이의 인터페이스이다. 이러한 이벤트의 의미론은 평가의 모든 이벤트 그래프를 고려하여 정의된다. 이들은 Static Semantics도 Runtime Semantics도 아니다. 입증된 알고리즘적 구현은 없고, 대신 특정 이벤트 그래프가 허용되는지 허용되지 않는지를 결정하는 제약 집합이 있다.

29.1 메모리 모델 기본 사항

공유 메모리 접근(읽기와 쓰기)은 아래에서 정의되는 두 그룹, atomic 접근과 data 접근으로 나뉜다. Atomic 접근은 순차적으로 일관된다. 즉, agent cluster의 모든 agent가 동의하는 이벤트의 엄격한 전체 순서가 있다. Non-atomic 접근은 모든 agent가 동의하는 엄격한 전체 순서를 가지지 않는다. 즉, unordered이다.

Note 1

release-acquire와 같이 순차적 일관성보다 약하고 unordered보다 강한 ordering은 지원되지 않는다.

Shared Data Block 이벤트ReadSharedMemory, WriteSharedMemory, 또는 ReadModifyWriteSharedMemory Record이다. read 이벤트는 ReadSharedMemory 또는 ReadModifyWriteSharedMemory이다. write 이벤트는 WriteSharedMemory 또는 ReadModifyWriteSharedMemory이다.

Table 92: ReadSharedMemory 이벤트 필드
필드 이름 의미
[[Order]] seq-cst 또는 unordered 해당 이벤트에 대해 메모리 모델이 보장하는 가장 약한 ordering.
[[NoTear]] Boolean 이 이벤트와 동일한 memory range를 가진 여러 write 이벤트에서 이 이벤트가 읽을 수 있는지 여부.
[[Block]] Shared Data Block 이벤트가 작동하는 block.
[[ByteIndex]] 음이 아닌 정수 [[Block]] 내 읽기의 바이트 주소.
[[ElementSize]] 음이 아닌 정수 읽기의 크기.
Table 93: WriteSharedMemory 이벤트 필드
필드 이름 의미
[[Order]] seq-cst, unordered, 또는 init 해당 이벤트에 대해 메모리 모델이 보장하는 가장 약한 ordering.
[[NoTear]] Boolean 이 이벤트와 동일한 memory range를 가진 여러 read 이벤트가 이 이벤트에서 읽을 수 있는지 여부.
[[Block]] Shared Data Block 이벤트가 작동하는 block.
[[ByteIndex]] 음이 아닌 정수 [[Block]] 내 쓰기의 바이트 주소.
[[ElementSize]] 음이 아닌 정수 쓰기의 크기.
[[Payload]] 바이트 값의 List 다른 이벤트가 읽을 바이트 값의 List.
Table 94: ReadModifyWriteSharedMemory 이벤트 필드
필드 이름 의미
[[Order]] seq-cst Read-modify-write 이벤트는 항상 순차적으로 일관된다.
[[NoTear]] true Read-modify-write 이벤트는 tear될 수 없다.
[[Block]] Shared Data Block 이벤트가 작동하는 block.
[[ByteIndex]] 음이 아닌 정수 [[Block]] 내 read-modify-write의 바이트 주소.
[[ElementSize]] 음이 아닌 정수 read-modify-write의 크기.
[[Payload]] 바이트 값의 List [[ModifyOp]]에 전달될 바이트 값의 List.
[[ModifyOp]] read-modify-write modification 함수 읽은 바이트 값의 List[[Payload]]로부터 수정된 바이트 값의 List를 반환하는 Abstract Closure.

Shared Data Block 이벤트는 추상 연산 또는 Atomics 객체의 메서드에 의해 candidate execution Agent Events Record에 도입된다. 일부 연산은 또한 필드가 없고 다른 이벤트의 허용된 ordering을 직접 제약하기 위해서만 존재하는 Synchronize 이벤트를 도입한다. 마지막으로, host-specific 이벤트가 있다. Memory 이벤트는 Shared Data Block 이벤트, Synchronize 이벤트, 또는 그러한 host-specific 이벤트이다.

Shared Data Block 이벤트 ememory rangee.[[ByteIndex]](포함)부터 e.[[ByteIndex]] + e.[[ElementSize]](제외)까지의 구간에 있는 모든 정수의 Set으로 둔다. 두 이벤트의 memory range는 이벤트가 같은 [[Block]], [[ByteIndex]], [[ElementSize]]를 가질 때 같다. 두 이벤트의 memory range는 이벤트가 같은 [[Block]]을 가지고, range가 같지 않으며, 교집합이 비어 있지 않을 때 overlapping이다. 두 이벤트의 memory range는 이벤트가 같은 [[Block]]을 가지지 않거나 range가 같지도 overlapping도 아닐 때 disjoint이다.

Note 2

고려되어야 할 host-specific synchronizing 이벤트의 예는 다음과 같다: 한 agent에서 다른 agent로 SharedArrayBuffer 보내기(예: 브라우저에서 postMessage 사용), agent 시작 및 중지, 공유 메모리 이외의 채널을 통한 agent cluster 내 통신. 특정 실행 execution에 대해, 이러한 이벤트는 host가 host-synchronizes-with strict partial order를 통해 제공한다. 추가로, host는 is-agent-order-before Relation에 참여하도록 host-specific synchronizing 이벤트를 execution.[[EventList]]에 추가할 수 있다.

이벤트는 아래에 정의된 관계에 의해 candidate execution 안에서 순서가 정해진다.

29.2 Agent Events Record

Agent Events Record는 다음 필드를 가진 Record이다.

Table 95: Agent Events Record 필드
필드 이름 의미
[[AgentSignifier]] agent signifier 이 ordering을 결과로 낳은 평가를 수행한 agent.
[[EventList]] Memory 이벤트List 이벤트는 평가 중 list에 추가된다.
[[AgentSynchronizesWith]] Synchronize 이벤트 쌍의 List 조작적 의미론에 의해 도입되는 Synchronize 관계.

29.3 Chosen Value Record

Chosen Value Record는 다음 필드를 가진 Record이다.

Table 96: Chosen Value Record 필드
필드 이름 의미
[[Event]] Shared Data Block 이벤트 이 chosen value를 위해 도입된 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트.
[[ChosenValue]] 바이트 값의 List 평가 중 비결정적으로 선택된 바이트.

29.4 Candidate Execution

agent cluster의 평가에 대한 candidate execution은 다음 필드를 가진 Record이다.

Table 97: Candidate Execution Record 필드
필드 이름 의미
[[EventsRecords]] Agent Events RecordList agent를 평가 중 추가된 Memory 이벤트List에 매핑한다.
[[ChosenValues]] Chosen Value RecordList ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트를 평가 중 선택된 바이트 값의 List에 매핑한다.

empty candidate execution은 필드가 빈 List인 candidate execution Record이다.

29.5 메모리 모델을 위한 추상 연산

29.5.1 EventSet ( execution )

The abstract operation EventSet takes argument execution (a candidate execution) and returns a Set of Memory events. It performs the following steps when called:

  1. events를 빈 Set으로 둔다.
  2. execution.[[EventsRecords]]의 각 Agent Events Record aer에 대해, 다음을 수행한다
    1. aer.[[EventList]]의 각 Memory 이벤트 event에 대해, 다음을 수행한다
      1. eventevents에 추가한다.
  3. events를 반환한다.

29.5.2 SharedDataBlockEventSet ( execution )

The abstract operation SharedDataBlockEventSet takes argument execution (a candidate execution) and returns a Set of Shared Data Block events. It performs the following steps when called:

  1. events를 빈 Set으로 둔다.
  2. EventSet(execution)의 각 Memory 이벤트 event에 대해, 다음을 수행한다
    1. eventShared Data Block 이벤트이면, eventevents에 추가한다.
  3. events를 반환한다.

29.5.3 HostEventSet ( execution )

The abstract operation HostEventSet takes argument execution (a candidate execution) and returns a Set of Memory events. It performs the following steps when called:

  1. SharedDataBlockEventSet(execution)에 없는 EventSet(execution)의 모든 요소를 포함하는 새로운 Set을 반환한다.

29.5.4 ComposeWriteEventBytes ( execution, byteIndex, writes )

The abstract operation ComposeWriteEventBytes takes arguments execution (a candidate execution), byteIndex (a non-negative integer), and writes (a List of either WriteSharedMemory or ReadModifyWriteSharedMemory events) and returns a List of byte values. It performs the following steps when called:

  1. byteLocationbyteIndex로 둔다.
  2. bytesRead를 새로운 빈 List로 둔다.
  3. writes의 각 요소 writeEvent에 대해, 다음을 수행한다
    1. Assert: writeEvent는 자신의 memory rangebyteLocation을 가진다.
    2. payloadIndexbyteLocation - writeEvent.[[ByteIndex]]로 둔다.
    3. writeEventWriteSharedMemory 이벤트이면,
      1. bytewriteEvent.[[Payload]][payloadIndex]로 둔다.
    4. 그렇지 않으면,
      1. Assert: writeEventReadModifyWriteSharedMemory 이벤트이다.
      2. bytesValueOfReadEvent(execution, writeEvent)로 둔다.
      3. bytesModifiedwriteEvent.[[ModifyOp]](bytes, writeEvent.[[Payload]])로 둔다.
      4. bytebytesModified[payloadIndex]로 둔다.
    5. bytebytesRead에 추가한다.
    6. byteLocationbyteLocation + 1로 설정한다.
  4. bytesRead를 반환한다.
Note 1

read-modify-write modification [[ModifyOp]]ReadModifyWriteSharedMemory 이벤트를 도입하는 Atomics 객체의 함수 프로퍼티에 의해 주어진다.

Note 2

추상 연산write 이벤트List를 바이트 값의 List로 구성한다. 이는 ReadSharedMemoryReadModifyWriteSharedMemory 이벤트의 이벤트 의미론에서 사용된다.

29.5.5 ValueOfReadEvent ( execution, readEvent )

The abstract operation ValueOfReadEvent takes arguments execution (a candidate execution) and readEvent (a ReadSharedMemory or ReadModifyWriteSharedMemory event) and returns a List of byte values. It performs the following steps when called:

  1. writesexecutionreads-bytes-from(readEvent)로 둔다.
  2. Assert: writes는 길이가 readEvent.[[ElementSize]]와 같은 WriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트의 List이다.
  3. ComposeWriteEventBytes(execution, readEvent.[[ByteIndex]], writes)를 반환한다.

29.6 Candidate Execution의 관계

다음 관계와 수학적 함수는 특정 candidate execution에 대해 매개변수화되며 그 Memory 이벤트에 순서를 부여한다.

29.6.1 is-agent-order-before

candidate execution execution에 대해, 그 is-agent-order-before Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation이다.

  • 이벤트 eventAeventB에 대해, execution.[[EventsRecords]] 안에 어떤 Agent Events Record aer가 있고 aer.[[EventList]]eventAeventB를 모두 포함하며 eventAaer.[[EventList]]List 순서에서 eventB보다 앞에 있다면, eventAexecution에서 eventB보다 is-agent-order-before이다.
Note

각 agent는 평가 중 agent별 엄격한 전체 순서로 이벤트를 도입한다. 이는 그러한 엄격한 전체 순서들의 합집합이다.

29.6.2 reads-bytes-from

candidate execution execution에 대해, 그 reads-bytes-from 함수는 SharedDataBlockEventSet(execution) 안의 Memory 이벤트SharedDataBlockEventSet(execution) 안의 이벤트 List에 매핑하는 수학적 함수이며 다음 조건을 만족한다.

candidate execution은 항상 reads-bytes-from 함수를 허용한다.

29.6.3 reads-from

candidate execution execution에 대해, 그 reads-from Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation이다.

  • 이벤트 readEventwriteEvent에 대해, SharedDataBlockEventSet(execution)이 readEventwriteEvent를 모두 포함하고, execution 안의 reads-bytes-from(readEvent)가 writeEvent를 포함한다면, readEventexecution에서 writeEvent로부터 reads-from한다.

29.6.4 host-synchronizes-with

candidate execution execution에 대해, 그 host-synchronizes-with Relation은 host가 제공하는 host-specific Memory 이벤트에 대한 strict partial order이며 최소한 다음을 만족한다.

  • execution에서 eventA host-synchronizes-with eventB이면, HostEventSet(execution)은 eventAeventB를 포함한다.
  • execution 안에서 host-synchronizes-with와 is-agent-order-before의 합집합에는 cycle이 없다.
Note 1

candidate execution execution 안의 두 host-specific 이벤트 eventAeventB에 대해, execution에서 eventA host-synchronizes-with eventB라는 것은 execution에서 eventA happens-before eventB임을 함의한다.

Note 2

Relation은 host가 HTML worker 사이의 postMessage와 같은 추가 synchronization 메커니즘을 제공할 수 있게 한다.

29.6.5 synchronizes-with

candidate execution execution에 대해, 그 synchronizes-with Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation이다.

  • 이벤트 readEventwriteEvent에 대해, readEventexecution에서 writeEvent로부터 reads-from하고, readEvent.[[Order]]seq-cst이며, writeEvent.[[Order]]seq-cst이고, readEventwriteEvent가 같은 memory range를 가지면, writeEventexecution에서 readEvent와 synchronizes-with한다.
  • execution.[[EventsRecords]]의 각 요소 eventsRecord에 대해, 다음이 참이다.
    • 이벤트 eventAeventB에 대해, eventsRecord.[[AgentSynchronizesWith]]가 (eventA, eventB)를 포함한다면, eventAexecution에서 eventB와 synchronizes-with한다.
  • 이벤트 eventAeventB에 대해, execution.[[HostSynchronizesWith]]가 (eventA, eventB)를 포함한다면, eventAexecution에서 eventB와 synchronizes-with한다.
Note 1

메모리 모델 문헌의 관례 때문에, candidate execution execution에서 read 이벤트write 이벤트와 synchronizes-with하는 대신 write 이벤트read 이벤트와 synchronizes-with한다.

Note 2

candidate execution execution에서, init 이벤트는 이 Relation에 참여하지 않으며 대신 happens-before에 의해 직접 제약된다.

Note 3

candidate execution execution에서 reads-from에 의해 관련된 모든 seq-cst 이벤트가 synchronizes-with에 의해 관련되는 것은 아니다. 같은 memory range도 가지는 이벤트만 synchronizes-with에 의해 관련된다.

Note 4

candidate execution execution 안의 Shared Data Block 이벤트 readEventwriteEvent에 대해, writeEventreadEvent와 synchronizes-with하더라도, readEventwriteEvent가 아닌 다른 write에서 reads-from할 수 있다.

29.6.6 happens-before

candidate execution execution에 대해, 그 happens-before Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation이다.

  • 이벤트 eventAeventB에 대해, 다음 조건 중 하나라도 참이면 eventAexecution에서 eventB보다 happens-before이다.

    • eventAexecution에서 eventB보다 is-agent-order-before이다.
    • eventAexecution에서 eventBsynchronizes-with한다.
    • SharedDataBlockEventSet(execution)이 eventAeventB를 모두 포함하고, eventA.[[Order]]init이며, eventAeventB가 overlapping memory range를 가진다.
    • execution에서 eventAeventC보다 happens-before이고 eventCeventB보다 happens-before인 이벤트 eventC가 있다.
Note

happens-before는 agent-order의 superset이므로, candidate execution은 ECMAScript의 single-thread 평가 의미론과 일치한다.

29.7 Valid Execution의 프로퍼티

29.7.1 Valid Chosen Reads

candidate execution execution은 다음 알고리즘이 true를 반환하면 valid chosen reads를 가진다.

  1. SharedDataBlockEventSet(execution)의 각 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 readEvent에 대해, 다음을 수행한다
    1. chosenValueRecord[[Event]] 필드가 readEventexecution.[[ChosenValues]]의 요소로 둔다.
    2. chosenValuechosenValueRecord.[[ChosenValue]]로 둔다.
    3. readValueValueOfReadEvent(execution, readEvent)로 둔다.
    4. chosenLenchosenValue 안의 요소 수로 둔다.
    5. readLenreadValue 안의 요소 수로 둔다.
    6. chosenLenreadLen이면,
      1. false를 반환한다.
    7. 0(포함)부터 chosenLen(제외)까지의 구간 안의 어떤 정수 i에 대해 chosenValue[i] ≠ readValue[i]이면,
      1. false를 반환한다.
  2. true를 반환한다.

29.7.2 Coherent Reads

candidate execution execution은 다음 알고리즘이 true를 반환하면 coherent reads를 가진다.

  1. SharedDataBlockEventSet(execution)의 각 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 readEvent에 대해, 다음을 수행한다
    1. writesexecutionreads-bytes-from(readEvent)로 둔다.
    2. byteLocationreadEvent.[[ByteIndex]]로 둔다.
    3. writes의 각 요소 writeEvent에 대해, 다음을 수행한다
      1. execution에서 readEvent happens-before writeEvent이면,
        1. false를 반환한다.
      2. byteLocation을 자신의 memory range에 가진 WriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 value가 존재하여, execution에서 writeEvent happens-before value이고 value happens-before readEvent이면,
        1. false를 반환한다.
      3. byteLocationbyteLocation + 1로 설정한다.
  2. true를 반환한다.

29.7.3 Tear Free Reads

candidate execution execution은 다음 알고리즘이 true를 반환하면 tear free reads를 가진다.

  1. SharedDataBlockEventSet(execution)의 각 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 readEvent에 대해, 다음을 수행한다
    1. readEvent.[[NoTear]]true이면,
      1. Assert: readEvent.[[ByteIndex]]readEvent.[[ElementSize]]로 나눈 나머지는 0이다.
      2. execution에서 readEventwriteEvent로부터 reads-from하고 writeEvent.[[NoTear]]true인 각 Memory 이벤트 writeEvent에 대해, 다음을 수행한다
        1. readEventwriteEvent가 같은 memory range를 가지고, valuewriteEvent가 같은 memory range를 가지며, value.[[NoTear]]true이고, writeEventvalue가 같은 Shared Data Block 이벤트가 아니며, readEventexecution에서 value로부터 reads-from하는 Memory 이벤트 value가 존재하면,
          1. false를 반환한다.
  2. true를 반환한다.
Note

Shared Data Block 이벤트[[NoTear]] 필드는 그 이벤트가 integer TypedArray 접근을 통해 도입되었을 때 true이고, floating point TypedArray 또는 DataView 접근을 통해 도입되었을 때 false이다.

직관적으로 이 요구 사항은 integer TypedArray를 통해 정렬된 방식으로 memory range에 접근할 때, 같은 range를 가진 다른 write 이벤트data race가 있으면 그 range의 단일 write 이벤트가 "win"해야 한다는 뜻이다. 더 정확히 말하면, 이 요구 사항은 정렬된 read 이벤트가 모두 같은 range를 가진 여러 다른 write 이벤트의 바이트로 구성된 값을 읽을 수 없다는 뜻이다. 그러나 정렬된 read 이벤트가 overlapping range를 가진 여러 write 이벤트에서 읽는 것은 가능하다.

29.7.4 순차적으로 일관된 Atomics

candidate execution execution에 대해, is-memory-order-before는 다음을 만족하는 EventSet(execution) 안의 모든 Memory 이벤트의 엄격한 전체 순서이다.

candidate execution은 is-memory-order-before Relation을 허용하면 sequentially consistent atomics를 가진다.

Note 3

is-memory-order-before는 EventSet(execution) 안의 모든 이벤트를 포함하지만, execution에서 happens-before 또는 synchronizes-with에 의해 제약되지 않는 이벤트는 그 순서 어디에나 있을 수 있다.

29.7.5 Valid Execution

candidate execution execution은 다음이 모두 참이면 valid execution(또는 단순히 execution)이다.

모든 프로그램은 최소 하나의 valid execution을 가진다.

29.8 Race

execution executionSharedDataBlockEventSet(execution)에 포함된 이벤트 eventAeventB에 대해, 다음 알고리즘이 true를 반환하면 eventAeventBrace에 있다.

  1. eventAeventB가 같은 Shared Data Block 이벤트가 아니면,
    1. execution에서 eventA happens-before eventB이고 eventB happens-before eventA인 경우가 아니면,
      1. eventAWriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트이고, eventBWriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트이며, eventAeventB가 서로소인 메모리 범위를 갖지 않으면,
        1. true를 반환한다.
      2. execution에서 eventAeventB로부터 reads-from하거나 eventBeventA로부터 reads-from하면,
        1. true를 반환한다.
  2. false를 반환한다.

29.9 Data Race

execution executionSharedDataBlockEventSet(execution)에 포함된 이벤트 eventAeventB에 대해, 다음 알고리즘이 true를 반환하면 eventAeventBdata race에 있다.

  1. eventAeventBexecution에서 race에 있으면,
    1. eventA.[[Order]]seq-cst가 아니거나 eventB.[[Order]]seq-cst가 아니면,
      1. true를 반환한다.
    2. eventAeventB가 overlapping memory range를 가지면,
      1. true를 반환한다.
  2. false를 반환한다.

29.10 Data Race Freedom

execution executionSharedDataBlockEventSet(execution) 안에 data race에 있는 두 이벤트가 없으면 data race free이다.

프로그램은 자신의 모든 execution이 data race free이면 data race free이다.

메모리 모델은 data race free 프로그램에 대해 모든 이벤트의 순차적 일관성을 보장한다.

29.11 공유 메모리 지침

Note 1

다음은 공유 메모리로 작업하는 ECMAScript 프로그래머를 위한 지침이다.

프로그램을 data race free로 유지할 것을 권장한다. 즉, 같은 메모리 위치에 대한 동시 non-atomic 연산이 존재할 수 없도록 하라. Data race free 프로그램은 각 agent의 평가 의미론에서 각 단계가 서로 interleaving되는 interleaving 의미론을 가진다. Data race free 프로그램의 경우 메모리 모델의 세부 사항을 이해할 필요가 없다. 그 세부 사항은 ECMAScript를 더 잘 작성하는 데 도움이 될 직관을 형성할 가능성이 낮다.

더 일반적으로, 프로그램이 data race free가 아니더라도 atomic 연산이 어떤 data race에도 관여하지 않고 race하는 연산들이 모두 같은 access size를 가지는 한 예측 가능한 동작을 할 수 있다. atomics가 race에 관여하지 않도록 하는 가장 단순한 방법은 atomic 연산과 non-atomic 연산이 서로 다른 memory cell을 사용하도록 하고, 서로 다른 크기의 atomic 접근이 동시에 같은 cell에 접근하는 데 사용되지 않도록 하는 것이다. 사실상 프로그램은 공유 메모리를 가능한 한 strongly typed로 취급해야 한다. race하는 non-atomic 접근의 ordering과 timing에는 여전히 의존할 수 없지만, 메모리가 strongly typed로 취급된다면 race하는 접근은 "tear"되지 않는다(값의 bit가 섞이지 않는다).

Note 2

다음은 공유 메모리를 사용하는 프로그램에 대해 컴파일러 변환을 작성하는 ECMAScript 구현자를 위한 지침이다.

multi-agent 프로그램에서 각 agent의 성능이 single-agent 설정에서와 마찬가지로 좋도록 보장하기 위해, single-agent 설정에서 유효한 대부분의 프로그램 변환을 multi-agent 설정에서도 허용하는 것이 바람직하다. 이러한 변환은 판단하기 어려운 경우가 많다. 우리는 규범적으로 받아들여지도록 의도된(메모리 모델에 의해 함의되거나 메모리 모델이 함의하는 것보다 더 강한) 프로그램 변환에 관한 몇 가지 규칙을 개괄하지만, 이는 완전하지 않을 가능성이 높다. 이러한 규칙은 is-agent-order-before Relation을 구성하는 Memory 이벤트의 도입에 앞서는 프로그램 변환에 적용되도록 의도된다.

agent-order slice를 단일 agent에 관련된 is-agent-order-before Relation의 부분집합으로 둔다.

read 이벤트possible read values를 모든 valid execution에 걸친 그 이벤트의 ValueOfReadEvent 값들의 집합으로 둔다.

공유 메모리가 없는 경우 유효한 agent-order slice의 모든 변환은 공유 메모리가 있는 경우에도 다음 예외를 제외하고 유효하다.

  • Atomics are carved in stone: 프로그램 변환은 [[Order]]seq-cst인 어떤 Shared Data Block 이벤트is-agent-order-before Relation에서 제거되게 해서는 안 되며, 서로에 대해 reorder되게 해서도 안 되고, [[Order]]unordered인 이벤트에 대해 agent-order slice 안에서 reorder되게 해서도 안 된다.

    (실제로 reordering 금지는 컴파일러가 모든 seq-cst 연산이 synchronization이고 최종 is-memory-order-before Relation에 포함된다고 가정하도록 강제하며, inter-agent 프로그램 분석이 없는 경우에도 보통 그렇게 가정해야 한다. 또한 memory-order에 대한 callee의 효과가 알려지지 않은 모든 호출이 seq-cst 연산을 포함할 수 있다고 컴파일러가 가정하도록 강제한다.)

  • Reads must be stable: 주어진 공유 메모리 read는 execution 안에서 단 하나의 값만 관찰해야 한다.

    (예를 들어, 프로그램에서 의미상 단일 read인 것이 여러 번 실행되면, 그 후 프로그램은 읽힌 값들 중 하나만 관찰하도록 허용된다. rematerialization으로 알려진 변환은 이 규칙을 위반할 수 있다.)

  • Writes must be stable: 공유 메모리에 대한 모든 관찰 가능한 write는 execution 안의 프로그램 의미론으로부터 따라야 한다.

    (예를 들어, 더 작은 datum을 쓰기 위해 더 큰 위치에 read-modify-write 연산을 사용하거나, 프로그램이 쓸 수 없었던 값을 메모리에 쓰거나, read 이후 다른 agent가 덮어쓸 수 있었던 위치에 방금 읽은 값을 다시 쓰는 것과 같은 특정 관찰 가능한 write를 변환이 도입해서는 안 된다.)

  • Possible read values must be non-empty: 프로그램 변환은 공유 메모리 read의 possible read values가 비게 만들 수 없다.

    (직관에 반하지만, 이 규칙은 사실상 write에 대한 변환을 제한한다. memory model에서 write는 read 이벤트에 의해 읽힐 수 있다는 점에서 힘을 가지기 때문이다. 예를 들어, write는 이동되고 병합될 수 있으며 때로는 두 seq-cst 연산 사이에서 reorder될 수도 있지만, 변환은 어떤 위치를 갱신하는 모든 write를 제거해서는 안 된다. 일부 write는 보존되어야 한다.)

여전히 유효한 변환의 예는 다음과 같다: 같은 위치에서 여러 non-atomic read 병합, non-atomic read reordering, speculative non-atomic read 도입, 같은 위치에 대한 여러 non-atomic write 병합, 서로 다른 위치에 대한 non-atomic write reordering, 그리고 종료에 영향을 미치더라도 loop 밖으로 non-atomic read hoisting. 일반적으로 aliased TypedArray는 위치가 다르다는 것을 증명하기 어렵게 만든다는 점에 유의하라.

Note 3

다음은 공유 메모리 접근을 위한 machine code를 생성하는 ECMAScript 구현자를 위한 지침이다.

ARM 또는 Power의 메모리 모델보다 약하지 않은 메모리 모델을 가진 아키텍처의 경우, non-atomic store와 load는 대상 아키텍처의 bare store와 load로 컴파일될 수 있다. Atomic store와 load는 순차적 일관성을 보장하는 명령어로 컴파일될 수 있다. 그러한 명령어가 없으면, bare store 또는 load의 양쪽에 barrier를 배치하는 것과 같은 memory barrier를 사용해야 한다. Read-modify-write 연산은 x86의 LOCK-prefix 명령어, ARM의 load-exclusive/store-exclusive 명령어, Power의 load-link/store-conditional 명령어와 같은 대상 아키텍처의 read-modify-write 명령어로 컴파일될 수 있다.

구체적으로, 메모리 모델은 다음과 같은 코드 생성을 허용하도록 의도된다.

  • 프로그램의 모든 atomic 연산은 필요하다고 가정된다.
  • Atomic 연산은 서로 또는 non-atomic 연산과 결코 재배열되지 않는다.
  • 함수는 항상 atomic 연산을 수행한다고 가정된다.
  • Atomic 연산은 더 큰 데이터에 대한 read-modify-write 연산으로 구현되지 않으며, 플랫폼에 적절한 크기의 atomic 연산이 없으면 non-lock-free atomics로 구현된다. (우리는 이미 모든 플랫폼이 모든 흥미로운 크기의 normal memory access 연산을 가진다고 가정한다.)

Naive code generation은 다음 패턴을 사용한다:

  • Regular load와 store는 단일 load 및 store 명령어로 컴파일된다.
  • Lock-free atomic load와 store는 full(순차적으로 일관된) fence, regular load 또는 store, 그리고 full fence로 컴파일된다.
  • Lock-free atomic read-modify-write 접근은 full fence, atomic read-modify-write 명령어 sequence, 그리고 full fence로 컴파일된다.
  • Non-lock-free atomics는 spinlock acquire, full fence, 일련의 non-atomic load 및 store 명령어, full fence, 그리고 spinlock release로 컴파일된다.

그 매핑은 memory range에 대한 atomic 연산이 non-atomic write 또는 다른 크기의 atomic 연산과 race하지 않는 한 올바르다. 그러나 우리가 필요한 것은 그것뿐이다: 메모리 모델은 race에 관여한 atomic 연산을 사실상 non-atomic 상태로 격하시킨다. 한편, naive mapping은 상당히 강하다: 이는 atomic 연산이 순차적으로 일관된 fence로 사용될 수 있게 하는데, 메모리 모델은 실제로 이를 보장하지 않는다.

메모리 모델의 제약을 따르는 한, 이러한 기본 패턴에 대한 local improvement도 허용된다. 예를 들어:

  • 중복 fence를 제거하는 명백한 platform-dependent improvement가 있다. 예를 들어 x86에서는 lock-free atomic load와 store 주변의 fence가 store 뒤의 fence를 제외하면 항상 생략될 수 있으며, lock-free read-modify-write 명령어에는 fence가 필요하지 않다. 이는 모두 LOCK-prefix 명령어를 사용하기 때문이다. 많은 플랫폼에는 여러 강도의 fence가 있으며, 순차적 일관성을 깨뜨리지 않고 특정 context에서 더 약한 fence를 사용할 수 있다.
  • 대부분의 현대 플랫폼은 ECMAScript atomics가 요구하는 모든 데이터 크기에 대해 lock-free atomics를 지원한다. non-lock-free atomics가 필요하다면, atomic 연산 본문을 둘러싼 fence는 보통 lock 및 unlock 단계 안으로 접힐 수 있다. non-lock-free atomics에 대한 가장 단순한 해결책은 SharedArrayBuffer마다 하나의 lock word를 두는 것이다.
  • 또한 일부 코드 분석이 필요한 더 복잡한 platform-dependent local improvement도 있다. 예를 들어, 연속된 두 fence는 종종 단일 fence와 같은 효과를 가지므로, 두 atomic 연산이 연속으로 생성되는 경우 그 사이에는 단 하나의 fence만 필요하다. x86에서는 atomic store를 분리하는 단일 fence조차 생략될 수 있는데, store 뒤의 fence는 store를 후속 load와 분리하는 데만 필요하기 때문이다.