Виртуальные машины, на языках типа С/Pascal, структуры и реализация
Кто что может сказать по этому поводу.
Я выбрал трехадресный код,
А что такое трехадресный код? Стек, даные, код?:???:
А что такое трехадресный код? Стек, даные, код?:???:
трехадресный код - это один два три
один - операия
два - аргумент
три - аргумент
(может быть и четыре - это результат)
можно представить мнемокод как тройки
к примеру :
op arg1 arg2 res
MOV a, 100
ADD a, b
...
вся виртуальная машина - это и есть таблица таких троек или четверок, в яве к примеру не так, там набор этих данных реализуется в стеках.
это дело можно компилировать в асме если транслировать те же самые четверки мнемокод асма.
2. c какими регистрами можно использовать команды, а с памятью как работать?
3. сколько битов идёт на описание команды, первого операнда, второго в байт-коде, его структура?
Кроме mov add они у меня называются plus assign
есть еще += -= /= ++ -- и т.д.
есть sin cos pow и т.д.
а есть
для работы с представлением(таблицей четверок): JMPF, JMP, PUSH, POP(для работы с программным стеком), CALL, RET.
Все эти команды работают с МОЕЙ виртуальной машиной.
Например, организация стека - в нем можно держать либо "чистые данные" и не озадачивать машину типами и проч., что позволит написать глупость вроде:
pushf 3.1415926 ; push 4-bytes float value
pushd 1234 ; push 4-bytes integer
addd ; add 4-bytes integers
Можно сделать более универсально:
push 3.1415926 ; push float
push 1234 ; push integer
add ; add float+integer => float
Что усложнит интерпретацию (немного), но код скорее всего работать будет быстрее.
Еще пример - работа с переменными:
pushd Var1 ; push 4-bytes pointer
rtif ; replace 4-bytes address by
; float value, pointed by this
; address
pushf 1.23
mulf ; mul two floats
pushd Var2 ; push 4-bytes pointer
popif ; pop a float to this pointer
А можно и проще:
push Var1
push 1.23
mul ; Машина сама загрузит переменную
; и преобразует типы
pop Var2
Можно ввести и регистры общего назначения помимо стека, но при этом интерпретация будет медленнее и сложнее - куча комманд, стек-регистр и все такое...
Так что, все зависит от конкретной задачи, для которой будет использоваться VM.
Адресное пространство переменных, виртуальная таблица для структур(уже существующих, т.е. скрипт не позволяет описывать свои struct/class)
стек программы для создания подпрограмм.
динамических переменных нет, все статические.
все вроде просто. Критические(нескриптовые) подпрограммы вызываются по идентификаторам, собственно как и набор комманд типа ADD MULL
SIN ... таких комманд ~32 (+критические функции) типа sin cos ...
Пускай код скрипта уже транслированный будет 10000
троек, значит максимум на одну тройку для обработки потребуется (max)40 сравнений примерно, ну типа:
switch ( op )
case ADD:
case MUL:
...
все это дело должно работать в интерактивном режиме да еще и с трехмерной графикой.
Вообще это нечно на VRML похоже, только посерьезнее, скорее X3D.
------------------------------------------------
Кстати, может знаете, область видимости подпрограмм, как реализовать для статических переменных.
Пускай код скрипта уже транслированный будет 10000 троек, значит максимум на одну тройку для обработки потребуется (max)40 сравнений
Вот эти рассуждения я что-то не очень понял... :)
Ну вобщем подход один: выбираем опкод и интерпретируем его:
while ( working ) {
fetch_opcode (opcode);
switch ( opcode ) {
...
default:
invalid_opcode ();
}
}
Если переписать в асме, можно это проделывать очень быстро, если коды команд выстроены по порядку:
working:
call fetch_opcode
cmp ax, 0
jz opcode_0x00
dec ax
jz opcode_0x01
dec ax
jz opcode_0x02
...
jmp working
Можно ввести статистику комманд, что несколько ускорит интерпретацию (например, после комманд типа cmp с очень большой вероятностью следуют комманды перехода и т.п.)
Кстати, может знаете, область видимости подпрограмм, как реализовать для статических переменных.
Т.е. нужно реализоват статические переменные в подпрограммах?
Ну, в Java это реализуется с помощью таблицы статических переменных. Такая таблица имеется для каждого метода (подпрограммы) класса. Соответственно, строится эта таблица в процессе компиляции. Т.е. есть глобальная таблица полей (для всего класса) и таблицы для методов.
Если код и данные совместить (использовать одно адресное пространство памяти), то можно выделять участки памяти в коде для статических переменных:
proc Foo
; Var = Var - 1
push Var
dec
pop Var
ret ; return
Var db 0x00
end
вопрос созрел:
сос статическими переменными понятно - это друг глобальных переменных только со своей областью видимости, НО а как быть с динамическими локальными переменными, (за одно формальными, фактическими) все через стек?
насчет опкодов: опкод - адрес на функию, т.к. тройки фиксированные, то функция с тремя параметрами(третий - результат).
Т.е. структура инсрукции такая:
<opcode><arg1><arg2><res> = 4 байта ?
Или про какие тройки речь? ;)
вопрос созрел:
сос статическими переменными понятно - это друг глобальных переменных только со своей областью видимости, НО а как быть с динамическими локальными переменными, (за одно формальными, фактическими) все через стек?
А что, без них никак?
Ну можно ввести native-инструкции выделения/освобождения памяти:
push 1234
call native malloc ; теперь в стеке аддрес на область памяти в 1234 байта
call native free ; освобождаем
Можно и через стек, если есть возможность общаться с ним не только push/pop, но и как с обычной памятью (как это в x86 делается).
А динамические/автоматические именно так, или CALL или runtime stack. Но вот область видимости на уровне блоков(как в С/C++). Деревом чтоли делать?
аргументы, и res у меня структурами, а там и массивы и флоаты и инты и векторы, может еще что нибудь будет.
Да, именно такие тройки(четверки).
А динамические/автоматические именно так, или CALL или runtime stack. Но вот область видимости на уровне блоков(как в С/C++). Деревом чтоли делать?
Область видимости проверяется на этапе компиляции, а локальные переменные заменяются реальными адресами. Или ты хочешь сохранить оригинальные имена переменных в байт-коде?
Если есть код типа:
...
int x;
... // -------------- здесь видна только переменная x
if ( x == 0 ) {
int y;
... // ------------ здесь видны x и y
}
... // ----- здесь опять только x
то компиллятор сгенерирует две локальные переменные в одном блоке (например, процедуре), а область видимости проверит на этапе компиляции, как я уже сказал. Правда многие "умные" компиляторы заменят такие переменные стеком или регистрами.
Байт код не генерируется сразу все транслируется в тройки и там ВМ крутит их.
ну а если рекурсия?
... ну а если рекурсия?
А статические переменные при рекурсивном вызове не сохраняются :)
Выполни такой код:
//
void foo ( bool stop ) {
static int x = 0;
x++;
if ( !stop )
foo ( true );
printf ("%d\n", x);
}
foo ( false );
//
Если ты ожидаешь на выходе получить "2 1", то это будет для тебя сюрпризом ;)
а не 2 2(как я понимаю на один адрес ссылается)
ну ну, вот мне и нужно , что бы оно вывело 1 1
а не 2 2(как я понимаю на один адрес ссылается)
Ну так не делает ниодин компилятор - всегда будет 2 2 :)
статические переменные - несложно реализовать.
Динамические, вот в чем вопрос.
----------
И как быть с областью видимости.
Когда создается static переменная, то можно если a
делать a1 во вложенных блоках
{static int a}{static int a(a1)}
но а если динамическая, с областью видимости?
потомучто static.
статические переменные - несложно реализовать.
Динамические, вот в чем вопрос.
----------
И как быть с областью видимости.
Когда создается static переменная, то можно если a
делать a1 во вложенных блоках
{static int a}{static int a(a1)}
но а если динамическая, с областью видимости?
Я не пойму в чем проблема.
Динамические переменные - это указатели на область памяти. В чем трудность-то?
А область видимости проверяй во время компиляции:
int x;
namespace B {
int x;
A.x = 10;
B.x = 20;
}
}
ну или используй какой другой синтаксис.