Научная статья на тему 'О корректности компиляции подмножествa обещающей модели памяти в аксиоматическую модель ARMv8. 3'

О корректности компиляции подмножествa обещающей модели памяти в аксиоматическую модель ARMv8. 3 Текст научной статьи по специальности «Компьютерные и информационные науки»

CC BY
107
21
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
МНОГОПОТОЧНОСТЬ / КОРРЕКТНОСТЬ КОМПИЛЯЦИИ / COMPILATION CORRECTNESS / СЛАБЫЕ МОДЕЛИ ПАМЯТИ / WEAK MEMORY MODELS / ARM / СЕМАНТИКА / SEMANTICS / CONCURRENCY

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Подкопаев Антон Викторович, Лахав Ори, Вафеядис Виктор

«Обещающая» модель памяти является перспективным решением проблемы задания семантики многопоточности в контексте императивных языков программирования, таких как С/C++ и Java. Естественным требованием, которое ставится перед моделью памяти языка программирования, является наличие эффективной и корректной схемы компиляции для распространенных процессорных архитектур. Ранее для обещающей модели была показана корректность компиляции в архитектуры x86, Power и для операционной модели памяти ARMv8 POP. В статье приведено доказательство корректности компиляции в аксиоматическую модель ARMv8.3. В доказательстве использован новый метод обхода исполнений аксиоматических моделей памяти. Этот метод является более общим, чем использованные ранее подходы, и может использоваться в последующих доказательствах корректности компиляции из обещающей модели памяти.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

On Compilation Correctness for a Subset of a Promising Memory Model to the ARMv8.3 Memory Model

A ‘promising’ memory model is an auspicious solution to the problem of defining semantics for an imperative language with concurrency, such as C/C++ or Java. An essential requirement for such a memory model is the existence of an effective and correct compilation scheme from the language to its target platforms. There are compilation correctness proofs from the promising model to x86 and Power as well as to an operational model ARMv8 POP. This paper presents such proof for an axiomatic memory model for ARMv8.3. In the proof, we use a new method of execution traversal, which might be used in other compilation correctness proofs for the promising memory model.

Текст научной работы на тему «О корректности компиляции подмножествa обещающей модели памяти в аксиоматическую модель ARMv8. 3»

Программное обеспечение вычислительных, телекоммуникационных и управляющих систем

DOI: 10.18721/JCSTCS.10405 УДК 004.4'422

о корректности компиляции подмножествд обещающей

МОДЕЛИ ПАМЯТИ В АКСИОМАТИЧЕСКУЮ МОДЕЛЬ ARMv8.3

А.В. Подкопаев1, O. Лахав2, В. Вафеядис3

'Санкт-Петербургский государственный университет, Санкт-Петербург, Российская Федерация;

2Тель-Авивский университет, Тель-Авив, Израиль;

3Институт имени Макса Планка: Программные системы, Кайзерслаутерн, Германия

«Обещающая» модель памяти является перспективным решением проблемы задания семантики многопоточности в контексте императивных языков программирования, таких как C/C++ и Java. Естественным требованием, которое ставится перед моделью памяти языка программирования, является наличие эффективной и корректной схемы компиляции для распространенных процессорных архитектур. Ранее для обещающей модели была показана корректность компиляции в архитектуры x86, Power и для операционной модели памяти ARMv8 POP. В статье приведено доказательство корректности компиляции в аксиоматическую модель ARMv8.3. В доказательстве использован новый метод обхода исполнений аксиоматических моделей памяти. Этот метод является более общим, чем использованные ранее подходы, и может использоваться в последующих доказательствах корректности компиляции из обещающей модели памяти.

Ключевые слова: многопоточность; корректность компиляции; слабые модели памяти; ARM; семантика.

Ссылка при цитировании: Подкопаев А.В., Лахав О., Вафеядис В. О корректности компиляции подмножества обещающей модели памяти в аксиоматическую модель ARMv8.3 // Научно-технические ведомости СПбГПУ. Информатика. Телекоммуникации. Управление. 2017. Т. 10. № 4. С. 51-69. DOI: 10.18721/JCSTCS.10405

ON COMPILATION CORRECTNESS FOR A SUBSET OF A PROMISING MEMORY MODEL TO THE ARMv8.3 MEMORY MODEL

A.V. Podkopaev ', O. Lahav2, V. Vafeiadis 3

'St. Petersburg State University, St. Petersburg, Russian Federation;

2Tel Aviv University, Tel Aviv, Israel;

3Max Planck Institute for Software Systems, Kaiserslautern, Germany

A 'promising' memory model is an auspicious solution to the problem of defining semantics for an imperative language with concurrency, such as C/C++ or Java.

An essential requirement for such a memory model is the existence of an effective and correct compilation scheme from the language to its target platforms. There are compilation correctness proofs from the promising model to x86 and Power as well as to an operational model ARMv8 POP. This paper presents such proof for an axiomatic memory model for ARMv8.3. In the proof, we use a new method of execution traversal, which might be used in other compilation correctness proofs for the promising memory model.

Keywords: concurrency; compilation correctness; weak memory models; ARM; semantics.

Citation: Podkopaev A.V., Lahav O., Vafeiadis V. On compilation correctness for a subset of a Promising memory model to the ARMv8.3 memory model. St. Petersburg State Polytechnical University Journal. Computer Science. Telecommunications and Control Systems, 2017, Vol. 10, No. 4, Pp. 51-69. DOI: 10.18721/JCSTCS.10405

1. Введение

Параллельные вычисления давно являются естественной частью прикладного программирования. Несмотря на это, до сих пор ведется активная работа по разработке формальных семантик для языков программирования и процессоров с много-поточностью, или моделей памяти [4, 7, 9, 13, 17, 20]. Под моделью памяти можно понимать функцию, которая по программе выдает множество возможных поведений, т. е. состояний системы, исполняющей программу, после выполнения программы. Чаще всего речь идет об оперативной памяти и значении регистров.

Наиболее известная модель памяти для многопоточных программ — модель последовательной консистентности (sequential consistency — SC) [14]. Любое поведение программы в данной модели может быть получено как некоторое попеременное исполнение команд разных потоков на одноядерном процессоре. К сожалению, такая модель памяти не способна описывать поведения, которые наблюдаются у программ, оптимизированных компилятором и запущенных на современных процессорах. Так, аналог следующей программы, написанный на C++, обработанный компилятором gcc и запущенный на процессоре семейства x86, может завершиться с результатом a = b = 01:

[x] := 1; a := [у]; //0

[у] := 1; (SB) b := [x]; //0 (SB)

1 Здесь и во всех последующих примерах мы предполагаем, что х, у являются различными адресами в памяти, в которые изначально записаны нули, а a, Ь являются локальными переменными (регистрами).

Данное поведение, которое в литературе именуется буферизацией записи (store buffering — SB) [23], не может быть получено поочередным исполнением потоков, т. к. при таком исполнении одна из записей ([x] := 1 и [у] := 1) должна случиться раньше любого из чтений (a := [у] и b := [x]). Поведение параллельной программы, которое выходит за рамки последовательной конси-стентности, называют слабым. В свою очередь, слабая модель памяти — это модель, допускающая слабые поведения.

Первая причина появления слабых поведений у многопоточных программ — компиляторные оптимизации. Например, компилятор gcc с включенным режимом оптимизации переставляет независимые обращения к памяти в рамках планирования кода. Соответствующая перестановка наблюдается для аналогов программы SB. Второй причиной является оптимизирующее поведение современных процессоров: процессоры могут исполнять инструкции не по порядку, а обращения к памяти могут быть переупорядочены за счет использования локальной памяти ядра.

Компиляторные и процессорные оптимизации стали широко применяться задолго до появления многопоточных процессоров; они дают существенный прирост производительности. Как следствие, большинство экспертов сходится во мнении, что лучше разрешить слабые поведения программ, чем запретить оптимизации.

В последние годы были созданы сла-

бые модели памяти для самых распространенных архитектур: x86 [23], Power [3, 21] и ARM [9, 20]. Эти модели разработаны в сотрудничестве с компаниями, создающими данные архитектуры. Они описывают не только поведения, наблюдаемые с помощью тестирования существующих процессоров, но и оставляют пространство для оптимизаций, планируемых в новых версиях архитектур.

Существуют слабые модели и для распространенных языков программирования, таких как Java [17] и C/C++ [7]. Данные модели входят в соответствующие стандарты языков. Источником слабых поведений в контексте моделей памяти языков программирования являются оптимизации, которые создатели стандарта хотят разрешить для реализации в компиляторах, а также слабые поведения процессоров, которые являются целевыми для языков и их платформ.

У существующих моделей памяти Java и C/C++ имеются недостатки: разрешение «значений из воздуха» (out-of-thin-air values) [8], некорректность широко применяемых на практике оптимизаций [24] и слишком строгие требования к семантике процессоров в контексте используемых схем компиляции [13, 16]. Недавно была представлена новая модель памяти [10], т. н. «обещающая» (promising) модель памяти, призванная решить упомянутые проблемы.

Потенциально, обещающая модель может стать частью стандартов языков Java и C/C++; обсуждение этого уже начинается в соответствующих комитетах. Для того чтобы обещающая модель смогла стать частью стандартов Java и C/C++, должна быть доказана корректность существующих (эффективных) схем компиляции [2] из обещающей модели в модели памяти основных процессорных архитектур (x86, Power, ARM).

Под корректностью компиляции схемы compile из модели A в модель B мы подразумеваем следующее:

MProg е A.[compile(Prog)]в £ [Prog]л,

а именно, что для любой программы Prog в языке определения модели A и ее ском-

пилированной версии compile(Prog) в языке определения модели B выполняется, что любое поведение compile(Prog) в A также является поведением Prog в A. Это естественное требование, позволяющее программисту быть уверенным в том, что написанная им программа работает только так, как описано в стандарте языка ее реализации, и никак иначе.

В работе [10] доказана корректность компиляции из обещающей модели в модели x86-TSO [23] и Power [3]; в [19] - корректность компиляции из подмножества обещающей модели в операционную модель памяти ARMv8 POP [9]. В апреле 2017 года была предложена новая модель памяти для архитектуры ARM, модель ARMv8.3 [1]. Как следствие, актуальна задача доказательства корректности компиляции в новую модель ARMv8.3. Решению этой задачи посвящена данная статья.

В статье показана корректность компиляции для подмножества обещающей модели без сертификации [10] в модель ARMv8.3. Рассматриваемое подмножество состоит из ослабленных (relaxed) обращений памяти (записей и чтений), высвобождающих (release) и приобретающих (acquire) барьеров памяти. Несмотря на то, что мы не доказываем корректность компиляции для всей модели, мы уверены, что доказательство может быть расширено, что является нашей дальнейшей работой. Но в связи с тем, что текущее доказательство достаточно объемное и сложное, а результат является базовым для дальнейшей работы, мы представляем доказательство корректности компиляции для подмножества модели.

Распространенным способом показать корректность компиляции из абстрактной машины A в машину B является доказательство симуляции между ними [18]. Для этого определяется отношение симуляции, связывающее состояния машин, и доказывается, что если текущие состояния машин A и B связаны отношением симуляции, и машина B делает шаг, то машина A может сделать ноль и более шагов так, чтобы новые состояния машин были также связаны отношением симуляции. Напрямую данный подход не применим для

доказательства корректности компиляции из обещающей модели в модель ARMv8.3, т. к. модель памяти ARMv8.3 является аксиоматической, т. е. семантика конкретной программы в этой модели есть множество графов, каждый из которых представляет некоторый конкретный запуск программы. Это означает, что в модели ARMv8.3 запуск программы определяется сразу и целиком, а не по шагам, как это происходит в обещающей модели, заданной в виде абстрактной машины. Чтобы преодолеть данное ограничение, мы разработали способ обхода графов модели ARMv8.3, который представлен в виде операционной семантики. Этот обход и используется при доказательстве симуляции между обещающей и ARMv8.3 моделями. Данный метод может применяться для доказательства корректности компиляции из обещающей в аксиоматические модели памяти и является более общим, чем использованные ранее методы (см. раздел 9), базирующиеся на особых свойствах целевых аксиоматических моделей.

2. Обещающая модель на примерах

В этом разделе мы приведем неформальное описание обещающей модели памяти [10] на нескольких примерах.

2.1. Базовые элементы обещающей модели. Рассмотрим программу MP (передача сообщения, message passing):

[x] := 1; [у] := 1;

a := [у]; //1 b := [x]; //0

(MP)

Эта программа является упрощенным вариантом шаблона, используемого для передачи данных между потоками. Первый поток записывает данные в локацию х и потом выставляет флаг (локация у), что данные подготовлены; в свою очередь, второй поток проверяет флаг, а потом читает данные. Модель последовательной конси-стентности гарантирует, что если второй поток увидел, что флаг выставлен (а = 1), то он увидит и подготовленные данные (Ь = 1). Данное заключение неверно для многих слабых моделей, в том числе для обещающей и лкму8.3 моделей. Рассмотрим, как слабое поведение а = 1, Ь = 0 достигается при исполнении МР в обещаю-

щей модели памяти.

Обещающая модель памяти задана операционным способом. Это означает, что существует некоторая абстрактная машина, связанная с моделью, которая исполняет программы по шагам, и множество всех конечных состояний этой машины для некоторой программы является семантикой программы в рамках данной модели. Далее мы будем использовать термин обещающая машина для обозначения упомянутой абстрактной машины.

Состояние обещающей машины включает в себя множество сообщений, которое называется памятью. Мы считаем, что память перед исполнением программы содержит по сообщению со значением ноль для каждой локации. Так, для приведенной выше программы начальная память будет состоять из двух сообщений:

Мш = {<х : 0 @ 0), <у : 0 @ 0)}.

Сообщение — это тройка из локации, значения и метки времени (Ите81атр, в приведенном примере — 0 после @). Метки времени используются для упорядочивания сообщений, связаных с одной и той же локацией.

Теперь покажем некоторое исполнение МР, которое приводит к а = 1, Ь = 0. Сначала левый поток программы МР выполняет обе записи, после чего память обещающей машины содержит четыре сообщения:

М = {<х : 0 @ 0), <у : 0 @ 0), <х : 1 @ 5), <у : 1 @ 3.5)}.

Заметим, что метки времени новых сообщений должны быть больше, чем метки инициализирующих сообщений, но между собой они никак не связаны и могли бы совпасть — выбор меток времени в обещающей машине недетерминирован. Так, конкретное значение метки времени сообщения не имеет значения, важно лишь соотношение между метками сообщений, относящихся к той же локации.

Следующим шагом в выбранном запуске, приводящем к а = 1, Ь = 0, правый поток выполняет чтение из локации у. Обещающая машина разрешает потоку произвести чтение из любого сообще-

ния локации y в памяти, {y : 0 @ 0) или (y : 1@3.5). Поскольку мы хотим получить a = 1, правый поток производит чтение из сообщения с меткой времени 3.5. Далее, если бы поток еще раз читал из локации y, ему было бы запрещено производить чтение из сообщений с меткой времени меньше 3.5. Интуитивно, если поток увидел более новое значение, то он больше не должен видеть более старое значение.

Для того чтобы обеспечить данное свойство, у каждого потока обещающей машины есть фронт — взгляд (view, viewfront) на память. Это функция, которая по локации возвращает метку времени последнего сообщения, относящегося к этой локации, которое было увидено потоком. Так, перед тем как левый поток выполнил инструкции записи, фронты левого и правого потоков указывают на изначальные сообщения:

T1 .view cur = [x @0, y @ 0] T2.viewcm = [x @ 0, y @ 0].

После того как левый поток выполнил обе инструкции записи, он больше не может видеть более старые значения. Это гарантируется тем, что его фронт по каждой локации увеличился и стал равным метки времени соответствующего сообщения:

T1 .view cur = [x @ 5, y @3.5] T2.view cur = [x @0, y @0].

После того как правый поток выполнил чтение из сообщения y с меткой времени 3.5, его фронт увеличивается по y (становится равным максимуму из старого значения по y, 0, и метки времени сообщения, 3.5):

T1 .view cur = [x @ 5, y @ 3.5] T2.viewcm = [x @ 0, y @3.5].

В программе остается невыполненной только второе чтение из локации x в правом потоке. Это чтение правый поток может совершить из сообщения с меткой времени 0, так как T2.viewcm(x) < 0.

2.2 Барьеры памяти. Как же гарантировать передачу сообщения между потоками в программе MP, а именно запретить результат a = 1, b = 0? Для этого нужно до-

бавить в программу MP несколько барьеров памяти (memory fences) — специальных инструкций, которые в реальных системах запрещают некоторые оптимизации со стороны компилятора и процессора. Например, высвобождающий (release, rel) барьер используется для того, чтобы запретить переупорядочивание инструкций записи, а приобретающий (acquire, acq) — инструкций чтения. Так, версия программы MP с высвобождающим и приобретающим барьерами в левом и правом потоках соответственно не может завершиться с результатом a = 1, b = 0:

[x] := 1; a := [y]; //1

fence(rel); fence(acq); (MP-rel-acq)

[y] := 1; b := [x]; //0

Для поддержки барьеров памяти нужно внести некоторые изменения в память обещающей машины — у каждого сообщения появляется свой фронт. Интуитивно, фронт сообщения msg указывает на те сообщения в памяти, которые становятся видны потоку, который читает из сообщения msg и после этого выполняет приобретающий барьер. Так, после выполнения инструкций записи левым потоком, память машины выглядит следующим образом:

M = {<x : 0 @ 0,[x @ 0]), <y : 0 @ 0,[y @ 0]), <x : 1 @ 5, [x @ 5, y @ 0]), <x : 1 @ 3.5, [x @ 5, y @3.5]),

В данном случае интерес представляет фронт второго сообщения локации y — [x@5, y@3.5]. После того как левый поток выполнит чтение из этого сообщения и исполнит приобретающий барьер, его фронт будет равен [x@5, y@3.5], что запретит ему чтение из сообщения <х : 0 @ 0,[x @ 0]).

Для поддержки барьеров нам также нужно добавить по два дополнительных фронта потокам — высвобождающий и приобретающий. Рассмотрим тот же запуск программы MP. Перед исполнением программы все три фронта левого потока равны [x@0, y@0]. После выполнения инструкции записи в локацию x, базовый и приобретающий фронты левого потока соответственным образом растут, тогда как высвобож-

дающий остается без изменений: T1.viewcm = [х @5, y @0] T1.viewacq = [х @5, y @0] T1.viewrel = [х @0, y @ 0].

После выполнения барьера высвобождающий фронт становится равным базовому:

T1.viewcm = [х @5, y @0] T1.viewacq = [х @5, y @0] T1.viewrel = [х @5, y @0].

Теперь, когда левый поток добавляет новое сообщение локации y в память, фронт этого сообщения равен высвобождающему фронту потока, увеличенному на [y@3.5] — метку времени самого сообщения.

Перейдем к правому потоку. Его фронты также изначально равны [х@0, y@0]. После того как поток читает из сообщения (y :1@3.5,[х @5, y @3.5]>, его базовый фронт увеличивается на метку времени самого сообщения, [y@3.5], тогда как приобретающий — на фронт сообщения:

T2.viewcur = [х @0, y @ 3.5]

T2.viewacq = [х @ 5, y @ 3.5]

T2.viewrel = [х @0, y @0].

Выполнение барьера приравнивает базовый фронт к приобретающему:

T2.viewсш = [х @5, y @3.5] T2.viewacq = [х @ 5, y @ 3.5] T2.viewrel = [х @0, y @ 0].

После этого правый поток не может прочитать сообщение (х : 0 @ 0,[х @ 0^, так как T2.viewcur > 0.

2.3 Механизм обещаний. Как мы только что убедились, обещающая модель позволяет задавать поведения многопоточной программы, выходящие за пределы простого попеременного исполнения потоков над обычной памятью. Это достигается за счет устройства памяти обещающей машины, которая может хранить более одного сообщения для каждой локации. Тем не менее этого недостаточно для выражения всех поведений, наблюдаемых на современных архитектурах. Например, следующая програм-

ма LB (load buffering) может завершиться с результатом a = b = 1:

= [х]; //1 [y] := 1;

b := [y]; //1 [х] := 1

(LB)

Такое поведение не может наблюдаться в версии обещающей машины, описанной выше, т. к. для получения a = b = 1 первой инструкцией должна выполниться одна из инструкций записи. Архитектурные модели памяти, такие как ARMv8 POP [9], явным образом разрешают исполнение инструкций не по порядку (out of order execution). В обещающей модели нет такой возможности. Зато любой поток в любой момент исполнения программы может пообещать сделать запись некоторого сообщения в память в будущем. Например, первым шагом обещающей модели для программы LB может быть обещание левым потоком сделать запись в локацию y. После этого в памяти машины появляется соответствующее сообщение:

M = {<х : 0 @ 0,[х @ 0]), <y : 0 @ 0,[y @ 0]), (y : 1@2, [х @0, y @2])}.

Теперь правый поток может выполнить свои инструкции: сначала прочитать из нового сообщения, а потом выполнить инструкцию записи обычным способом, без обещаний. После этого память будет содержать четыре сообщения:

M = {(х : 0 @ 0,[х @ 0]), (y : 0 @ 0,[y @ 0]),

(y : 0 @0, [y @ 0]), (х : 1@2,[х @2, y @ 0])},

и левый поток может прочитать из сообщения (х : 1 @ 2} и после этого выполнить обещание записать сообщение (y : 1@2,[y@2]) в память.

Как чтение из памяти потоком ограничено его базовым фронтом, так и для обещаний существует ограничение: поток не может читать из сообщений, которые он пообещал, но еще не выполнил. Для поддержания этого инварианта у каждого потока имеется компонента состояния, которая хранит множество еще не выполненных обещаний.

3. Модель ARMv8.3 на примерах

Модель памяти ARMv8.3 представляет семантику программы как множество гра-

a

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

фов, каждый из которых соответствует некоторому конкретному запуску программы [20]. Так, например, для программы МР, которую мы уже рассматривали в разделе 2, исполнение программы с результатом a = 1, Ь = 0 выглядит следующим образом:

В графовом представлении каждый узел представляет некоторое событие (event) — операцию над памятью. Событие может быть записью (например, W(x, 1)), чтением (например, R(y, 1)) или барьером памяти (например, F(acq)). В представленном выше графе W(x, 0) и W(y, 0) обозначают инициализацию, а остальные вершины — операции левого и правого потоков. Далее в статье мы будем использовать W, R и F как для обозначения меток конкретных событий, так и для обозначения подмножества событий соответствующего типа.

Ребра в графе используются для обозначения различных отношений между событиями. Стрелки без подписи обозначают po — отношение программного порядка (program order). Программный порядок связывает инициализирующие записи со всеми остальными событиями в графе, а также является полным порядком на событиях одного и того же потока2.

События записи в одну и ту же локацию тотально упорядочены с помощью co — отношения согласованности (coherence order). Это отношение выполняет ту же функцию, что и метки времени в рамках обещающей машины.

Отношение rf («читает из», reads from) связывает события записи с событиями чтения, причем у каждого чтения должна существовать одна и только одна запись, с

которой оно (чтение) связано данным отношением, и целевые локации и значения событий должны совпадать.

Отношение rb («читает ранее», reads before) является производным от rf и co:

rb = rf-1;co,

и связывает чтение a с записью, которая co-следует за записью, из которой читает a. Здесь и далее композиция отношений ';' задается так:

A; B = {<a, b) | 3c.<a, с) e A л <c, b) e B}.

Как и в случае обещающей семантики, добавление в программу MP барьеров памяти запрещает результат a = 1 и b = 0:

[x] := 1; fence(sy); [y] := 1;

a := [y]; //1

fence(ld); (MP-sy-ld) b := [x]; //0

Данная программа не соответствует один в один программе МР-ге1-асд из раздела 2, но является результатом компиляции МР-ге1-асд в ассемблер лкму8.3.

Каким образом модель лкму8.3 запрещает поведение a = 1 и Ь = 0 для программы МР-8у-Ш? Рассмотрим граф, который соответствует данному поведению:

2 Программный порядок транзитивен, но в приведенном графе мы оставили только непосредственные ребра.

В этом графе мы опустили инициализирующие записи и добавили отношение барьеров bob (barrier-ordered-before), заданное так:

bob 4 po; [F(sy)] u [F(sy)]; po u [R]; po;[F(ld)] u [F(ld)];po,

где [A] = {(a, a) | a e A}. Кроме того, мы заменили ребра отношений rf и rb ребрами отношений rfe и rbe, где e (external) означает, что соответствующее отношение связывает только события разных потоков.

В приведенном графе есть цикл из ребер bob, rfe и rbe, что противоречит одной из аксиом, которая используется в модели ARMv8.3 для определения ARM-согласованного исполнения (подробнее в

разделе 6). Таким образом граф не является ARM-согласованным, и поведение a = 1 и b = 0 запрещено для программы MP-sy-ld в рамках модели.

4. Структура доказательства корректности компиляции

Центральным результатом данной работы является доказательство следующей теоремы.

Теорема 4.1. Для любых программы Prog, результата ее компиляции ProgARM и ARM-согласованного исполнения G программы ProgARM существует исполнение Prog обещающей машиной, т. ч. финальное состояние памяти машины совпадает с состоянием памяти G.

Здесь под «финальным состоянием памяти» мы понимаем значение последних записей в локации, т. е. записей с наибольшими метками времени, в случае обещающей семантики, и событий-максимумов по отношению частичного порядка G.co, в случае ARMv8.3. Обещающая и ARMv8.3 модели памяти заданы в существенно разных стилях и имеют множество концептуальных отличий. Для доказательства теоремы 4.1 необходимо преодолеть данные различия.

Главным отличием является то, что обещающая модель памяти представлена опе-рационно, в терминах шагов некоторой абстрактной машины, тогда как аксиоматическая модель памяти ARMv8.3 задает семантику конкретного исполнения программы в виде графа, для которого выполняются определенные свойства. Для решения данной проблемы мы вводим операционную семантику обхода графа ARM-согласованного исполнения, которому обещающая машина может следовать (раздел 7).

Другим существенным отличием является то, как модели запрещают «плохие» поведения программ: например, чтение потоком старой записи в х, если поток уже читал из более новой записи в х. Для этого модель памяти ARMv8.3 использует аксиомы, которые проверяют отсутствие циклов в графе, тогда как в обещающей модели для этого используются фронты потоков и сообщений. Для того чтобы обойти

описанное различие между моделями, мы вводим дополнительные отношения на вершинах ARM согласованного исполнения, которые тесно связаны с фронтами обещающей модели (раздел 8). Используя описанные выше идеи, мы доказываем корректность компиляции между обещающей и ARMv8.3 моделями с помощью симуляции (раздел 8).

5. Формальное определение обещающей модели

В этом разделе мы формально описываем подмножество обещающей модели памяти [10].

Состояние обещающей машины StatePromise является парой (TS, M}. При этом M с Msg — это память машины, являющаяся множеством сообщений вида (l : val @ т >, view): Msg, состоящих из целевой локации, l: Loc, значения, val : Val, метки времени, т: Time = Q, и фронта сообщения, view : V = Loc ^ Time. TS : Tid ^ TS — это функция, которая по идентификатору потока возвращает его состояние. Состояние потока, TS : TS, является тройкой (ст, V, P); ст — локальное состояние потока в рамках описанной ниже помеченной системы переходов (labeled transition system — LTS); V = (view , view , view ,) :

^ /' \ cur' acq' rel/

Vx Vk V — базовый, приобретающий и высвобождающий фронты потока; P с Msg — множество сообщений, которые были обещаны потоком, но еще не выполнены.

Как было отмечено выше, обещающая модель памяти задана на помеченной системе переходов, а не на программах непосредственно. Сами метки в системе переходов могут быть: (1) операцией чтения из локации l значения v(R(l, v)); (2) операцией записи в локацию l значения v(W(l, v)); (3) барьером памяти с модификатором fmod (F(fmod)) или (4) внутреннему переходу (е). Последний тип описывает действия потока, которые не затрагивают память, например, присваивание в локальную переменную и исполнение оператора условного перехода.

Перед исполнением любой программы память обещающей машины состоит из записей во все локации, Mmit = {<l : 0 @ 0, viewMt \í e Loc}, где

(read-helper)

cur(i) < t cur' = cur U [Шт] acq'

acq U cur

(cur, acq. rel)-> (cur, acq , rel)

(read)

<7

(i : v@t. b) g m

R(e,v)

->• cr

((<j,V,P),M)

Promise tid

(acq-fence)

F(acq) / СГ -» <7

((a, (cur. acq. rel), P), M) , )

Promise ttd

((a', (acq, acq, rel), P), M)

(silent)

СГ A cr'

«a.V,P),A/>

Promise tid

(global)

>«<r',V,P),M) (TS,M)

(write-helper)

cur(£) < i i? = rel U [Шт] cur' = cur U [Шт] acq' = acq U cur'

(cur. acq. rel) -> (cur. acq , rel)

(write)

W(£,v)

a

» cr

m = (Z : i?)

W, тегу'. (£ : г/@т, mew') £ M M' M U {m} Р' = Р\ {m}

((a,V,P),M)

Promise tid

(rel-fence)

F(rel) , a-> a

{{(7, (cur. acq. rel), 0), M) . >

Promise tid

((cr'.( cur.acq.cur),0),M)

(promise)

Vf, mew. (ra.i : f;@m.r, view) ^ M P' = PU {m} M' = M U {???}

W. 3i>, view. (£ : v@m.view(£), view) G M'

Promise tid

{(a.V.P)AI)

» (TS',M')

Promise tid

>((<7.V,P'),M')

Promise

> (Г5[Ш н^ Г5'], M')

Переходы обещающей машины

viewinit = М.0. В целом, изначальное состояние машины выглядит следующим образом:

plnlt 4 {Xtid.(а = alnlt, V = (viewinlt, viewinlt, viewinlt ),P = 0),Mlnlt).

Перейдем к описанию шагов исполнения в обещающей машине (см. рисунок). Единственным правилом, оперирующим на всем состоянии машины, является правило (GLOBAL). В версии обещающей машины без сертификации [10] оно не несет смысловой нагрузки, а только позволяет записать остальные правила локально для потоков (см. описание ниже).

Выполнение приобретающего барьера (ACQ-FENCE) делает базовый фронт пото-

ка v/ewcur равным приобретающему фронту потока v/ewacq, где приобретающий фронт — объединение фронтов сообщений3, прочитанных потоком к этому моменту, и меток времени записей, произведенных потоком.

Выполнение высвобождающего барьера (REL-FENCE) делает высвобождающий фронт потока v/ewrel равным базовому фронту потока viewcur. Как результат, фронты сообщений, которые поток добавит в память после выполнения высвобождающего барьера, будут содержать информацию о записях, произведенных потоком

3 Под объединением фронтов A и B мы понимаем фронт C = Х£. max(A(/), B(/)).

до выполнения высвобождающего барьера. Кроме того, переход, соответствующий высвобождающему барьеру, имеет дополнительное ограничение: в момент его выполнения у потока не должно быть невыполненных обещаний, т. е. P должен быть равен пустому множеству.

Чтение из локации l (READ) поток выполняет следующим образом. Поток выбирает некоторое сообщение (l : val @ т, view) из памяти машины. При этом метка времени записи т должна быть больше, чем значение базового фронта потока viewcur(l), а само сообщение не должно быть среди обещанных, но не выполненных потоком сообщений P. Это правило увеличивает базовый фронт потока на [l @ т], а приобретающий — на view.

Обещание записи {l : val @ т, view^j (PROMISE) добавляет сообщение в память машины и во множество P. При этом целевая локация l и значение val могут быть произвольными, т. е. они не зависят от локального состояния потока ст. Метка времени должна быть уникальной среди сообщений локации l, уже находящихся в памяти. Этот переход не обновляет фронты потока. Также легко заметить, что этот переход существенно недетерминирован, однако он ограничен сертификацией.

Выполнение обещания {l : val @ т, view^j (WRITE) удаляет сообщение из множества невыполненных обещаний P. При этом данный переход должен быть возможен в рамках помеченной системы переходов локальных состояний, т. е. должно существовать состояние ст', такое что ст связан с ст' переходом W(l, val). При этом метка времени т должна быть больше значения базового фронта view cur(l), и фронт сообщения view должен быть равен композиции высвобождающего фронта viewKl и [l @ т]. Переход также увеличивает базовый и приобретающий фронты потока на [l @ т].

Внутренний переход (SILENT) соответствует шагу исполнения потока, который не взаимодействует с памятью — присваивание в локальную переменную, исполнение условного перехода или пустой операции. Данный переход меняет только локальное состояние потока.

6. Формальное определение модели ARMv8.3

В этом разделе модель ARMv8.3 описывается формально, в соответствии с [20]. Мы не будем явным образом использовать язык ARM-ассемблера и будем считать, что он совпадает с исходным языком с точностью до модификаторов барьера памяти:

fmodarm ::= sy I ld.

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

Также мы будем считать, что результат компиляции любой программы Prog на исходном языке является той же программой, в которой модификаторы rel заменены на sy, а acq — на ld.

Определение. Исполнение G — это граф, который состоит из следующих компонент.

1. Конечное множество событий E с Z, которое включает выделенное множество начальных событий E0 = {а£ | x е Loc}. Мы используем a, b, ... как переменные для обозначения событий.

2. Функция tid , которая по событию из E возвращает номер потока, породившего событие. При этом начальные события аХ £ E0 мы считаем относящимися к потоку с номером 0, tid( а0) = 0.

3. Функция lab, которая присваивает метку каждому событию из E. Метки могут обозначать следующие операции:

• чтение, R(x, v), где x £ Loc — локация, из которой событие читает значение v £ Val;

• запись, W(x, v), где x £ Loc — локация, в которую событие записывает значение v £ Val;

• барьер памяти, F(o), где o £ {Id, sy} — тип барьера, причем sy-барьеры строже, чем ld, ld с sy.

Каждое начальное событие — это инициализирующая запись в некоторую локацию Va0 £ E0.lab(а£) = W(x, 0). Функция lab естественным образом определяет следующие частично определенные функции, которые по событиям возвращают:

• typ — тип события (R, W или F);

• mod — тип барьера;

• loc — целевую локацию;

• valr — прочитанное значение;

• val — записанное значение.

w

Далее мы будем использовать R, W и F также и для обозначения соответствующих множеств событий (например, R для обозначения {e е E | typ(e) = R}).

4. Строгий частичный порядок на событиях po с Е х Е, называемый программным порядком, который является объединением непересекающихся множеств {po¡ }ie{0}uTid, где po0 = E0 х (E \ E0) и для каждого потока i е Tid отношение po¡ является полным порядком на множестве событий этого потока {a е E | tid(a) = i}.

5. Отношения data, addr, ctrl с po \ po0, которые удовлетворяют следующим ограничениям:

• data с R х W;

• addr с R х (R ^ W);

• ctrl; po с ctrl.

Они представляют зависимости по данным, по целевому адресу инструкции и по потоку управления соответственно.

6. Отношение rf с [W]; =loc;[R], которое

obs 4 dob 4

bob 4

Определение. Исполнение G является ARM-согласованным, если выполняется следующее:

• R = codom(rf); (COMPLETENESS)

• отношение po |loc иrb и co и rf не имеет циклов; (INTERNAL)

• отношение obs и dob и bob не имеет циклов. (EXTERNAL)

7. Обход ARM-согласованных исполнений

В разделе 4 мы обсудили, что наше доказательство корректности компиляции представляет собой симуляцию обещающей машиной некоторого обхода ARM-согласованных исполнений. В этом разделе мы введем данный обход. Для этого мы сначала определим несколько вспомогательных понятий, потом формально введем шаг обхода с требуемыми ограничениями,

удовлетворяет следующим ограничениям:

• valw (a) = valr (b) для любой пары < a, b > е rf;

• al = a2, если (aí, b>, (a2, b> е rf.

Мы также вводим функцию rf-1, которая определена на области отображения rf, codom(rf), и для любого a из codom(rf) верно, что (rf-1(a), a> е rf.

7. Строгий частичный порядок co на элементах W, который является объединением отношений {co x} x¡ELoc, где cox — полный порядок на элементах Wx = {e е W | loc(e) = x}. Мы также используем некоторые производные отношения. Так, rb = rf-1; co — это отношение связывает событие чтения с событием записи, которое co-следует за прочитанным событием записи. С помощью суффиксов i и e обозначаются подмножества отношений, которые связывают события одного и разных потоков соответственно. Например, coi = {(w, w '> е co | tid(w) = tid(w ')} и rfe = {(w, r> е rf | tid(w) = tid(r)}.

а потом докажем существование последовательности шагов, которые полностью обходят любое ARM-согласованное исполнение.

Определение. Конфигурацией обхода для ARM-согласованного исполнения G называется пара множеств (C, /), такихчто

• C с G.E;

• dom(G.po;[C]) с C;

• C n G.W с I с W.

Элементы С мы будем называть покрытыми (covered), а I — выпущенными (issued).

Интуитивно, в рамках симуляции покрытые события будут соответствовать префиксу программы, который исполнен обещающей семантикой, тогда как выпущенные — записями, находящимся в памяти обещающей машины в соответствующий момент исполнения.

rfe и rbe и coe (observed-by) (addr и data); rfi? и

(ctrl и data); [W]; coi? и (dependency-ordered-before)

addr; po; [W]

po;[F(sy)] и [F(sy)]; po и

гтэт гттллм гтгл^м (barrier-ordered-before) [R]; po; [F(ld)] и [F(ld)];po

Определение. Множеством следующих событий Next(G, С) для исполнения G и его покрытия С с G.E называется множество, состоящие из событий, все po-предшествующие события которых уже покрыты:

Next(G, С) 4 4 {b е G.E | dom(G.po;[b]) с С} \ С.

В рамках симуляции данное множество будет содержать по одному событию для каждого еще не завершенного потока в обещающей семантике, причем события будут соответствовать следующему переходу в потоке, который не является обещанием. Так, если бы обещающая семантика была определена поверх некоторого синтаксиса, то множество следующих событий соответствовало инструкциям, на которые указывают счетчики команд (program counter) потоков в обещающей модели.

Определение. Событие w является покрываемым для исполнения G в конфигурации обхода (С, I), a е Coverable(G, С, I),

если а является:

• событием чтения и связанное событие записи является выпущенным (а е О.Я и гТ^а) е I),

• выпущенным событием (а е /) или

• барьером памяти (а е О.Р).

В нашем обходе, заданном как операционная семантика, есть шаг, который «покрывает» событие, т. е. добавляет его во множество покрытых. Событие, покрываемое данным правилом, должно соответствовать приведенным выше ограничениям, которые, в свою очередь, соответствуют ограничениям обещающей семантики. Например, обещающая семантика может исполнить инструкцию чтения и прочитать из некоторого конкретного сообщения только в том случае, если данное сообщение есть в памяти, т. е. соответствующее ему событие м уже выпущено, w е I.

Определение. Событие м называется выпускаемым для исполнения О в конфигурации обхода (С, I), м е ^иаЬ1е(О, С, I), если выполняется следующее:

• w является событием записи (w е G.W);

• все po -предшествующие барьеры покрыты (dom([F];G.po; [w] с С));

• все записи других потоков, от которых зависит w, выпущены (dom(G.rfe;G.dob+ ;[w]) с I).

(WRITE-BOB) (WRITE-DOB)

Шаг обхода, который «выпускает» событие записи, соответствует в симуляции обещанию, которое делает обещающая семантика. Ограничение WRITE-BOB является более строгим, чем ограничение из обещающей семантики: обещание может быть сделано «через» приобретающий барьер, который соответствует fence(ld) в

a e Next(G!, С) n Coverable(G, С, I) G h (С, I> ^tc <С u {a}, I>

Здесь левое правило покрывает событие, если событие принадлежит множеству следующих событий и при этом покрываемо в данной конфигурации. Правое правило выпускает событие записи, если событие выпускаемо и при этом еще не выпущено.

Поскольку каждое конкретное ARM-согласованное исполнение является некоторым конечным графом, а приведенные

ARMv8.3, однако чение позволяет

более строгое ограни-упростить симуляцию. Ограничение WRITE-DOB нужно для того, чтобы обещающая семантика могла сертифицировать обещание, которое она делает.

Шаги обхода задаются следующим образом:

м е ^иаЬ1е(О, С, I) \ I

G h (C, I> ^tc (C, I u {w}>

выше шаги обхода наращивают конфигурации, то для доказательства наличия полного обхода графа достаточно показать, что для каждой неконечной конфигурации существует шаг к новой конфигурации.

Теорема 7.1. Пусть (С,/) является конфигурацией ARM-согласованного исполнения О, при этом конфигурация достижима из начальной (О Ь <0,0) ^Тс (С, I))

и С = G .E. Тогда существуют С ' и I ', т. ч. G h (С, I> ^tc (С ', I '>.

Доказательство теоремы основано на следующих вспомогательных леммах:

Лемма 7.2. Пусть G h (0,0> ^TC (С,I>. Тогда С ç Coverable(G, С, I)

и I ç Issuable(G, С, I).

Доказательство. Следует из определений Coverable и Issuable и того, что множества покрытых и выпущенных событий растут в течение обхода G.

Лемма 7.3. Пусть G h (0,0> ^TC (С, I>. Тогда W n Next(G, С) ç Issuable(G, С, I).

Доказательство. Зафиксируем w e W n Next(G, С) и покажем, что w e Issuable(G, C, I). Так как w e Next(G, С), то все ро-предшествующие события являются покрытыми (т. е. находятся в С), из чего напрямую следует, что выполняется WRITE-BOB. Кроме того, из леммы 7.1 следует, что все покрытые события являются покрываемыми, из чего следует WRITE-DOB.

Доказательство теоремы 7.1. Нам известно, что Next(G, С ) = 0, так как С = G .E. В доказательстве мы рассматриваем два варианта. Первым мы рассматриваем вариант, когда существует элемент Next(G, С ), который покрываем, или сначала выпускаем, а потом покрываем. Вторым мы рассматриваем вариант, когда все элементы в Next(G, С) не w не

выпускаемы. Для этой ситуации мы показываем, что в G существует событие записи, которое выпускаемо в данной конфигурации.

Первый вариант. Зафиксируем элемент a e Next(G, С). Если a e R и rf-1(a) e I, или a e F, или a e W n I, тогда a покрываем по определению. Если a e W \ I, тогда a — выпускаемо по лемме 7.3.

В т о р о й в а р и а н т . В этом случае мы предполагаем, что нет события из Next(G, С ), которое покрываемо или выпускаемо. Таким образом, Next(G, С) ç R является следствием леммы 7.2 и определения покрываемого события. Кроме того, для любого чтения события r из Next(G, С) мы знаем, что rf-1(r) g I. Далее мы покажем, что существует событие записи, которое выпускаемо в данной конфигурации. Для этого введем вспомогательное отно-

шение eord = (obs u dob u bob)+, которое антирефлексивно по определению ARM-согласованности EXTERNAL.

Мы знаем, что есть как минимум одно событие (чтения) из Next(G, С), которое при этом не покрываемо. Это означает, что существует событие записи, которое еще не выпущено. Выберем событие записи w e W \ I, которое является минимальным по отношению eord среди записей, которые еще не выпущены, то есть Vw ' e W \ I.eord(w', w). Осталось показать, что событие w выпускаемо.

Так как w / Next(G, С), то существует событие чтения r e Next(G, С), такое что ро(г, w) и rf-1(r) (/ I. При этом rf " 1(r) = rfe~ 1(г), так как С n W œ I и Ve e С.dom(po;[e]) e С. Для доказательства того, что w выпускаемо, нужно показать, что два утверждения выполняются.

WRITE-BOB: Пусть f e F, такое что po( f, w ). Предположим, что f eC. Тогда po(r, f) и (rfe-1(r), w^ obs;bob+ ç eord. Так как rfe-1(r) (/ I, то существует eord-предшествующее w событие записи, которое не выпущено. Это противоречит выбору w.

WRITE-DOB: Пусть существует событие чтения r ', такое что dob + (r ', w). Если rfe-1(r ') =±, то (rfe- 1(r ', w) e rfe; dob+ ç obs;dob+ ç eord. По определению w это означает, что rfe-1 (r ') e I. ■

8. Симуляция обхода обещающей машиной

В этом разделе мы приводим доказательство основной теоремы 4.1.

Теорема 4.1. Для любых программы Prog, результата ее компиляции ProgARM и ARM-согласованного исполнения G программы ProgARM существует исполнение Prog обещающей машиной, т. ч. финальное состояние памяти машины совпадает с состоянием памяти G.

Д оказательство. Зафиксируем ARM-согласованное исполнение G. Далее мы покажем, что обещающая семантика может симулировать некоторый обход исполнения G, который существует согласно теореме 7.1.

Почему нам достаточно рассмотреть некоторый обход исполнения G? Это так, по-

скольку сам обход задает нам лишь схему индукции по исполнению G и не влияет на финальное состояние памяти G, поскольку оно полностью определяется отношением С.со.

Для того чтобы показать симуляцию между обещающей семантикой и обходом, мы введем отношение симуляции X, которое будет связывать конфигурацию обхода G с состоянием обещающей машины. Формально утверждение о наличии симуляции выглядит следующим образом.

Лемма 8.1. Существуют такие 75 и М, что X (Э .Е ^ W, 75,М) и <Т51П11,М1П11 > <75, М).

При этом мы определим отношение симуляции X так, чтобы из него было очевидно, что финальное состояние памяти М совпадает с состоянием памяти исполнения G, а также, что X(0,0,75ши МШ1) выполняется. Тогда доказательство теоремы будет непосредственно следовать из доказательства леммы 8.1. Сама же лемма доказывается индукцией с использованием леммы 8.2, которая показывает, что для любой неполной конфигурации обхода существует шаг, который обещающая машина может симулировать.

Лемма 8.2. Пусть для некоторых конфигураций обхода (С, /) и <С', /') исполнения G, а также некоторого состояния обещающей машины <75, М > выполняется G Ь <С, I> ^тс <С', I') и X (С, 1, 75, М). Тогда существуют 75', М', такие что <75,М> ^ргот1зе <75 ',М') и X(С', I', 75 ',М').

Далее в этом разделе мы опишем вспомогательные отношения на графах исполнения, которые будут нужны для выражения связи с фронтами обещающей модели в отношении симуляции, и само отношение симуляции. Как и в доказательстве теоремы 4.1, мы зафиксируем АЯМ-согласованное исполнение G и будем вводить все определения в контексте G.

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

8.1 Аналоги фронтов для АЯМ-согласованных исполнений. Введем вспомогательную функцию Т : Т1теМар = W ^ М, сопоставляющую событиям записи в G их порядковые номера в отношении со так, чтобы соггесМтар^, Т) выполнялось:

соп-еС;-1тар(С, Т) = Vw, w'. (w') е со ^ Т(w) < Т^')) л (w е codom(co) ^ Т(w) = 0).

Эта функция фактически вводит метки времени на событиях записи G. Далее введем вспомогательные отношения ^уп-сНгопкеё-'шШ) и ЬЪ (Иарреш-ЪеГоге):

sw = р^у)]; po; rf; po; р(М)]

hb = (sw и po)+.

Здесь отношение sw представляет пути в графе, соответствующие передачи информации о событиях записи с помощью АЯМ-аналогов высвобождающего (Р^у)) и приобретающего (Р(Ы)) барьеров памяти. Отношение ИЪ мы далее используем, чтобы определить отношения сиг-ге1, acq-rel и ге1-ге1. Они в паре с функцией Т будут использованы в симуляции как ограничения для базового, приобретающего и высвобождающего фронтов потока в обещающей модели памяти:

Шг-гс1 = rf?;hb acq-rel = Г?; hb; ([F(sy)]; ро; Г; ро)? ге1-ге1 = rf?;hb;[F(sy)];po.

Аналогичным образом отношение msg-re1 с W х W представляет ограничение на фронт сообщения из обещающей модели. Так, если <и>, и ') е msg-re1 и события и и и' соответствуют сообщениям т и т' в памяти обещающей семантики, то т.т = Т(и), т' .т = Т(и') и т' .ггеи(1ос(и)) > т.т

msg-re1 = гГ?; ИЪ; [F(sy)]; ро и .

8.2 Отношение симуляции X. Отношение симуляции мы определяем следующим образом:

X(С, 1, 75, М) 4 Xmeml(C, 1, 75, М) л

л Xmem2(C, 1, М) Л Xy1ew(C, 75) Л

Л Xy1ew-re1

(75)

Л Xstate (С, 75).

Xmem1(C, 1, 75, М) утверждает, что все выпущенные события I имеют аналоги в памяти обещающей машины, причем выполненные обещания соответствуют по-

крытым событиям записи:

Х^^С, I, TS, M) 4 Vw е ISview <

< dom-view(msg-rel;[w]).

let msg 4 (loc(w) : valw (w) @ T(w), view) in let P 4 TS(tid(w)).P in (w е С ^ msg е M \ P) л (w £ С ^ msg е P).

Здесь dom-view является вспомогательной функцией, которая по множеству записей строит соответствующий множеству фронт:

set-view(S) 4 Ai.max{T(w) | w е S, loc(w) = /}; dom-view(^) 4 set-view(dom(^)).

Xmem2 (С, I, M) обозначает обратную связь: для каждого сообщения из M существует соответствующее ему выпущенное событие в Х:

Xmem2 (С, I, M) 4 V(^ : v @ т, view) е е M.t = 0 ^ 3w е Х.

I = loc(w) л v = valw(w) л т = T(w) л view <

< dom-view(msg-rel;[w]).

Xview (С, TS) утверждает, что для любого элемента множества следующих событий (Next(G, С)) фронты, представляющие связанные с ним отношениями cur-rel, acq-rel и rel-rel события записи, больше, чем базовый, приобретающий и высвобождающие фронты соответствующего потока:

Xview(С, TS) 4 Ve е Next(G,С).

let (cur, acq, rel) 4 TS(tid(e)).V in

cur < dom-view(cur-rel;[e]) л

acq < dom-view(acq-rel;[e]) л

rel < dom-view(rel-rel;[e]).

Xview-rel(TS) показывает, что все еще не выполненные обещания имеют специальную форму их фронтов — значение высвобождающего фронта соответствующего потока плюс метка времени самого обещания:

xview(TS) 4 Vtid, (£ : т, view) е TS(tid).P. view = \i @ т] U TS(tid).V.rel.

Х^ДС, TS) утверждает, что для каждого потока tid существует список переходов {ti }ig=[i к], который соответствует событиям в

tid-подграфе исполнения G, и текущее состояние потока TS(tid ).о получено с помощью p переходов из списка, где p — количество покрытых событий, | Etid п С |.

Xst^, TS) 4 Vtid,

к = | Etid | .^{стг Ье[0..к],{ti Ье[1..к].

let p 4| Etid п С | in TS(tid ).о =

S

= °p л (Vj е [0..к _ 1].оj V ^ оj+i) л Vn е [1..k], a е nth([Etid];po;

[EM])(n _ 1).tn+i « lab(a).

Здесь функция nth porder n возвращает элементы с номером n из отношения частичного порядка porder, a предикат t« lab(a) выполняется тогда и только тогда, когда метка перехода t соответствует метке события a.

9. Связанные работы

Корректность компиляции в общем смысле является большой, проработанной областью, которая продолжает развиваться. Так, недавно были представлены верифицированные компиляторы для языков С, CompCert [15] и ML, CakeML [11]. Эти компиляторы для однопоточных программ, но существуют и компиляторы для многопоточных программ в архитектуры со слабыми моделями памяти, в частности из языка C в архитектуру x86-TSO [22]. В работах [5, 6] приведены доказательства корректности компиляции из аксиоматической модели C/C++11 [7] в модели архитектур x86-TSO и Power.

Наиболее близкими работами к нашей являются [10] и [19]. В [10] приведены доказательства корректности компиляции из обещающей модели в модели памяти x86-TSO [23] и Power [3], которые, так же как и [20], являются аксиоматическими. Доказательства для моделей x86-TSO и Power имеют следующую структуру: (1) обе модели памяти, x86-TSO и Power, представляются как некоторые более строгие модели и набор трансформаций над программами; (2) приводится доказательство того, что трансформации корректны в рамках

обещающей модели памяти, т. е. поведения трансформированной программы являются поведениями изначальной программы в обещающей модели памяти; (3) показывается корректность компиляции для более строгих версий моделей.

Первый пункт этого доказательства приведен в [12]. Так, каждое поведение некоторой программы А в рамках модели х86-Т80 является поведением программы В в рамках модели последовательной конси-стентности, где В может быть получена из А путем (многократного) применения двух трансформаций — переупорядочивание инструкции записи с непосредственно следующей операцией чтения из другой локации и удаления повторного чтения:

[x] := 1; a := [y];

a := [y]; b := [y];

a := [y]; [x] := 1;

a := [y]; b := a

Для модели Power приведен аналогичный результат: каждое поведение программы A в рамках модели Power является поведением программы B в рамках более строгой модели StrongPower, запрещающей часть поведений, наблюдаемых в модели Power. Здесь B может быть получена из A путем (многократного) применения трансформации, которая переупорядочивает произвольные независимые инструкции чтения или записи, следующие друг за другом.

Мы пытались применить аналогичный подход для доказательства корректности компиляции для модели ARMv8.3 [20], но отказались от него ввиду того, что существует программа, одно из поведений которой разрешено моделью ARMv8.3 и не выразимо с помощью ранее использованных трансформаций кода над более строгой моделью памяти. Упомянутая программа выглядит следующим образом:

a ••= [x]; //1 if a = 1 then b := [y]; //0

[y] := 1;

fence(sy); [x] := 1

В левом потоке между операциями чтения есть зависимость по управлению, а в

правом потоке используется барьер памяти, который запрещает исполнение записей не по порядку. Модель памяти ARMv8.3 разрешает программе завершиться с a = 1 и b = 0, при этом инструкции в обоих потоках не могут быть переставлены ввиду наличия зависимости и барьера.

В работе [19] приведено доказательство корректности компиляции из обещающей модели в модель ARM POP [9]. Несмотря на то, что этот результат выглядит очень близким к тому, что предлагается в данной статье, существует важное различие: модель ARM POP представлена в операционном стиле. Последнее позволило авторам [19] провести доказательство через непосредственную симуляцию модели ARM POP обещающей моделью. При этом модель ARM POP обладает более широкими возможностями по исполнению инструкций не по порядку по сравнению с обещающей моделью. Для решения этой проблемы в [19] используется т. н. запаздывающая симуляция, которая позволяет обещающей семантике в рамках симуляции повторять действия модели ARM POP в возможном для обещающей семантики порядке. Наша операционная семантика обхода графа исполнения использует похожую идею, но в приложении к аксиоматической модели ARMv8.3.

10. Заключение

В данной статье приведено доказательство корректности компиляции для подмножества обещающей модели памяти [10], состоящего из ослабленных операций чтения и записи, приобретающих и высвобождающих барьеров памяти, в модель памяти ARMv8.3 [20]. Доказательство базируется на новой идее обхода исполнений в аксиоматических семантиках, который может симулировать обещающая модель памяти. Мы убеждены в том, что данная идея может использоваться для прямых доказательств корректности компиляции в другие модели памяти, а подобные задачи регулярно появляются ввиду бурного развития области.

Несмотря на то, что подмножество обещающей модели, рассмотренное в статье, достаточно ограничено, доказательство кор-

—>

—>

ректности компиляции для него достаточно сложно и объемно. В наши дальнейшие планы входит расширение доказательства до полной обещающей модели памяти. Для этого нужно будет поддержать операции приобретающего чтения (read acquire) и высвобождающей записи (write release), атомарные инструкции чтения и записи (read-modify-write), частным случаем которых является сравнение с обменом (compare-and-set — CAS), а также полные барьеры памяти

(SC fences). Поддержка данных инструкций потребует небольшого усложнения обхода ARM-согласованных исполнений, а также существенного усложнения отношения симуляции ввиду большого количества мелких деталей, связанных с упомянутыми инструкциями в рамках обещающей модели памяти.

Работа выполнена при поддержке компании JetBrains (http://jetbrains.com).

СПИСОК ЛИТЕРАТУРЫ

1. ARM architcture reference manual: ARMv8, for ARMv8-A architecture profile // URL: https:// developer.arm.com/docs/ddi0487/latest/ arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile (Дата обращения: 16.05.2017).

2. C/C++11 mappings to processors // URL: https://www.cl.cam.ac.uk/~pes20/ cpp/ cpp0xmappings.html. (Дата обращения: 16.05.2017).

3. Alglave J., Maranget L., Tautschnig M.

Herding cats: Modelling, simulation, testing, and data mining for weak memory // ACM Trans. Program. Lang. Syst. 2014. Vol. 36(2). Pp. 7:1-7:74. DOI: 10.1145/2627752

4. Batty M., Memarian K., Nienhuis K., Pichon-Pharabod J., Sewell P. The problem of programming language concurrency semantics // ESOP. Springer, 2015. Vol. 9032 of LNCS. Pp. 283-307. DOI: 10.1007/978-3-662-46669-8_12

5. Batty M., Memarian K., Owens Sc., Sarkar S., Sewell P. Clarifying and compiling C/C++ concurrency: From C++11 to POWER // POPL 2012. ACM, 2012. DOI: 10.1145/2103621.2103717.

6. Batty M., Owens Sc., Sarkar S., Sewell P., Weber T. Mathematizing C++ concurrency: The post-Rapperswil model. technical report n3132, iso iec jtc1/sc22/wg21 // URL: http://www.open-std. org/jtc1/sc22/wg21/docs/papers/2010/n3132.pdf.

7. Batty M., Owens Sc., Sarkar S., Sewell P., Weber T. Mathematizing C++ concurrency // POPL 2011. ACM, 2011. Pp. 55-66. DOI: 10.1145/1925844.1926394

8. Boehm H.-J., Demsky B. Outlawing ghosts: Avoiding out-of-thin-air results // MSPC 2014. ACM, 2014. Pp. 7:1-7:6. DOI: 10.1145/2618128.2618134

9. Flur Sh., Gray K.E., Pulte Ch., Sarkar S., Sezgin A., Maranget L., Deacon W., Sewell P.

Modelling the ARMv8 architecture, operationally: Concurrency and ISA // POPL 2016. ACM, 2016. Pp. 608-621. DOI: 10.1145/2837614.2837615

10. Jeehoon Kang, Chung-Kil Hur, Lahav O.,

Vafeiadis V., Dreyer D. A promising semantics for relaxed-memory concurrency // POPL 2017. ACM, 2017. DOI: 10.1145/3009837. 3009850

11. Kumar R., Myreen M.O., Norrish M., Owens Sc. CakeML: A verified implementation of ML // POPL 2014: Proc. of the 41st ACM SIGPLAN-SIGACT Symp. on Principles of Programming Languages. ACM Press, 2014. Pp. 179-191. DOI: 10.1145/2535838.2535841

12. Lahav O., Vafeiadis V. Explaining relaxed memory models with program transformations // FM 2016. Springer, 2016. DOI: 10.1007/978-3-319-48989-6_29

13. Lahav O., Vafeiadis V., Jeehoon Kang, Chung-Kil Hur, Dreyer D. Repairing sequential consistency in C/C++11 // In PLDI 2017. ACM, 2017.

14. Lamport L. How to make a multiprocessor computer that correctly executes multiprocess programs // IEEE Trans. Computers. 1979. Vol. 28(9). Pp. 690-691. DOI: 10.1109/TC.1979.1675439

15. Leroy X. A formally verified compiler backend // J. Autom. Reasoning. 2009. Vol. 43(4). Pp. 363-446. URL: https://doi.org/10.1007/s10817-009-9155-4. DOI: 10.1007/s10817-009-9155-4

16. Manerkar Ya.A., Trippel C., Lustig D., Pellauer M., Martonosi M. Counterexamples and proof loophole for the C/C++ to POWER and ARMv7 trailing-sync compiler mappings. CoRR, abs/1611.01507, 2016 // URL: http://arxiv.org/ abs/1611.01507.

17. Manson J., Pugh W., Adve S.V. The Java memory model // POPL 2005. ACM, 2005. Pp. 378-391. DOI: 10.1145/1040305.1040336.

18. Milner R. Communication and concurrency // PHI Series in Computer Science. Prentice Hall, 1989.

19. Podkopaev A., Lahav O., Vafeiadis V.

Promising compilation to ARMv8 POP // ECOOP 2017. Schloss Dagstuhl-Leibniz-Zentrum fuer Informatik, 2017.

20. Pulte Ch., Flur Sh., Deacon W., French J., Sarkar S., Sewell P. Simplifying ARM concurrency: Multicopy-atomic axiomatic and operational models for ARMv8 // Accepted to POPL'18.

21. Sarkar S., Sewell P., Alglave J., Maranget L., Williams D. Understanding POWER multiprocessors // PLDI 2011. ACM, 2011. Pp. 175-186. DOI: 10.1145/1993498. 1993520

22. Sevcik J., Vafeiadis V., Nardelli F.Z., Jagannathan S., Sewell P. Relaxed-memory concurrency and verified compilation // Proc. of the 38th ACM SIGPLAN-SIGACT Symp. on Principles of Programming Languages. POPL

Статья поступила в редакцию 27.09.2017.

2011. Austin, TX, USA, 2011. Pp. 43-54. URL:http://doi.acm.org/10.1145/1926385.1926393. DOI: 10.1145/1926385.1926393

23. Sewell P., Sarkar S., Owens Sc., Nardelli F.Z., Myreen M.O. x86-TSO: A rigorous and usable programmer's model for x86 multiprocessors // Commun. ACM, 2010. Vol. 53(7). Pp. 89-97. DOI: 10.1145/1785414.1785443

24. Vafeiadis V., Balabonski Th., Chakraborty S., Morisset R., Nardelli F.Z. Common compiler optimisations are invalid in the C11 memory model and what we can do about it // POPL 2015. ACM, 2015. Pp. 209-220.

REFERENCES

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

1. ARM architcture reference manual: ARMv8, for ARMv8-A architecture profile. Available: https:// developer.arm.com/docs/ddi0487/latest/ arm-architecture-reference-manual-armv8-for-armv8-a-architecture-profile (Accessed: 16.05.2017).

2. C/C++11 mappings to processors. Available: https://www.cl.cam.ac.uk/~pes20/cpp/ cpp0xmappings.html. (Accessed: 16.05.2017).

3. Alglave J., Maranget L., Tautschnig M. Herding cats: Modelling, simulation, testing, and data mining for weak memory. ACM Trans. Program. Lang. Syst., 2014, Vol. 36(2), Pp. 7:1-7:74. DOI: 10.1145/2627752

4. Batty M., Memarian K., Nienhuis K., Pichon-Pharabod J., Sewell P. The problem of programming language concurrency semantics. In ESOP. Springer, 2015, Vol. 9032 of LNCS, Pp. 283-307. DOI: 10.1007/978-3-662-46669-8_12

5. Batty M., Memarian K., Owens Sc., Sarkar S., Sewell P. Clarifying and compiling C/C++ concurrency: From C++11 to POWER. In POPL 2012, ACM, 2012. DOI: 10.1145/2103621.2103717

6. Batty M., Owens Sc., Sarkar S., Sewell P., Weber T. Mathematizing C++ concurrency: The post-Rapperswil model. technical report n3132, iso iec jtcl/ sc22/wg21. Available: http://www.open-std.org/jtc1/ sc22/wg21/docs/papers/2010/n3132.pdf.

7. Batty M., Owens Sc., Sarkar S., Sewell P., Weber T. Mathematizing C++ concurrency. In POPL 2011, ACM, 2011, Pp. 55-66. DOI: 10.1145/1925844.1926394

8. Boehm H.-J., Demsky B. Outlawing ghosts: Avoiding out-of-thin-air results. In MSPC 2014, ACM, 2014, Pp. 7:1-7:6. DOI: 10.1145/ 2618128.2618134

9. Flur Sh., Gray K.E., Pulte Ch., Sarkar S., Sezgin A., Maranget L., Deacon W., Sewell P. Modelling the ARMv8 architecture, operationally: Concurrency and ISA. In POPL 2016, ACM, 2016,

Pp. 608-621. DOI: 10.1145/2837614.2837615

10. Jeehoon Kang, Chung-Kil Hur, Lahav O., Vafeiadis V., Dreyer D. A promising semantics for relaxed-memory concurrency. In POPL 2017. ACM, 2017. DOI: 10.1145/3009837.3009850

11. Kumar R., Myreen M.O., Norrish M., Owens Sc. CakeML: A verified implementation of ML. In POPL '14: Proceedings of the 41st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, ACM Press, 2014, Pp. 179-191. DOI: 10.1145/2535838.2535841

12. Lahav O., Vafeiadis V. Explaining relaxed memory models with program transformations. In FM 2016. Springer, 2016. DOI: 10.1007/978-3-319-48989-6_29

13. Lahav O., Vafeiadis V., Jeehoon Kang, Chung-Kil Hur, Dreyer D. Repairing sequential consistency in C/C++11. In PLDI 2017. ACM, 2017.

14. Lamport L. How to make a multiprocessor computer that correctly executes multiprocess programs. IEEE Trans. Computers, 1979, Vol. 28(9), Pp. 690-691. DOI: 10.1109/TC.1979.1675439

15. Leroy X. A formally verified compiler back-end. J. Autom. Reasoning, 2009, Vol. 43(4), Pp. 363-446. Available: https://doi.org/10.1007/ s10817-009-9155-4. DOI: 10.1007/s10817-009-9155-4

16. Manerkar Ya.A., Trippel C., Lustig D., Pellauer M., Martonosi M. Counterexamples and proof loophole for the C/C++ to POWER and ARMv7 trailing-sync compiler mappings. CoRR, abs/1611.01507, 2016. Available: http://arxiv.org/ abs/1611.01507.

17. Manson J., Pugh W., Adve S.V. The Java memory model. In POPL 2005, ACM, 2005, Pp. 378-391, DOI: 10.1145/1040305.1040336.

18. Milner R. Communication and concurrency. PHI Series in Computer Science. Prentice Hall, 1989.

19. Podkopaev A., Lahav O., Vafeiadis V.

Promising compilation to ARMv8 POP. In ECOOP 2017. Schloss Dagstuhl-Leibniz-Zentrum fuer Informatik, 2017.

20. Pulte Ch., Flur Sh., Deacon W., French J., Sarkar S., Sewell P. Simplifying ARM concurrency: Multicopy-atomic axiomatic and operational models for ARMv8. Accepted to POPL'18.

21. Sarkar S., Sewell P., Alglave J., Maranget L., Williams D. Understanding POWER multiprocessors. In PLDI 2011, ACM, 2011, Pp. 175-186. DOI: 10.1145/1993498. 1993520

22. Sevcik J., Vafeiadis V., Nardelli F.Z., Jagannathan S., Sewell P. Relaxed-memory concurrency and verified compilation. Proceedings

Received 27.09.2017.

of the 38th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, POPL 2011, Austin, TX, USA, 2011, Pp. 43-54. Available: http://doi.acm.org/10.1145/1926385.1926393. DOI: 10.1145/1926385.1926393

23. Sewell P., Sarkar S., Owens Sc., Nardelli F.Z., Myreen M.O. x86-TSO: A rigorous and usable programmer's model for x86 multiprocessors. Commun. ACM, 2010, Vol. 53(7), Pp. 89-97. DOI: 10.1145/1785414.1785443

24. Vafeiadis V., Balabonski Th., Chakraborty S., Morisset R., Nardelli F.Z. Common compiler optimisations are invalid in the C11 memory model and what we can do about it. In POPL 2015, ACM, 2015, Pp. 209-220.

СВЕДЕНИЯ ОБ АВТОРАХ / THE AUTHORS

ПОДКОПАЕВ Антон Викторович PODKOPAEV Anton V.

E-mail: a.podkopaev@2009.spbu.ru

ЛАХАВ Ори LAHAV Ori

E-mail: orilahav@tau.ac.il

вафеядис Виктор VAFEIADIS Viktor

E-mail: viktor@mpi-sws.org

© Санкт-Петербургский политехнический университет Петра Великого, 2017

i Надоели баннеры? Вы всегда можете отключить рекламу.