Pull to refresh
Comments 57
Chiken требует сам себя, и я не нашел исходники bootstrap-версии, чтобы скомпилировать первичный компилятор. В принципе, я не против многофазовой сборки, но без первичной С-версии это не удовлетворяет требованию максимальной переносимости.
Python зря исключили Cython мог бы быть интересен, например

P.S. про переносы строк пропустил
У меня очень богатый опыт внедрения Python (и написание расширений на Cython), и могу сразу сказать, что он имеет достаточно большой вклад в размер конечного бинарника (мы его встраивали в железку под урезанной версией Linux). Нам пришлось его сильно резать, включая изменения исходников (насколько я помню, он требовал pthread и эта библиотека не была нам доступна, но не уверен что современный Python имеет те же требования). К сожалению, Python не удовлетворяет нашим требованиям, но в принципе я могу его включить в проект, если это интересно.
Могу ошибаться, но мне кажется, зависимость от pthread опциональна (не знаю, как проверить, не потратив уйму времени)

Добавил Python (с примером использования Cython: https://gitlab.com/nuald-grp/embedded-langs-footprint#user-content-using-cython ) и MicroPython. Как я и предполагал, Python достаточно тяжелый (и тестовая программа даже не включала всю стандартную библиотеку, а только базовый необходимый набор).


К моему удивлению, MicroPython очень тяжело встраивать, пришлось использовать различные трюки, чтобы заставить его работать без написания больших Makefile-ов.

Так а какие минусы у других языков? Например, чем не понравился Lua?
Lua не поддерживает минификацию исходных скриптов, т.к. чувствителен к переносу строк (да и в целом к форматированию, хотя конечно не так сильно как Python). Поэтому он был исключен из рассмотрения.

Что касается других языков, я не говорил, что они мне не понравились. Везде есть свои плюсы и минусы, но если сравнивать по возможностям при минимальном размере, то Chibi-Scheme в лидерах. Lua + LuaRocks — достаточно интересный вариант тоже, но с другой стороны, он уже включен в проект и все желающие могут сами с ним поиграться (хотя я думаю, все заинтересованные лица уже давно знают про Lua).

Насколько мне известно, в lua можно спокойно все переносы строк заменять на пробелы (с обычным исключением в виде многострочных строковых литералов). Вот ещё и пробелы убрать часто будет нельзя, но сделать программу для удаления тех, каких можно, довольно легко.


А почему, кстати, вы говорите именно про переносы строк? Перенос строки, пробел, точка с запятой или ещё какой разделитель все занимают по одному байту, а вам важно уменьшить число служебных символов в принципе (чему больше мешает do…end вместо фигурных скобок, чем какие‐то там новые строки), а не конкретно переносов строк.

Да и Forth никак особо на переносы строк не смотрит, а по компактности программы на форте одни из лучших даже без минимизации.

Как я понимаю, в Forth-е нельзя просто удалить новые строки (но, возможно, замена пробелами не сломает программу). Насчет компактности я согласен, но с другой стороны еще есть вопрос читабельности. Т.е. код пишется для человека, но потом минифицируется для машины. Компактный код на Forth-е не всегда будет достаточно читабельным.

Говорить о читабельности и при этом выбрать Лисп…

И я поддерживаю, что минимификация исходного текста по всем параметрам — скорость исполнения, размер, используемая память, проигрывает байкоду при его наличии, и является неудачным требованием. Которое ко всему прочему еще и сильно ограничило выбор языков.

В форте новые строки точно можно убирать. А читабельность — смотря как писать. Лет пять назад я для развлечения писал решето Эратосфена на разных языках — по одному на каждую букву алфавита. Меньше всего получился скрипт для dc, но я его уже через день не понимал. Второй был форт — и он и сейчас с лёгкостью читается.
Ни до ни после я никогда на форте профессионально не писал, так что большого опыта чтения форта никак нет

Компактный код на Forth-е не всегда будет достаточно читабельным.

Мoжет такое дискуссионное обсуждение, не претендующее на полноту, кому то даст «пищу» для понимания возможностей и «выживаемости» языка в современных реалиях IT :)
Почему обречён язык Форт

Переносы строк — это просто один из примеров, конечно использование do/end тоже может сильно влиять. Более того, замена на пробел != полное удаление. В одном из моих проектов мы делали минификацию встроенного HTML, и простое удаление переносов позволило сэкономить 5% от суммарного размера исходных файлов.


Обновлено, 30% — полная минификация, удаление новых строк дало 5% (HTML был автогенерирован, так что там было достаточно много новых строк).

DOM — это отдельная история, переносы строк на сам рендеринг HTML не влияют (по-крайней мере, наши тестеры и клиенты из Fortune 500 не жаловались, при всем разнообразии поддерживаемых браузеров включая IE6). Естественно, могут быть нюансы, но кросс-браузерная разработка давно заставила всех использовать только безопасное API с правильными селекторами (или просто использовать библиотеки, где это все учитывается).

напомнило старую эпичную тему про "синтаксический оверхед"

Lua не поддерживает минификацию исходных скриптов.

Читаемость/редактируемость минимизированных скриптов при этом отпадает, почему бы тогда сразу в луа-байткод не "компилировать"?
Ну и раз сравнение именно по размеру, ванильная lua собранная TCC+UPX в 64кБ помещается.


QuickJS ещё добавить к сравнению можно было бы.

Луа не чувствителен к переносу строк, если только речь не о многострочных строковых литералах. Более того, если нужна минификация, можно вообще скомпилировать скрипты в байт-код, и в подавляющем большинстве случаев он прозрачно подхватится.
Трудно быть беспристрастным, когда сам являюсь автором языка Umka из упомянутого обширного перечня. Однако таблица со сравнением по одному лишь размеру файла кажется несколько странной, как и то, что в ней перемешаны процедурные и функциональные языки (неужели нет априорных предпочтений на сей счёт?).
Кстати, формально Umka вполне удовлетворяет перечисленным требованиям. Любопытно мнение автора, почему он не попал в итоговый список.

Насколько я вижу, Umka чувствителен к новым строкам, их нельзя просто удалить, так что требования к возможности минификации скриптов не удовлетворяются. Насчет предпочтений процедурных vs функциональных — абсолютно нет. Если уж совсем захочется каких-то возможностей, то всегда можно организировать транспиляцию (и тогда возможность ЯП работать с машинно-генерируемым скриптом тоже пригодится, и я это включаю в категорию "дружелюбность к минификации").


Прошу заметить, что я не считаю, что у вашего продукта есть какие-то недостатки. Естественно, у него есть своя ниша и он там занимает достойное положение. Просто немного не подходит под мои требования. Впрочем, т.к. я уже включил Lua и собираюсь включить Python, я могу включить и ваш ЯП, если есть интерес.

А чем пробел ' ' принципиально отличается от новой строки '\n', который обязателен в используемом Lua (и присутствует в примерах каждого скрипта)? Или под «чувствителен к новым строкам» имеется в виду «выравнивание — часть синтаксиса» как в python?

Под «чувствителен к новым строкам» я подразумеваю, что если полность удалить переносы строк, код перестанет работать. Замены на пробелы потенциально может работать, но насколько я понимаю, это особенность Lua (признаюсь, про которую я не знал).

Мне всё же не совсем понятно, чем '\n' в данном случае будет принципиально отличаться от того же ';' как разделитель выражений. Какой-то символ-терминатор всё равно будет.

Думаю, лучше привести примеры кода (JS):


function f1() {}
function f2() {}

Можно объединить без разделителей (а ";" в выражениях и так нужна, хотя она и опциональная): function f1() {}function f2() {}
В Scheme/Lisp вообще нет разделителей:


(define a 1)
(define b 1)

Объединяются в одну строку: (define a 1)(define b 1)


Так что все зависит от ЯП.

В Umka, как в Go, разделителем является ';' — либо реальная, либо подразумеваемая (в конце строки). Так что просто удалить переносы строк нельзя, однако всегда можно заменить их на ';'. Впрочем, как заметил другой комментатор, какой-то разделитель всё равно нужен. Вы привели пример на Lisp, однако там в той же роли выступают скобки, причём скобка — парный символ, так что в общей сложности их оказывается больше, чем нужно для разделения.

Если уж искать в Umka препятствия к минимализации, то это будет необходимость указывать типы аргументов и результата функции — очевидная издержка статической типизации. Но тогда и само понятие о минимализации надо расширять и уточнять.

Umka пока не столь уж известен, чтобы я оскорбился его отсутствием в вашем списке :) Однако с первого прочтения я действительно не понял, какому именно требованию он не удовлетворил. И в целом, казалось странным, как эти требования оставили от пары сотен языков всего 8, причём довольно экзотических.

Под какую платформу это компилировалось?
И какая область применения? Для stm32 вроде жирновато, а для апельсинки и питон годится.

Под обычный Linux — некоторые ЯП достаточно экзотичны и имеют весьма скудную документацию. Собственно, некоторые я смог завести (чтобы подключить их в виде библиотеки) только изучив их исходный код, так что поддержка кросс-компиляции — это отдельная история, на которую у меня, к сожалению, нет времени.


Область применения — массовые потребительские устройства (включая бюджетные типа телефонов на KaiOS), микроконтроллеры и SoC у нас есть в планах, но далеких. Т.к. на некоторых устройствах (типа той же KaiOS) нет возможности исполнять нативный код, то одна из поддерживаемых target — это WASM, и засовывать туда 5 мегабайт пайтона сверху (которые надо будет пользователю скачать в условиях ограниченного хранилища и дорогого интернета) — это роскошь.

А вы не думали просто использовать WASM? И компилировать в него всё что душе угодно, включая C/C++? Заодно нет оверхеда на рантайм.

довольно бестолковое сравнение. На кой ляд учиывать переносы строк, если можно запускать уже скомпилированный байткод? Наприиегр в луа, при рравильном использовании local и переименовании global, это даст не только компактный размер, но и бесплатную обфускацию.


Но это все лирика. Главный минус статьи — отсутствие оценки потребления RAM. А это основное ограничение встраиваемых систем. В тот же STM32F103 луа поместится запросто, (особенно в верию с 1мб флеша), а вот оперативки свободной не будет уже после пары сотен строк реального кода. Почитайте как допиливали белку, чтобы ужать потребление ОЗУ на микроконтроллерах. Там и разделяемые (shared) строки, и жадное создание констант, и перепаковка полей обьектов, целый букет оптимизаций.

Не все встраиваемые ЯП поддерживают байткод. Более того, возможно мы немного по-разному понимаем обфускацию. Для меня это возможность минимизировать и запутать код (вплоть до изменения хода выполнения кода путем вставления и реорганизации вызовов). Байткод не обязательно обфусцированный, хотя конечно, он компактнее исходного кода. luac -l -l -p дает достаточно читабельное представление байткода Lua, хотя, конечно, я верю, что есть специальные обфускаторы. Однако все это весьма специфично для конкретных ЯП и если есть возможность обфусцировать на уровне исходного кода, это предпочтительнее для нас (но, естественно, может быть неудобно для других и им лучше использовать байткод и сторонние утилиты).


Согласен, что неплохо было бы показать использование RAM. К сожалению, для "Hello world" примера числа будут весьма "бестолковые" тоже, т.к. только специфические конструкции или библиотеки могут вызывать большой расход памяти. Однако какие-то числа лучше чем вообще никакие, так что я добавлю это в тесты (https://gitlab.com/nuald-grp/embedded-langs-footprint/-/issues/4)

Посмотрите ещё на mruby.
И для некоторых реализаций можно выключить парсер/компилятор, оставив только VM, что положительно сказывается на объёме результирующего исполняемого файла.

Ruby чувствителен к переносам строк, так что его реализации были исключены из списка. Но т.к. я включил Lua и собираюсь включить Python, могу и включить mruby в список, если интересно. Но парсер и компилятор будут включены, т.к. необходимо позволять редактировать скрипты в любое время, при этом не требуя дополнительных инструментов.

Забавно, что при задаче «наименьший эффект на размер конечного бинарного файла» – ни одного языка, умещающегося в 64k (Форт, если мне не изменяет память, с необходимым набором слов помещался то ли в 4, то ли в 8 – но он, конечно, не решает задачу типизации; бейсик на 8-битных машинах обычно лежал в ROM размером 16k – на этом фоне 86-1879k кажутся огромными).

ЗЫ: я понимаю, что в нынешних масштабах это несущественно, и реальная задача скорей будет вроде «не больше 2 мегабайт, но максимально удобный нашим юзерам / максимально просто интегрирующийся с нашей системой».
Спасибо автору за столь подробную подборку фреймворков скриптовых языков.
Честно говоря, не ожидал, что для C такое множество решений для встраивания.
Но мне интересно, почему Opject Pascal остался обделённым?
Каким критериям автора он не удовлетворяет?
Не подскажите?

Object Pascal не является таким переносимым, как Си — например, так просто скомпилировать его в WASM, или для микроконтроллеров (как вышеупомяный stm32) вряд ли получится (даже если решения есть, они не такие распространенные, и могут иметь проблемы).

GPL-лицензия, не подходит для коммерческого использования.

Там компиляция в прямой машинный код в адресном пр-ве вызывающей программы.

Скриптом легко уронить хоста.

спавнить отдельный процесс,
пусть там падает сколько угодно.

tcc уже несколько лет как фактически заморожен, т.к. его автор больше над ним не работает (https://bellard.org/tcc/):


[Note: I am no longer working on TCC. Check the mailing list to get up to date information.]

Плюс он компилирует в машинный код, что нам не особо подходит (например, не будет работать в WASM VM).

А что на счёт WebAssembly? Рантаймов разных много на любой вкус.
И ЯП под это дело можно выбрать по вкусу. Например https://vlang.io/

Мы поддерживаем компиляцию в WASM, но внедрение его VM — это совершенная другая история. Мы рассматривали это, но не подошло, в частности из-за того, что не нашли подходящую под наши требования VM. Все, что мы нашли, были реализованы:


  • как надстройка к node.js -> V8. Слишком тяжелый рантайм, плюс сборка V8 — это отдельное приключение (возможно, Blink собирается проще, но все-равно тяжеловесный);
  • на Go (например, https://github.com/perlin-network/life). Go добавляет свой собственный рантайм, плюс может иметь проблемы с биткодом (https://github.com/golang/go/issues/22395 — возможно, это решено сейчас, но зная Apple, они могут это сломать в любой момент)
  • на Rust (например, https://github.com/bytecodealliance/wasmtime). Проблем с рантаймом нет, но зато есть с биткодом (https://github.com/rust-lang/rust/issues/35968)
  • компилируют на ходу (например, https://github.com/WAVM/WAVM). Включают очень тяжелый LLVM.

К сожалению, мы не нашли реализацию на чистом C/C++, но если вы знаете какую-либо, дайте знать пожалуйста.

Есть один интересный проект: https://github.com/wasm3/wasm3. Это обычный интерпретатор. Некоторые фичи там еще не реализованы, но проект по мне очень перспективный. Однако в прод такое пока еще страшно отпускать.

Спасибо за ссылку, действительно интересный проект. Судя по всему, он удовлетворяет нас по всем требованиям, и есть возможность биндинга нативных функций, пусть даже еще не задокументированная (https://github.com/wasm3/wasm3/pull/71). Насчет прода я не особо переживаю, если проект стоящий, то можно найти ресурсы или спонсоров, чтобы его допилили.


Я постараюсь включить его в тесты, и дам вам знать результаты.

Добавил тест и обновил статью. Документации достаточно мало, плюс нет API для взаимодействия, поэтому написал небольшой менеджер памяти для обмена данных (у меня совсем вылетело из головы, что в WASM-е строк и вообще работы с памятью толком нет, и многие решения основаны на том, что все запускается в JS).


В принципе, качество работы вполне удовлетворительное (правда, C++ биндинги подкачали, но это дело наживное). Будем обсуждать с коллегами, но лично я вижу два главных препятствия для того, чтобы wasm3 стал первичным кандидатом для нас:


  • большой расход памяти (проверил valgrind-ом, утечек нет, просто расход большой);
  • [не специфично для wasm3] требуется большой рантайм в самом WASM для хорошей функциональности (я попробовал два варианта: 1) no_std дал размер в несколько десятков байт, но весьма скудная функциональность; 2) полноценный Rust добавил сразу большой рантайм, и хотя стало комфортно программировать, WASM-файл занял несколько сотен килобайт ). Я пытался урезать, но толку мало, все-равно достаточно большие файлы без no_std.

Конечно, компилятор Rust не единственный, можно и Emscripten, но он тоже добавляет свой рантайм, а функциональность будет намного хуже чем в Rust. Впрочем, к самому проекту это отношения не имеет, это отдельная история, и можно рассмотреть те же v-lang, nim и т.п. Дополнительно остается, конечно, вопрос производительности, но этим я буду заниматься в рамках других проектов. Еще раз спасибо за ссылку, при всех сомнениях wasm3 все-равно остается хорошим кандидатом для встраивания.

Не очень несовсем нифига непонятны ваши цели. ТЗ нет, и мечетесь как селедки в трале. wasm он как то попендикулярен всему

Не совсем понял, как это wasm перпендикулярен? Байт-код — это подмножество скриптов, какая разница это бинарный формат или строковой. Lua-байткод не подходил, потому что это проприетарный формат, а оригинальный код не совсем соответствовал требованиям. Wasm стандартизирован, и имеет богатый тулчайн, вплоть до того, что мы можем спокойно модифировать сам байткод, не беспокоясь, что это сломается завтра.

Wasm перпендикулярен ЯП, потому что многие ЯП имеют генерацию в код Wasm. (И должны удовлетворять требованиям отсевки, по идее???)

Какая то двойственность: этот байт код (wasm) нам подходит, а этот — не подходит; этот ЯП подходит, поскольку минимифицируется, а этот не подходит, потому что компилируется в байткод… да просто так захотелось.

Создается впечатление подгонки к определенному результату.
Squirrel достаточно компактный, но достаточно сложно встраивать (любая ошибка в работе со стеком вызовов приводит к краху приложения — у них недостаточно хорошая система обработки ошибок).

Используем в проекте его вот уже больше 10 лет, и я категорически не согласен с приведённым утверждением.
Но некоторые ошибки иногда бывает сложно диагностировать.

И, кстати, на Squirrel-е можно написать вот так:
fn <- @() "Hello, "+read();
Only those users with full accounts are able to leave comments. Log in, please.