This is a card in Dave's Virtual Box of Cards.

Named Register Passing

Created: 2022-10-05 Updated: 2022-10-22

Terrible ideas ahead! You have been warned!

Related to concatenative programming and Forth (especially register-forth).

I’m implementing a wacky idea right now: forth-concatenative ("a pure inlining compiler").

And one thing that I’ve noticed is that I initially defined my "words" (functions) as regular assembly routines which expect values in particular registers. Silly example which adds two numbers:

foo:
    add eax, ebx

To call foo, you have to have put the values to add in registers eax and ebx:

mov eax, 1
mov ebx, 2
jmp foo

Both traditional function call languages and "concatenative" languages like Forth solve this by pushing the two values to stack memory in a particular order:

foo:
    pop eax
    pop ebx
    add eax, ebx

push 2 ; note the reverse order of pushing!
push 1
jmp foo

And that’s exactly what I’m going to do in my "pure inlining compiler".

But what’s occurred to me is that a lot (most?) of the stack manipulation doesn’t really need to happen! In many cases, the values either need to be moved from one register to another or they’re already where they need to be!

So what if I could "tag" a variable name on a particular register for the input and output of a function? Then the assembler (or compiler!) could work out the manipulations for us.

I’m imagining something like this pseudo-code:

// Define them
function get_start(object) returns start
    return object.start
end

function get_speed(thrust, mass) returns speed
    <do something>
    return speed
end

function move(start, speed) returns position
    <do something>
    return position
end

// Call them
rocket get_start get_speed move

Where each "return" is putting a value on the stack. The compiler/assembler would see that the same "tag" was used and would automatically shuffle the registers around so that the output of one would match the input of the next.

This would obviously put a "stack depth" limit on the number of registers allotted for that purpose. And would mean only that number of "active" params could exist at any one time (which is a good thing in some ways - in a functional language kind of way).

Making names ("tags") match up would be really interesting. I’m genuinely not sure if it would be kinda neat or extremely painful in practice.

Probably extremely painful.

But it’s an interesting exercise. After all, that’s basically what we’re doing in assembly anyway, but without the benefit of any sort of mnemonics!

Dead-end syntax musing:

It could even look like a traditional function call:

foo(a:eax, b:ebx):
    add eax, ebx

jmp foo(a=1, b=2)

which would assemble as:

foo:
    add eax, ebx

mov eax 1
mov ebx 2
jmp foo

Or (more interesting), wiring up two functions in a concatenative way. Here’s some new syntax I’m making up on the spot which shows the output of one function and the input of another. Hopefully this makes sense to me later:

bar(a:eax) -> (a:ebx, b:ecx, c:edx)
    mov ebx, eax ; a=a
    mov ecx, 2   ; b=2
    mov edx, 3   ; c=3

foo(a:eax, b:ebx, c:edx) -> (a:eax)
    add eax, ebx ; a=a+b
    add eax, ecx ; a=a+c

mov eax, 1
jmp bar
jmp foo

would assemble the last three lines as:

mov eax, 1
jmp bar
mov eax, ebx ; bar's output "a" to foo's input "a"
mov ebx, ecx ; bar's output "b" to foo's input "b"
jmp foo

The "a" and "b" would be moved (in the right order to prevent clobbering! guess a temporary space would have to be used for swaps) but "c" wouldn’t need to be moved because it was already the right register.

I don’t think this causes more problems than it solves, but it’s hard to see that in this simple example. I think using variables in this way would make WAY more sense in a higher level language…​but showing it in pseudo-assembly makes it clearer what’s happening? OR NOT?