페이지 표시 설정

29 메모리 모델

메모리 일관성 모델, 즉 메모리 모델은 SharedArrayBuffer를 기반으로 하는 TypedArray 인스턴스에 접근하거나 Atomics 객체의 메서드를 통해 발생하는 Shared Data Block 이벤트의 가능한 순서를 지정합니다. 프로그램에 데이터 경합(아래 정의)이 없으면 이벤트 순서는 순차적 일관성을 갖는 것처럼, 즉 각 에이전트의 동작이 서로 교차하여 실행되는 것처럼 보입니다. 프로그램에 데이터 경합이 있으면 공유 메모리 연산은 순차적으로 일관되지 않게 보일 수 있습니다. 예를 들어 프로그램은 인과성을 위반하는 동작과 그 밖의 놀라운 동작을 보일 수 있습니다. 이러한 놀라운 동작은 컴파일러 변환과 CPU 설계(예: 비순차 실행 및 추측 실행)에서 발생합니다. 메모리 모델은 프로그램이 순차적으로 일관된 동작을 보이는 정확한 조건과 데이터 경합에서 읽을 수 있는 가능한 값을 모두 정의합니다. 즉, 정의되지 않은 동작은 없습니다.

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

참고

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

29.1 메모리 모델의 기본 사항

공유 메모리 접근(읽기 및 쓰기)은 아래에 정의된 원자적 접근과 데이터 접근이라는 두 그룹으로 나뉩니다. 원자적 접근은 순차적으로 일관됩니다. 즉, 에이전트 클러스터의 모든 에이전트가 동의하는 이벤트의 엄격한 전순서가 있습니다. 비원자적 접근에는 모든 에이전트가 동의하는 엄격한 전순서가 없습니다. 즉, 순서가 지정되지 않습니다.

참고 1

순차적 일관성보다 약하고 순서 없음보다 강한 release-acquire와 같은 순서는 지원되지 않습니다.

Shared Data Block 이벤트는 ReadSharedMemory, WriteSharedMemory, ReadModifyWriteSharedMemory Record 중 하나입니다. 읽기 이벤트는 ReadSharedMemory 또는 ReadModifyWriteSharedMemory입니다. 쓰기 이벤트는 WriteSharedMemory 또는 ReadModifyWriteSharedMemory입니다.

표 97: ReadSharedMemory 이벤트 필드
필드 이름 값 의미
[[Order]] seq-cst or unordered 메모리 모델이 이 이벤트에 대해 보장하는 가장 약한 순서입니다.
[[NoTear]] a Boolean 이 이벤트가 자신과 동일한 메모리 범위를 갖는 여러 쓰기 이벤트에서 읽는 것이 허용되는지 여부입니다.
[[Block]] a Shared Data Block 이벤트가 동작하는 블록입니다.
[[ByteIndex]] a non-negative integer [[Block]]에서 읽기의 바이트 주소입니다.
[[ElementSize]] a non-negative integer 읽기의 크기입니다.
표 98: WriteSharedMemory 이벤트 필드
필드 이름 값 의미
[[Order]] seq-cst, unordered, or init 메모리 모델이 이 이벤트에 대해 보장하는 가장 약한 순서입니다.
[[NoTear]] a Boolean 이 이벤트를 자신과 동일한 메모리 범위를 갖는 여러 읽기 이벤트가 읽는 것이 허용되는지 여부입니다.
[[Block]] a Shared Data Block 이벤트가 동작하는 블록입니다.
[[ByteIndex]] a non-negative integer [[Block]]에서 쓰기의 바이트 주소입니다.
[[ElementSize]] a non-negative integer 쓰기의 크기입니다.
[[Payload]] a List of byte values 다른 이벤트가 읽을 바이트 값의 List입니다.
표 99: ReadModifyWriteSharedMemory 이벤트 필드
필드 이름 값 의미
[[Order]] seq-cst Read-modify-write 이벤트는 항상 순차적으로 일관됩니다.
[[NoTear]] true Read-modify-write 이벤트는 찢어질 수 없습니다.
[[Block]] a Shared Data Block 이벤트가 동작하는 블록입니다.
[[ByteIndex]] a non-negative integer [[Block]]에서 read-modify-write의 바이트 주소입니다.
[[ElementSize]] a non-negative integer read-modify-write의 크기입니다.
[[Payload]] a List of byte values [[ModifyOp]]에 전달할 바이트 값의 List입니다.
[[ModifyOp]] a read-modify-write modification function 읽은 바이트 값의 List와 [[Payload]]에서 수정된 바이트 값의 List를 반환하는 Abstract Closure입니다.

Shared Data Block 이벤트는 추상 연산 또는 Atomics 객체의 메서드에 의해 후보 실행의 Agent Events Records에 도입됩니다. 일부 연산은 필드가 없으며 순전히 다른 이벤트의 허용 가능한 순서를 직접 제한하기 위해 존재하는 Synchronize 이벤트도 도입합니다. 마지막으로 호스트별 이벤트가 있습니다. Memory 이벤트는 Shared Data Block 이벤트, Synchronize 이벤트 또는 그러한 호스트별 이벤트 중 하나입니다.

Shared Data Block 이벤트 e의 메모리 범위를 e.[[ByteIndex]](포함)부터 e.[[ByteIndex]] + e.[[ElementSize]](제외)까지 구간에 있는 모든 정수의 Set으로 둡니다. 두 이벤트가 같은 [[Block]], [[ByteIndex]], [[ElementSize]]를 가지면 두 이벤트의 메모리 범위는 같습니다. 두 이벤트가 같은 [[Block]]을 가지고 범위가 같지 않으며 교집합이 비어 있지 않으면 두 이벤트의 메모리 범위는 겹칩니다. 두 이벤트가 같은 [[Block]]을 갖지 않거나 그 범위가 같지도 겹치지도 않으면 두 이벤트의 메모리 범위는 서로 분리되어 있습니다.

참고 2

고려해야 하는 호스트별 동기화 이벤트의 예로는 한 에이전트에서 다른 에이전트로 SharedArrayBuffer를 보내는 것(예: 브라우저의 postMessage), 에이전트를 시작하고 중지하는 것, 공유 메모리 이외의 채널을 통해 에이전트 클러스터 내에서 통신하는 것이 있습니다. 특정 실행 execution에 대해 이러한 이벤트는 host-synchronizes-with 엄격 부분 순서를 통해 호스트가 제공합니다. 또한 호스트는 is-agent-order-before Relation에 참여하도록 호스트별 동기화 이벤트를 execution.[[EventList]]에 추가할 수 있습니다.

이벤트는 아래에 정의된 관계에 따라 후보 실행 내에서 순서가 정해집니다.

29.2 Agent Events Records

Agent Events Record는 다음 필드를 갖는 Record입니다.

표 100: Agent Events Record 필드
필드 이름 값 의미
[[AgentSignifier]] an agent signifier 평가로 인해 이 순서가 발생한 에이전트입니다.
[[EventList]] a List of Memory events 이벤트는 평가 중 이 List에 추가됩니다.
[[AgentSynchronizesWith]] a List of pairs of Synchronize events 연산 의미론이 도입한 Synchronize 관계입니다.

29.3 Chosen Value Records

Chosen Value Record는 다음 필드를 갖는 Record입니다.

표 101: Chosen Value Record 필드
필드 이름 값 의미
[[Event]] a Shared Data Block event 이 선택된 값에 대해 도입된 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트입니다.
[[ChosenValue]] a List of byte values 평가 중 비결정적으로 선택된 바이트입니다.

29.4 후보 실행

에이전트 클러스터 평가의 후보 실행은 다음 필드를 갖는 Record입니다.

표 102: 후보 실행 Record 필드
필드 이름 값 의미
[[EventsRecords]] a List of Agent Events Records 에이전트를 평가 중 추가된 Memory 이벤트의 List에 매핑합니다.
[[ChosenValues]] a List of Chosen Value Records ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트를 평가 중 선택된 바이트 값의 List에 매핑합니다.

빈 후보 실행은 필드가 빈 List인 후보 실행 Record입니다.

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

29.5.1 EventSet ( execution )

추상 연산 EventSet. 인수 execution (a candidate execution). 반환값: a Set of Memory events. 호출되면 다음 단계를 수행한다.

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

29.5.2 SharedDataBlockEventSet ( execution )

추상 연산 SharedDataBlockEventSet. 인수 execution (a candidate execution). 반환값: a Set of Shared Data Block events. 호출되면 다음 단계를 수행한다.

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

29.5.3 HostEventSet ( execution )

추상 연산 HostEventSet. 인수 execution (a candidate execution). 반환값: a Set of Memory events. 호출되면 다음 단계를 수행한다.

  1. SharedDataBlockEventSet(execution)에 포함되지 않은 EventSet(execution)의 모든 요소를 포함하는 새로운 Set을 반환합니다.

29.5.4 ComposeWriteEventBytes ( execution, byteIndex, writes )

추상 연산 ComposeWriteEventBytes. 인수 execution (a candidate execution), byteIndex (음이 아닌 정수) 및 writes (리스트 (요소: (WriteSharedMemory 또는 ReadModifyWriteSharedMemory events))). 반환값: 리스트 (요소: 바이트 값). 호출되면 다음 단계를 수행한다.

  1. byteLocation을 byteIndex로 둡니다.
  2. bytesRead를 새로운 빈 List로 둡니다.
  3. writes의 각 요소 writeEvent에 대해 다음을 수행합니다.
    1. 단언: writeEvent는 자신의 메모리 범위에 byteLocation을 포함합니다.
    2. payloadIndex를 byteLocation - writeEvent.[[ByteIndex]]로 둡니다.
    3. writeEvent가 WriteSharedMemory 이벤트이면, 다음을 수행합니다.
      1. byte를 writeEvent.[[Payload]][payloadIndex]로 둡니다.
    4. 그렇지 않으면,
      1. 단언: writeEvent는 ReadModifyWriteSharedMemory 이벤트입니다.
      2. bytes를 ValueOfReadEvent(execution, writeEvent)로 둡니다.
      3. bytesModified를 writeEvent.[[ModifyOp]](bytes, writeEvent.[[Payload]])로 둡니다.
      4. byte를 bytesModified[payloadIndex]로 둡니다.
    5. byte를 bytesRead에 추가합니다.
    6. byteLocation을 byteLocation + 1로 설정합니다.
  4. bytesRead를 반환합니다.
참고 1

read-modify-write 수정 [[ModifyOp]]는 ReadModifyWriteSharedMemory 이벤트를 도입하는 Atomics 객체의 함수 프로퍼티에 의해 제공됩니다.

참고 2

이 추상 연산은 쓰기 이벤트의 List를 바이트 값의 List로 구성합니다. ReadSharedMemory 및 ReadModifyWriteSharedMemory 이벤트의 이벤트 의미론에서 사용됩니다.

29.5.5 ValueOfReadEvent ( execution, readEvent )

추상 연산 ValueOfReadEvent. 인수 execution (a candidate execution) 및 readEvent ((a ReadSharedMemory 또는 ReadModifyWriteSharedMemory event)). 반환값: 리스트 (요소: 바이트 값). 호출되면 다음 단계를 수행한다.

  1. writes를 execution에서 reads-bytes-from(readEvent)으로 둡니다.
  2. 단언: writes는 길이가 readEvent.[[ElementSize]]와 같은 WriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트의 List입니다.
  3. ComposeWriteEventBytes(execution, readEvent.[[ByteIndex]], writes)를 반환합니다.

29.6 후보 실행의 관계

다음 관계와 수학적 함수는 특정 후보 실행에 대해 매개변수화되며 그 Memory 이벤트의 순서를 정합니다.

29.6.1 is-agent-order-before

후보 실행 execution에 대해 해당 is-agent-order-before Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation입니다.

  • 이벤트 eventA와 eventB에 대해, execution.[[EventsRecords]]에 eventA와 eventB를 모두 포함하는 [[EventList]]를 가진 Agent Events Record eventsRecord가 있고 eventsRecord.[[EventList]]의 List 순서에서 eventA가 eventB보다 앞서면 execution에서 eventA is-agent-order-before eventB입니다.
참고

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

29.6.2 reads-bytes-from

후보 실행 execution에 대해 해당 reads-bytes-from 함수는 SharedDataBlockEventSet(execution)의 Memory 이벤트를 SharedDataBlockEventSet(execution)의 이벤트 List에 매핑하며 다음 조건을 만족하는 수학적 함수입니다.

후보 실행에는 항상 reads-bytes-from 함수가 존재합니다.

29.6.3 reads-from

후보 실행 execution에 대해 해당 reads-from Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation입니다.

  • 이벤트 readEvent와 writeEvent에 대해 SharedDataBlockEventSet(execution)이 readEvent와 writeEvent를 모두 포함하고 execution에서 reads-bytes-from(readEvent)이 writeEvent를 포함하면 execution에서 readEvent reads-from writeEvent입니다.

29.6.4 host-synchronizes-with

후보 실행 execution에 대해 해당 host-synchronizes-with Relation은 적어도 다음을 만족하는 호스트별 Memory 이벤트에 대한 호스트 제공 엄격 부분 순서입니다.

  • execution에서 eventA host-synchronizes-with eventB이면 HostEventSet(execution)은 eventA와 eventB를 포함합니다.
  • execution에서 host-synchronizes-with와 is-agent-order-before의 합집합에는 순환이 없습니다.
참고 1

후보 실행 execution의 두 호스트별 이벤트 eventA와 eventB에 대해 execution에서 eventA host-synchronizes-with eventB이면 execution에서 eventA happens-before eventB입니다.

참고 2

이 Relation을 통해 호스트는 HTML worker 사이의 postMessage와 같은 추가 동기화 메커니즘을 제공할 수 있습니다.

29.6.5 synchronizes-with

후보 실행 execution에 대해 해당 synchronizes-with Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation입니다.

  • 이벤트 readEvent와 writeEvent에 대해 execution에서 readEvent reads-from writeEvent이고, readEvent.[[Order]]가 seq-cst이고, writeEvent.[[Order]]가 seq-cst이며, readEvent와 writeEvent의 메모리 범위가 같으면 execution에서 writeEvent synchronizes-with readEvent입니다.
  • execution.[[EventsRecords]]의 각 요소 eventsRecord에 대해 다음이 참입니다.
    • 이벤트 eventA와 eventB에 대해 eventsRecord.[[AgentSynchronizesWith]]가 (eventA, eventB)를 포함하면 execution에서 eventA synchronizes-with eventB입니다.
  • 이벤트 eventA와 eventB에 대해 execution에서 eventA host-synchronizes-with eventB이면 execution에서 eventA synchronizes-with eventB입니다.
참고 1

메모리 모델 문헌의 관례에 따라 후보 실행 execution에서는 읽기 이벤트가 쓰기 이벤트와 synchronizes-with하는 것이 아니라 쓰기 이벤트가 읽기 이벤트와 synchronizes-with합니다.

참고 2

후보 실행 execution에서 init 이벤트는 이 Relation에 참여하지 않으며 대신 happens-before에 의해 직접 제약됩니다.

참고 3

후보 실행 execution에서 reads-from으로 관련된 모든 seq-cst 이벤트가 synchronizes-with로 관련되는 것은 아닙니다. 메모리 범위도 같은 이벤트만 synchronizes-with로 관련됩니다.

참고 4

후보 실행 execution에서 writeEvent synchronizes-with readEvent인 Shared Data Block 이벤트 readEvent와 writeEvent에 대해 readEvent는 writeEvent 이외의 다른 쓰기에서도 reads-from할 수 있습니다.

29.6.6 happens-before

후보 실행 execution에 대해 해당 happens-before Relation은 다음을 만족하는 Memory 이벤트에 대한 최소 Relation입니다.

  • 이벤트 eventA와 eventB에 대해 다음 조건 중 하나라도 참이면 execution에서 eventA happens-before eventB입니다.

    • execution에서 eventA is-agent-order-before eventB입니다.
    • execution에서 eventA synchronizes-with eventB입니다.
    • SharedDataBlockEventSet(execution)이 eventA와 eventB를 모두 포함하고, eventA.[[Order]]가 init이며, eventA와 eventB의 메모리 범위가 겹칩니다.
    • execution에서 eventA happens-before eventC이고 eventC happens-before eventB인 이벤트 eventC가 존재합니다.
참고

happens-before는 agent-order의 상위 집합이므로 후보 실행은 ECMAScript의 단일 스레드 평가 의미론과 일관됩니다.

29.7 유효한 실행의 프로퍼티

29.7.1 유효한 선택 읽기

다음 알고리즘이 true를 반환하면 후보 실행 execution은 유효한 선택 읽기를 갖습니다.

  1. SharedDataBlockEventSet(execution)의 각 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 readEvent에 대해 다음을 수행합니다.
    1. chosenValueRecord를 [[Event]] 필드가 readEvent인 execution.[[ChosenValues]]의 요소로 둡니다.
    2. chosenValue를 chosenValueRecord.[[ChosenValue]]로 둡니다.
    3. readValue를 ValueOfReadEvent(execution, readEvent)로 둡니다.
    4. chosenLength를 chosenValue의 요소 수로 둡니다.
    5. readLength를 readValue의 요소 수로 둡니다.
    6. chosenLength ≠ readLength이면, 다음을 수행합니다.
      1. false를 반환합니다.
    7. 0(포함)부터 chosenLength(제외)까지의 구간에 있는 어떤 정수 i에 대해 chosenValue[i] ≠ readValue[i]이면, 다음을 수행합니다.
      1. false를 반환합니다.
  2. true를 반환합니다.

29.7.2 일관된 읽기

다음 알고리즘이 true를 반환하면 후보 실행 execution은 일관된 읽기를 갖습니다.

  1. SharedDataBlockEventSet(execution)의 각 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 readEvent에 대해 다음을 수행합니다.
    1. writes를 execution에서 reads-bytes-from(readEvent)으로 둡니다.
    2. byteLocation을 readEvent.[[ByteIndex]]로 둡니다.
    3. writes의 각 요소 writeEvent에 대해 다음을 수행합니다.
      1. execution에서 readEvent happens-before writeEvent이면, 다음을 수행합니다.
        1. false를 반환합니다.
      2. 자신의 메모리 범위에 byteLocation을 포함하는 WriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 value가 존재하여 execution에서 writeEvent happens-before value이고 value happens-before readEvent이면, 다음을 수행합니다.
        1. false를 반환합니다.
      3. byteLocation을 byteLocation + 1로 설정합니다.
  2. true를 반환합니다.

29.7.3 찢어짐 없는 읽기

다음 알고리즘이 true를 반환하면 후보 실행 execution은 찢어짐 없는 읽기를 갖습니다.

  1. SharedDataBlockEventSet(execution)의 각 ReadSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트 readEvent에 대해 다음을 수행합니다.
    1. readEvent.[[NoTear]]가 true이면, 다음을 수행합니다.
      1. 단언: readEvent.[[ByteIndex]]를 readEvent.[[ElementSize]]로 나눈 나머지는 0입니다.
      2. execution에서 readEvent reads-from writeEvent이고 writeEvent.[[NoTear]]가 true인 각 Memory 이벤트 writeEvent에 대해 다음을 수행합니다.
        1. readEvent와 writeEvent의 메모리 범위가 같고, value와 writeEvent의 메모리 범위가 같고 value.[[NoTear]]가 true이며 writeEvent와 value가 동일한 Shared Data Block 이벤트가 아니고 execution에서 readEvent reads-from value인 Memory 이벤트 value가 존재하면, 다음을 수행합니다.
          1. false를 반환합니다.
  2. true를 반환합니다.
참고

Shared Data Block 이벤트의 [[NoTear]] 필드는 해당 이벤트가 정수 TypedArray에 접근하여 도입되었으면 true이고, 부동 소수점 TypedArray 또는 DataView에 접근하여 도입되었으면 false입니다.

직관적으로 이 요구 사항은 정수 TypedArray를 통해 정렬된 방식으로 메모리 범위에 접근할 때 동일한 범위를 가진 다른 쓰기 이벤트와 데이터 경합이 발생하면 해당 범위에 대한 하나의 쓰기 이벤트가 “승리”해야 한다는 의미입니다. 더 정확하게는 정렬된 읽기 이벤트가 모두 동일한 범위를 갖는 서로 다른 여러 쓰기 이벤트의 바이트로 구성된 값을 읽을 수 없다는 의미입니다. 그러나 정렬된 읽기 이벤트가 범위가 겹치는 여러 쓰기 이벤트에서 읽는 것은 가능합니다.

29.7.4 순차적으로 일관된 원자적 연산

후보 실행 execution에 대해 is-memory-order-before는 EventSet(execution)의 모든 Memory 이벤트에 대한 엄격 전순서이며 다음을 만족합니다.

후보 실행에 is-memory-order-before Relation이 존재하면 순차적으로 일관된 원자적 연산을 갖습니다.

참고 3

is-memory-order-before는 EventSet(execution)의 모든 이벤트를 포함하지만, execution에서 happens-before 또는 synchronizes-with에 의해 제약되지 않는 이벤트는 순서의 어느 위치에서든 발생할 수 있습니다.

29.7.5 유효한 실행

다음이 모두 참이면 후보 실행 execution은 유효한 실행(또는 간단히 실행)입니다.

모든 프로그램에는 적어도 하나의 유효한 실행이 있습니다.

29.8 경합

실행 execution과 SharedDataBlockEventSet(execution)에 포함된 이벤트 eventA 및 eventB에 대해 다음 알고리즘이 true를 반환하면 eventA와 eventB는 경합 상태입니다.

  1. eventA와 eventB가 동일한 Shared Data Block 이벤트가 아니면, 다음을 수행합니다.
    1. execution에서 eventA happens-before eventB이고 eventB happens-before eventA인 것이 모두 참인 경우가 아니면, 다음을 수행합니다.
      1. eventA가 WriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트이고, eventB가 WriteSharedMemory 또는 ReadModifyWriteSharedMemory 이벤트이며, eventA와 eventB의 메모리 범위가 서로 분리되어 있지 않으면, 다음을 수행합니다.
        1. true를 반환합니다.
      2. execution에서 eventA reads-from eventB이거나 eventB reads-from eventA이면, 다음을 수행합니다.
        1. true를 반환합니다.
  2. false를 반환합니다.

29.9 데이터 경합

실행 execution과 SharedDataBlockEventSet(execution)에 포함된 이벤트 eventA 및 eventB에 대해 다음 알고리즘이 true를 반환하면 eventA와 eventB는 데이터 경합 상태입니다.

  1. execution에서 eventA와 eventB가 경합 상태이면, 다음을 수행합니다.
    1. eventA.[[Order]]가 seq-cst가 아니거나 eventB.[[Order]]가 seq-cst가 아니면, 다음을 수행합니다.
      1. true를 반환합니다.
    2. eventA와 eventB의 메모리 범위가 겹치면, 다음을 수행합니다.
      1. true를 반환합니다.
  2. false를 반환합니다.

29.10 데이터 경합 없음

SharedDataBlockEventSet(execution)에 데이터 경합 상태인 두 이벤트가 없으면 실행 execution은 데이터 경합이 없습니다.

프로그램의 모든 실행에 데이터 경합이 없으면 프로그램에는 데이터 경합이 없습니다.

메모리 모델은 데이터 경합이 없는 프로그램의 모든 이벤트에 대해 순차적 일관성을 보장합니다.

29.11 공유 메모리 지침

참고 1

다음은 공유 메모리를 사용하는 ECMAScript 프로그래머를 위한 지침입니다.

프로그램에 데이터 경합이 없도록 유지할 것을 권장합니다. 즉, 동일한 메모리 위치에서 동시 비원자적 연산이 발생하는 것이 불가능하도록 해야 합니다. 데이터 경합이 없는 프로그램은 각 에이전트의 평가 의미론의 각 단계가 서로 교차하여 실행되는 의미론을 갖습니다. 데이터 경합이 없는 프로그램에서는 메모리 모델의 세부 사항을 이해할 필요가 없습니다. 세부 사항은 더 나은 ECMAScript 코드를 작성하는 데 도움이 되는 직관을 형성할 가능성이 낮습니다.

더 일반적으로 프로그램에 데이터 경합이 있더라도 원자적 연산이 어떤 데이터 경합에도 관여하지 않고 경합하는 연산이 모두 같은 접근 크기를 갖는다면 예측 가능한 동작을 가질 수 있습니다. 원자적 연산이 경합에 관여하지 않게 하는 가장 간단한 방법은 원자적 연산과 비원자적 연산이 서로 다른 메모리 셀을 사용하도록 하고 서로 다른 크기의 원자적 접근이 동일한 셀에 동시에 접근하지 않도록 하는 것입니다. 사실상 프로그램은 공유 메모리를 가능한 한 강한 타입이 지정된 것처럼 취급해야 합니다. 경합하는 비원자적 접근의 순서와 타이밍에 의존할 수는 없지만, 메모리를 강한 타입이 지정된 것처럼 취급하면 경합하는 접근이 “찢어지지” 않습니다(값의 비트가 서로 섞이지 않습니다).

참고 2

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

다중 에이전트 프로그램에서 각 에이전트의 성능이 단일 에이전트 환경에서만큼 좋도록 하기 위해, 단일 에이전트 환경에서 유효한 대부분의 프로그램 변환을 다중 에이전트 환경에서도 허용하는 것이 바람직합니다. 이러한 변환은 판단하기 어려운 경우가 많습니다. 여기서는 규범적으로 받아들여지도록 의도된(메모리 모델에 의해 암시되거나 메모리 모델이 암시하는 것보다 더 강한) 프로그램 변환에 관한 몇 가지 규칙을 개략적으로 설명하지만, 완전한 목록은 아닐 가능성이 높습니다. 이러한 규칙은 is-agent-order-before Relation을 구성하는 Memory 이벤트가 도입되기 전에 이루어지는 프로그램 변환에 적용하도록 의도됩니다.

단일 에이전트와 관련된 is-agent-order-before Relation의 부분집합을 agent-order slice라고 합니다.

읽기 이벤트의 가능한 읽기 값을 모든 유효한 실행에 걸쳐 해당 이벤트의 ValueOfReadEvent가 가질 수 있는 모든 값의 집합이라고 합니다.

공유 메모리가 없을 때 유효한 agent-order slice의 모든 변환은 다음 예외를 제외하고 공유 메모리가 있을 때도 유효합니다.

  • Atomics는 변경할 수 없습니다: 프로그램 변환은 [[Order]]가 seq-cst인 Shared Data Block 이벤트를 is-agent-order-before Relation에서 제거하거나, 서로의 순서를 바꾸거나, [[Order]]가 unordered인 이벤트와 관련하여 agent-order slice 안에서 순서를 바꾸어서는 안 됩니다.

    (실제로 순서 변경 금지는 컴파일러가 모든 seq-cst 연산을 동기화로 간주하고 최종 is-memory-order-before Relation에 포함된다고 가정하도록 강제합니다. 에이전트 간 프로그램 분석이 없다면 어차피 일반적으로 그렇게 가정해야 합니다. 또한 호출 대상의 memory-order에 대한 효과를 알 수 없는 모든 호출이 seq-cst 연산을 포함할 수 있다고 컴파일러가 가정하도록 강제합니다.)

  • 읽기는 안정적이어야 합니다: 주어진 공유 메모리 읽기는 한 실행에서 하나의 값만 관찰해야 합니다.

    (예를 들어 프로그램에서 의미상 하나의 읽기가 여러 번 실행되면 프로그램은 그 뒤 읽힌 값 중 하나만 관찰할 수 있습니다. rematerialization으로 알려진 변환은 이 규칙을 위반할 수 있습니다.)

  • 쓰기는 안정적이어야 합니다: 공유 메모리에 대한 모든 관찰 가능한 쓰기는 실행의 프로그램 의미론에서 비롯되어야 합니다.

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

  • 가능한 읽기 값은 비어 있지 않아야 합니다: 프로그램 변환으로 인해 공유 메모리 읽기의 가능한 읽기 값이 빈 집합이 되어서는 안 됩니다.

    (직관과 달리 이 규칙은 사실상 쓰기에 대한 변환을 제한합니다. 메모리 모델에서 쓰기는 읽기 이벤트가 읽을 수 있다는 점에서 효력을 갖기 때문입니다. 예를 들어 두 seq-cst 연산 사이에서 쓰기를 이동하고 병합하며 때로는 순서를 바꿀 수 있지만, 변환은 위치를 업데이트하는 모든 쓰기를 제거해서는 안 됩니다. 일부 쓰기는 보존해야 합니다.)

계속 유효한 변환의 예로는 동일한 위치에서 여러 비원자적 읽기를 병합하는 것, 비원자적 읽기의 순서를 바꾸는 것, 추측적 비원자적 읽기를 도입하는 것, 동일한 위치에 대한 여러 비원자적 쓰기를 병합하는 것, 서로 다른 위치에 대한 비원자적 쓰기의 순서를 바꾸는 것, 종료 여부에 영향을 미치더라도 비원자적 읽기를 루프 밖으로 끌어올리는 것이 있습니다. 일반적으로 별칭이 있는 TypedArray는 위치가 서로 다름을 증명하기 어렵게 한다는 점에 유의하시기 바랍니다.

참고 3

다음은 공유 메모리 접근을 위한 기계어 코드를 생성하는 ECMAScript 구현자를 위한 지침입니다.

메모리 모델이 ARM 또는 Power보다 약하지 않은 아키텍처에서는 비원자적 store와 load를 대상 아키텍처의 단순 store 및 load로 컴파일할 수 있습니다. 원자적 store와 load는 순차적 일관성을 보장하는 명령으로 컴파일할 수 있습니다. 그러한 명령이 없으면 단순 store 또는 load의 양쪽에 배리어를 배치하는 것과 같은 메모리 배리어를 사용해야 합니다. read-modify-write 연산은 x86의 LOCK 접두사가 붙은 명령, ARM의 load-exclusive/store-exclusive 명령, Power의 load-link/store-conditional 명령과 같은 대상 아키텍처의 read-modify-write 명령으로 컴파일할 수 있습니다.

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

  • 프로그램의 모든 원자적 연산은 필요하다고 가정합니다.
  • 원자적 연산은 서로 또는 비원자적 연산과 절대 순서를 바꾸지 않습니다.
  • 함수는 항상 원자적 연산을 수행한다고 가정합니다.
  • 원자적 연산은 더 큰 데이터에 대한 read-modify-write 연산으로 구현하지 않으며, 플랫폼에 적절한 크기의 원자적 연산이 없으면 non-lock-free 원자적 연산으로 구현합니다. (우리는 이미 모든 플랫폼이 관심 있는 모든 크기의 일반 메모리 접근 연산을 갖는다고 가정합니다.)

단순한 코드 생성은 다음 패턴을 사용합니다:

  • 일반 load 및 store는 단일 load 및 store 명령으로 컴파일합니다.
  • lock-free 원자적 load 및 store는 전체(순차적 일관성) fence, 일반 load 또는 store, 전체 fence로 컴파일합니다.
  • lock-free 원자적 read-modify-write 접근은 전체 fence, 원자적 read-modify-write 명령 시퀀스, 전체 fence로 컴파일합니다.
  • non-lock-free 원자적 연산은 spinlock 획득, 전체 fence, 일련의 비원자적 load 및 store 명령, 전체 fence, spinlock 해제로 컴파일합니다.

메모리 범위에 대한 원자적 연산이 비원자적 쓰기 또는 다른 크기의 원자적 연산과 경합하지 않는 한 이 매핑은 올바릅니다. 그러나 이것이면 충분합니다. 메모리 모델은 경합에 관여하는 원자적 연산을 사실상 비원자적 상태로 강등합니다. 반면 단순 매핑은 상당히 강합니다. 원자적 연산을 순차적으로 일관된 fence로 사용할 수 있게 하는데, 이는 메모리 모델이 실제로 보장하지는 않습니다.

메모리 모델의 제약을 따르는 한 이러한 기본 패턴에 대한 로컬 개선도 허용됩니다. 예를 들면:

  • 중복 fence를 제거하는 명백한 플랫폼 의존적 개선이 있습니다. 예를 들어 x86에서는 lock-free 원자적 load 및 store 주변의 fence를 항상 생략할 수 있으며 store 뒤의 fence만 예외입니다. lock-free read-modify-write 명령은 모두 LOCK 접두사가 붙은 명령을 사용하므로 fence가 필요하지 않습니다. 많은 플랫폼에는 여러 강도의 fence가 있으며 특정 컨텍스트에서는 순차적 일관성을 깨뜨리지 않고 더 약한 fence를 사용할 수 있습니다.
  • 대부분의 현대 플랫폼은 ECMAScript 원자적 연산에 필요한 모든 데이터 크기에 대해 lock-free 원자적 연산을 지원합니다. non-lock-free 원자적 연산이 필요한 경우 원자적 연산 본문 주변의 fence를 보통 lock과 unlock 단계에 통합할 수 있습니다. non-lock-free 원자적 연산의 가장 간단한 해결책은 SharedArrayBuffer마다 하나의 lock word를 두는 것입니다.
  • 일부 코드 분석이 필요한 더 복잡한 플랫폼 의존적 로컬 개선도 있습니다. 예를 들어 연속된 두 fence는 하나의 fence와 같은 효과를 갖는 경우가 많으므로 두 원자적 연산이 연속되는 코드를 생성한다면 하나의 fence만으로 분리할 수 있습니다. x86에서는 store 뒤의 fence가 store를 이후 load와 분리하기 위해서만 필요하므로 원자적 store를 분리하는 단일 fence조차 생략할 수 있습니다.