LINUX.ORG.RU

Из простых только Lua и JavaScript. Все со статической - не простые. Чем Go не устроил? Язык быстро компилируемый, статический, и JIT даже не нужен. JIT жрёт память и CPU, хороший jit всегда сложный, лучший jit на сегодня у движка V8, но он, например, запрещён на айфонах, там свой JavaScript движок.

menangen ★★★★★ ()
Ответ на: комментарий от anonymous

Добавочка.

К wiki правда нужно относиться «осторожно», а то там «научат».
К примеру суждения о иерархических базах данных
вызывает только одно чувство - «Зачем автор пишет о том, о чем слышал „на другом конце села“».

Владимир.

anonymous ()
Ответ на: комментарий от menangen

Меня интересует реализация линкования модулей/функций и hot code reloading (не знаю как правильно перевести на русский). А те же Lua и JS довольно громоздкие в этом плане проекты и просто так их исходники не прочесть

guixoid ()
Ответ на: комментарий от guixoid

Где-то на просторах инета был загрузочный образ, где первым делом запускался tcc, собирал исходники Linux и сразу же запускал собранное. Бессмысленно, но довольно эпично.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

А я думал из AST ассемблится минимальный блок готовый к исполнению и исполняется, потом следующий блок и так далее тоесть как бы потоковое исполнение.

Тоесть если я напишу gcc main.c;./a.out это типа колхозный jit?

LINUX-ORG-RU ★★ ()
Ответ на: комментарий от LINUX-ORG-RU

ассемблится минимальный блок готовый к исполнению и исполняется, потом следующий блок и так далее

Так делают, потому что не могут обработать программы сразу. В языках с динамической типизацией и всем вообще динамическим по другому не получится. Там можно вообще во время работы сгенерить новый исходный код и засунуть его в eval, и это никак не предсказать.

ТС просит статической типизации. В Си такая есть. И он достаточно ограничен, чтобы его можно было просто целиком один раз собрать.

Тоесть если я напишу gcc main.c;./a.out это типа колхозный jit?

Недавний jit-компилятор для ruby так и делает. Я тоже обычно не это представляю, когда говорят «jit». Но, по всей видимости, это считается JIT.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Когда код полностью компилируется перед запуском, называется Ahead of time compilation (AOT)

https://en.wikipedia.org/wiki/Ahead-of-time_compilation

Такое раньше использовалось в андроидах: приложение при установке, до первого запуска, компилировалось в нативный код, практически без оптимизаций. Затем, во время работы приложения, подключался еще и jit и, на основе статистики, перекомпилировал горячие куски кода с повышенными оптимизациями.

Это было где-то во времена 5-6 андроидов. Сейчас, насколько я помню, от этого отказались. Т.к. установка приложений занимала кучу времени. Вроде, подобные механизмы запускаются по ночам, когда телефон заряжается. Но это не точно.

Из плюсов AOT - моментальный запуск приложения. На телефонах это важно. На серверах — не очень.

anonymous ()
Ответ на: комментарий от guixoid

А те же Lua и JS довольно громоздкие в этом плане проекты и просто так их исходники не прочесть

Стоит ли читать исходники интерпритаторов вопрос философский.

Небольшие реализации, занимающие считанные тысячи строк, годятся разве что для эмбедеда. В них не реализованы большинство оптимизаций и продвинутые GC. Они как раз и годятся только для того, чтобы хоть как-то запустить выполнение кода в ограниченной среде.

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

anonymous ()
Ответ на: комментарий от anonymous

Ну как бы да, но как бы нет. Компиляция происходит уже после запуска tcc «скрипта», и результат не сохраняется на диск для последующих запусков. Он живёт только в памяти, пока программа работает.

i-rinat ★★★★★ ()

Немного не так выразился с простыми языками. Под простым языком понимаю простой рантайм/VM, та же JVM довольно большая,а CLR так вообще гигантский.

@no-such-file

HHVM вроде довольно комплексная штуковина,но спасибо,гляну.

@LINUX-ORG-RU @i-rinat

Я не знаю на сколько верно называть 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();
}
guixoid ()
Ответ на: комментарий от guixoid

для меня JIT компиляция это когда код функции генерируется во время исполнения и сразу может исполнятся.

А если ты, скажем, в программе на ходу позовёшь gcc для файла, который на ходу сгенерируешь, потом загрузишь получившуюся so-шку и вызовешь её функцию, это будет JIT?

i-rinat ★★★★★ ()
Ответ на: комментарий от guixoid
// tabulate.c
#define _GNU_SOURCE
#include <assert.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    if (argc < 2) {
        printf("argc < 2\n");
        return 2;
    }

    int pipe1[2];
    int pipe2[2];

    pipe(pipe1);
    pipe(pipe2);

    int c_in = pipe1[1];
    int c_out = pipe2[0];

    if (fork() == 0) {
        close(pipe1[1]);
        close(pipe2[0]);

        dup2(pipe1[0], 0);
        dup2(pipe2[1], 1);

        execlp("gcc", "gcc", "-shared", "-fPIC", "-x", "c", "-", "-lm", "-o",
               "-", NULL);
        return 1;
    }

    close(pipe1[0]);
    close(pipe2[1]);

    FILE *c_inp = fdopen(c_in, "w");
    fprintf(c_inp, "#include <math.h>\n");
    fprintf(c_inp, "double func(double x) {\n");
    fprintf(c_inp, "  return %s;\n", argv[1]);
    fprintf(c_inp, "}\n");
    fclose(c_inp);

    int code_fd = memfd_create("code", 0);
    assert(code_fd);
    FILE *c_outp = fdopen(c_out, "r");
    assert(c_outp);
    while (!feof(c_outp)) {
        char buf[4096];
        size_t read_bytes = fread(buf, 1, sizeof(buf), c_outp);
        write(code_fd, buf, read_bytes);
    }
    fclose(c_outp);

    char code_fd_name[64];
    snprintf(code_fd_name, sizeof(code_fd_name), "/proc/self/fd/%d", code_fd);

    void *handle = dlopen(code_fd_name, RTLD_NOW);
    assert(handle);
    close(code_fd);

    double (*func)(double);
    func = dlsym(handle, "func");
    assert(func);

    printf("tabulate %s:\n", argv[1]);
    for (double x = 0; x <= 1.0; x += 0.125) {
        printf("%5.3f  %f\n", x, func(x));
    }

    dlclose(handle);
    return 0;
}

Собирать так:

gcc tabulate.c -ldl -o tabulate

Использовать примерно так:

$ ./tabulate "sin(x) + cos(x) * cos(x)"
tabulate sin(x) + cos(x) * cos(x):
0.000  1.000000
0.125  1.109131
0.250  1.186195
0.375  1.232117
0.500  1.249577
0.625  1.242758
0.750  1.217007
0.875  1.178420
1.000  1.133398

Я надеюсь, понятно, что делает программа, и как именно она это делает. (И почему так делать не стоит.)

Это считается за JIT или нет? :-D Лишних файлов в файловой системе не создаётся.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Ахаха, ахаха… нет.

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

Я придумал, как долить наркомании в твой пример. Скорми арифметическое выражение компилятору, но не выполняй код, а выдерни ответ в виде константы из какого-нибудь LLVM IR, который он выплюнет. И пусть следующий за мной думает, как это назвать, а кто придумает, повышает градус неадекватства.

t184256 ★★★★★ ()
Ответ на: комментарий от t184256

Как-то так:

#!/usr/bin/xonsh

import re

def eval_int_c(expr):
    code = f'int main() { return {expr}; }'
    ir = $(echo @(code) | clang -O2 -S -emit-llvm -x c - -o-)
    return re.findall(r'ret i32 (.*)', ir)[0]

print(eval_int_c(input()))

Кто классифицирует это?

t184256 ★★★★★ ()
Ответ на: комментарий от t184256

не является альтернативой другом доступном в том же рантайме способу исполнения

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

Я вот не называл бы JIT-(пере)компиляцией сценарии

В ядре можно выключить eBPF интерпретатор, и оставить только eBPF JIT-компилятор. То есть любая программа, загружаемая в ядро сначала преобразуется в родной код для процессора, и только потом исполняется. По твоему определению это получается не-JIT.

i-rinat ★★★★★ ()
Ответ на: комментарий от t184256

сначала преобразуется в родной код для процессора, и только потом исполняется

Другой вариант будет: сначала исполняется, а уже потом генерируется код. И это уже будет настоящий JIT? Я всё понял!

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Ить, вы двое глупые. Если программа сначала конпеляется, а потом запускается — это aot. Если сначала запускается, а потом конпеляется — это jit. Если запускается и не конпеляется — это интерпретатор.

Зовите, когда дойдёте до контейнеров против виртуальных машин.

anonymous ()
Ответ на: комментарий от i-rinat

Скажу, что питоноисходник конпеляется в питонобайтокод, а питонобайтокод интерпретируется и это две раздельных операции. А раз они раздельные, нафига их загонять под одно определение-то?

anonymous ()
Ответ на: комментарий от anonymous

В Erlang/Elixir под hot code reloading понимают возможность накатить новую версию, не останавливая приложение и не теряя данные, которые есть у него в памяти, открытые сетевые соединения и пр.

theNamelessOne ★★★★★ ()
Последнее исправление: theNamelessOne (всего исправлений: 1)
Ответ на: комментарий от theNamelessOne

В Erlang/Elixir под hot code reloading понимают возможность накатить новую версию, не останавливая приложение и не теряя данные, которые есть у него в памяти, открытые сетевые соединения и пр.

Ну, подобный механизм можно реализовать подгрузкой/выгрузкой динамических библиотек.

В Visual Studio под виндой для C/C++ есть механизм edit-and-continue или что-то в этом роде. Суть в том, что при отладке, остановившись на брекпойнте, ты мог подправить код и продолжить выполнение с измененным кодом. Как я понимаю, это реализовано следующим образом: в старом коде в нужное место вставляется jump на память, куда был помещен скомпилированный измененный кусок кода, в конце которого есть jump на оставшуюся часть старого кода.

Получается, что и у C++ есть hot code reloading, если сильно нужно.

anonymous ()