Из простых только Lua и JavaScript. Все со статической - не простые. Чем Go не устроил? Язык быстро компилируемый, статический, и JIT даже не нужен. JIT жрёт память и CPU, хороший jit всегда сложный, лучший jit на сегодня у движка V8, но он, например, запрещён на айфонах, там свой JavaScript движок.
К wiki правда нужно относиться «осторожно», а то там «научат».
К примеру суждения о иерархических базах данных вызывает только одно чувство - «Зачем автор пишет о том, о чем слышал „на другом конце села“».
Меня интересует реализация линкования модулей/функций и hot code reloading (не знаю как правильно перевести на русский). А те же Lua и JS довольно громоздкие в этом плане проекты и просто так их исходники не прочесть
Где-то на просторах инета был загрузочный образ, где первым делом запускался tcc, собирал исходники Linux и сразу же запускал собранное. Бессмысленно, но довольно эпично.
ассемблится минимальный блок готовый к исполнению и исполняется, потом следующий блок и так далее
Так делают, потому что не могут обработать программы сразу. В языках с динамической типизацией и всем вообще динамическим по другому не получится. Там можно вообще во время работы сгенерить новый исходный код и засунуть его в eval, и это никак не предсказать.
ТС просит статической типизации. В Си такая есть. И он достаточно ограничен, чтобы его можно было просто целиком один раз собрать.
Тоесть если я напишу gcc main.c;./a.out это типа колхозный jit?
Недавний jit-компилятор для ruby так и делает. Я тоже обычно не это представляю, когда говорят «jit». Но, по всей видимости, это считается JIT.
Такое раньше использовалось в андроидах: приложение при установке, до первого запуска, компилировалось в нативный код, практически без оптимизаций. Затем, во время работы приложения, подключался еще и jit и, на основе статистики, перекомпилировал горячие куски кода с повышенными оптимизациями.
Это было где-то во времена 5-6 андроидов. Сейчас, насколько я помню, от этого отказались. Т.к. установка приложений занимала кучу времени. Вроде, подобные механизмы запускаются по ночам, когда телефон заряжается. Но это не точно.
Из плюсов AOT - моментальный запуск приложения. На телефонах это важно. На серверах — не очень.
А те же Lua и JS довольно громоздкие в этом плане проекты и просто так их исходники не прочесть
Стоит ли читать исходники интерпритаторов вопрос философский.
Небольшие реализации, занимающие считанные тысячи строк, годятся разве что для эмбедеда. В них не реализованы большинство оптимизаций и продвинутые GC. Они как раз и годятся только для того, чтобы хоть как-то запустить выполнение кода в ограниченной среде.
Серьезные решения намного более сложные. Их так просто не осилишь. Если интересуешься темой, то стоит смотреть блоги разработчиков, документацию, выборочные куски кода. Читать теорию. Конкретные реализации, без теории, не принесут много толку.
Ну как бы да, но как бы нет. Компиляция происходит уже после запуска tcc «скрипта», и результат не сохраняется на диск для последующих запусков. Он живёт только в памяти, пока программа работает.
Я не знаю на сколько верно называть gcc file.c; a.out JIT,для меня JIT компиляция это когда код функции генерируется во время исполнения и сразу может исполнятся.
Вот пример JIT’a,код генерируется во время исполнения,аллоцируется память для функции и ее сразу вызывают:
int main(int argc, char *argv[]) {
// Machine code for:
// mov eax, 0
// ret
unsigned char code[] = {0xb8, 0x00, 0x00, 0x00, 0x00, 0xc3};
if (argc < 2) {
fprintf(stderr, "Usage: jit1 <integer>\n");
return 1;
}
// Overwrite immediate value "0" in the instruction
// with the user's value. This will make our code:
// mov eax, <user's value>
// ret
int num = atoi(argv[1]);
memcpy(&code[1], &num, 4);
// Allocate writable/executable memory.
// Note: real programs should not map memory both writable
// and executable because it is a security risk.
void *mem = mmap(NULL, sizeof(code), PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, -1, 0);
memcpy(mem, code, sizeof(code));
// The function will return the user's value.
int (*func)() = mem;
return func();
}
для меня JIT компиляция это когда код функции генерируется во время исполнения и сразу может исполнятся.
А если ты, скажем, в программе на ходу позовёшь gcc для файла, который на ходу сгенерируешь, потом загрузишь получившуюся so-шку и вызовешь её функцию, это будет JIT?
Я вот не называл бы JIT-(пере)компиляцией сценарии, в которых (пере)компиляция не является альтернативой другом доступном в том же рантайме способу исполнения. Но мир со мной не согласен, и называет JIT-компиляцией вообще любую компиляцию во время исполнения, а, значит, и твой пример тоже.
Я придумал, как долить наркомании в твой пример. Скорми арифметическое выражение компилятору, но не выполняй код, а выдерни ответ в виде константы из какого-нибудь LLVM IR, который он выплюнет. И пусть следующий за мной думает, как это назвать, а кто придумает, повышает градус неадекватства.
не является альтернативой другом доступном в том же рантайме способу исполнения
Писать интерпретатор выражений мне лениво. Но в этом нереального нет. Будет опция командной строки, управляющая вариантом подсчёта, либо интерпретация, либо предварительная компиляция и вызов функции.
Я вот не называл бы JIT-(пере)компиляцией сценарии
В ядре можно выключить eBPF интерпретатор, и оставить только eBPF JIT-компилятор. То есть любая программа, загружаемая в ядро сначала преобразуется в родной код для процессора, и только потом исполняется. По твоему определению это получается не-JIT.
Ить, вы двое глупые. Если программа сначала конпеляется, а потом запускается — это aot. Если сначала запускается, а потом конпеляется — это jit. Если запускается и не конпеляется — это интерпретатор.
Зовите, когда дойдёте до контейнеров против виртуальных машин.
Скажу, что питоноисходник конпеляется в питонобайтокод, а питонобайтокод интерпретируется и это две раздельных операции. А раз они раздельные, нафига их загонять под одно определение-то?
В Erlang/Elixir под hot code reloading понимают возможность накатить новую версию, не останавливая приложение и не теряя данные, которые есть у него в памяти, открытые сетевые соединения и пр.
В Erlang/Elixir под hot code reloading понимают возможность накатить новую версию, не останавливая приложение и не теряя данные, которые есть у него в памяти, открытые сетевые соединения и пр.
Ну, подобный механизм можно реализовать подгрузкой/выгрузкой динамических библиотек.
В Visual Studio под виндой для C/C++ есть механизм edit-and-continue или что-то в этом роде. Суть в том, что при отладке, остановившись на брекпойнте, ты мог подправить код и продолжить выполнение с измененным кодом. Как я понимаю, это реализовано следующим образом: в старом коде в нужное место вставляется jump на память, куда был помещен скомпилированный измененный кусок кода, в конце которого есть jump на оставшуюся часть старого кода.
Получается, что и у C++ есть hot code reloading, если сильно нужно.