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

Work with arduinostl #2

Merged
merged 16 commits into from
Sep 29, 2020

Conversation

matthijskooijman
Copy link
Contributor

See #1

@hideakitai
Copy link
Owner

And at the right time, can you add example which use this library with ArduinoSTL?

@matthijskooijman
Copy link
Contributor Author

I forgot to post my test sketch yesterday, but please find it below now. I'm not sure whether this would be good for an actual example, though, as it just tries to cover some corner cases which would be weird in a real-world example.

#if 1 // Either include ArxTypeTraits or standard system headers (setting this to 0 might break e.g. void_t below)
#include "ArxTypeTraits.h"
#else
#include <functional>
#include <limits>
#endif

#if 0 // Check with standard libray or just ArxTypeTraits
#if __AVR__
#include <ArduinoSTL.h>
#endif
#include <limits>
#else
// Allow using arx::std using the std:: name
//namespace std { using namespace arx::arx_std; }
#endif

// ArxTypeTraits handles these, but undef these in case we're not including ArxTypeTraits
#undef min
#undef max

// This breaks currently
//using namespace arx;

std::function<void()> foo = []() {};

// When using ArduinoSTL, this is *not* constexpr
int y = std::numeric_limits<int>::max();
// But this is :-D
constexpr int z = arx::arx_std::numeric_limits<int>::max();

// Check that void_t is available and not ambiguous
using x = std::void_t<int>;

void setup() {
  Serial.begin(115200);
  randomSeed(analogRead(0));
  if (random(2)) {
    foo = []() {
      Serial.println("A");
    };
  } else {
    new (&foo) std::function<void()>([]() {
      Serial.println("B");
    });
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  foo();
}

@matthijskooijman
Copy link
Contributor Author

Cool! I'll try to clean up the history a bit more before merging this, though.

I'm also currently trying to compile this under cygwin, just to doublecheck whether that works.

@hideakitai
Copy link
Owner

Ok, thanks! I'll check it again after the refactor :)

I think ArxTypeTraits itself looks perfect, but compiling your example with ArduinoSTL (making this flag to 1) causes some errors.

#if 1 // Check with standard libray or just ArxTypeTraits
ArduinoSTL/src/del_opvs.cpp:25:53: error: 'std::size_t' has not been declared
 _UCXXEXPORT void operator delete[](void * ptr, std::size_t) throw(){

ArduinoSTL/src/del_ops.cpp:25:50: error: 'std::size_t' has not been declared
 _UCXXEXPORT void operator delete(void* ptr, std::size_t) throw(){

ArduinoSTL/src/del_opnt.cpp:25:56: error: 'nothrow_t' in namespace 'std' does not name a type
 _UCXXEXPORT void operator delete(void* ptr, const std::nothrow_t& ) throw() {

ArduinoSTL/src/del_opvnt.cpp:25:58: error: 'nothrow_t' in namespace 'std' does not name a type
 _UCXXEXPORT void operator delete[](void* ptr, const std::nothrow_t& ) throw(){

Though I have tried it with original one, do you use one of forks?

@matthijskooijman
Copy link
Contributor Author

Oh, right, provided you're testing on AVR, that's this issue: mike-matera/ArduinoSTL#56

It happens regardless of ArxTypeTraits, it's a conflict due to the Arduino core offering the <new> header since recently on AVR. A workaround is to downgrade the AVR core to 1.8.2, which is what I've been testing with so far. This should eventually be fixed in Arduino and/or ArduinoSTL, I think (I already submitted a PR for making <new> complete in Arduino, and have done some work there).

@hideakitai
Copy link
Owner

hideakitai commented Sep 24, 2020

Ah, it's cool, thanks!

This is not normally a problem, but clang warns that MSVC might produce
linker errors for this.
This is needed for WCHAR_MAX and similar macros. This was only a problem
when ArxTypeTraits.h was included without stdint.h being included
already (e.g. through Arduino.h usually), but include it just in case.
On some architectures, various C++98 and C++11 things were defined by
this library. On other architectures, these are assumed to be provided
from the system libraries and some headers are included to make sure
they are actually available after including ArxTypeTraits.

However, the list of included headers was not complete, so not all names
normally defined might be available on these architectures. This has
probably not shown up so far since the headers that were included might
include other headers internally as well (e.g. on ESP32,
`numeric_limits` was available even though `<limits>` was not included,
but on SAMD this is not the case).

This makes the list of includes complete, so all names should now be
guaranteed to be available.
This moves these replacements into their own file for clarity and
includes that file earlier in ArxTypeTraits.h already, before including
standard C++ headers.

This fixes compilation on platforms that *do* have libstdc++ but also
define min/max macros. One example of such platform is SAMD, though that
was not currently broken because there ArxTypeTraits defined its own
functions rather than including libstdc++ (but that can now be changed).

This has probably not shown up as a problem before, since the platforms
where libstdc++ was used, probably did not define min/max macros (but
min/max functions). However, this is not necessarily true and any
platform can define such macros, so just do the replacement always
(provided these macros are defined, of course).
This prevents the macros from creating issues when Arduino.h is not yet
inluded but is included later.
This makes it easier to update the copy of this code embedded in the
ArxContainer library, since now the entire file can be copied as-is.

This commit changes the preprocessor guard that was around the the
definition of initializer_list, to protect it from being double-defined
by ArxContanier as well. It is now a standard include guard named after
the file. This might cause problems when combined with ArxContainer,
which should be solved as soon as ArxContainer also includes this same
file.
These all refer to things inside the current std namespace, so no need
to qualify them.
This declares new stuff in another namespace to prevent conflicts when
standard system libraries are available even when we thought they were
not.

By importing the new arx::arx_std namespace *into* the std namespace,
users of this library do not need to be updated, they can still
reference using the std namespace as before (except that in case of a
conflict, they get the standard library version rather than a
compilation error).

In addition, this imports all of std into arx::arx_std as well, so you
can *also* explicitly reference namespace arx::arx_std for everything,
except that in case of a conflict, you get the arx version rather than
the standard library version (this is relevant when arx defines a newer
version of some name than the standard libraries, such as the C++11 arx
version of numeric_limits vs the C++98 ArduinoSTL version of it).

Note that it would have been nicer to name this namespace arx::std, but
when doing `using namespace arx`, which imports the `std` name, this
creates a conflict with the global `std` namespace.
These were added to prevent conflicts with OpenFrameworks also defining
some things in the std namespace. But now that we define things in our
own namespace, this conflict can no longer occur and these guards can be
removed.
Previously, on some specific architectures, an inline placement new was
declared, and on others `<new.h>` was included. But `<new.h>` is a
non-portable header available only in some Arduino architectures, while
declaring our own placement new might conflict with standard libraries
that also declare an inline version (such as uclibc++/ArduinoSTL).

This commit changes the behaviour to always include `<new>` if it is
available (assuming it is complete or at least has placement new, which
seems to be the case for existing Arduino cores). If `<new>` is not
available (which only seems to be the case for megaavr and older avr
cores currently), just declare placement new inline. This has a small
chance of conflict, but seems to be ok for avr and megaavr now.
This ensures that initializer_list is really only defined when not
already available from the standard-named header.

Unlike the other things defined by this library, initializer_list *must*
be defined in std, importing it into std is not enough. This means that
if it would already have been defined elsewhere, this will produce a
conflict (unlike for the other things defined).
These platforms have libstdc++ available normally, so just use it. This
has become possible with some recent changes that prevent conflicts
with the libstdc++ implementation on those platforms.
Previously, on the (mega)AVR architectures, no standard c++ library was
assumed to be available. Only on these architectures, controlled by the
`ARX_TYPE_TRAITS_DISABLED` macro, all the C++98 and C++11 stuff would be
defined. On all architectures (include (mega)AVR), the `__cplusplus`
macro would be used to decide whether or not to define the C++14 and
C++17 stuff.

This is now made a lot more generic, where `TypeTraits.h` now defines a
single macro with the C++ version supported by the standard library.

In the other headers, this macro is used to decide what to define and
what to include from the standard library. The C++98 and C++11 stuff is
now split, so on e.g. AVR with ArduinoSTL the C++98 stuff can be
included but the C++11 stuff still defined (which prevents defining
stuff that is not needed).

Determining the supported C++ version no longer relies on the Arduino
architecture macros, but instead sees which library is available
exactly. For gcc's libstdc++ and clang's libc++, the compiler
`__cplusplus` version is assumed to be correct, for uclibc++ C++98 is
assumed. Other standard libraries raise an error, since we have no idea
about their capabilities. If no standard library is available (e.g. AVR
without ArduinoSTL, the version macro is set to 0).
Even when the standard library might not support C++11 yet, the compiler
should to allow things like variadic templates and other C++11 language
constructs to be used.
@matthijskooijman
Copy link
Contributor Author

Ok, a couple of dozen rebases later and I think the history looks nice and clean now. I now did the namespace rename before the library-detection refactor, and it turns out the namespace rename was actually enough to make things work with ArduinoSTDL (but the refactor is still nice, because the code is now cleaner and only defines what is really needed). I also split off some smaller commits here and there and improved the commit messages, should be easy to review now.

I also moved the initializer_list into its own file as discussed and did some other small improvements.

I think that this PR is ready for another review and merging as far as I'm concerned :-)

@matthijskooijman matthijskooijman marked this pull request as ready for review September 25, 2020 21:12
@matthijskooijman
Copy link
Contributor Author

Here's a slightly updated version of the example, which also tests initializer_list:

#include "ArxTypeTraits.h"

#if 0// Check with standard libray or just ArxTypeTraits
#if __AVR__
#include <ArduinoSTL.h>
#endif
#include <limits>
#endif

// Check this doesn't break
using namespace arx;

std::function<void()> foo = []() {};

// When using ArduinoSTL, this is *not* constexpr
int y = std::numeric_limits<int>::max();
// But this is :-D
constexpr int z = arx::arx_std::numeric_limits<int>::max();

// Check that void_t is available and not ambiguous
using x = std::void_t<int>;

// Check that initializer_list works
size_t listlen(std::initializer_list<int> s) { return s.size(); }
const size_t len = listlen({1, 2, 3});

void setup() {
  Serial.begin(115200);
  randomSeed(analogRead(0));
  if (random(2)) {
    foo = []() {
      Serial.println("A");
    };
  } else {
    new (&foo) std::function<void()>([]() {
      Serial.println("B");
    });
  }
}

void loop() {
  // put your main code here, to run repeatedly:
  foo();
}

I compile-tested this on ESP32, SAMD and AVR.

@hideakitai
Copy link
Owner

Thanks so much! I've checked this PR also in my several libraries which uses ArxTypeTraits. Currently only one problem is about ESP8266. ESP8266's __has_include seems not working properly, it causes compile errors. Adding || defined(ESP8266) to __has_include flags like #if __has_include(<cstdlib>) || defined(ESP8266) works fine, but... not smart is it? What do you think about it?

@matthijskooijman
Copy link
Contributor Author

Hm, it seems that ESP8266 still uses gcc 4.8, and __has_include was introduced in gcc 5.0: https://gcc.gnu.org/gcc-5/changes.html

However, in my local test, I get errors about redefinition of initializer_list, as if __has_include just returned false, rather than this error:

#if !defined(__has_include)
#error "Compiler does not support __has_include, please report a bug against the ArxTypeTraits library about this."
#endif

Turns out the ESP8266 libc defines this macro manually: https://github.com/esp8266/Arduino/blob/cc042b99d1c966a8b3e92ca9b7ffb3b497d0af98/tools/sdk/libc/xtensa-lx106-elf/include/sys/cdefs.h#L79-L81

Which is a bit of a bummer, since to ArxTypeTraits work on ESP8266, __has_include just needs to return true for all the checks we do, but we can't just redefine it. I guess we could define our own ARX_HAS_INCLUDE(x) which then just forwards to __has_include except on ESP8266, where it just returns true? I'll give that a try.

Turns on ESP8266 does not support `__has_include`, but its libc defines
a dummy version of it that always returns false, causing conflicts
between system headers and ArxTypeTraits.

This makes the following changes:
 - Replace uses of `__has_include` with a new `ARX_SYSTEM_HAS_INCLUDE`
   header (which defaults to just forwarding to `__has_include`).
 - On ESP8266, let `ARX_SYSTEM_HAS_INCLUDE` always return true (since we
   know that it supports the headers we check for).
 - On other platforms that use gcc < 5 (which does not have
   `__has_include` yet), flag an error (even if `__has_include` is
   defined, since it must then be a dummy version).
 - Move all this into its own header file, so it can be easily copied
   into ArxContainer along with "initializer_list.h".
@hideakitai
Copy link
Owner

Thanks, it looks good!

However, in my local test, I get errors about redefinition of initializer_list, as if __has_include just returned false, rather than this error:

Ah yes, I also got about initializer_list and placement new.

@matthijskooijman
Copy link
Contributor Author

Done, see the latest commit. Compiletest on ESP8266, ESP32, SAMD and AVR (the last one with and without ArduinoSTL).

Copy link
Owner

@hideakitai hideakitai left a comment

Choose a reason for hiding this comment

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

All commits looks fine and clear, and testing on my libraries depending on ArxTypeTraits also works fine. Thank you so much for your awesome contribution to ArxTypeTraits! 👏

@hideakitai hideakitai merged commit bebb5e3 into hideakitai:master Sep 29, 2020
hideakitai added a commit that referenced this pull request Sep 29, 2020
@hideakitai
Copy link
Owner

After merging, I made some modifications I missed.

  • move ARX_HAVE_LIBSTDCPLUSPLUS to separated file for portability (to another Arx Series)
  • DUE has compile error because compiler is GCC 4.8.3 and no stdlibc++, so force ARX_HAVE_LIBSTDCPLUSPLUS 0
  • fix std::move ambiguity (this is long existed bug which is not related to this PR)

Please confirm them and let me know if you have any comments :)

@matthijskooijman
Copy link
Contributor Author

I left some inline comments on some of your issues.

As for Due, your observation surprises me, since (just checked), it does have libstdc++. But when I use the same approach as for ESP8266 (let __has_include return 1), then I get:

/tmp/arduino_build_static/sketch/test.cpp.o: In function `loop':
/tmp/arduino_build_static/sketch/test.cpp:47: undefined reference to `std::__throw_bad_function_call()'
collect2: error: ld returned 1 exit status

So that's a bit of a different problem. In fact, the problem seems to be that linking is done by gcc rather than g++. This should probably be fixed in the SAM core, but for now just ignoring the libstdc++ on Due is maybe not too bad (can always revert this when SAM is fixed). I also think SAM should (and can) be upgrade to a new gcc (same as SAMD), so I'll see if I can nudge people to do that as well :-)

@hideakitai
Copy link
Owner

Thanks for your comment, I've fixed those comments.

And sorry when I thought !defined(__has_include) line means does not have libstdc++ (of course I know it's not...) even though it is arm board... I will do things more carefully :( Anyway, this would be better to 1 not 0, but currently remain as-is because of the error caused by std::function you mentioned. I will wait for you to nudge people :)

Thanks again!

@hideakitai
Copy link
Owner

If you can, can I ask you how you find which is the problem inside GCC (g++) like this?

the problem seems to be that linking is done by gcc rather than g++

I'm not confident about such details currently, but I want to be go deeper if I can. I could go to functional of arm-none-eabi 4.8.3 inside of sam directory, but I couldn't go further like you. How could you find that cause? If you can, let me know. (The only way is to read and understand codes inside such toolchains maybe...)

Thanks, in advance.

@matthijskooijman
Copy link
Contributor Author

How could you find that cause?

I first switched SAM from gcc 4 to gcc 7 (the one from SAMD). I used a bit of a hack, replacing the 4.8.something directory in ~/.arduino/packages/arduino/tools/arm-gcc-none-eabi/ (or thereabouts) with a symlink to the 7 version. After fixing a problem with printf, I got this linker error. Iooked to see where this std::__throw_bad_function_call was supposed to be defined (grepping around the SAM and SAMD cores and both gcc versions) but couldn't find any place. Because it works for SAMD, I knew it had to come from somewhere. So I compared compiler commands between SAM and SAMD, then noticed gcc vs g++ and realized that g++ just magically inserts these symbols apparently. I left out some details and other small things I tried, but this was the general flow :-)

@hideakitai
Copy link
Owner

Thank you, great. I should go more along with the compiler... This library was the result of my research to understand what the type_traits works (and how to write it), so I will go further with compiler by improving (and debugging) this library :) Thank you so much!
If you have nothing to comment currently, I will release the next version.

@matthijskooijman
Copy link
Contributor Author

For the Due, I filed a fix for the linker error here and suggested upgrading the compiler here.

Some more minor things:

  • move std::move from C++11 to C++14 section #3
  • The README now says "C++11 (defined only for platforms above which cannot use type_traits)", but there's no platforms defined above anymore. Maybe put this under "Supported Class Templates" instead?

When something is available from the standard library, it is included and can be used directly. If something from the below list is missing from the standard library, it is defined by ArxTypeTraits and can be accessed through the std namespace as normal. When the standard library defines an older version of a name (e.g. the non-constexpr C++98 version of numeric_limits defined by ArduinoSTL), then Arx will define the newer version (e.g. the constexpr C++11 version of numeric_limits), but it must then be accessed through the arx::arx_std namespace (since the standard library is preferred when using the std namespace).

  • I still wonder how nice arx::arx_std really is. I wonder if we shouldn't rename to e.g. arx::stdx (as you suggested somewhat earlier) instead, which looks cleaner to me. Should be still ok to rename now without any compatibility issues.

@hideakitai
Copy link
Owner

hideakitai commented Sep 29, 2020

#3

Ah, yes, it looks better. I will check if it works tomorrow (probably it works), and merge it.

README

Yeah, there is no "platform above"... I'm going to delete it.

Renaming arx::arx_std to e.g. arx::stdx

I prefer arx::stdx because it's short and pretty (and doesn't lack the meaning) for me :)
I'm going to rename to arx::stdx.

hideakitai added a commit that referenced this pull request Sep 30, 2020
hideakitai added a commit that referenced this pull request Sep 30, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants