Как я разворачиваю (компилируют) цикл интерпретатора?

synchronized - это ключевое слово в Java, которое используется для выполнения перед связью в многопоточной среде, чтобы избежать несогласованности памяти и ошибки вмешательства потока.

14
задан Unknown 8 February 2009 в 21:06
поделиться

8 ответов

"Разворачивание цикла" обычно означает заменять повторение последовательностью действий. Цикл:

for (int i = 0; i < 4; ++i) {
    a[i] = b[i] + c[i];
}

развернул бы в эквивалент:

a[0] = b[0] + c[0];
a[1] = b[1] + c[1];
a[2] = b[2] + c[2];
a[3] = b[3] + c[3];

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

Ваш образец обычно вызывался бы в интерпретаторе, который обходит дерево узлов AST, которые могли бы выглядеть примерно так:

 ASSIGN
    |
 +--+---+
 |      |
REF   MINUS
 |      |
 x   +--+---+
     |      |
    VAR    PLUS
     |      |
     a   +--+--+
         |     |
        VAR  CONST
         |     |
         b     3

и эти interpret функция имела бы дополнительные опции:

int interpret(node) {
    switch(node) {
        case PLUS:
             return interpret(child(0))+interpret(child(1));
        case MINUS:
             return interpret(child(0))-interpret(child(1));       
        case ASSIGN:
             return set(child(0), interpret(child(1));
        case VAR:
             return fetch(child(0));
        case CONST:
             return value(child(0));
        ...
    }
}

при обходе AST с тем interpet функция (на самом деле выполнение операций) Вы интерпретируете. Но если функция записи действия, которые будут выполнены, а не выполнение их, Вы компилируете. В псевдокоде (на самом деле, псевдокодируйте дважды , поскольку я принимаю гипотетическую стековую машину как цель компиляции):

string compile(node) {
    switch(node) {
        case PLUS:
             return(compile(child(0))) + compile(child(1)) + ADD);
        case MINUS:
             return(compile(child(0))) + compile(child(1)) + SUB);
        case ASSIGN:
             return(PUSHA(child(0))) + compile(child(1)) + STORE);
        case REF:
             return(PUSHA(child(0)));
        case VAR:
             return(PUSHA(child(0)) + FETCH);
        case CONST:
             return(PUSHLIT + value(child(0)));
        ...
    }
}

Вызов compile, на что AST (игнорирующий любые опечатки псевдокода ;-) выложил бы что-то как:

PUSHA x
PUSHA a
FETCH
PUSHA b
FETCH
PUSHLIT 3
ADD 
SUB
STORE

FWIW, я был бы склонен думать об этом как о разворачивании AST, вместо того, чтобы разворачивать интерпретатор, но не подвергну критике чью-либо метафору, не читая его в контексте.

14
ответ дан 1 December 2019 в 12:14
поделиться

Я немного смущен. Я не думаю, 'разворачивая цикл', правильное слово здесь. Даже при рефакторинге кода, чтобы не иметь какие-либо рекурсивные вызовы, Вы будете все еще использовать интерпретатор.

Можно скомпилировать эту программу с GCC. Затем у Вас будет скомпилированная программа, хотя скомпилированная программа будет интерпретировать AST.

Один способ превратить это в компилятор был бы вместо выполнения return interpret(child(0))+interpret(child(1));, Вы генерировали бы инструкции по сборке, которые сделают дополнение и затем произведут инструкции по сборке в файл.

3
ответ дан 1 December 2019 в 12:14
поделиться

Фабрика является основанным на стеке языком, не основанным на AST интерпретатором.

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

Каждая функция реализована как функция, которая берет стек и возвращает стек (в моем случае видоизмененная версия того же стека, я не уверен, функционален ли Фактор или видоизменяется). В моих интерпретаторах каждая функция также помещает продолжение функции на вершине стека, таким образом, интерпретатор знает, что сделать затем:

, Таким образом, интерпретатор для вызывания следующей функции на стеке является чем-то как:

for (;;)
    stack = (stack[0].function_pointer)(stack);

Рассмотрение функционального нечто:

def foo (x,y):
   print( add(x, y) )

добавляют, мог быть определен как:

pop a
pop b
stack[ return_offset ] = a + b
return stack 

и нечто как:

pop x
pop y
push _
push &print
push y
push x
push &add

и стек для вызова нечто (5,6) был бы, развивают что-то вроде этого на каждом шаге в цикле:

>> foo(5,6)
[&foo, 5, 6]
[&add, 5, 6, &print, _]
[&print, 11]
=> 11
[]

А простой компилятор мог развернуть цикл для функции нечто, генерируя эквивалентный потоковый код:

compiled_foo (stack): 
    stack = begin_foo(stack) // arranges stack for call to add
    stack = add(stack)
    stack = print(stack)
    return stack
2
ответ дан 1 December 2019 в 12:14
поделиться

У Вас действительно нет цикла здесь с тех пор не все вызовы к interpret последние вызовы.

Компилятор, самый близкий к Вашему, принимая модель стека...

int compile(node)
{
    switch(node) {
        case PLUS:
             return compile(child(0))&&compile(child(1))&&compile_op(op_plus);
        case MINUS:
             return compile(child(0))&&interpret(child(1))&&compile_op(op_minus);       
    }
}

но я думаю, разворачивая в этом контексте, более применимо к интерпретатору кода байта, а не интерпретатору AST. Инструкции по байт-коду обычно интерпретируются в цикле. Затем "разворачивающая" техника состоит в том, чтобы испустить код, соответствующий каждой инструкции по байт-коду.

Фактор подобен Forth. Обычно Forth имеет внешний интерпретатор, который генерирует потоковый код. Потоковый код может быть предположен массив указателей функции (существует несколько вариантов, прямой распараллелил, косвенный распараллелил, подпрограмма распараллелила, и т.д.). Потоковый код выполнен внутренним интерпретатором. Разворачивание интерпретатора в этом случае относится к внутреннему интерпретатору и является вопросом конкатенации потокового кода.

2
ответ дан 1 December 2019 в 12:14
поделиться

Это не может быть связано, но также и проверить вторую проекцию Futamura

http://en.wikipedia.org/wiki/Futamura_projection

, который говорит, что компилятор является просто интерпретатором с частичной оценкой/сворачиванием констант (хороший в теории, но не практике).

2
ответ дан 1 December 2019 в 12:14
поделиться

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

1
ответ дан 1 December 2019 в 12:14
поделиться

Интерпретатор сканирует каждый байт-код (или узел AST) во времени выполнения и отправке к вызовам функций (обычно использующий оператор переключения в бесконечном цикле).

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

0
ответ дан 1 December 2019 в 12:14
поделиться

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

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

0
ответ дан 1 December 2019 в 12:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: