Pull to refresh

Comments 46

«Очень хороший вопрос! Я ждал этого вопроса. Пожалуйста, следующий вопрос!» ©
Я не разбираюсь в теме, но мне кажется, что разработку / поддержку двух сильно разных компиляторов МЦСТ не потянет — хорошо бы хоть LCC пилить, не отставая от нововведений C++XX.
Так реализация llvm как раз позволит не пилить свой компилятор, а воспользоваться clang, а может быть и gcc (не знаю, на сколько его llvm backend стабилен).
Да и другие языки можно было бы использовать, скажем, Rust.
> для высокоуровневого разбора исходных кодов используется фронтэнд от Edison Design Group
Они не пилят.
Кроме того, видимо, биткод не очень хорош для влива, и используются другие структуры данных, более подходящие.
Для VLIW нельзя не пилить свой компилятор, одна из главных черт этого класса архитектур — упрощение структуры за счет того, что значительная часть работы перекладывается на компилятор, который в принципе является неотъемлемой частью процессора.
Нужно поддерживать только backend. В представлении LLVM достаточно информации для оптимизации.
А что я то? Я точно не гуру. :) Ну а так, конечно LLVM можно хотя и очевидно, что сложнее чем не VLIW.
С другой стороны в LLVM уже есть несколько VLIW архитектур и в последнее время была проделана работа в этом направлении.

Для VLIW нельзя не пилить свой компилятор

Ошибочное утверждение. Хотя конечно с LLVM не всё будет просто.

Меня бесит больше то, что они юзают GPL ный GCC, а исходники не открывают.
Вроде же утверждается что у них фронтэнд от EDG?
Я не верю, что они правили только фронтенд. Кроме того libc то же никто не открывает.
Если я не ошибаюсь, то можно тот же rust скомпилить в C, с помощью соответствующего бэкенда к llvm, а получившийся код потом уже, например, в этот LCC засунуть.
Все жалуются на скорость компиляции, а еще один проход C-компилятора ее не прибавит.
Я слышал о том что некие коллективы делали что то на базе LLVM для МЦСТ. Вполне вероятно, что речь была о бакенде для Эльбруса.

P.S.: По поводу инструкции return в середине. Могу предположить, что здесь происходит что-то аналогичное delay slot из мира MIPS (обратите там внимание на примеры). Если инструкция упала в конвейер в общем кортеже, то она будет ждать выполнения хвоста. А потом управление будет передано в соответствии с return.
nop 5 — скорее всего, действительно ожидание результата. Но вот return %ctp — это гораздо хуже — это подготовка перехода, который можно выполнить когда угодно. Если бы там была только игра с задержками, им бы пришлось ставить return за фиксированное число тактов конвейера до актуального момента перехода (решение более элегантное, хотя и менее гибкое).
Насчет операции return, — погуглите «split branch sh5», это нечто похожее. Общий смысл такой: процессоре есть три так называемых «станка подготовки перехода», для можно запустить подготовку перехода заранее, а затем инструкцией «ct» выполнить переход на один такт (если он будет подготовлен к этому моменту).
Крут. VLIW на пользовательском компьютере — это очень круто. С отладочными платами не очень-то развернёшься (если хочется обрабатывать что-нибудь, кроме сигналов), а тут всё под рукой.
Если исполнение команд процессора хоть чем-то похоже на процессоры TI C6xxxx (а на первый взгляд, сходства очень много), то разобраться в ассемблерных программах без очень подробного описания будет совершенно нереально. Одно только отложенное получение результата чего стоит (это когда в конвейере устройства находится сразу несколько команд на разных стадиях выполнения). И куча команд nop 5 указывает, что ждать результатов действительно приходится.
Надо книжку почитать.
И куча команд nop 5 указывает, что ждать результатов действительно приходится.
Это могут быть delay slot-ы, о которых я выше уже отписался.
Почитал… Ну они и наворотили. Команды для явного управления памятью L2… надо же такое придумать.
А почему команда return стоит в начале — это отложенная команда перехода. Она заносит в %ctpr3 (не знаю, что это такое) информацию о переходе и начинает загружать и обрабатывать команды, которые будут выполняться после перехода. И по команде ct %ctpr3 выполняется уже сам переход — конвейер уже полностью подготовлен.
Даааа, вспоминаются лабораторные ВУЗовские по TMS, где чтение из памяти на 4 такта отложено, и эти 4 такта можно оперировать старым значением регистра.
Плюс 5 тактов на выполнение перехода. И при этом цикл из 8 команд при большой удаче можно упаковать в один такт.
Про переход за 5 тактов не помню, почему-то кажется, что в 1 делалось. По крайней мере, на том процессоре.
Да, упаковка хороша. Помню, была задача обработать массив с количеством тактов меньше кол-ва элементов в массиве. Ох, сидели мы, строили графики «лесенкой», паковали инструкции и в итоге-таки добились того, что с точки зрения программиста на x86 просто невозможно.
Выполняется переход, конечно, за 1 такт. Но заказывать его надо за 5 тактов до перехода. На процессорах TMS320C6xxx.
А обработать массив быстрее, чем за такт на элемент — тут сильно зависит от наличия команды загрузки двойного регистра (LDDW) — кажется, она появилась только в C64xx. Без неё удастся в лучшем случае посчитать сумму элементов.
Не помню такого, вроде за 1 такт и непосредственно по «заказу». Но модель не помню, допускаю, что есть различия.
А там какой-то примитив навроде суммы и был. Все равно, по сравнению с х86 удивительно было.
Давненько я не брал в руки шашек…
Можно даже сказать, никогда.
Тем не менее, код работает.
sum:
            CMPGT   .L2     B4,0,B0
||          ZERO    .L1     A5
||          ZERO    .S2     B5
||          ZERO    .S1     A3

   [!B0]    B       .S2     _END
	    NOP             5

	    MVC             CSR,B6
	    AND             B6,-2,B0
	    MVC             B0,CSR

            AND     .L2     B4,1,B1
||          ADD     .S2X    4,A4,B2
||          SHR     .S1X    B4,1,A2

   [B1]     LDW     .D1     *A4++[2],A3
|| [!A2]    B       .S2     _END
|| [A2]     SUB     .L1     A2,1,A2

            LDW     .D1     *A4++[2],A0
||          LDW     .D2     *B2++[2],B0
|| [A2]     SUB     .S1     A2,1,A2
|| [A2]     B       .S2     _LOOP

            LDW     .D1     *A4++[2],A0
||          LDW     .D2     *B2++[2],B0
|| [A2]     SUB     .S1     A2,1,A2
|| [A2]     B       .S2     _LOOP

            LDW     .D1     *A4++[2],A0
||          LDW     .D2     *B2++[2],B0
|| [A2]     SUB     .S1     A2,1,A2
|| [A2]     B       .S2     _LOOP

            LDW     .D1     *A4++[2],A0
||          LDW     .D2     *B2++[2],B0
|| [A2]     SUB     .S1     A2,1,A2
|| [A2]     B       .S2     _LOOP

            LDW     .D1     *A4++[2],A0
||          LDW     .D2     *B2++[2],B0
|| [A2]     SUB     .S1     A2,1,A2
|| [A2]     B       .S2     _LOOP

_LOOP:
            ADD     .L1     A5,A0,A5
||          ADD     .L2     B5,B0,B5
||          LDW     .D1     *A4++[2],A0
||          LDW     .D2     *B2++[2],B0
|| [A2]     SUB     .S1     A2,1,A2
|| [A2]     B       .S2     _LOOP
_END:
 		B       .S2     B3
 		MVC             B6,CSR
            ADD     .L1     A5,A3,A4
            ADD     .L1X    A4,B5,A4
            NOP             2

Что-то типа такого. Кстати, судя по всему вы правы — переход через 5 тактов:
 .ref _c_int00     ;точка входа
_c_int00:
	
;///////////////////////////////////////	
 .data   ;секция данных
 
array1:	.ushort 1,2,3,1,2,6,7,8,1,1,1,1,1,2,1,2,3,4,5,6,7,8,1,2,6    ;создаем массив 32 разрядных чисел
size    .set 23		  	 	;размер массива(>1)(препроцессорная константа)  	


;///////////////////////////////////////
 .text   ;секция кода 
 


        ;Инициализация:
 		MVKL .S1 array1,A7    ;загружаем адрес массива1 в A3
		MVKH .S1 array1,A7
;		ADD .S1 A3, 2, A7		
		MVK .S2 size,B2    ;загружаем колво элементов массива в A2
;		SUB .L2 B2,1,B2
		MVK .S1 1,A5	   ;ПРОИЗВЕДЕНИЕ ТЕКУЩИХ элементов массива	
		MVK .S1 2,A6	   ;сумма произведений

		MVKL .S1 0X20001, A12 
		MVKH .S1 0X20001, A12 

		MVK .S1 0,A1
		MVK .S1 0,A4	;GGGG
		MVK .S1 0,A8


		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||ADD .S1 A7,2,A7
		NOP 4
		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||ADD .S1 A7,2,A7
		NOP 4
		||LDW .D1 *A7, A0
		||ADD .S1 A7,2,A7
		NOP 4
		||LDW .D1 *A7, A0
		||ADD .S1 A7,2,A7
		NOP 4

		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||ADD .S1 A7,2,A7

		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||ADD .S1 A7,2,A7

		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||ADD .S1 A7,2,A7


LOOP:
		
		[B2] B .S2 LOOP
		||SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||CMPEQ .L1 A0,A12,A2
		||ADD .S1 A7,2,A7
		||[A2] ADD .L2 B4,1,B4
		
		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||CMPEQ .L1 A0,A12,A2
		||ADD .S1 A7,2,A7
		||[A2] ADD .L2 B4,1,B4

		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||CMPEQ .L1 A0,A12,A2
		||ADD .S1 A7,2,A7
		||[A2] ADD .L2 B4,1,B4

		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||CMPEQ .L1 A0,A12,A2
		||ADD .S1 A7,2,A7
		||[A2] ADD .L2 B4,1,B4

		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||CMPEQ .L1 A0,A12,A2
		||ADD .S1 A7,2,A7
		||[A2] ADD .L2 B4,1,B4

		SUB .D2 B2,1,B2
		||LDW .D1 *A7, A0
		||CMPEQ .L1 A0,A12,A2
		||ADD .S1 A7,2,A7
		||[A2] ADD .L2 B4,1,B4

THEEND:
		NOP


Не уверен, что это хороший код, но рабочий. У меня лежит примерно 30 вариантов, в каждом по несколько циклов ужимается, а вот какой финальный — не помню. Интересный был предмет.
Непонятно, как он может быть рабочим. Ведь в этих процессорах нельзя читать память по невыровненному адресу (точнее, можно — но только начиная с C64, и используя команду невыровненного чтения — LDNW, а не LDW).
Серьёзная задача. Похоже, что можно упаковать обработку 8 элементов в 6 тактов. Но это будет очень непросто.
Примерно так.
	.global find12
find12:
	AND	.L2	B4,3,B2
||	SHR	.S1X	B4,2,A2
||	ADD	.D2XA4,4,B4
||	ZERO	.L1	A0
||	ZERO	.S2	B0
||	MV	.D1	A6,A8

	ZERO	.L2	B2
||	SUB	.L1	A2,2,A2
||	MV	.D2XA6,B8
||	MVC             CSR,B9
	AND             B9,-2,B1
	MVC             B1,CSR
||	LDW	.D1	*A4++[2],A5  ;; [0@0]
||	LDW	.D2	*B4++[2],B5  ;; [0@0]

	NOP	2

	LDW	.D1	*A4++[2],A5  ;; [0@3]
||	LDW	.D2	*B4++[2],B5  ;; [0@3]

	NOP

	CMPEQ	.L1	A5,A8,A1	 ;; [5@0]
||	SHR	.S1	A5,16,A7	 ;; [5@0]

	LDW	.D1	*A4++[2],A5  ;; [0@6]
||	LDW	.D2	*B4++[2],B5  ;; [0@6]
||	ADD	.S1	A0,A1,A0     ;; [6@0]
||	B	.S2	_F12LOOP

	SUB	.D2	B5,B8,B1	 ;; [7@0]
||	SHL	.S1X	B5,16,A6	 ;; [7@0]
||	SHR	.S2	B5,16,B7	 ;; [7@0]
||	ZERO	.L2	B6			 ;; [7@0]
|| [A2]	SUB	.D1	A2,1,A2		 ;; [7@0]

	CMPEQ	.L1	A5,A8,A1	 ;; [5]
||	SHL	.S2X	A5,16,B6	 ;; [5]
||	SHR	.S1	A5,16,A7	 ;; [5]
|| [!B1] ADD	.L2	B0,1,B0	 	 ;; [8]
||	ADD	.D1	A6,A7,A6	 ;; [8]
||	ADD	.D2	B2,B6,B2	 ;; [8]

_F12LOOP:
	LDW	.D1	*A4++[2],A5  ;; [0]
||	LDW	.D2	*B4++[2],B5  ;; [0]
||	ADD	.S1	A0,A1,A0     ;; [6]
||	ADD	.L2	B6,B7,B6     ;; [6]
|| 	CMPEQ	.L1	A6,A8,A1	 ;; [9]
|| [A2]	B	.S2	_F12LOOP	 ;; [9]

	SUB	.D2	B5,B8,B1	 ;; [7]
||	SHL	.S1X	B5,16,A6	 ;; [7]
||	SHR	.S2	B5,16,B7	 ;; [7]
||	CMPEQ	.L2	B6,B8,B6	 ;; [7]
|| [A2]	SUB	.D1	A2,1,A2		 ;; [7]
||	ADD	.L1	A0,A1,A0	 ;; [10]

	CMPEQ	.L1	A5,A8,A1	 ;; [5]
||	SHL	.S2X	A5,16,B6	 ;; [5]
||	SHR	.S1	A5,16,A7	 ;; [5]
|| [!B1] ADD	.L2	B0,1,B0	 	 ;; [8]
||	ADD	.D1	A6,A7,A6	 ;; [8]
||	ADD	.D2	B2,B6,B2	 ;; [8]

	;; end loop

	B	.S2	B3
   	CMPEQ	.L1	A6,A8,A1	 ;; [9]
	ADD	.L2	B0,B2,B0
  	ADD	.L1	A0,A1,A0	 ;; [10]
	ADD	.L1X	A0,B0,A4
	MVC	.S2	B9,CSR


3 такта на 4 полуслова.
Правда, этот код работает только для массивов длиной, кратной 4 и не меньше 12, но добавить дополнительную обработку для коротких массивов — дело техники.
А уж как в этих процах (TI) тормозит кастрированный C++ код… Интересно как тут оптмизатор с плюсами справляется.
А как с ними вообще можно справиться? Для вызова виртуальной функции нужно, как минимум, двойное обращение к памяти (плюс какой-нибудь анализ на случай множественного наследования), а потом — сам вызов. Тоже весьма долгий, так как адрес заранее неизвестен. Хорошо, если параллельно с подготовкой вызова можно ещё что-нибудь поделать. А если нет — остаётся только ждать.
Очень круто вы все описываете — тема сложная, но изложено все грамотно и системно, в отличие от многих подобных статей на хабре. Спасибо!
Сколько же этот Эльбрус был у вас «проездом», что так детально удалось покопаться? :)
Ну почему же: есть он и его хватает…
Основной порядок байт там little endian. На уровне системы команд очень просто указать конкретной операции обращения в/из памяти поменять порядок байт.
Я имел в виду не саму операцию перестановки байт, а мнемонику SEX.
(книгу не смотрел)
Вангую, что расположение инструкции return явно учитывает наличие конвейера. return начинает загружать инструкции из точки возврата в конвейер, а пока до них дойдёт управление, выполняются идущие перед ними инструкции текущей функции.
Не только. По коду видно, что там примерно 15 тактов. Загрузка конвейера, конечно, медленный процесс, но не настолько же…
Штатным компилятором языков C/C++ в операционной системе «Эльбрус» является LCC — собственная разработка фирмы МЦСТ, совместимая с GCC.


Эмм… А разве у них используется не допиленная версия вот этого LLC?
«Керниган и Ритчи — не близнецы-братья, а просто однофамильцы». (старый анекдот)

Нет, в данном случае буква «Эль» означает «Эльбрус», а «CC» — compiler collection, наверное (как и у GCC, с которым LCC старается быть совместим). И это совершенно иная разработка.
Есть ли возможность скачать где-то LCC от МЦСТ, в виде кросс компилятора с x86/amd64 или в виде исходников?
А в чём выражется совместимость с GCC? Можно ли Аду открутить от GCC и поставить на LCC?
Имеется в виду способность выглядеть как GCC, плавать как GCC (но не крякать как GCC, правда) — с точки зрения языков C, C++, Fortran. То есть поддерживаются все те же ключи запуска и дополнения к языковым стандартам. Компилируемая программа думает, что ею занимается GCC; хотя, если она в курсе про существование LCС, то, конечно, может детектировать его по соответствующим define'ам.
Мда. Очень печально, что адскими проверками нельзя насладиться на архитектуре, которая реализует их аппаратно.

Интересно было бы сравнить Эльбрус+LCC и CHERI+BERI. CHERI поизвестнее LCC будет, однако внезапно Эльбрус у нас оказался более массовым, а я думаю, Эльбрусов выпущено гораздо больше, чем BERI.
Если есть транслятор с нужного языка в C/C++, то насладиться получится. Если нет, то придётся портировать компилятор этого языка самостоятельно.
Only those users with full accounts are able to leave comments. Log in, please.