Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCC refuses to emit long calls for operator new/delete on PowerPC

Tags:

c++

gcc

powerpc

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)

like image 250
Michael Mrozek Avatar asked Mar 11 '13 22:03

Michael Mrozek


1 Answers

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);
}
like image 129
aschepler Avatar answered Oct 15 '22 04:10

aschepler