Коли має сенс спершу скласти власну мову до коду С?


34

Коли розробляється власна мова програмування, коли має сенс написати перетворювач, який приймає вихідний код і перетворює його в код C або C ++, щоб я міг використовувати існуючий компілятор, наприклад, gcc, щоб закінчити машинний код? Чи є проекти, які використовують такий підхід?



4
Якщо ви подивитесь на минулий C, ви побачите, що C # і Java також компілюються на проміжні мови. Ви врятуєтесь від необхідності переробити багато роботи, яку вже зробив хтось інший, націлюючи на проміжну мову, а не прямуючи до складання.
Кейсі

1
@emodendroket Однак, C # і Java компілюються в IL, який призначений для ІЛ загалом і конкретно для C # / Java, тому багато в чому байт-код CIL і JVM є більш розумним і зручним як ІЛ, ніж коли-небудь C. Справа не в тому, чи потрібно використовувати якусь проміжну мову, а в тому, яку проміжну мову використовувати.

1
Подивіться на кілька вільних реалізацій програмного забезпечення, що генерують код C. І я сподіваюся, що ви зробите для своєї мовної реалізації безкоштовне програмне забезпечення.
Василь Старинкевич

2
Ось оновлене посилання з коментаря @ RobertHarvey: yosefk.com/blog/c-as-an-intermediate-language.html .
Крістіан Дін

Відповіді:


52

Переклад на код С - це дуже усталена звичка. Оригінальний C з класами (і ранніми C ++ реалізаціями, тоді званими Cfront ) зробив це успішно. Деякі реалізації Lisp або Scheme роблять це, наприклад , Chicken Scheme , scheme48 , Bigloo . Деякі люди в перекладі Пролог до C . Так само з'явилися деякі версії Моцарта (і були спроби скласти байт-код Ocaml до C ). Система CAIA зі штучним інтелектом Дж. Пітрата також завантажується і генерує весь свій C код. Vala також перекладається на C для коду, пов'язаного з GTK. Книга Квінека Ліп у невеликих шматочках мати деякий розділ про переклад C.

Одне з питань при перекладі на C - це хвостово-рекурсивні дзвінки . Стандарт C не гарантує, що компілятор C перекладає їх належним чином (до "стрибка з аргументами", тобто без їжі стека викликів), навіть якщо в деяких випадках останні версії GCC (або Clang / LLVM) роблять цю оптимізацію .

Ще одне питання - вивезення сміття . Декілька реалізацій просто використовують консервативний сміттєзбірник Boehm (який сприятливий для C ...). Якщо ви хотіли зібрати код для збору сміття (як це роблять кілька реалізацій Lisp, наприклад SBCL), це може бути кошмаром (ви хотіли б dlcloseна Posix).

Ще одне питання стосується першокласного продовження та виклику / куб . Але розумні хитрощі можливі (дивіться всередині схеми з куркою). Доступ до стеку викликів може зажадати численних хитрощів (але див. Зворотній зв'язок GNU тощо). Ортогональна стійкість продовження (тобто стеків або ниток) буде важкою для C.

Поводження з винятками часто викликає спритні дзвінки в longjmp тощо.

Ви можете створити (у своєму випромінюваному коді С) відповідні #lineдирективи. Це нудно і вимагає багато роботи (ви хочете, щоб, наприклад, створити gdbпростіший код, який легко відрегулювати).

Мій MELT Lispy мову домен специфічний (для настройки або розширень GCC ) переводяться на C ( на насправді поганий C ++ в даний час). У ньому є власне поколінне копіювальне сміття. (Можливо, вас зацікавить MPS Qish або Ravenbrook ). Насправді, покоління GC легше в машиногенерованому коді С, ніж у рукописному коді С (адже ви підганяєте генератор коду С для вашого бар'єру запису та обладнання GC).

Я не знаю жодної мовної реалізації, що перекладається на справжній код C ++, тобто використовуючи деяку техніку "збирання сміття під час компіляції", щоб випромінювати код C ++, використовуючи багато шаблонів STL та поважаючи ідіому RAII . (скажіть, будь ласка, чи знаєте ви її).

Сьогодні смішно - це те, що (на сучасних робочих столах Linux) компілятори C можуть бути досить швидкими, щоб реалізувати інтерактивний цикл читання-друку верхнього рівня, перекладений на C: ви видаватимете код C (кілька сотень рядків) у кожного користувача Взаємодія, ви будете forkкомпіляцією цього об'єкта в спільний об'єкт, який ви б тоді dlopen. (MELT робить це все готово, і зазвичай це досить швидко). Все це може зайняти кілька десятих частин секунди і бути прийнятним для кінцевих користувачів.

Коли це можливо, я б рекомендував перекладати на C, а не на C ++, зокрема, тому що компіляція на C ++ відбувається повільно.

Якщо ви реалізуєте свою мову, ви можете також розглянути (замість того, щоб видавати код C) деякі бібліотеки JIT, такі як libjit , GNU lightning , asmjit або навіть LLVM або GCCJIT . Якщо ви хочете перевести на C, ви можете іноді використовувати tinycc : він дуже швидко збирає згенерований код C (навіть у пам'яті) для уповільнення машинного коду. Але в цілому ви хочете скористатися оптимізаціями, виконаними справжнім компілятором C, таким як GCC

Якщо ви перекладете на мову С, переконайтеся, що спочатку побудуйте весь AST згенерованого коду С у пам'яті (це також полегшить спочатку генерувати всі декларації, потім усі визначення та код функції). Ви могли б зробити деякі оптимізації / нормалізації таким чином. Також вас можуть зацікавити кілька розширень GCC (наприклад, обчислені готи). Ймовірно, ви хочете уникнути генерації величезних функцій C - наприклад, сто тисяч ліній згенерованих C - (вам краще розділити їх на більш дрібні шматки), оскільки оптимізація компіляторів C дуже незадоволена дуже великими функціями C (на практиці, і експериментально,gcc -Oчас складання великих функцій пропорційний квадрату розміру коду функції). Отже, обмежте розмір створених функцій C декількома тисячами рядків кожен.

Зауважте, що і Clang (через LLVM ), і GCC (через libgccjit ) компілятори C & C ++ пропонують певний спосіб випромінювати деякі внутрішні представлення, що підходять для цих компіляторів, але це може бути (чи ні) складніше, ніж випускати код C (або C ++), і є специфічним для кожного компілятора.

Якщо ви розробляєте мову для перекладу на C, ви, мабуть, хочете мати кілька хитрощів (або конструкцій), щоб створити суміш C зі своєю мовою. Мій документ DSL2011 MELT : Мова для перекладеного домену, вбудований у компілятор GCC, повинен дати вам корисні підказки.


Ви посилаєтесь на "Курячу схему?"
Роберт Харві

1
Так. Я дав URL-адресу.
Василь Старинкевич

Чи відносно практично зробити віртуальну машину, як-от Java або щось подібне, компілювати байт-код на C, а потім використовувати gcc для компіляції JIT? Або вони повинні просто перейти від байт-коду до збірки?
Panzercrisis

1
@Panzercrisis Більшість компіляторів JIT вимагають їх машинного коду для підтримки таких завдань, як заміна функції та виправлення існуючого коду дверима для стрибка / пастки. Крім цього, gcc конкретно ... архітектурно менш підходить для компіляції JIT та інших випадків використання. Перевірте libgccjit, хоча: gcc.gnu.org/ml/gcc-patches/2013-10/msg00228.html та gcc.gnu.org/wiki/JIT

1
Чудовий орієнтаційний матеріал. Спасибі!
caпр

7

Має сенс, коли час для генерування повного машинного коду переважає незручність проміжного кроку компіляції вашого "IL" в машинний код за допомогою компілятора C.

Зазвичай мови, що стосуються домену, написані таким чином, використовується система дуже високого рівня для визначення або опису процесу, який потім компілюється у виконуваний файл або dll. Час, необхідний для виготовлення робочої / хорошої збірки, набагато більше, ніж для створення C, і C досить близький код збірки для продуктивності, тому має сенс генерувати C і повторно використовувати навички авторів компілятора C. Зауважте, що це не просто компіляція, але й оптимізація - хлопці, які пишуть gcc або llvm, витратили багато часу на виготовлення оптимізованого машинного коду, було б намагатися відтворити всю свою важку роботу.

Може бути більш прийнятним повторне використання резервного пакета компілятора LLVM, який IIRC є нейтральним до мови, тому ви генеруєте інструкції LLVM замість коду C.


Схоже, що бібліотеки є досить вагомою причиною вважати це теж.
Кейсі

Коли ви говорите "свій" IL ", про що ви маєте на увазі? Абстрактне синтаксичне дерево?
Роберт Харві

@RobertHarvey ні, я маю на увазі код C. У випадку з ОП це проміжна мова на півдорозі між власною мовою високого рівня та машинним кодом. Я поставив це в цитатах, щоб спробувати донести цю думку, що це не IL, як використовуються багатьма людьми (наприклад, .NET IL Microsoft Майкрософт)
gbjbaanb

2

Написання компілятора для створення машинного коду може бути не набагато складніше, ніж написання такого, який виробляє C (в деяких випадках це може бути простіше), але компілятор, який виробляє машинний код, зможе створювати програми, що можна запускати, лише на тій платформі, для якої це було написано; компілятор, який виробляє код C, навпаки, може бути в змозі створити програму для будь-якої платформи, яка використовує діалект С, який створений код підтримує. Зауважте, що у багатьох випадках можливо створити код C, який є повністю портативним і який буде вести себе за бажанням без використання поведінки, не гарантованої стандартом C, але код, який спирається на гарантовану платформою поведінку, може працювати набагато швидше на платформах, які надають ті гарантії, ніж код, яких немає.

Наприклад, припустимо, що мова підтримує функцію отримання атрибуту UInt32з чотирьох послідовних байтів довільно вирівняних UInt8[], інтерпретованих способом big-endian. На деяких компіляторах можна написати код як:

uint32_t dat = *(__packed uint32_t*)p;
return (dat >> 24) | (dat >> 8) | ((uint32_t)dat << 8) | ((uint32_t)dat << 24));

і змусити компілятор створити операцію завантаження слів з подальшим вказівкою зворотного байта в слові. Деякі компілятори, однак, не підтримують модифікатор __packed, і за його відсутності генерується код, який би не працював.

Можна також записати код як:

return dat[3] | ((uint16_t)dat[2] << 8) | ((uint32_t)dat[1] << 16) | ((uint32_t)dat[0] << 24);

такий код повинен працювати на будь-якій платформі, навіть CHAR_BITSна тій, де немає 8 (якщо припустити, що кожен октет вихідних даних опинився в окремому елементі масиву), але такий код, ймовірно, може працювати не так швидко, як, наприклад, непортативний версія на платформах, що підтримують колишню.

Зауважте, що портативність часто вимагає, щоб цей код був надзвичайно ліберальним із типовими типами та подібними конструкціями. Наприклад, код, який хоче перемножити два 32-бітні непідписані цілі числа та отримати нижчі 32 біти результату, для переносимості повинен бути записаний як:

uint32_t result = 1u*x*y;

Без цього 1uкомпілятор у системі, де INT_BITS коливався від 33 до 64, міг законно робити все, що хотів, якщо добуток x і y перевищує 2,147,483,647, а деякі компілятори схильні скористатися такими можливостями.


1

Ви маєте кілька чудових відповідей вище, але враховуючи, що в коментарі ви відповіли на питання "Чому ви хочете створити власну мову програмування в першу чергу?" З "Це було б переважно з метою навчання" я " я буду відповідати з іншого кута.

Має сенс написати перетворювач, який приймає вихідний код і перетворює його в код C або C ++, щоб ви могли використовувати наявний компілятор, наприклад, gcc, щоб закінчити машинний код, якщо вам більше цікаво дізнатися про лексичні, синтаксичні та семантичний аналіз, ніж ви дізнаєтесь про генерацію та оптимізацію коду!

Написання власного генератора машинного коду - це досить вагома робота, якої можна уникнути, компілюючи код C, якщо це не те, що вас насамперед цікавить!

Якщо, однак, ви перебуваєте в програмі складання і захоплюєтеся проблемами оптимізації коду на найнижчому рівні, то, будь-якими способами, самі пишіть генератор коду для досвіду навчання!


-7

Це залежить від того, яку операційну систему ви використовуєте, якщо ви використовуєте Windows, є Microsoft IL (проміжний мова), який перетворює ваш код в проміжну мову, щоб не потрібно часу, щоб скласти його в машинний код. Або якщо ви використовуєте Linux, для цього є окремий компілятор

Повертаючись до свого питання, коли ви розробляєте власну мову, вам слід мати окремий компілятор або перекладач для цього, оскільки машина не знає мови високого рівня. Ваш код повинен бути складений в машинний код, щоб зробити його корисним для машини


2
Your code should be compiled into machine code to make it useful for machine- Якщо ваш компілятор видав код коду як вихід, ви можете помістити код c у компілятор змінного струму для створення машинного коду, правда?
Роберт Харві

так. бо машина не
володіє

2
Правильно. Тож питання було "Коли має сенс випромінювати c та використовувати компілятор змінного струму, а не безпосередньо випромінювати машинну мову чи байт-код?"
Роберт Харві

насправді він просить розробити свою мову програмування, в якій він просить "перетворити його на код C або C ++". Тому я пояснюю це, якщо ви розробляєте свою власну мову програмування, чому ви повинні використовувати компілятор c або c ++. якщо ви досить розумні, вам варто створити свій власний
Tayyab Gulsher Vohra

8
Я не думаю, що ви розумієте питання. Дивіться yosefk.com/blog/c-as-an-intermediate-language.html
Роберт Харві
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.