> > "Jay Freeman (saurik)" <sau...@saurik.com>
> "Ian Lance Taylor" <i...@google.com>

> Thanks for the bug report and the analysis.  I think it does simply
> require an '&'.  That makes it analogous to the way
> __morestack_release_segments is used in generic-morestack-thread.c. 

The only reason I hesitated on that is that it might not make sense to update 
the pointer in the context. In my specific case, that will actually cause it to 
crash ;P, as while the current stack I'm calling __splitstack_releasecontext 
from is valid, the context pointer I'm passing is actually stored on the old 
stack, and will be unallocated by __morestack_releasse_segments.

I can always just change my code to copy the context to the other stack before 
calling __splitstack_releasecontext, however, so that isn't a problem for me. 
Though, I also wasn't certain what the releasecontext function actually wanted 
to do with that pointer, as I hadn't yet read much of the morestack code; I now 
see that it is just the head of a linked list, so yeah: passing the address out 
of the context seems fine.

> As you know, I wanted to allow for future expansion.  I agree that it
> would be possible to avoid storing MORESTACK_SEGMENTS--that would trade
> off space for time, since it would mean that setcontext would have to
> walk up the list.  I think CURRENT_STACK is required for
> __splitstack_find_context.  And __splitstack_find_context is required
> for Go's garbage collector.  At least, it's not obvious to me how to
> avoid requiring CURRENT_STACK for that case.

The basis of that suggestion was not just that the items in the context could 
be removed, but that the underlying state used by split stacks might not need 
the values at all. In this case, I am not certain why __morestack_segments is 
needed: it seems to only come in to play when __morestack_current_segment is 
NULL (and I'm not certain how that would happen) and while deallocating dynamic 
blocks (which is already linear).

I might provide a patch to better describe what I mean by this. I've started 
the process of getting a copyright assignment in place (sent an e-mail to 
fsf-reco...@gnu.org per http://gcc.gnu.org/wiki/CopyrightAssignment).

> I agree.  Want to write a patch?  Or at least file a bug report.

Sure.

> [paragraph moved below]

> > 7) Using the linker to handle the transition between split-stack and
> > non-split-stack code seems like a good way to solve the problem of "we
> > need large stacks when hitting external code", but in staring at the
> > resulting code I have in my project I'm seeing that it isn't reliable:
> > if you have a pointer to a function the linker will not know what you
> > are calling. In my case, this is coming up often due to using
> > std::function.
> 
> Yes, good point.  I think I had some plan for handling that but I no
> longer recall what it was.

After getting more sleep, I realize that this problem is actually much more 
endemic than I had even previously thought. Most any vaguely object-oriented 
library is going to have tons of function pointers in it, and you often 
interact solely with those function pointers (as in, you have no actual symbol 
references anywhere). A simple example: in the case of C++, any call to a 
non-split-stack virtual function will fail.

"""Function pointers are a tricky case. In general we don't know whether a 
function pointer points to split-stack code. Therefore, all calls through a 
function pointer will be modified to call (or jump to) a special function 
__fnptr_morestack. This will use a target specific function calling sequence, 
and will be implemented as though it were itself a function call instruction. 
That is, all the parameters will be set up, and then the code will jump to 
__fnptr_morestack. The __fnptr_morestack function takes two parameters: the 
function pointer to call, and the number of bytes of arguments pushed on the 
stack. (This is not yet implemented.)"""

That paragraph is from your design document (SplitStacks on the GCC wiki). I 
presume that this solution would only work if __fnptr_morestack always assumed 
that the target did not support split-stack? Alternatively, I can see having 
that stub look at the function to see if its first instruction was a comparison 
to the TCB stack limit entry (using similar logic to that used by the linker)? 
[also, see below in this e-mail]

> > More awkwardly, split-stack functions that mention (but do not call)
> > non-split-stack functions (such as to return their address) are being
> > mis-flagged by the linker. Honestly, I question whether the linker
> > fundamentally has enough information about what is going on to be able
> > to make sufficiently accurate decisions with regards to stack
> > constraints to warrant the painful abstraction breakage that
> > split-stack uses. :(
> 
> Your're right that the linker doesn't really have enough information.
> But is a split-stack function that returns the address of a
> non-split-stack function really so frequent that it's worth worrying
> about?

I guess the question I have is: is one of the goals to make this option "safe 
to turn on for a random project"? Given the abstraction break that was made 
between the compiler and the linker, it would seem like this was a rather 
critically important goal (as now both the linker and the compiler are less 
modular and more difficult to modify), but in fact the result doesn't manage to 
solve seemingly simple corner cases.

The reason I'm running into these issues is not due to virtual dispatch (at 
least yet: this codebase was C 5 years ago, but is now being ported to C++), 
but instead due to higher-order functions. I'm finding myself in situations 
where std::function and std::bind are disconnecting the symbol references from 
the call sites sufficiently (even moving them to different stacks ;P) to cause 
the linker to make seemingly random decisions.

That said, I can demonstrate a really common idiom, from C (not C++), that is 
almost always going to involve non-split-stack code (as malloc and free are 
normally going to be in libc, compiled without -fsplit-stack), and that is 
morally equivalent to "returning a function pointer and using it later": data 
structures that keep information on a block of dynamically allocated memory and 
"how to free it". Here's a lame version:

struct String {
    const char *data;
    void (*free)(void *);
};

void ClearString(String *string) {
    if (string->data != NULL && string->free != NULL)
        string->free(string->data);

    string->data = NULL;
    string->free = NULL;
}

void SetString(String *string, const char *data, bool alloc) {
    ClearString(string);

    string->data = data;
    string->free = alloc ? &free : NULL;
}

void f(String *string) {
    SetString(string, "hello", true);
    ClearString(string); /* potential stack overflow */
}

(Incidentally, if you use std::vector with a custom allocator that has any kind 
of indirection in it, this is going to come up quite a lot. The code for vector 
instantiated over your allocator will be compiled as part of your code with 
-fsplit-stack, but if the memory allocator being used is something compiled 
without then you are going to end up with a really complex version of the above 
code and a stack overflow.)

[paragraph from above]
> It would certainly be possible for the compiler to arrange to allocate a
> large stack as it called the non-split-stack function.  Unfortunately, I
> don't see how the linker could do it.  And it's the linker, not the
> compiler, that knows that it is a call to a non-split-stack function.

However, the linker doesn't actually have any notion of "calls", which is what 
causes the previous problems. In a language like C++ (or even C) it isn't 
really true that a function that calls another function will go through a 
symbol reference to do it. Anyone who uses code that involves dlsym, 
higher-order functions, or polymorphic object-oriented libraries will run into 
cases that the current -fsplit-stack implementation doesn't even provide good 
(certainly not documented) workarounds for.

Part of me (and I realize that this causes other tradeoffs, and I'm therefore 
not even recommending it: more just musing) feels like the notion of "supports 
split stack" is more of a calling convention. In the same way that gcc 
currently supports regparm, stdcall, thiscall, fastcall... it seems like it 
might simply be a new attribute (probably orthogonal to the calling convention) 
a function can have (and would not have by default): splitcall.

In such an implementation, like many of the existing calling-convention related 
attributes, splitcall would be considered part of the type signature (and 
thereby would not be allowed to be put on a definition and not on the related 
prototype), and could be opted in for a large block of code using a #pragma or 
a compiler-switch. (Again: this is just musing. I haven't put much thought into 
whether this would actually be semantically reasonable yet.)

[see above in this e-mail] For cases where the compiler "simply doesn't know", 
the solution that was brought up for function pointers could be used: have a 
level of indirection in the calls that includes the number of arguments. That 
code could then read the target of the call to see whether the function at the 
other side looked like it supported split-stack, and if not it could allocate 
more stack at the time of that call.

The developer would now be put in the position of thinking about what they are 
calling sometimes (and making certain that their usage of the pragma and header 
files lined up), but honestly I already am having to think about that (due to 
the linker having both false positives and false negatives for all of the above 
reasons, whether the inliner conflating calls or function pointers obfuscating 
them), and I have no explicit mechanism to override it.

In fact, I almost want to say that the worst-case scenario in the "rely on the 
compiler" is the developer throwing up their hands in defeat and attempting to 
recompile "the world" (including libgcc, libsupc++, etc.) with -fsplit-stack... 
but that's where I already am at with the current linker-based implementation: 
the main/only way I'm going to be able to avoid having function pointers to 
non-split-stack code is to recompile every library I need with -fsplit-stack.

> > A specific idea that might help, however, is to set things up so that
> > the PLT actually handles the stack increases when you are linking to
> > functions that are in a dynamic library. That way, calls to open (for
> > example) would not cause the function that called it to suddenly
> > require a large stack, but instead only as control is transferred to
> > open would the stack size increase. (This might be quite complex,
> > though.)
> 
> Yes, again you have to know how many bytes of arguments were pushed on
> the stack.  You can pretty much know this for open, of course, but it's
> a lot more complex for printf (if printf were compiled in split-stack
> mode it would straightforward, but of course in this example it is
> not).
> 
> I agree that this could be a lot nicer.  It's a bit less important for
> Go because obviously the Go compiler is completely in control of all
> functions called by Go code.

In this model (still using the linker, but pushing the stack-split into or 
around the @plt stub function), I would have to propose that variadic functions 
are treated specially (possibly using a similar/identical setup to the one you 
were proposing for function pointers) where the argument count was also passed. 
This could be pushed onto the stack right before the call and popped/thrown off 
the stack first thing in the stub when not needed (which has the benefit of 
being portable between targets and not messing with the existing argument 
placement).

> > That said, I don't have a better solution to suggest right now (I
> > really want to say that having attributes available to declare
> > split-stack functionality in the code would be better, but that has
> > other ramifications), but I do have concerns that due to attempts to
> > keep the ABI fixed decisions made now (when there seem to only be a
> > single major user, Go) will lock in how the mechanism is capable of
> > functioning in the future.
> 
> I may misunderstand your suggestion, but I think that keeping the ABI
> fixed is a requirement.  Any ABI change would require rebuilding all
> libraries and changing the debugger.  The result would not be usable
> for most people.

What I meant by these "concerns" is that it seems like the current mechanism 
for -fsplit-stack is going to get locked into place (and be unable to change 
due to ABI breakage) in a state where even with the abstraction leak between 
the compiler and the linker (which I feel is quite costly) it doesn't really 
solve the problem for many users (and possibly even, any other than Go, which 
might have enough constraints to make this work).

However, this might not actually be that big of a concern, thinking about it 
more. As the existing implementation is, as we both believed, unlikely to be 
incorporated into the default library build, it is really then just a matter 
that -fsplit-stack has to exist with this implementation and Gold needs to 
continue to supporting it; gcc already has tons of ld-specific flags: this 
could just be another one. A later/different implementation of split-stack 
could be -fsplit-stack-ex or something, and existing independently and in 
parallel.

[vaguely in reply to everything above]

Actually, thinking about it more: it seems like 99% of these problems could be 
solved by providing a second symbol definition for the split-stack prologue and 
binding that as part of the type signature. So, you could either call the 
"original implementation" of a function using its normal symbol, or you could 
call the split-stack prologue version of the same function using one that had 
been mangled with some prefix.

extern "C" int test() {
    return 0xdeadbeef;
}

0000000000404920 <test>:
  404920:       64 48 3b 24 25 70 00    cmp    %fs:0x70,%rsp
  404927:       00 00 
  404929:       72 06                   jb     404931 <test+0x11>
  40492b:       b8 ef be ad de          mov    $0xdeadbeef,%eax
  404930:       c3                      retq   
  404931:       45 31 d2                xor    %r10d,%r10d
  404934:       45 31 db                xor    %r11d,%r11d
  404937:       e8 6d 6b 00 00          callq  40b4a9 <__morestack>
  40493c:       c3                      retq   
  40493d:       eb ec                   jmp    40492b <test+0xb>
  40493f:       90                      nop

In this case (and yes: this is an example of a function that shouldn't need 
this prologue at all, but it was short ;P), the existing implementation of 
-fsplit-stack has modified the function to fundamentally check its stack. No 
matter how you attempt to call it, we now have to know whether the function 
supports the split-stack protocol using an out-of-line mechanism, and we cannot 
enforce our beliefs in the compiler: the linker is complete control of this 
decision. However, we could instead have it do this:

0000000000404920 <.split.test>:
  404920:       64 48 3b 24 25 70 00    cmp    %fs:0x70,%rsp
  404927:       00 00 
  404929:       72 06                   jb     404931 <test+0x6>
000000000040492b <test>:
  40492b:       b8 ef be ad de          mov    $0xdeadbeef,%eax
  404930:       c3                      retq   
  404931:       45 31 d2                xor    %r10d,%r10d
  404934:       45 31 db                xor    %r11d,%r11d
  404937:       e8 6d 6b 00 00          callq  40b4a9 <__morestack>
  40493c:       c3                      retq   
  40493d:       eb ec                   jmp    40492b <test>
  40493f:       90                      nop

Now the decision to call either test or .split.test becomes explicit. This 
would allow us to get a linker error if we made an incorrect decision in my 
earlier not-really-a-suggestion-more-of-a-musing of making this knowledge 
explicit in the compiler akin to a calling convention. If the compiler decided 
that something wasn't split-stack, then it would just handle allocating the 
larger stack before the call to the underlying function; or, if it decided the 
function was split-stack, the linker would enforce it, and the user would get a 
reasonable error.

This also has the amazing benefit that it no longer would extract a runtime 
performance cost for libraries such as libc, libsupc++, and libgcc to be 
compiled with -fsplit-stack. The existing people who were using the library 
would continue to call the original version of the code, and only people who 
were trying to opt-in to the split-stack universe would be using the new 
split-stack variations. You could imagine an entire distribution of Linux (or 
whatever) that was compiled with this feature active, and the result would only 
be a slight increase in code-size.

So: thoughts? I really do think that there is a way to do this -fsplit-stack 
feature that allows more people to use it and for it to "change everything" / 
"take over the world" ;P. In my eyes, doing that would require a) that there is 
no impediment to just compiling everything with -fsplit-stack and b) the 
functionality to work with a stock linker (as many platforms are not supported 
by Gold). I think that with some variation on some of my above implementation 
ideas, this feature could be done generically in the compiler (and libgcc) for 
all platforms.

(Obviously, though: this is something I've only been looking at for days, and 
only seriously thinking about the implementation concerns of for hours, so I 
could easily be overlooking something obvious that you thought about two years 
ago that makes any or all of these ideas untenable. I certainly will not be 
bothered to learn that this is all stupid, and in fact will highly appreciate 
the feedback. Again: thank you so much for even reading any of these thoughts 
in the first place. ;P)

Sincerely,
Jay Freeman (saurik)
sau...@saurik.com

Reply via email to