SubX currently allows one to test the exit()
syscall. It does so using a dependency-injected wrapper called stop
that takes an exit-descriptor as an argument. If the exit-descriptor is null the program really exit
s. If it is created using tailor-exit-descriptor
stop
unwinds the stack until the frame that called tailor-exit-descriptor
.
But tailor-exit-descriptor
is klunky. The way it currently works is, you pass in the number of args of the function that's going to get passed in the exit-descriptor, and it computes where on the stack the return address for the current stack frame is going to be, saving it to the exit-descriptor. That way all stop
has to do is set ESP to the return address in the exit-descriptor and then call c3/return
.
If we moved to a more HLL syntax where function calls were all in a single line, we'd like to make tailor-exit-descriptor
cleaner or maybe do away with it altogether.
One easier way to explain tailor-exit-descriptor
is that it is equivalent to a special function call. Now for background, regular calls in SubX look like this:
- push all args
- call
- discard args (say by adding a constant to
ESP
)
The special call to a function that may want to call stop
looks like this:
- push all the args (which must include the address to the exit-descriptor)
- save ESP-4 to the exit-descriptor
- call
- discard args
Only the second step is new. But we now get to replace all of tailor-exit-descriptor
with a single instruction (assuming the address of the exit-descriptor is always in a register).
Now my question becomes: what does a syntax for this special call look like?
If regular calls look like f(arg1, arg2, ... argn)
, then some possibilities:
ed = tailor-exit-descriptor(...total size of args to f...)
f(arg1, arg2, ...ed, ...argn)
w/exit-descriptor(ed) f(arg1, arg2, ...ed, ...argn)
I don't like either, but they do have some benefits. The first allows for a single exit-descriptor to be reused across function calls in the same stack frame (totally safe). The second is all on one line to indicate that conceptually it's all a single call.
On the other hand, the second now looks like two operations on a single line, which is confusing and potentially sets a bad precedent. So I wonder what sort of approach we may take that makes it look less like two calls in a single line.
- Baroque:
w/exit-descriptor{|ed| f(arg1, arg2, ...ed, ...argn) }
That's a lot of grammar for just one construct.
try f(arg1, arg2, ...ed, ...argn)
We'd need to somehow figure out where ed
is.
protect[ed] f(arg1, arg2, ...ed, ...argn)
f<ed>(arg1, arg2, ...ed, ...argn)
- Some other random ideas: perhaps we should try to generalize the syntax with any other operations that involve munging the stack. Maybe closures or more general exceptions or first-class continuations.
Anyways, that's my brain dump.
cc @charles-l