Язык программирования Т++
Язык программирования T++ является синтаксически и семантически гладким языковым расширением стандартного языка программирования C++. Под синтаксической и семантической гладкостью здесь понимается прежде всего наличие естественного вложения конструкций языка C++ в расширенный (по отношению к нему) синтаксис и семантику языка T++. Естественность вложения означает использование уже существующих синтаксических
и семантических понятий и их расширение, в соответствии с целью создания нового языка,
то есть с целью возможности указания программистом той специфики, которая необходима
для эффективной реализации динамического распараллеливания и других новых возможностей, предоставляемых расширенной средой исполнения программ.
В качестве среды исполнения программ на языке T++ в настоящее время используется
Т-система с открытой архитектурой (OpenTS). Описание базовых сущностей OpenTS дается в отдельном приложении.
При создании языка T++ преследовались следующие цели:
Язык должен быть максимально простым, то есть являться минимально возможным
расширением языка C++, обеспечивающим решение поставленной задачи;
Языковое расширение должно быть прозрачно по отношению к языку C++,
то есть должна быть обеспечена возможность компиляции и запуска программы на языке T++ при помощи стандартного С++-компилятора и стандартной среды исполнения
в последовательном режиме. Такая возможность должна обеспечиваться при соответствующем определении специфических ключевых слов расширенного синтаксиса на уровне стандартного препроцессора.
В языке должны быть оставлены возможности для отражения дополнительных
(нестандартных) атрибутов, которые бы определялись спецификой реализации языка.
Иными словами, в языке помимо основных конструкций должна быть стандартная
возможность для введения специфических конструкций, зависящих от реализации.
Краткое описание языка T++
Поскольку язык программирования T++ является расширением языка С++, то мы
перечислим новые добавленные в язык С++ конструкции; контексты, в которых возможно
их употребление, и то, как они влияют на выполнение итоговой (то есть откомпилированной
с помощью соответствующего компилятора и запущенного в соответствующей среде исполнения) программы.
Краткое описание синтаксиса
Описание синтаксиса дадим как перечисление новых ключевых слов и контекстов,
в которых возможно их употребление
Всего к стандартному набору С++ добавляется пять ключевых слов: tfun, tval, tptr, tout, tct
и две новых стандартных функции: tdrop, twait
Первые четыре ключевых слова tfun, tval, tptr, tout являются по сути синонимами для
более общей конструкции tct (определение Т-контекста) с определенными параметрами.
То есть можно сказать, что первична только одна конструкция определяющая --
Т-контекст в данной точке C++-программы. На основании этого Т-контекста
средства поддержки языка T++ и обеспечивают ортогональную модификацию семантики относительно стандартной C++-семантики. Конструкция trt позволяет также определять модификаторы Т-контекста, которые не входят в стандарт языка T++ (то есть которые определяются на уровне реализации). Предполагается семантическая гладкость этих нестандартных возможностей по отношению к стандартным.
Две новых стандартных функции twait и tdrop необходимы для выполнения специфических
операций, у которых нет прямых аналогов в языке C++. Их также можно трактовать как
модификаторы Т-контекста в коде T++-программы, определяемые в форме вызова функции на этапе исполнения программы.
Ниже смысл и контексты применения каждого ключевого слова рассмотрены подробнее.
tfun - атрибут, который можно указать непосредственно перед описанием типа функции.
Функция не может являться методом; это должна быть обычная top-level функция.
Описанная с помощью этого ключевого слова функция кратко называется Т-функцией.
tval – атрибут, который можно указать непосредственно перед описанием типа переменной.
Описанная с помощью этого ключевого слова переменная кратко называется Т-переменной;
В качестве значения Т-переменная содержит неготовую величину (Т-величину)
tptr – T++-аналог для определения указателей (ссылок). Используется для описания глобальных указателей в структурах данных. Описанный с помощью этого ключевого слова указатель кратко называется Т-указателем.
tout - атрибут, который можно указать непосредственно перед описанием типа выходного
аргумента Т-функции. T++-аналог для определения аргументов, передаваемых по ссылке
('&') для их дальнейшей модификации в теле функции.
tct – явное определение T-контекста. Служит для определения дополнительных свойств
Т-сущностей (специфических сущностей, поддерживаемой Т-системой).
В любом случае синтаксически описание Т-контекста содержит не более двух конструкций расширенного синтаксиса: одну из основных конструкций (tfun, tval, tptr, tout) и дополнительную конструкцию tct((...)), где в скобках указываются параметры изменения
Т-контекста, конкретные значения и смысл которых определяются реализаций языка.
tdrop – стандартная функция от одного
аргумента. Может быть вызвана от любой
Т-величины.
twait - стандартная функция от двух аргументов: Т-сущности и паттерна событий.
Возвращает статус произошедших с Т-сущностью соответствующих указанному паттерну событий.
Кроме этих двух функций также допускается модификация Т-контекста с помощью вызова функции tct от одного параметра, значение и смысл которого определяется реализацией языка.
Краткое описание семантики
Семантика Т-переменных
Т-переменные содержат неготовые величины (T-величины). Неготовые величины производятся с помощью вызова Т-функций и в каждый момент времени либо неготовы (при этом их значение не определено, а попытка обращения влечет за собой приостановку обращающейся функции), либо готовы (то есть уже посчитаны), при этом из значение фиксируется и в дальнейшем не меняется. Нельзя изменить собственно неготовую величину, но можно присвоить Т-переменной другую неготовую величину.
Т-переменные совместимы по большинству операций с обычными переменными;
их можно присваивать таким же образом, обращаться за их значением, брать ссылку
с помощью оператора & (указатель на Т-переменную является Т-указателем).
Связь с неготовой величиной разрывается в момент уничтожения Т-переменной
(например, при выходе из блока, в котором Т-переменная была объявлена),
а также при вызове функции tdrop. При этом Т-переменная связывается с новой,
только что созданной неготовой величиной.
Для неготовых величин, которые не связаны более ни с одной Т-сущностью (Т-переменной или Т-указателем) должен работать распределенный алгоритм сборки мусора.
Семантика Т-функций
Т-функции являются функциями, которые выполняются каждая в своем потоке
управления (треде). При этом они могут одновременно выполняться на разных
процессорах в многопроцессорной системе.
Т-функции взаимодействуют между собой при помощи Т-переменных, которые
содержат Т-величины. Семантика Т-переменных обеспечивает необходимую
синхронизацию и семантическую совместимость , которую может ожидать программист
от аналогичной C++-программы. Поддержка семантики Т-функций и Т-переменных обеспечивается соответствующей средой исполнения T++-программ.
При вызове Т-функции ей в качестве входных аргументов передаются те данные, которые
содержались в точке вызова; дальнейшие изменении Т-переменных вызывающей
Т-функцией допускаются произвольное число раз и не приводят к видимому
эффекту для вызванной Т-функции.
В качестве выходных аргументов (описываемых при помощи атрибута tout) указываются
собственные Т-величины, при этом они становятся неготовыми, а их поставщиком
является вызванная Т-функция. Значением Т-функций также является неготовая величина,
которая может быть присвоена (в точке вызова) любой собственной Т-величине.
Т-функции, не производящие более Т-значений, на которые имелись бы ссылки у потребителей их значений, должны эффективно завершаться (должно обеспечиваться средой исполнения).
Семантика Т-указателей
Т-указатели являются аналогами C-указателей для неготовых величин.
Т-указатели совместимы по большинству операций с обычными указателями;
их можно присваивать таким же образом, обращаться за их значением, обращаться
к значению помощью операторов -> и * (результатом применения оператора * является
неготовая величина).
Семантика дополнительных функций tdrop и twait
Дополнительные функции tdrop и twait позволяют совершать специфические по отношению
к языку C++ операции: разрыв связи с неготовой величиной и ожидание определенных
событий (как правило ожидание готовности одной или нескольких Т-величин)
При вызове функции tdrop Т-функцией-поставщиком Т-значения неготовая величина
становится готовой и обретает то значение, которое было последним ей присвоено.
При этом возникает соответствующее событие, которое влечет за собой продолжение
исполнения всех приостановленных по причине ожидания потоков.
При вызове функции tdrop Т-функцией-потребителем Т-значения ссылка на неготовую
величину теряется и счетчик ссылок на неготовой величине уменьшается. Обнуление
счетчика ссылок на неготовой величине влечет за собой инициацию процесса остановки
Т-функции-поставщика Т-значения, если только Т-функции-поставщик не производит
других значений для кем-либо ожидаемых неготовых величин.
При вызове функции twait указывается Т-сущность (обычно Т-величина или массив
Т-величин) и паттерн для ожидаемых событий. Возвращается статус событий.
Конкретные паттерны могут определяться реализацией языка T++; от реализации
требуется поддержка возможности определить статусы готовности одной неготовой величины, а также возможность для получения индексов Т-величин в массиве неготовых
величин в порядке наступления их готовности (для реализации недетерминированной альтернативы, что бывает полезно при реализации некоторых переборных алгоритмов)
Пример программы на языке T++
Ниже приведен пример простой программы, которая может быть
откомпилирована и выполнена с помощью соответствующих
программных средств для языка T++.
Программа вычисляет N-ную строку треугольника Паскаля,
используя при этом рекурсию (что порождает большое количество
одновременно работающих функций и является хорошим тестом
для системы динамического распараллеливания)
Специфические ключевые слова языка T++ выделены.
Как видно из программы, их количество весьма незначительно;
они потребовались лишь для указания, что функцию GetNumber
можно вызывать как отдельный параллельный процесс, и для
описания массива неготовых значений values.
#include <stdio.h>
tfun int GetNumber( int numberOfRow, int position ) {
if ( position == 1 || position == numberOfRow ) return 1;
else return GetNumber( numberOfRow - 1, position - 1 ) +
GetNumber( numberOfRow - 1, position );
}
tfun int main( int argc, char** argv ) {
if ( argc != 2 ) {
printf( "Usage: pascal <numberOfRow>\n" );
return 1;
}
int n = atoi( argv [1] );
tval int values [ n ];
for ( int i = 0; i < n; i++ )
values [i] = GetNumber( n, i + 1 );
for ( int i = 0; i < n; i++ )
printf( "%d ", (int) values [i] );
printf( "\n" );
return 0;
}
Требования к компилятору
К компиляторам с языка T++ предъявляются следующие требования:
Компилятор языка T++ должен переводить программу на языке T++ в машинный код
и обеспечивать возможности для компоновки готовых к исполнению в соответствующей
среде приложений. То есть для прикладного программиста должна быть обеспечена возможность откомпилировать свою программу, состоящую из одного или нескольких модулей на языке T++ с помощью одного или нескольких запусков компилятора и получить готовый к запуску в используемой среде распараллеливания исполняемый модуль. Рекомендуемое сокращение для фронтенда компилятора – t++. При компиляции программы с помощью команды t++ должна обеспечиваться подлинковка соответствующих библиотек поддержки времени исполнения.
Компилятор должен допускать и нормально обрабатывать раздельную компиляцию: прототипы Т-функций и их реализация могут быть описаны в разных файлах.
Компилятор должен допускать и нормально обрабатывать специфические для данной базисной среды конструкции; например, в случае использования в качестве базисной среды инфраструктуры GNU/GCC компилятор должен допускать и нормально обрабатывать синтаксические расширения, поддерживаемые базисным компилятором.
Компилятор должен поддерживать как минимум три стандартных режима компиляции и компоновки исполняемых модулей:
Режим отладки (полная отладочная информация + соответствующая библиотека с поддержкой отладки). В этом режиме должна быть обеспечена быстрая компиляция (оптимизация не требуется) и максимально возможная поддержка для отладки.
Режим обычного запуска (оптимизация кода + поддержка диагностики). В этом режиме осуществляется большинство запусков на этапе опытной эксплуатации приложения и возможна частичная поддержка отладки и диагностики при возникновении различных исключительных ситуаций.
Режим максимальной оптимизации (максимально возможная оптимизация + отсутствие санитарных проверок на этапе выполнения). Используется для хорошо отлаженных приложений. Должна выполняться максимально возможная оптимизация и из кода должны удаляться все диагностические проверки, оказывающие сколь либо заметное влияние на быстродействие программы.
Конвенции компиляции T++ -> C++ (для среды исполнения OpenTS)
Конвенции компиляции описывают правила преобразования исходной программы на языке T++ в эквивалентную ей программу на языке C++, использующую сущности, определенные в заголовочном файле Т-рантайма trt.
Описываемые ниже конвенции предполагают, что исходная программа уже была обработана стандартным препроцесcором языка C++ (то есть что все макроопределения уже раскрыты и текст исходной программы синтаксически удовлетворяет грамматике языка C++).
Мы дадим описание преобразований с использованием макроподстановок: если в описании преобразования указывается макроопределение (по выбранной конвенции – прописными буквами), то это означает, что в результирующем тексте нужно дополнительно сделать макроподстановки в соответствии с указанными макроопределениями (определены в заголовочном файле txx).
Компиляция определений Т-функций
Определения (прототипы) Т-функций обнаруживаются в теле исходной программы и первое из них заменяется на следующее выражение (все последующие заключаются в комментарии или удаляются):
TFUNDEF(type,fun,pars,stas,args,argi,outs,outi,msize)
,где
type – тип значения, содержащегося в Т-величине, возвращаемой Т-функцией;
fun – имя Т-функции;
pars – заключенные в скобки определения параметров (аргументов) Т-функции (разделитель - запятая);
stas – игнорируется (может быть пустым)
args – входные аргументы Т-функции (разделитель – точка с запятой);
argi – инициализация входных аргументов Т-функции (операторы присваивания входным аргументам Т-функции входных параметров; разделитель – точка с запятой);
outs - выходные аргументы Т-функции (разделитель – точка с запятой);
outi – инициализация выходных аргументов (операторы присваивания переданным выходным аргументам выходов Т-функции; разделитель – точка с запятой);
msize – размер хэш-массива мемо-таблицы (для мемо-функций, '0' для всех остальных)
Примечание:
В случае анонимных параметров компилятор должен самостоятельно сгенерировать
их имена; например для прототипа tfun int f (int *, char **) можно сгенерировать имена
tfun int f (int* arg1, char** arg2).
Компиляция реализации (программного кода) Т-функций
Заголовки реализаций Т-функций обнаруживаются в теле исходной программы и заменяются на следующее выражение:
TFUNIMPL(type,fun,pars,stas,args,argi,outs,outi,msize) {
<function implementation code>
}
,где
type – тип значения, содержащегося в Т-величине, возвращаемой Т-функцией;
fun – имя Т-функции;
pars – заключенные в скобки определения параметров (аргументов) Т-функции (разделитель - запятая);
stas – определения переменных, объявленных в теле до миграционного цикла в мобильных Т-функциях (разделитель – точка с запятой);
args – игнорируется (может быть пустым);
argi – игнорируется (может быть пустым)
outs - игнорируется (может быть пустым);
outi – игнорируется (может быть пустым);
msize – игнорируется (может быть пустым)
Примечание:
В случае отсутствия ранее объявленного прототипа перед компиляцией реализации
Т-функции должно имитироваться наличие прототипа: перед компиляцией кода следует сгенерировать и откомпилировать соответствующий прототип.
Компиляция объявления Т-переменных
Объявления Т-переменных (конструкции вида tval type var1 [,var2]...; ) должны преобразовываться в операторы объявления Т-переменных Т-суперструктуры
(конструкции вида TVar<type> var1 [,var2]...;)
Компиляция объявления Т-указателей
Объявления Т-указателей (конструкции вида type tptr ptr1 [,tptr ptr2...] [,var3]...; ) должны преобразовываться в операторы объявления Т-указателей Т-суперструктуры
(конструкции вида TPtr<type> ptr1 [, ptr2...]; [TVar<type> var3]...;)
...
Исследованные технологии для построения компиляторов
Для построения компиляторов языка T++ в настоящее время используется две технологии.
Первая основана на технологии конвертирования языковых расширений C++ с помощью
технологии OpenC++, вторая использует инфраструктуру компилятора GCC, в который
добавляется языковой фронтенд для языка T++.
К достоинствам первой технологии можно отнести относительную простоту, возможность
контролировать ход трансляции рассматривая листинг отконвертированной
T++-программы. При этом также возможно применение различных компиляторов
для обработки отконвертированного кода (в частности, оптимизирующего компилятора фирмы Intel). К недостаткам следует отнести отсутствие поддержки некоторых специфических языковых расширений.
Достоинствами второй технологии является прямая интеграция с компилятором GCC:
после обработки специфических конструкций языка T++ итоговая программная структура
обрабатывается стандартным для GCC образом. Кроме этого, автоматически обеспечивается поддержка всех специфических для GCC языковых расширений.
В обоих случаях программа к некоторый момент времени преобразуется в семантически эквивалентную ей C++-программу, в которую добавлены соответствующие операторы и определения из заголовочного файла txx (конвенции компиляции для языка T++) и
trt (T-runtime). Дальнейшее обеспечение семантики конструкций языка T++ обеспечивается средой исполнения (библиотека libtrt).