Из определения трансляции. Если есть программа Х на языке ХХ, то результат трансляции - программа Y на языке YY, котора эквивалентна Х с точки зрения семантики. Иначе это не трансляция, а хуета.
Так как для виснущей программы думаю найдутся уловки доказать, что это вырожденный случай.
Нет, это невырожденный случай, потому как нам известно множество примеров полезных виснущих программ. Эвентлупы, сервера и т.п.. - они работают и никогда не вырубаются (потенциально) и соответствующий им по такой схеме код - бесконечный.
Конечно неоднозначно. Программы, в которых данные однозначно заданы, никому не нужны. Потому что можно просто вместо этой программы подставить сразу результат ее выполнения.
машинный код тоже может быть актуально бесконечный по числу команд и времени.
Нет, машинный код всегда конечен. Любая программа - это конечная последовательность термов. Машинный код - тоже программа, просто на языке процессора. Трансляция - это функция, которая берет конечный терм на одном языке и возвращает конечный терм на другом языке (который семантически эквивалентен исходному терму).
Достаточно одной команды JMP, которая безусловно переходит сама на себя. Где ты увидел «конец»?
Тут и увидел. В это случае - программа состоит всего из одной единственной команды, собственно, джампа.
то, что ты говоришь «команд процессора нет и сделать их невозможно из интерпретируемого кода».
Команды процессора есть. Нету _конечной последовательности команд_, которую можно было бы считать переводом. Потому как бесконечная последовательность команд - текстом не является. И потому переводом быть не может.
При чем тут частичные вычисления? У нас есть программа, ее нельзя оттранслировать, но можно интерпретировать. Все. Ч. Т. Д. Одной такой программы для одного какого-нибудь ЯП (пусть даже выдуманного специально для доказательства нашего тезиса) достаточно, чтобы утверждать, что интерпретатор, в общем случае, не является транслятором.
От того, что ты написал эти могучие цифры, код цикличным не стал. Я приведу конкретный пример конкретного кода из реальности тогда, чтобы совсем наверняк:
на каждом шаге генерится код, он компилируется в байткод и тот потом компилируется в машкод джитом. После джита на каждом шаге получается разный код, в силу того, что в нем разные константы (значения i). И этот машкод генерируется до бесконечности. В итоге выполняемый машкод будет следующим:
и далее, бесконечная портянка (между этими командами понятно еще код виртуальной машины, я его опускаю). Видно, что этот код нецикличен - никакого подмножества команд, копиями которой этот код мог бы быть, тут не существует, так как в каждой паре своя собситвенная уникальная константа.
Да, но разница в том что если мы залогируем выполняемые команды, то в случае
for (i = 0; i; i++){
print(i);
}
мы получим последовательность
push i
call print
push i
call print
...
Которая, очевидно, циклична (на деле вместо i будет адрес в памяти или регистр, это не суть, важно что это будет один и тот же адрес или один и тот же регистр). А в случае с евалом будет то, что я написал выше, отсутствие цикличности.
А код без ветвлений, чисто линейный, по здравому размышлению, бесконечным быть не может.
может. На бесконечной ленте машины Тьюринга. На конечной ленте с n битами возможно ровно 2ⁿ состояний, после которых будет повтор прошлой комбинации(или раньше). Уже обсудили.
нет. Мы получим цикл. Только на ассемблере. В ассемблере x86 есть команда jnz, которая переходит если не ноль. Тебе этого мало? Почему ты тело цикла переводишь в код, а сам цикл for(;;) не переводишь, и куда он у тебя исчез? Опять магия?
От того, что ты написал эти могучие цифры, код цикличным не стал. Я приведу конкретный пример конкретного кода из реальности тогда, чтобы совсем наверняк:
теперь докажи и покажи мне эту свою «портянку» из одних и тех же команд, и покажи, куда делся цикл?
Я тебе не верю, а дебажить твой говнокод на лиспе мне лень.
на практике анонимус принимает желаемое за действительное, и показывает только те команды, которые понимает (почему-то push+printf без pop). С такой ерундой через микросекунду будет переполнение стека и сегфолт.
А в теории это обычное дело для наших борщеедов-скобочников, любящих заниматься теоретическими размышлениями. Лисперы они вообще живут в другой реальности. Как и их гипотетический код. Которого в этой реальности нет.
да, у меня где-то валялся интерпретатор/компилятор для brainfuck'а. Это очень простой ЯП, и если ты попробуешь сделать его компилятор/интерпретатор, то ты поймёшь, что тут на самом деле всё просто. Там всего 8 команд: см. http://ru.wikipedia.org/wiki/Brainfuck
Т.ч. компилятор/интерпретатор будет очень простым. Что бы ещё упростить задачу, можно компилировать не в машкод, а в C код, который затем прогонять оптимизирующим компилятором типа gcc.
Данный ЯП обладает Тьюрринг-полнотой, а следовательно на нём можно переписать _любую_ задачу с _любого_ ЯП. Это доказано.
откуда я знаю? Конечно запостить на ЛОР. Что же ещё?
Это интерпретатор типизированного лямбда-исчисления с числами и их арифметикой.
ТЫ рассказываешь как его превратить в компилятор из лямбда-термов в ассемблер x86_64 (если это «формально одно и то же» — этот интерпретатор на x86_64 запускается и работает, я проверил :)).
facepalm.
Ничего не фейсплалм. Бери любой интерпретатор байткода и любой компилятор и смотри какие там техники реализаций ([1], [2], [3], bytecode translation, bytecode execution, virtual machines & runtime, JIT, tracing JIT).
ТЫ объясняешь как они «формально одно и то же».
Давай с твоим выше разберёмся.
Так разобрались же уже — вот тебе и сказать нечего.
У нас есть программа, ее нельзя оттранслировать, но можно интерпретировать
Вот там и превращают I_static * I_dynamic -> O в I_dynamic -> O предвычислением I_static, но это последнее I_dynamic -> O всегда остаётся и не устраняется, это то что ты говоришь — интерпретация всегда возможна, а трансляция только частично — всё равно останется кусок на интерпретацию.
за то это _полный_ компилятор/интерпретатор, а не просто составление AST. Потому ты и путаешься в примитивных вещах, т.к. у тебя нет опыта перевода AST на уровень железа. Даже для brainfuck'а.
теперь докажи и покажи мне эту свою «портянку» из одних и тех же команд, и покажи, куда делся цикл?
Он никуда не девался, его там не было. На каждой итерации объявляется новая функция, компилируется джит-компилятором и генерируется новый машкод, отличный от машкода других итераций. Потом этот новый машкод выполняется.
на практике анонимус принимает желаемое за действительное, и показывает только те команды, которые понимает (почему-то push+printf без pop).
Я опустил несущественные команды. pop в самом printf. От того что ты добавишь туда еще каких-то команд код все равно не станет циклическим, он нециклический из-за констант. Пока ты не уберешь константы - он не может быть приведен к циклическому виду.
Как и их гипотетический код. Которого в этой реальности нет
Я тебе привел конкретный код, который есть в реальности, который можно выполнить и который работает вполне конкретным образом. Но ты же в домике.
Внимательно смотрим на различие в типах, сигнатурах, коде и назначении.
(1) — интерпретатор по мере исполнения своей программы работает с состоянием своей машины (регистры, адреса, эффекты исполнения), транслятор занимается синтаксическим переводом своей программы (имена, метки, генерация инструкций).
(2) — интерпретатору HLT это остановка, транслятору — нет, просто очередной кусок терма для перевода.
(3) — если выполнение интерпретатором закончится, то останется конечное состояние машины, трансляция всегда закончится, результатом будет выхлоп в виде перевода исходной программы.
(4) — обход программы интерпретатором начинается с исходного состояния машины и продолжается сколько угодно долго, это её выполнение, в т.ч. с зависаниями, у транслятора — один проход по программе за конечное время, это перевод, он начинается с одной программы и даёт в итоге другую.
(5) — даром что оба AST-driven, потому что примитивнейшие.
(6) — сами по себе действия разные.
И ты не отвечаешь про интерпретатор лямбда-исчисления с арифметикой — если что, то мне в x86_64, пжалуста, без стека, но с аллокацией регистров и оптимизацией выражений :)
И про реальные(tm) интерпретаторы с компиляторами тоже ничего не рассказываешь :(
Неверное. jnz ничем не поможет, т.к. на каждой итерации надо перескакивать на _новый_ кусок машкода. И эти новые куски генерятся до бесконечности. Никакого конечного цикла, если эти куски склеить, не получится. На самом деле, там в коде есть три куска, первый и третий постоянны, а второй - генерится на каждой итерации, замещая предыдущий. То есть:
1. прыгнули на начало цикла, сгенерили машкод
2. прыгнули на сгенеренный машкод, вывели число, прыгнули на конец цикла
3. инкремент i, проверка на выход, прыжок на начало цикла
Писать «компилятор/интерпретатор» с намёком на то что это одно и то же есть смысл только если ты хочешь чтобы тебя приняли за идиота или человека который не знает значения используемых слов.
Потому ты и путаешься в примитивных вещах, т.к. у тебя нет опыта перевода AST на уровень железа. Даже для brainfuck'а.
Ты путаешься в примитивных вещах, т.к. у тебя нет опыта разработки хоть сколько-нибудь сложного транслятора (brainfuck'и не считаются, а то может ты там напился, вот у тебя «компилятор/интерпретатор» и вышел).
Или мы уже перешли на рассмотрение JIT-компиляции, вместо классической?!
Можно и без jit. Можно либо не генерить машкод вовсе (и интерпретировать то что внутри евала), либо как будет в cl - генерить машкод ф-и сразу при ее объявлении (а не при вызове). Но один хрен на каждой итерации будет создан новый, отличный от других кусок машкода. И «классическая компиляция» тут просто невозможна. Нету кода, который можно получить до запуска программы. Он генерится во время работы программы (такова семантика программы) и потому бесконечен (т.к. программа его бесконечно генерит и выполняет и генерит...). Это как раз то, что получается, когда мы пытаемся компилировать интерпретируемый по дизайну язык.
Это как раз то, что получается, когда мы пытаемся компилировать интерпретируемый по дизайну язык.
То есть либо мы такое дело интерпретируем (и никакого машкода нету вовсе), либо мы можем компилировать «кусками», но тогда получается бесконечный машкод. Скомпилировать в конечный машкод же мы не в состоянии. По-этому трансляции не происходит. Нету конечного машкода, который был бы семантически эквивалентен исходной программе.
Нет, меняется вся итерация. Без евала просто выводится символ, с евалом - объявляется функция, компилируется в машкод, выполняется этот машкод. На каждой итерации.
Он никуда не девался, его там не было. На каждой итерации объявляется новая функция, компилируется джит-компилятором и генерируется новый машкод, отличный от машкода других итераций. Потом этот новый машкод выполняется.
это называется «неявный цикл». Типа вот этого:
#include <stdio.h>
void f(int counter, int *y)
{
· if(counter == 0)· return;
· *y *= counter;
· f(counter-1, y);
}
int main(int argc, char *argv[])
{
· int n = 5;
· int y = 1;
· f(n, &y);
· printf("%d!=%d\n", n, y);
· return 0;
}
думаешь тут нет цикла? Это ты так думаешь, а gcc считает иначе:
08048450 <f>:
8048450: 8b 44 24 04 mov eax,DWORD PTR [esp+0x4]
8048454: 8b 4c 24 08 mov ecx,DWORD PTR [esp+0x8]
8048458: 85 c0 test eax,eax
804845a: 74 0c je 8048468 <f+0x18>
804845c: 8b 11 mov edx,DWORD PTR [ecx]
804845e: 66 90 xchg ax,ax
8048460: 0f af d0 imul edx,eax
8048463: 48 dec eax
8048464: 75 fa jne 8048460 <f+0x10>
8048466: 89 11 mov DWORD PTR [ecx],edx
8048468: c3 ret
как видишь, gcc свернул рекурсию в цикл. Твой хаскель/лисп тоже так делает. Причём это возможно всегда, хотя часто только ценой дополнительной памяти для стека.
Без разворачивания нечего сворачивать. Чтобы получить свернутый машинный код, нам надо вообще для начала получить машинный код. А он бесконечный и нециклический. Мы можем сразу преобразовать в машинный код обвязку цикла, но то что внутри евала так и останется нескомпилированным кодом на лиспе до тех пор, пока выполнение туда не дойдет.
Нет, это не «неявный цикл». Тут вообще нету цикла.
как видишь, gcc свернул рекурсию в цикл.
То есть в твоем коде был цикл. Просто ты его записал в виде рекурсии. Если развернуть твой код в машкод мы получим последовательность повторяющихся команд. Она и сворачивается.
Твой хаскель/лисп тоже так делает.
Нет, не делает. Более того - это невозможно сделать. Еще раз - на каждой итерации генерируется новый машинный код и этот новый код исполняется. Потом идет переход к следующей итерации, генерация нового кода, исполнение. И так до бесконечности. Итерации никогда не повторяются (т.к. в машкоде разные константы).
Я опустил несущественные команды. pop в самом printf
нет там pop. Учи матчасть, printf делает «push/pop» только для своих локальных переменных, для внешних локальных «push/pop» делает main.
От того что ты добавишь туда еще каких-то команд код все равно не станет циклическим, он нециклический из-за констант. Пока ты не уберешь константы - он не может быть приведен к циклическому виду.
это тебе так кажется. Ты путаешь «я не знаю как» с «это невозможно».
Я тебе привел конкретный код, который есть в реальности, который можно выполнить и который работает вполне конкретным образом. Но ты же в домике.
ещё раз повторяю: рекурсия всегда сворачивается в цикл. Только каждая итерация этого цикла может выполняться в своём собственном контексте. Т.ч. любой _код_ можно всегда свернуть в цикл, однако для этого может потребоваться неизвестно сколько памяти (точнее столько, сколько будет проходов рекурсии/цикла). Однако код может быть представлен в конечном виде всегда.