Terrible ideas ahead! You have been warned!
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
foo, you have to have put the values to add in
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?