- 1
- 2
- 3
- 4
- 5
- 6
function main() {
const x = 21;
let s = "foo";
const r = `a${x * 2}X${s}X${s}Z`;
print(r);
}
Нашли или выдавили из себя код, который нельзя назвать нормальным, на который без улыбки не взглянешь? Не торопитесь его удалять или рефакторить, — запостите его на говнокод.ру, посмеёмся вместе!
+1
function main() {
const x = 21;
let s = "foo";
const r = `a${x * 2}X${s}X${s}Z`;
print(r);
}
Продолжаем будни говно-писания говно-компилятора на LLVM. серия - а ваш говно-компилятор может так...
и результат работы
>>
C:\temp\MLIR_to_exe>out.exe
a42XfooXfooZ
Тем, что в нём нету регистра 33?
Если я сделаю процессор, в котором можно "создать" 64 регистра, то я смогу запустить на нём llvm?
А что, их вообще может быть сколько угодно много? Фронтэнд может сгенерить код, в котором будет 65536 регистров?
Ну да, это просто форма записи такая. Все переменные, промежуточные результаты и т.п. попадают в "регистры". Если функция сложная, там запросто нагенерится тыща "регистров".
Ну и да, посколько это single assignment, то там одна переменная может в десяток-другой раскрыться, чтобы для оптимизатора поток данных нагляднее был.
А вот как это в gcc сделано? Там же тоже есть фронт и бек, но там не генерится промежуточный код? Или генерится? А тогда в чем разница?
Ябло вроде хвасталось, что перейдя на llvm они смогли применять какие-то фиксы (типа ARC или чего-то такого) уже вот прямо к llvm коду
В gcc тоже промежуточный код был, как без него. Вроде даже джва. Но он там пиздец запутанный и фазы не так чётко разделены как в llvm, емнип.
вот clang же высирает код в llvm, а далее llvm генерит по нему код под x86, верно?
как же сlang может чего-то не знать?
А соглашения о вызовах, аллокация конкретных регистров, выбор конкретных инструкций -- это уже задача бэка.
* в "промежуточном коде" LLVM есть понятие "регистр"
* в этом коде регистров может быть неограниченное количество
* clang генерирует это код, используя понятие "регистр"
* clang может создать промежуточный код, в котором используется 234234 регистра
* если доступ к первым пяти будет дешевым, а к остальным дорогим (бо в реальной архитектуре они эмулируются), то clang сгенерит неоптимизированный код
Или бекенд может "оптимизировать" код, удалив лишне регистры, если он знает, что делает код для гиоптетчиской LLVM машины с пятью регистрами?
Типа clang насрал 100500 регистров, а бек их причесал?
Какие из них горячие, а какие нет фронт не знает. Да его это и не должно ебать. Выделять регистры и слоты на стеке -- задача бэка и только бэка. Ибо только он знает сколько там регистров на таргете и видит более-менее финальную форму кода после кучи перестановок и оптимизаций.
LLVM IR регистры — абстрактные, и к хардварным отношения не имеют.
> в этом коде регистров может быть неограниченное количество
> clang может создать промежуточный код, в котором используется 234234 регистра
Да, потому что SSA, и LLVM "регистры" иммутабельные. Если фронтенд заанроллит цикл с 234234 итерациями, то под i понадобится 234234 регистров.
> clang генерирует это код, используя понятие "регистр"
...из LLVM IR.
> Или бекенд может "оптимизировать" код, удалив лишне регистры, если он знает, что делает код для гиоптетчиской LLVM машины с пятью регистрами?
Бекенд по определению не делает код для гипотетической машины, он делает код для конкретной хардварной рахитектуры.
я просто думал, реально ли сделать такое
фронт --> LLVM код --> реальный проц
но уже понял, что без бека это нереально
А почему? Их ленивые thunk'и плохо вписались в LLVM IR?
Интересненько.
А у них до сих пор в качестве «промежуточного» кода урезанная Сишка?
Этот "универсальный" бэкенд настолько же универсален, как и какое-нибудь внутреннее говнопредставление из GCC. Нихуя такого прорывного там нет с т.з. фундаментальных вещей, просто еще одна питушня для промежуточного представления питушни с целью трансформации-оптимизации, коих было дохуя, есть дохуя, и будет еще дохуя. Не очень-то ясно, с хуев каких на него так все надрачивают.
Очевидно же. Питузу в нём проще разобраться, чем в хитросплетениях гцц.
И любой питуз может клепать на коленке свои анскильные rustы и кричать на каждом углу «йаже как Сишка».
Нет. Он знает про то, что такой-то процессор имеет такой-то размер указателя и такие-то требования по выравниванию, а это влияет много на что, например на чтение-загрузку структур с указателями
Вот пример https://godbolt.org/z/ednTbTfMM - align 4 для 32 бит и и align 8 для 64 бит
А если clang будет генерить код под процессор, где например char 16-битный, там указатели на char (и указатели на void) будут не i8* а i16*
А еще https://llvm.org/docs/LangRef.html#x86-amx-type есть вот такая x86-специфичная хуйня, которой очевидно нет нигде, кроме x86.
Так что говорить что фронт ничего не знает про процессор - некорректно. Он вообще-то дохуя чего знает
> X86_mmx Type
Какое говно )))
Они добавили эту крайне специфичную питушню к таким базовым, универсальным вореционным типам как i8, i16, i32 и плавающим fp32, fp64, fp128.
Так-то этот LLVM может теоретически изначально оперировать generic-хуитой типа i8 i16 и проч, и на какой-то из говностадий перефигачить это во всякое процессорозависимое говно типа X86_mmx. Это еще называется "lowering"
https://llvm.org/docs/WritingAnLLVMBackend.html#basic-steps
> Describe the selection and conversion of the LLVM IR from a Directed Acyclic Graph (DAG) representation of instructions to native target-specific instructions. Use TableGen to generate code that matches patterns and selects instructions based on additional information in a target-specific version of TargetInstrInfo.td. Write code for XXXISelDAGToDAG.cpp, where XXX identifies the specific target, to perform pattern matching and DAG-to-DAG instruction selection. Also write code in XXXISelLowering.cpp to replace or remove operations and data types that are not supported natively in a SelectionDAG.
Я думал, что если я создам такую машину, которая запускает LLVM код напрямую, и в ней будет 4 регистра (а остальные эмулируются), то код будет не оптимальным
Но теперь я понял, что мне всё равно нужен будет для нее бек, и превратить идеальный LLVM код в реальный это будет задача уже бека
Ну да, если ты надумаешь исполнять LLVM код "вживую", он будет неоптимальным. Ну потому что эта промежуточная форма весьма далека от железа.
А если у тебя будет бэк, то ты уже не LLVM код исполняешь, лол.
Чтобы "вживую" его исполнять, нужно иметь соответствующий процессор, а такого процессора нет, да и сделать его похоже что нереально, ну вот как ты будешь регистры хуй знает какого размера в процессоре реализовывать? LLVM разве что интерпретировать можно вживую
Сложнее с количеством. Видимо придется штук 30 держать регистрами, а остальное своповать в память.
Сомнительная идея. Не всякий регистр ты сможешь микрокодом эмулировать.
Проэмулируй мне регистр размером в килобайт в микрокоде
пусть ОСили прошивка загрузит ему адрес куска памятив регистр, как таблы страниц в CR3 (или куда их там грузят)
вот как освобождать -- это интересно
сделайте в процессоре GC
Уже сделали: https://en.wikipedia.org/wiki/Symbolics#The_3600_series
> Hardware support was provided for virtual memory, which was common for machines in its class, and for garbage collection, which was unique.
Но эти аппаратные лисп-машины закономерно обосрались. Железо это не говноязык типа "C++", что туда всякое говно можно набрасывать спокойно, и ничего особо плохого не будет
так и случилось
На стеке же. А в реальный регистр положить её адрес и длину. Освободится вместе с фреймом.
А если стека окажется недостаточно для такого жирного регистра? Ну и к тому же регистр можно возвратить из функции, а стек нельзя, как эта проблема будет решаться?
Мне вот интересно, что реальные бекенды с этим делают. Просто шлют нахуй или эмулируют через длинную арифметику?
Думаю что middle-end говнопредставление перед переходом в back-end должно все такое говно вычистить. Т.е. хуйня с жирными LLVM регистрами должна в этом LLVM там как-то заоптимизироваться и потом в итоге переписаться в регистры с допустимым размером.
А где эта грань? Любой проц интерпретирует какой-то байткод.
Реальная конпеляция разве что в верилоге да вхдл, там не доебёшься.
Да как это? Загружали команды по одной и исполняли их. Что это, если не интерпретация?
Причём микрокодом. И со стадиями конвейера (начиная с 486).
В x86 микрокод был всегда.
Пруфом тому количество тактов на команду.
Причём всё было наоборот, они только где-то с 486 научились некоторые инстры исполнять напрямую.
У тебя тут "rol" а какая-то хуйня.
Если ты сдвигаешь 32-битного питуза, надо return (x << 15) | (x >> (32-15) );