Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move heap implementation to :platform:heap and make optional #364

Merged
merged 6 commits into from
Mar 25, 2020

Conversation

salkinium
Copy link
Member

@salkinium salkinium commented Mar 21, 2020

This moves the heap implementation from the :platform:core modules to it's own :platform:heap modules, which allows to build apps without dynamic memory at all (which is the vast majority of modm's examples). The Block allocator is moved to :driver:block.allocator where it belongs much better.

Unfortunately when not including the module and then calling malloc() or new, there aren't any compiler errors, only linker errors, often cryptic (relating to missing sbrk). Not sure if this is a great usibility improvement. But not including this code by default make a difference for tiny targets.

The goal is to add better memory allocators in future, since TLSF has a huge static overhead (several kBs) and newlib's allocator can't deal with mutiple memory regions and is pretty heavy handed too. The Block allocator should be retired.

Fixes #357, fixes #368.

TODO:

  • Find all examples that require :platform:heap
  • Linkstep error message when using malloc/new without implementation
  • Better documentation

@rleh
Copy link
Member

rleh commented Mar 21, 2020

Nice! 👍


calling malloc() or new, there aren't any compiler errors, only linker errors, often cryptic (relating to missing sbrk)

As far a I know you could explicitly delete the new operators.


Not sure if this is a great usability improvement.

Definitively not.
Couldn't we by default include the :platform:heap module (as dependency of :platform:core or :architecture:memory) unless the user disables heap via lbuild option?
I think it would save a lot of people from getting annoying mistakes.

@salkinium
Copy link
Member Author

As far a I know you could explicitly delete the new operators.

GCC disagrees 🤨

In file included from modm/src/modm/platform.hpp:18,
                 from modm/src/modm/board/board.hpp:16,
                 from modm/src/modm/board.hpp:12,
                 from main.cpp:12:
modm/src/modm/heap/cortex/newdelete_noheap.hpp:15:7: error: deleted definition of 'void* operator new(std::size_t)'
   15 | void* operator new(std::size_t size) = delete;
      |       ^~~~~~~~
In file included from modm/src/modm/heap/cortex/newdelete_noheap.hpp:12,
                 from modm/src/modm/platform.hpp:18,
                 from modm/src/modm/board/board.hpp:16,
                 from modm/src/modm/board.hpp:12,
                 from main.cpp:12:
/usr/local/Cellar/arm-gcc-bin/9-2019-q4-major/arm-none-eabi/include/c++/9.2.1/new:125:26: note: previous declaration of 'void* operator new(std::size_t)'
  125 | _GLIBCXX_NODISCARD void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)
      |                          ^~~~~~~~

@salkinium
Copy link
Member Author

But I can mark the prototypes as deprected! And at least that way you get a good error message:

[[deprecated("Heap is not implemented!")]]
void* operator new(std::size_t size);
// all others new() types too

Results in

main.cpp: In function 'int main()':
main.cpp:30:34: warning: 'void* operator new [](std::size_t)' is deprecated: Heap is not implemented! [-Wdeprecated-declarations]
   30 |  uint32_t *ptr = new uint32_t[100];
      |                                  ^
In file included from modm/src/modm/platform.hpp:26,
                 from modm/src/modm/board/board.hpp:16,
                 from modm/src/modm/board.hpp:12,
                 from main.cpp:12:
modm/src/modm/platform/core/newdelete.hpp:18:7: note: declared here
   18 | void* operator new[](std::size_t size);
      |       ^~~~~~~~
main.cpp:30:34: warning: 'void* operator new [](std::size_t)' is deprecated: Heap is not implemented! [-Wdeprecated-declarations]
   30 |  uint32_t *ptr = new uint32_t[100];
      |                                  ^
In file included from modm/src/modm/platform.hpp:26,
                 from modm/src/modm/board/board.hpp:16,
                 from modm/src/modm/board.hpp:12,
                 from main.cpp:12:
modm/src/modm/platform/core/newdelete.hpp:18:7: note: declared here
   18 | void* operator new[](std::size_t size);
      |       ^~~~~~~~
main.cpp:30:12: warning: unused variable 'ptr' [-Wunused-variable]
   30 |  uint32_t *ptr = new uint32_t[100];
      |            ^~~

@rleh
Copy link
Member

rleh commented Mar 21, 2020

@salkinium
Copy link
Member Author

salkinium commented Mar 22, 2020

#pragma GCC poison malloc new

Interesting, learned something new today.

However, it's actually worse than deprecation, I cannot add a message why it was poisened, the pragma doesn't allow adding a comment. So you get only this without context:

main.cpp:31:18: error: attempt to use poisoned "new"
   31 |  uint32_t *ptr = new uint32_t[100];
      |                  ^
main.cpp:32:15: error: attempt to use poisoned "malloc"
   32 |  void *cptr = malloc(100);
      |               ^

You can however still combine it with the deprecation notice, so that it doesn't compile (what we want) and shows why (also what we want).

There's just one problem, the pragma poisons every call to malloc and new including calls that are never used. This is an issue with header files, say I want to use std::addressof or some other utility from <memory> in a modm module, the header may get included after the #pragma declaration like so:

#pragma GCC poison malloc new // included unknowingly via <modm/platform.hpp> for example
#include <memory>

Results in this shitshow:

In file included from /usr/local/Cellar/arm-gcc-bin/9-2019-q4-major/arm-none-eabi/include/c++/9.2.1/memory:64,
                 from main.cpp:13:
/usr/local/Cellar/arm-gcc-bin/9-2019-q4-major/arm-none-eabi/include/c++/9.2.1/bits/stl_construct.h:75:9: error: attempt to use poisoned "new"
   75 |     { ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
      |         ^
/usr/local/Cellar/arm-gcc-bin/9-2019-q4-major/arm-none-eabi/include/c++/9.2.1/bits/stl_construct.h:90:9: error: attempt to use poisoned "new"
   90 |     { ::new(static_cast<void*>(__p)) _T1; }
      |         ^
// ... many more lines of this ...

This doesn't happen with the deprecation notice, it only warns about used calls (or, rather non-optimized out calls).

However, even if you could use this #pragma, you may still link against the static libraries of newlib or stdlibc++ that call malloc/new, and this isn't cought neither with poison NOR with deprecation. Example:

int res = at_quick_exit([](){/*tumbleweed*/});

Results in:

Linking········ modm/build/nucleo_f103rb/blink/release/blink.elf
arm-none-eabi/bin/ld: arm-none-eabi/lib/thumb/v7-m/nofp/libnosys.a(sbrk.o): in function `_sbrk':
sbrk.c:(.text._sbrk+0x18): undefined reference to `end'
collect2: error: ld returned 1 exit status

However, since I had some trouble finding a libc function that actually uses malloc (and arguably this one isn't applicable for embedded systems) I assume this case is fairly rare, and the stdlibc++ too is mostly in headers and little in pure source code, so most of it won't be linked in either.

What I would do now is:

  • Use deprecation only, it gets included with <modm/platform.hpp>, which you must include somewhere in your application.
  • Add the linker errors in the description of :platform:heap, so that you can find it through via lbuild search and the Doxypress and homepage search fields.

cc @chris-durand You're more knowledgeable of the stdlibc++, any better ideas?

@salkinium
Copy link
Member Author

salkinium commented Mar 22, 2020

This doesn't happen with the deprecation notice, it only warns about used calls (or, rather non-optimized out calls).

Actually it does happen, it's just that for system headers, the warnings are ignored.
So for example, including the modm:utils headers but not using them results in this:

In file included from modm/src/modm/utils/allocator.hpp:17,
                 from modm/src/modm/container/dynamic_array.hpp:20,
                 from modm/src/modm/math/geometry/point_set_2d.hpp:17,
                 from modm/src/modm/math/geometry/circle_2d.hpp:20,
                 from modm/src/modm/math/geometry.hpp:18,
                 from main.cpp:17:
modm/src/modm/utils/allocator/dynamic.hpp: In member function 'T* modm::allocator::Dynamic<T>::allocate(size_t)':
modm/src/modm/utils/allocator/dynamic.hpp:64:56: warning: 'void* operator new(std::size_t)' is deprecated: Heap is not implemented! Please include the `:platform:heap` module in your project! [-Wdeprecated-declarations]
   64 |     return static_cast<T*>(::operator new(n * sizeof(T)));
      |                                                        ^
In file included from modm/src/modm/platform.hpp:26,
                 from modm/src/modm/board/board.hpp:18,
                 from modm/src/modm/board.hpp:12,
                 from main.cpp:16:
modm/src/modm/platform/core/no_heap.hpp:18:7: note: declared here
   18 | void* operator new(std::size_t);
      |       ^~~~~~~~

So this is a false positive… Maybe we can inject error messages into the linker? 😓

Furthermore, code that doesn't include the deprecation or #pragma header inside modm compile fine of course. The :freertos module doesn't include anything of course, so the link step fails with an error message.

@salkinium
Copy link
Member Author

salkinium commented Mar 22, 2020

Maybe we can inject error messages into the linker?

🤔🤔🤔🤔🤔🤔 What if…

modm_section(".Heap_is_not_implemented__Please_include_the__platform_heap__module_in_your_project")
void* operator new(std::size_t) { return (void*)-1; }

And in the linkerscript:

/DISCARD/ :
{
	*(.Heap_is_not_implemented__Please_include_the__platform_heap__module_in_your_project)
}

Results in:

Linking········ modm/build/nucleo_f103rb/blink/release/blink.elf
`_Znaj' referenced in section `.text.startup.main' of modm/build/nucleo_f103rb/blink/release/main.o: defined in discarded section `.Heap_is_not_implemented__Please_include_the__platform_heap__module_in_your_project' of modm/build/nucleo_f103rb/blink/release/modm/libmodm.a(no_heap.o)
collect2: error: ld returned 1 exit status

It… works? I should not be allowed near C++…

@salkinium salkinium force-pushed the feature/heap branch 2 times, most recently from 7b29a04 to 1f9b3de Compare March 22, 2020 09:53

// ----------------------------------------------------------------------------
modm_section("{{ no_heap_section }}") modm_weak
void* _sbrk_r(struct _reent *r, ptrdiff_t size)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually perfect, since _sbrk_r is not implemented in newlib at all, so I can just implement it here as a weak function with just the section attribute and no other linker flags (particularly no function wrapping).

This means the user can supply their own malloc implementation if they do not include the :platform:heap module, by either just simply overwriting _sbrk_r to use the default newlib allocator or by additionally wrapping the _malloc_r etc function via the project collectors settings.

@salkinium salkinium force-pushed the feature/heap branch 5 times, most recently from 430fa3b to 4e64b7a Compare March 22, 2020 10:51
@chris-durand
Copy link
Member

cc @chris-durand You're more knowledgeable of the stdlibc++, any better ideas?

I don't have any better ideas.

@salkinium salkinium force-pushed the feature/heap branch 4 times, most recently from 63cef92 to a12bf3f Compare March 23, 2020 02:55
@salkinium salkinium marked this pull request as ready for review March 23, 2020 02:57
while(1) ;
}
return (void*) heap;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now all that's needed to define your own heap region.

* null pointer, at which time it throws std::bad_alloc.
*/
if (std::get_new_handler()) std::get_new_handler()();
else break;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chris-durand is this correctly implemented?

@salkinium
Copy link
Member Author

I'm done now, and tested this on a bunch of hardware.

@salkinium
Copy link
Member Author

Absent of dissenting reviews, I'll be merging this this afternoon.

@salkinium salkinium merged commit b8648be into modm-io:develop Mar 25, 2020
@salkinium salkinium deleted the feature/heap branch March 25, 2020 03:13
@rleh rleh mentioned this pull request Apr 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

[c++] Recovery from out-of-heap-memory condition is not possible Turn the heap into an optional module
3 participants