boost-ext / di

C++14 Dependency Injection Library
https://boost-ext.github.io/di
1.16k stars 140 forks source link

How to disable heap allocations at all? #349

Closed akowalew closed 5 years ago

akowalew commented 6 years ago

Expected Behavior

I would like to use Boost::DI in an embedded system, where heap allocation is disabled in the whole project and everything has to be allocated on stack. In projects like this, members for objects are usually passed in constructors as references or just by values. There is no calls to std::shared_ptr, std::unique_ptr or even operator new.

It would be nice to have the option to disable usage of heap, unique and shared pointers in Boost::DI.

Actual Behavior

This is the very simple application, which just uses pseudo OutputPin object to toggle some pin (attributes was added to prevent optimizations):

struct OutputPin {
    __attribute__((noinline))
    OutputPin(int mask)
        :   mask(mask)
    {}

    __attribute__((noinline))
    void toggle()
    {
        *reinterpret_cast<volatile int*>(0xdeadbeef) ^= mask;
    }

    int mask;
};

struct App {
    App(OutputPin& pin)
        :   pin(pin)
    {}

    void run()
    {
        while(true)
        {
            pin.toggle();
        }
    }

    OutputPin& pin;
};

int main()
{
    di::make_injector(
        di::bind<>().to(0x8)
    ).create<App>().run();
}

Even if this code doesn't use heap allocations at all (only simple reference to OutputPin), Boost::DI has added a lot of code, which creates shared pointers and calls operator new. Below is assembly from my Cortex-M4 CPU:

000002ac <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::~_Sp_counted_ptr()>:
...
000002b0 <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::_M_get_deleter(std::type_info const&)>:
...
000002b4 <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::_M_dispose()>:
...
000002bc <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::~_Sp_counted_ptr()>:
...
000002cc <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::_M_destroy()>:
...
000002d4 <std::shared_ptr<OutputPin>::~shared_ptr()>:
...
0000031c <main>:
 31c:   b5f8        push    {r3, r4, r5, r6, r7, lr}
 31e:   4e13        ldr r6, [pc, #76]   ; (36c <main+0x50>)
 320:   6833        ldr r3, [r6, #0]
 322:   f013 0501   ands.w  r5, r3, #1
 326:   d005        beq.n   334 <main+0x18>
 328:   4c11        ldr r4, [pc, #68]   ; (370 <main+0x54>)
 32a:   6824        ldr r4, [r4, #0]
 32c:   4620        mov r0, r4
 32e:   f7ff ffed   bl  30c <OutputPin::toggle()>
 332:   e7fb        b.n 32c <main+0x10>
 334:   2004        movs    r0, #4
 336:   f000 f82d   bl  394 <operator new(unsigned int)>
 33a:   4c0d        ldr r4, [pc, #52]   ; (370 <main+0x54>)
 33c:   4607        mov r7, r0
 33e:   2108        movs    r1, #8
 340:   f7ff ffe2   bl  308 <OutputPin::OutputPin(int)>
 344:   e9c4 7500   strd    r7, r5, [r4]
 348:   2010        movs    r0, #16
 34a:   f000 f823   bl  394 <operator new(unsigned int)>
 34e:   4603        mov r3, r0
 350:   2501        movs    r5, #1
 352:   4908        ldr r1, [pc, #32]   ; (374 <main+0x58>)
 354:   60c7        str r7, [r0, #12]
 356:   4a08        ldr r2, [pc, #32]   ; (378 <main+0x5c>)
 358:   6019        str r1, [r3, #0]
 35a:   4620        mov r0, r4
 35c:   6063        str r3, [r4, #4]
 35e:   e9c3 5501   strd    r5, r5, [r3, #4]
 362:   4906        ldr r1, [pc, #24]   ; (37c <main+0x60>)
 364:   6035        str r5, [r6, #0]
 366:   f000 f865   bl  434 <__aeabi_atexit>
 36a:   e7de        b.n 32a <main+0xe>
 36c:   20000094    .word   0x20000094
 370:   20000098    .word   0x20000098
 374:   00000590    .word   0x00000590
 378:   20000000    .word   0x20000000
 37c:   000002d5    .word   0x000002d5

Generally I don't understand, why constructor of OutputPin is invoked after the call to toggle method. What I've tried was to change the scope of OutputPin to scoped:

    di::make_injector(
        di::bind<>().to(0x8)
        di::bind<OutputPin>().in(di::extension::scoped)
    ).create<App>().run();

The assembly now looks like:

000002ac <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::~_Sp_counted_ptr()>:
...
000002b0 <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::_M_get_deleter(std::type_info const&)>:
...
000002b4 <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::_M_dispose()>:
...
000002bc <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::~_Sp_counted_ptr()>:
...
000002cc <std::_Sp_counted_ptr<OutputPin*, (__gnu_cxx::_Lock_policy)0>::_M_destroy()>:
...
000002e8 <main>:
 2e8:   b510        push    {r4, lr}
 2ea:   2004        movs    r0, #4
 2ec:   f000 f828   bl  340 <operator new(unsigned int)>
 2f0:   2108        movs    r1, #8
 2f2:   4604        mov r4, r0
 2f4:   f7ff ffee   bl  2d4 <OutputPin::OutputPin(int)>
 2f8:   2008        movs    r0, #8
 2fa:   f000 f821   bl  340 <operator new(unsigned int)>
 2fe:   2010        movs    r0, #16
 300:   f000 f81e   bl  340 <operator new(unsigned int)>
 304:   4a04        ldr r2, [pc, #16]   ; (318 <main+0x30>)
 306:   60c4        str r4, [r0, #12]
 308:   2301        movs    r3, #1
 30a:   e9c0 2300   strd    r2, r3, [r0]
 30e:   6083        str r3, [r0, #8]
 310:   4620        mov r0, r4
 312:   f7ff ffe1   bl  2d8 <OutputPin::toggle()>
 316:   e7fb        b.n 310 <main+0x28>
 318:   00000518    .word   0x00000518

It produces less code, order of calls to OutputPin is now correct, but it is still invoking operator new, and, std::_Sp_counted_ptr<OutputPin*... bodies are also still compiled. I've tried also to add 'Custom Provider' example into that code, but nothing has changed significantly.

Steps to Reproduce the Problem

  1. #include <boost/di.hpp>
  2. CXX_FLAGS: -std=c++17 -fno-rtti -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables -fno-threadsafe-statics --specs=nano.specs

Specifications

krzysztof-jusiak commented 5 years ago

@akowalew, just add BOOST_DI_DISABLE_SHARED_PTR_DEDUCTION to the compilation flag -> https://godbolt.org/z/waxFpq