Стек-машина

 

Придумывать процессоры, а особенно стек-процессоры - такое же развлечение программистов недавнего времени, как придумывание способов сортировки 15 лет тому назад. Не избежал этого дела и я.

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

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

Описание - по-английски исторически.

NB! Don't take it too serious. :-)

Structure

Stack:

From the end of thread address space downwards, addressable as usual memory.

Memory:

Nothing special.

Registers:

IP - instruction pointer
SP - stack pointer

 

Notation:

[addr] means 'contents of memory at address addr'

drop means 'remove stack top' or 'increment SP'

dup means [SP-1]=[SP]; SP--;

 

Instruction modifiers

dup' duplicate stack top before executing instruction
imm' first operand is immediate (replace first pop with instruction parameter)
ind1' first operand is taken indirectly (memory contents read)
ind2' second op. is taken indirectly (memory contents read/write)
'drop stack top is removed afterwards

 

Instruction set

There are just 8 (move, add, sub, nand, skipz, skipn, call and sys) instructions and, additionally, only 4 of them is required and all the others can be implemented as "syscalls", or, in other words, internal methods calls. Optional ones are marked with *.

There is a lot of 'derivative' instructions exist, which are just a specific modifications of base instructions. Derivatives are listed in indented blocks below.

Instruction descriptions depend at arg1 and arg2 terms. Here they are described:

arg1 is pop if imm' not given, and is followed by memory read if ind1 is given, see below.

default form: arg1 = [stack top]; drop;
imm' form: arg1 = immediate data;
ind' form: arg1 = [[SP]]; drop;
imm'ind1' form: arg1 = [immediate data];

arg2 is push, pop or nothing (depending on instruction) if no ind2 is given and is a memory access at address popped from stack top if  ind2 is given and instruction is not a sys. See specific sys codes on ind2 usage for that instruction.

move: tmp = arg1; arg2 = tmp;

nop: in default form move is effectively nop
dup, drop: gives dup and drop with corresponding modifiers alone
load, store: is memory load, store or move if ind1, ind2 or both is given
is immediate load or move immediate to memory if imm1 or imm1 and ind2 given
is memory read with immediate address if imm and ind1 are given

 

add*: tmp = arg1; arg2 += tmp;

in default form adds two stack elements on top, removing them and leaving summ instead
sub const, inc, dec: imm'add is a way to add a constant. By negating constant you can implement subtraction of a constant too.

sub*: tmp = arg1; arg2 = tmp - arg2;

neg: imm'sub 0

nand*: tmp = arg1; arg2 = ~(tmp | arg2);
skipz*: next instruction is skipped if arg1 - arg2 is zero.
skipn: next instruction is skipped if arg1 - arg2 is more than zero.

This is, really, 'skip negative' because default form for skips is imm'skipx 0, which negates arg. value before testing, so it will skip if stack top is negative.

call: tmp = arg1; arg2 = IP; IP = tmp;

in default form is simple call, address is taken from top of stack
jump: call'imm'drop
return: call'drop
call'ind2 is a way to implement special call stack or state save for task switching.

Example of nontrivial usage:
imm'move &coproc_temp; dup'ind1'ind2'call; is a coprocess switch :-). Address, where peer coprocess stopped is taken from coproc_temp and jumped to and our coprocess next address is saved in coproc_temp for later continuation.

sys: tmp = arg1; exec syscall which number is in tmp.

Syscall parameters (in1-inx) are popped from stack first and results (out1-outx) are pushed afterwards. It is up to the syscall how to implement ind2. If no special treatment of ind2 is given in syscall definition, first argument of syscall or first result, if there is no args is subject to usual ind2-dependent indirection.

(it is possibly better to give syscall number as forced immediate parameter and leave both inds and imm for actual call parameters)

 

That's all!