Исполняемый (интерпретируемый) код - то есть надземелье.

Это не слишком принципиальный момент. Ввиду наличия прорвы фундаментальных ограничений фантомовский код должен легко поддаваться бинарной трансляции, а значит должно быть несложно в будущем перевести ОС на другое "наречие". Да и, собственно, ничто не мешает держать два интерпретатора. Или, если система будет работать на специализированном железе, прямо исполнять код, совместимый с железом и интерпретировать старый.

Очевидно, что интерпретатор находится под землёй.

Все классы делятся на предопределённые (встроенные) и обычные. Предопределённые реализованы под землёй, к ним относятся Class, Language.* и System.*, остальные классы реализованы обычным образом.

Ключевое отличие вот где. Обычные классы состоят ТОЛЬКО из указателей. Соответственно, только указателями интерпретируемый код и умеет манипулировать. Предопределённые состоят из чего угодно, код их состоит из одной псевдокоманды, которая реализует "нырок" в подземелье и команду return одновременно. Это позволяет им не отличаться от других классов внешне при максимуме эффективности.

Поскольку мы работаем только с указателями, на уровне "ассемблера" нет никаких вычислений и операций с данными вообще. Адресная арифметика тоже отсутствует. Есть поток управления, и это - вся, собственно, наша забота.

Мелкая проблема заключается в том, что нужен ещё какой-то метод реализации условных переходов. Для этого есть завязанные на реализацию классов операторы ассемблера. Например, if - он условно выполняет следующий за ним оператор, если bool на вершине стека - true. Если это не bool - получим exception.

Виртуальная машина

Регистры:

IP - адрес следующей инструкции

OP - адрес текущего объекта, в контексте которого мы находимся

DSP, DS, DSL - стек данных. DSL - нижняя граница стека, попытка уйти ниже - exception

ISP, IS - стек кода

Отметим, что DS содержит только адреса объектов (не кода!). Никаких других типов значений там не бывает и быть не может. Если написано "int на верхушке стека", то это int, на который ссылается верхушка стека.

Оба стека растут вверх, SP указывает на верхушку. (А не выше её на 1.)

Команды:

call число_параметров, номер метода

сохраняет IP, OP и DSL в ISP, фиксирует состояние DS (DSP минус число_параметров) в DSL, грузит OP, IP.

Верхушка стека содержит адрес объекта, в контексте которого будет работать метод. Пара (номер метода, объект) используется для получения собственно адреса перехода - по адресу объекта находим VMT, дальше - понятно. Адрес объекта, напомню, является парой - собственно адрес объекта и адрес его класса (объекта класса class).

Ниже в стеке идут параметры, числом - как указано в аргументе.

return

TMP = DS[DSP], DSP = DSL, DSL, OP, IP = IS[ISP--], DSP++, DS[DSP] = TMP

Словами: возвращаемое значение на верхушке стека сохраняем, стек сбрасываем ниже всех переданных параметров, возвращаемое значение кладём на стек. Если возвращаемого значения не было, на верхушке стека продублируется какая-то ерунда - что попало под руку. Если вызывающая программа попыталась это использовать - сама дура. Если она ничего не ждёт - пусть сделает drop.

if - проверяется состояние объекта типа bool, на который указывает элемент на верхушке стека. Если он true - следующая команда исполняется. Иначе - пропускается. В принципе в целях повышения эффективности можно опустить контроль типа объекта - это не создаёт опасностей, насколько я вижу.

jump addr - он и есть. Адрес - только константа. Относительный. Вопрос - как гарантировать невыход за границы кода метода. И нужно ли этим заморачиваться.

djnz addr - int на верхушке стека --, если не ноль - jump addr.

switch scale, shift, max - int на верхушке стека уменьшаем на shift, делим на scale, если он больше max или меньше нуля - проваливаемся насквозь. Иначе получившееся число используем как индекс в следующую за оператором в коде таблицу относительных адресов, выбираем адрес и переходим по нему.

dup - продублировать верхушку

swap - поменять местами два элемента на верхушке

drop - удалить верхний элемент

copy - продублировать объект на верхушке вызовом копи-конструктора. Верхушка указывала на оригинал, начинает указывать на копию.

push addr - содержимое поля addr текущего объекта заталкиваем на стек. DS[++DSP] = OP[addr]

pop addr - OP[addr] = DS[DSP--]

move up disp - ячейка стека со смещением disp вверх от DSL копируется в новый элемент на верхушке стека.

move down disp - верхушка стека снимается и значение её копируется в ячейку со смещением disp вверх от DSL.

push catcher - зарегистрировать ловушку исключений. На верхушке стека - пойнтер на тип. Отрабатывается как call, только на стек вызовов кладётся ещё и пойнтер на объект класса Class, соответствующий типу ловушки. call в этом месте кладёт NULL, естественно. В отличие от call не меняет состояния виртуальной машины за исключением стека.

pop catcher - снять последнюю ловушку исключений. Идентичен return-у, но не меняет IP и рожает исключение, если снимает со стека не то.

throw - возбудить искючение. На стеке - кидаемый объект. Отрабатывает так: выполняем return до тех пор, пока не найдём на стеке подходящую ловушку. Если нашли - переходим по указанному в ней адресу.

const value - непосредственно в коде лежит константа одного из встроенных типов. Создаёт объект нужного типа, адрес его кладёт на стек.

sys n

Вызов "подземного" метода номер n, затем - исполнение команды return. Вызов выполняется по VMT текущего класса (см. OP) и если класс - не встроенный - exception. Это - гарантия от хака посредством применения встроенного кода к данным невстроенных классов, что позволило бы получить новые адреса объектов и нарушить защиту.

Здесь запрятаны сложения и вычитания, работа со строками и вообще всё, что возможно сделать со значениями встроенных типов.

hint

Сообщение исполняющему уровню о том, что произошло нечто любопытное и, вероятно, есть причины проявить внимание к тому или иному объекту. Например, если компилятор считает, что все ссылки на объект пропали, он может рекомендовать уничтожение этого объекта. Это может сократить потребности системы в памяти и избавить garbage collector-а от 90% работы.

int op

Комплект операций над целыми - для скорости вынесен сюда, хотя вообще-то, по хорошему, это всё - методы класса Language.Int32

summon type

Сокращение для стандартной процедуры получения типа по имени. Работает так же, как и стандартная, но реализовано всё чисто подземно, и потому быстро.

summon thread

(Не нужна? Удалить?)

Кладёт на верхушку стека адрес объекта, описывающего текующую нить. Это - единственный путь добраться до контекста, до операционной системы вообще, до интерфейса к именам классов, объектам типа класс, что, в свою очередь, позволяет вообще создавать объекты. Именно подменой этого адреса на соответствующий прокси в ОС вводится какая-либо security-подсистема. Или подсистемы.

При создании первой нити в системе объект, её олицетворяющий имеет полный доступ. Создаваемые им нити могут быть ограничены в правах.

Вероятный интерфейс:
class Thread : public Object {

	Object *owning_object() const; 
	// напомню, каждая нить - принадлежность какого-либо объекта,
	// но не каждый объект имеет нить. Только тот, у класса которого есть метод main.
	// Видимо, правильным будет требовать наследования соотв. класса от thread



	Kernel *kernel() const;

	// Путь к ядру. Или к фильтру полномочий...




	void finish();
	// Закончить работу нити
 	};




class Interface  : public Object  {




	Method *method[];  // ссылки на код
	};


class Class : public Object  {


	// Object *new( Kernel *k = NULL ); 
	// Создать новый объект соответствующего классу типа,
	// так, что все порождаемые в его пределах нити будут
	// из метода kernel возвращать то, что передано здесь аргументом.
	// Этот метод реально возвращает не Object *, а указатель типа,
	// описываемого данным классом.



	int byte_size() const; // число байт в двоичном представлении объекта
	// для Kernel::allocate_object()




	Method *method[];  // тут ХРАНИТСЯ код


   	Interface interface[type];		

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

	// именно на свою "дольку" здесь


	bool is_derived_from( Class daddy );
	};



class Kernel : public Object  {

	// Базовый Kernel предоставляет все эти интерфейсы. Порождённые могут
	// ограничивать доступ к ним.
	
	Class *find_class( string );
	void register_class( string name, Class *definition );


	void *allocate_object( Class * );
	};



class Object {

	// Object dup() const;
	// Возвращает указатель на копию данного объекта того же типа,
	// что и он сам. Используется рантаймом для генерации копий,	
	// copy-конструктор переопределяет именно этот метод.


	Class *our_type;
	};

 


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

Эффективность

Понятно, что если допустить расположение на стеке хотя бы целых чисел и ввести в набор команд операции над ними, то эффективность значительно вырастет. Это можно сделать, но тогда придётся ввести контроль типов объектов на стеке и следить за тем, чтобы из int никаким образом нельзя было получить указатель.

Расположение кода в системе

Код лежит в объектах типа language.code.executable.interpreted (просто бинарные контейнеры, фактически), которые лежат в class. Там же - VMT. Доступа на чтение к обоим у наземных сущностей нет вообще. Происходит из-под земли. На запись - только посредством конструктора. Создал класс - гуляй, Вася. Теперь - только исполнять.

Вероятно, тут же обеспечивается контроль за выходом за пределы кода метода при переходе - объект типа language.code.executable.interpreted знает границы своего кода.

Методы нумеруются (не именуются) по позициям в VMT.

Проблемы.

Не реализовано множественное наследование. Как-то даже не задумывался, как его сделать. А надо бы. Отмазку, что это неконцептуально я сформулировать не сумел - кажется, вполне концептуально. :-) Увы. Это, впрочем, не означает такой уж обязательности его наличия. Можно обойтись и яваобразной затычкой, но это будет лишь затычка.


Двоичный формат.

 

 

 

 

Используются технологии uCoz