Динамический против статического компилятора (JavaScript)

В настоящее время я пишу компилятор JavaScript на ANTLR + Java.

Я читал здесь вопросы о переполнении стека о том, как продолжить выполнение - и всегда отвечу, что было бы слишком сложно выполнить статическую компиляцию (без JIT-информации) динамического языка, но почему именно это ? Конечно, есть очевидная проблема "разрешения типов", и в JavaScript, возможно, проблема с функцией eval - но есть ли другие причины? (потому что их не сложно преодолеть чисто статически (без JITS))

Я исключаю компиляцию на основе JIT, потому что считаю, что мне было бы слишком сложно ее реализовать.

Имею некоторый опыт написания статических компиляторов с выполнением байт-кода.

ОБНОВИТЬ:

Все ваши ответы действительно полезны для понимания проблемы. Чтобы уточнить, означает ли это, что JavaScript сложнее реализовать, чем другие динамические языки?

И означает ли это, что мне лучше использовать интерпретатор на основе дерева, чем, например, Байт-код (если мы забудем о том свойстве, что JS всегда поставляется в необработанном исходном коде - следовательно, добавляется дополнительное время для генерации и IR, а затем выполняется его)? - или они должны быть примерно одинаково легкими / сложными?

(Я новичок в SOF; не знаю, является ли это предпочтительным способом обновления вопроса?)


person Sune1987    schedule 24.08.2011    source источник


Ответы (3)


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

Например:

var myObj = {};

function configureObject() {
    if (something in the environment) {
        myObj.myfunc = function () {alert("Hi");}
    } else {
        myObj.myfunc = function () {document.write("Hello");}
    }
}

Теперь, когда-нибудь позже в коде, который вы вызываете myObj.myfunc();. Во время компиляции неизвестно, что такое myfunc и является ли это атрибутом myObj. Это должен быть поиск во время выполнения.

В другом примере возьмите эту строку кода:

var c = a + b;

Его средства полностью зависят от типов a и b, и эти типы не известны во время компиляции.

Если a и b оба числа, то это оператор сложения, а c будет числом.

Если a или b является строкой, тогда другой будет приведен к строке, а c будет строкой.

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

person jfriend00    schedule 24.08.2011
comment
+1 Это. Винтовые машины Тьюринга, проблем с реальным кодом достаточно. Лучшее, на что вы можете надеяться скомпилировать это (то есть статически), - это развернутая версия того, что интерпретатор будет делать в любом случае. - person ; 25.08.2011

Проблема с написанием статического компилятора JavaScript состоит в том, что в целом неразрешимо сложно определить, на какие объекты ссылаются в любой точке программы или какие функции вызываются. Я мог бы использовать тот факт, что JavaScript является динамическим, чтобы решить, какую функцию вызывать, на основе вывода некоторой машины Тьюринга. Например:

var functionName = RunTuringMachineAndReportOutputOnTape(myTM, myInput);
eval(functionName + "();");

На этом этапе, если у вас нет предварительных знаний о том, что такое myTM и myInput, доказуемо невозможно решить, какая функция будет вызвана при вызове eval, поскольку невозможно определить, что находится на Тьюринге. ленту машины, если она останавливается (вы можете уменьшить проблему остановки до этой проблемы). Следовательно, независимо от того, насколько вы умны и насколько хорош статический анализатор, который вы построите, вы никогда не сможете правильно статически разрешить все вызовы функций. Вы не можете даже связать набор функций, которые могут быть вызваны здесь, поскольку выходные данные машины Тьюринга могут определять некоторую функцию, которая затем выполняется указанным выше кодом.

Что вы можете сделать, так это скомпилировать код, который при каждом вызове функции включает дополнительную логику для разрешения вызова и, возможно, использует такие методы, как встроенное кеширование, чтобы ускорить работу. Кроме того, в некоторых случаях вы можете доказать, что вызывается определенная функция (или что будет вызвана одна из небольшого числа функций), а затем можете жестко запрограммировать эти вызовы. Вы также можете скомпилировать несколько версий фрагмента кода, по одной для каждого общего типа (объектный, числовой и т. Д.), А затем выдать код для перехода к соответствующей скомпилированной трассировке на основе динамического типа.

person templatetypedef    schedule 24.08.2011
comment
Существует любое количество языков, которые скомпилированы статически, но не могут статически разрешать все сайты вызовов функций. В C указатели на приведенные функции имеют такой эффект. Все компиляторы для широко используемых языков решают эту проблему, перенаправляя некоторые подмножества разрешений сайтов вызова до времени выполнения с использованием различных механизмов, включая простое разыменование указателя, виртуальные таблицы, динамическую загрузку кода, поиск по хэш-таблице и другие. В JavaScript нет ничего особенного, что предотвращает это, о чем свидетельствует тот факт, что это было сделано. - person Mike Samuel; 24.08.2011
comment
@Mike Samuel: Ах, я понимаю, о чем вы говорите. Я думаю, что интерпретировал вопрос OP, чтобы сказать, что он может статически разрешать вызовы функций и доступы, что явно не работает. Однако я думаю, что мой ответ касается этого - я описываю во второй половине ответа потенциальные способы компиляции кода, когда вы не знаете, что звоните. Этого недостаточно? - person templatetypedef; 24.08.2011
comment
Справедливо. Только ОП может прояснить их вопрос. - person Mike Samuel; 24.08.2011

V8 делает это. См. Компиляция JavaScript в собственный код с V8

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

Учитывать

function f(o, x, y) {
  with (o) { return x + y + z; }
}

когда звонят с

o = {};
o = { z: 3 };
o = { x: 1, z: 2 };
Object.prototype.z = 3, o = {};

и согласно EcmaScript 3,

x = (function () { return toString(); })()

должен давать совсем другой результат, чем

x = toString();

потому что EcmaScript 3 определяет запись активации как объект с цепочкой прототипов.

person Mike Samuel    schedule 24.08.2011