Язык TC создавался в качестве входного языка Т-системы [GRACE] как синтаксически и семантически "гладкое" расширение языка C. Под "гладкостью" здесь понимается то, что дополнительные конструкции синтаксически и семантически увязаны с конструкциями основного языка C.
Выражаясь математическим языком, хотелось сделать коммутативной следующую диаграмму:
Последовательный компьютер -------> Язык C | | | | | | | | V V Параллельный компьютер -------> Языки TC
Синтаксически TC максимально приближен к C, и конвертируется в C++ для последующей обработки обычным оптимизирующим компиляторм gcc.
Явные параллельные конструкции, понимаемые в привычном смысле, в TC отсутствуют (т.е. программист не указывает, какие части программы следует выполнять параллельно), и реально счетные гранулы выделяются во время работы программы. Указаниями для такого выделения являются расширения синтаксиса и семантики языка TC, которые наиболее адекватно отражают архитектуру, функциональность и назначение Т-системы, а также зависимости по данным между отдельными счетными гранулами.
В то же время эти расширения выглядят достаточно прозрачными для синтаксиса и семантики языка C: программу на языке TC можно разрабатывать и отлаживать без использования Т-системы. Для этого достаточно переопределить с помощью макроопределений ключевые слова, добавленны в язык C.
Разумеется, данное свойство радикальным образом упрощает первый этап цикла разработки программ, позволяя отлаживать последовательные части реализованного функционалного алгоритма в наиболее удобной, привычной для программиста среде.
Рассмотрим в качестве примера вычисление программу на языке TC, вычисляющую N-е число Фиббоначи наиболее неэффективным способом:
#include <tc.h> #include <stdio.h> #include <stdlib.h> unsigned cfib (unsigned _n) { return _n < 2 ? _n : cfib(_n - 1) + cfib(_n - 2); } tfun void fib (unsigned _n, tout unsigned *_res) { if (_n < 20) { *_res = cfib(_n); } else { tval unsigned res1, res2; fib(_n - 1, &res1); fib(_n - 2, &res2); *_res = res1 + res2; } } int tmain (int argc, char* argv[]) { unsigned n; tval unsigned res; if (argc < 2) { fprintf(stderr, "Usage:\n" "gr_fib <number>\n" ); return -1; } n = (unsigned)atoi(argv[1]); fib(n, &res); printf("fib(%u) = %u\n", n, res); return 0; }
На примере этой программы видно, что все отличие от последовательного варианта заключается в использовании ключевых слов tval, tfun и tout.
Программа на языке TC должна начинаться включением загаловочного файла <tc.h>
Затем идут включения других загаловочных файлов, причем те файлы, которые содержат TC-конструкции, должны включаться способом 1, а файлы, не предназначенные для препроцессирования конвертером TC2C -- способом 2
Затем, как и в обычной программе на C, должны идти определения типов данных, которые используются в программе (и, в том числе, определяют неявным образом формат сообщений межмашинного взаимодействия)
После этого могут идти определения тел обычных функций и Т-функций (т.е. фрагментов кода, который может выполняться на различных узлах и в различных контекстах)
Входная функция программы должна иметь имя tmain, и имеет в точности такой же прототип, как и в программах на языке C.
#ifdef TSYSTEM // grace.hh required for parallel execution #ifdef __cplusplus #include "grace/grace.hh" #endif #define TCALL(texpr,tinfo) { CallInfo call_info_default tinfo; texpr; } #define talloc(size) (shared_allocator->allocate(size)) #define tfree(ptr) (shared_allocator->deallocate(ptr)) #define tnew(type) new T_Val<type> #define tnewarray(type,n) new T_Val<type>[n] #define tdelete(ptr) delete ptr #define tdeletearray(ptr) delete[] ptr #define tvalidate(val) ((val).validate()) #define abort grace::abort #define tptrwait(x) (x).wait() #define atexit grace::atexit #else // TSYSTEM // Dummy stuff for sequential TC-program execution #define tfun #define tval #define tout #define tptr * #define tmain main #define TCALL(texpr,tinfo) texpr #define talloc malloc #define tfree free #define tnew(type) ((type*)malloc(sizeof(type))) #define tnewarray(type,n) ((type*)malloc(n*sizeof(type))) #define tdelete(ptr) free(ptr) #define tdeletearray(ptr) free(ptr) #define tvalidate(val) (val) #define tprint printf #define terrprint(x...) fprintf(stderr, x) #define tptrwait(x) do{}while(0) #define finish() exit(0) #ifdef DEBUG #define dmesg printf #else #define dmesg(x...) #endif #define vmesg printf #endif // TSYSTEM
В примерах можно найти использование специфических функций и макроопределений, свойственных языку TC.
Их также можно отнести к расширениям, введенным в язык C для работы в системе со многими процессорами и распределенной памятью
#define TCALL(texpr,tinfo) { CallInfo call_info_default tinfo; texpr; }
Этот макрос используется для передачи дополнительной информации в вызов Т-функции. По умолчанию, вызываемая Т-функция может быть исполненна на любом вычислительном узле в контексте счетного процесса. С помощью макроса TCALL можно фиксировать номер вычислительного узла, где должна быть исполнена функция, а также тип процесса, в котором следует ее вызывать (счетный или системный).
Допускаются любые комбинации этих двух возможностей.
Пример использования:
TCALL((f(a,b,&c),(0,GR_CI_FIXED|GR_CI_SYSTEM)) -- Позвать функлцию f, принимающую аргументы a,b и возвращающую результат c на нулевом узле (в нумерации MPI), в контексте системного процесса (т.е. для выполнения обращений к невычислиытельным системным библиотекам)
#define talloc(size) (shared_allocator->allocate(size))
Этот макрос следует использовать для захвата памяти вместо malloc.
По причине того, что Т-система позволяет различным процессам работать с одной и той же памятью как в пределах одного узла, так и (в перспективе) в пределах кластера, то ей требуется четкое понимание, какая память ее, а какая - нет.
Пример использования:
struct mystruct *p = talloc(sizeof mystruct) -- захватить память под структуру языка C mystruct
#define tfree(ptr) (shared_allocator->deallocate(ptr))
Парная функция для освобождения памяти для talloc.
Замечание: в скором времени будет реализована поддержка неявного освобождения памяти (автоматическая распределенная сборка мусора)
Пример использования:
tfree(p); -- освободить память, захваченную ранее.
#define tnew(type) new T_Val<type>
Этот макрос следует использовать для захвата Т-структур в куче памяти, содержащих неготовые значения (т.е. поля с атрибутами tval и tptr).
Это необходимо делать постольку, поскольку соответсвующие оберточные классы C++ имеют непустые конструкторы, и, соответсвенно, требуется их корректная инициализация.
Пример использования:
struct line_tptr { struct line tptr v; }; struct line_tptr_array { struct line_tptr a[N]; }; struct line_tptr_array tptr aa = tnew(struct line_tptr_array); Здесь вложеннся структура line_tptr имеет поле с атрибутом tptr, поэтому требуется ее захват с помощью tnew.
#define tnewarray(type,n) new T_Val<type>[n]
Тоже, что и предыдущий макрос, но для массива T-структур.
Пример использования:
tnewarray(struct line_tptr, 20) Захватить 20 неготовых значений
#define tdelete(ptr) delete ptr
Удаление захваченной в куче памяти Т-структуры
Пример использования:
tdelete(p)
#define tdeletearray(ptr) delete[] ptr
Удаление захваченного в куче памяти массива Т-структуры
Пример использования:
tdeletearray(p);
#define tvalidate(val) ((val).validate())
Этот макрос делает неготовое значение готовым. По умолчанию, все результаты Т-функции автоматически становятся готовыми после ее завершения, однако можно сделать значение готовым и раньше с помощью данной возможноси.
Этот макрос также часто используется при изготовлении Т-значения в обычных функциях C.
Пример использования:
tvalidate(*p);
Корректное аварийное завершение параллельной программы.
Пример использования:
abort();
#define tptrwait(x) (x).wait()
Явное ожидание Т-значения, на которое указывает неконстантный Т-указатель.
Временно введенная возможность, которая является излишней и вскоре будет удалена.
Пример использования:
tptrwait(p);
Регистрация функции - финализатора.
Пример использования:
atexit(my_terminator);