16

PowerPC branches only have 24 bits available for the target offset, so if the text section gets too big, branches on one end won't be able to reach targets on the other. There's a longer sequence of instructions that can reach targets farther away (the offset is 32 bits instead of 24), but GCC doesn't use it by default unless you pass it the -mlongcall option. However, even with this option on, GCC still generates short calls for certain functions, namely operator new and operator delete

For example, given this code:

extern void foo();

int main(int argc, char** argv) {
    foo();
    new char;
}

A normal run of GCC will generate the assembly:

bl _Z3foov // void foo()
bl _Znwj   // operator new(unsigned int)

Running GCC with the -mlongcall option generates:

lis r9, _Z3foov@ha
addi r9, r9, _Z3foov@l
mtctr r9
bctrl
bl _Znwj

The first four instructions are a long call to foo(), as expected, but the call to operator new is unchanged. Calls to random libc and libstdc++ functions are all converted to long calls as expected. Why do operator new and operator delete calls still end up as bl instructions? Is there any way to force GCC to make them long calls as well? I'm using GCC 4.7.2 on a 64-bit PowerPC Fedora machine (although I'm building 32-bit)

Michael Mrozek
  • 169,610
  • 28
  • 168
  • 175
  • 2
    Which version of GCC? Did you made a bug report on http://gcc.gnu.org/bugzilla/ ? Did you try with the latest GCC (4.7.2 and soon 4.8)? – Basile Starynkevitch Mar 11 '13 at 22:35
  • 5
    Pinch me, it's late here, and maybe I'm already dreaming. Or did I just really find a good question? –  Mar 11 '13 at 22:36
  • 2
    Just wondering, is it the same if you define a replacement `void* operator new(std::size_t);`? – aschepler Mar 11 '13 at 22:37
  • @BasileStarynkevitch 4.7.2. I don't know that it is a bug; I assume GCC has a good reason, I just don't know what it is – Michael Mrozek Mar 11 '13 at 22:44
  • @aschepler That fixes it (it turns into a long call). Is there any downside to doing that? It still compiles, even if I don't actually implement the function; apparently it still picks up the standard one – Michael Mrozek Mar 11 '13 at 22:50
  • Hmm. This is one of the eight functions which must be defined by the C++ standard library but can also be replaced by a user-defined function. Which explains why the linker acts differently for it, but beyond that it's got to be just a bug. – aschepler Mar 11 '13 at 23:32
  • `new char` is not a direct call of `operator new(1)`, what happens if you do call it directly? – MSalters Mar 12 '13 at 08:53
  • @MSalters It comes out the same (the object file is identical) – Michael Mrozek Mar 12 '13 at 13:57
  • @Ben "if the text section gets too big, branches on one end won't be able to reach targets on the other" – Michael Mrozek Mar 12 '13 at 16:34
  • @MichaelMrozek, 24bits = 16MB is a large section but not impossible. But does it still generate short calls in that case (which presumably would SEGV)? Does OP have text sections that big which he cannot split? I suspect it will generate long calls but OP is not specific on that point - hence the question. – Ben Mar 12 '13 at 16:41
  • @Ben It's actually 32MB (it's signed, so 8MB, but PPC offsets get multiplied by 4 to align with the instruction size, so 32MB), but I've managed to hit it; long story. It's also possible to have relocs that cross sections, like PLTREL24, so it can happen even with smaller text sections. The object files have short calls; the linker fails to resolve the relocs on the `bl` instructions because it can see that the value it would need to insert into the offset field is too large, so it just errors out – Michael Mrozek Mar 12 '13 at 16:48
  • @aschepler Want to call your idea an answer? It's working for me, even though it's a bit of a hack – Michael Mrozek Mar 12 '13 at 18:37
  • @MichaelMrozek: This smells like a bug to me. I would report it. Your post is pretty much perfect as-is for a bug report. – nneonneo Mar 12 '13 at 19:56

2 Answers2

3

It seems the g++ toolchain has some sort of bug in how it calls the eight "replaceable" functions of the C++ Standard Library on your architecture, if those functions are not in fact replaced by user code.

A portable replacement implementation for all eight is:

#include <memory>
#include <cstdlib>

// May never return a null pointer.
void* operator new(std::size_t size) {
    void* p = std::malloc(size, 1);
    while (!p) {
        std::new_handler handler = std::get_new_handler();
        if (handler) {
            handler();
        } else {
            throw std::bad_alloc();
        }
        // A handler is only allowed to return if it did something to make more
        // memory available, so try again.
        p = std::malloc(size, 1);
    }
    return p;
}

void operator delete(void* p) noexcept {
    if (p) std::free(p);
}

void* operator new(std::size_t size, const std::nothrow_t&) noexcept {
    void* p = nullptr;
    try {
        p = operator new(size);
    } catch(...) {}
    return p;
}

void operator delete(void* p, const std::nothrow_t&) noexcept {
    operator delete(p);
}

// May never return a null pointer.
void* operator new[](std::size_t size) {
    return operator new(size);
}

void operator delete[](void* p) noexcept {
    operator delete(p);
}

void* operator new[](std::size_t size, const std::nothrow_t& nt) noexcept {
    return operator new(size, nt);
}

void operator delete[](void* p, const std::nothrow_t& nt) noexcept {
    operator delete(p, nt);
}
aschepler
  • 70,891
  • 9
  • 107
  • 161
0

If we can define this function under #pragma long_calls or declare long-call attribute inside this function, we can force GCC to make them long calls as well. Checkout GCC-options.

sebi
  • 1