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

Allow to use as a library #6

Merged
merged 7 commits into from
Jan 14, 2020
Merged

Allow to use as a library #6

merged 7 commits into from
Jan 14, 2020

Conversation

mcspr
Copy link
Contributor

@mcspr mcspr commented Dec 23, 2019

Hey,

About a month ago I was trying to unit testing support to an esp8266 project and it turned out to be quite easy to setup this repo as a base for Arduino emulation layer. But, because Arduino.cpp file defines main(), it is not possible to use it as a library (e.g., see this configuration file for PlatformIO - https://github.com/xoseperez/espurna/blob/dev/code/test/platformio.ini)

It is easily solved by adding __attribute__((weak)) for main()
Also, in case someone would want to call this main() anyway, define the existing symbol by another name so it does not get stripped and user code can call __main()

PS. I did see the README notice :)

Add weak attribute for main()
Arduino.cpp Outdated
@@ -123,7 +123,7 @@ static void handleControlC(int /*sig*/) {
// Main loop. User code will provide setup() and loop().
// -----------------------------------------------------------------------

int main(int argc, char** argv) {
extern "C" int __main(int argc, char** argv) {
Copy link
Owner

@bxparks bxparks Dec 29, 2019

Choose a reason for hiding this comment

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

Thanks for the PR, this is a great idea.

I did some testing and I found that __attribute__((weak, alias(".."))) works on g++7/Linux, and clang++8/Linux, but is not supported on clang++11/Darwin (i.e. MacOS). The error message is:

error: aliases are not supported on darwin
___attribute__((weak, alias("__main")));

I think the workaround is to remove the alias() part. Because as far as I can tell, the purpose of the (alias()) is to point a large number of weak functions to a single strong function, to save on generated code. Since we have only a single weak function, we don't really need it.

The code that worked on both Linux and Mac, on both g++ and clang++ was:

extern "C"
int main(int argc, char** argv)
__attribute__((weak));                          
  
int main(int argc, char** argv) {
  signal(SIGINT, handleControlC);
  atexit(disableRawMode);
  enableRawMode();                                                              
  
  setup();
  while (true) {                                                                
    char c = '\0';                                                              
    read(STDIN_FILENO, &c, 1);                                                  
    if (c) Serial.insertChar(c);                                                
    loop();
    yield();
  }
} 

Can you verify that this new version works for you? If so, we can check that in.

(BTW, I notice a mix of spaces and tabs in this file. That was not intentional, I use only spaces for indent. I will fix that after you submit your code.)

Copy link
Owner

@bxparks bxparks Dec 29, 2019

Choose a reason for hiding this comment

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

Oh, re-reading your comments, I see that the other reason for delegating to __main() is to allow someone to call it directly. So how about:

extern "C"
int main(int argc, char** argv)
__attribute__((weak));

int main(int argc, char** argv) {
  return __main(argc, argv):
}

extern "C"
int __main(int argc, char** argv) {
  ...
}

Copy link
Contributor Author

@mcspr mcspr Jan 2, 2020

Choose a reason for hiding this comment

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

Makes sense, but I have another issue with that approach. Here are a couple of builds to showcase compilation errors when adding dummy pinMode(...) calls, causing Arduino.o to be linked into the binary.
I'd like to replace the current patch with the last one, I hope comment above + (copyright) move is fine?

.pio/build/test/libe77/libUnixHostDuino.a(Arduino.o): In function `__main(int, char**)':
Arduino.cpp:(.text+0x38c): undefined reference to `setup'
Arduino.cpp:(.text+0x3c6): undefined reference to `loop'
collect2: error: ld returned 1 exit status
$ .pio/build/test/program
fish: “.pio/build/test/program” terminated by signal SIGSEGV (Address boundary error)

From what I remember, extern "C" was added for alias specifically. Since none of the patches above use extern and still compile and work just fine, I would guess it is not needed here.

Copy link
Owner

@bxparks bxparks Jan 12, 2020

Choose a reason for hiding this comment

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

Hi,
(It took me a bit of effort to figure out that I should click through the mcspr@xxxxx links to see the code fragments that you were referring to!)

Your last patch creates a main.cpp, and converts __main() into an inline function. But I think this makes __main() invisible to any outside code. Which I think is fine. But if __main() is not usable, we can achieve the same result by simply using a single main(). So I propose going back to the code I posted in my first reply, (and not create a main.cpp):

Arduino.cpp:

extern "C"
int main(int argc, char** argv)
__attribute__((weak));                          
  
int main(int argc, char** argv) {
  signal(SIGINT, handleControlC);
  atexit(disableRawMode);
  enableRawMode();                                                              
  
  setup();
  while (true) {                                                                
    char c = '\0';                                                              
    read(STDIN_FILENO, &c, 1);                                                  
    if (c) Serial.insertChar(c);                                                
    loop();
    yield();
  }
} 

If I understand things correctly, if your PlatformIO code provides its own main(), it will clobber this one, and the references to loop() and setup() will be removed, which fixes the linker problem. Yes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! Sorry for confusion.
I think inline is not intended there... Otherwise, that patch is the only way it builds with that configuration. Simply adding weak attribute to the main has the same effect as having weak main and __main, despite main being weak and supposedly be unused.

I wonder if object link order matters somehow? After platformio builds all packages:

g++ -o .pio/build/test/program .pio/build/test/test/output_export.o .pio/build/test/test/runner.o -L.pio/build/test -Wl,--start-group .pio/build/test/lib6e3/libUnixHostDuino.a .pio/build/test/libaf3/libStreamString.a .pio/build/test/libUnityTestLib.a -Wl,--end-group

I forgot to actually push the patch here, so I'll do that now.

@mcspr
Copy link
Contributor Author

mcspr commented Jan 13, 2020

The main difference is that there is no inline anymore, since it did not do the thing I though it will do. Without optimisation options symbol is still there, but with optimisation it disappears and causes link error when trying to call it.
And __ seems to be for compiler reserved names, plus looks nicer

@bxparks
Copy link
Owner

bxparks commented Jan 14, 2020

Ok, thanks, I'm going to merge your stuff in. I can do some small fixes afterwards (like some trailing spaces). I'm pretty sure that the extern "C" is not necessary, because main() will never be called by a C program directly.

@bxparks bxparks merged commit 67e78a7 into bxparks:develop Jan 14, 2020
@bxparks
Copy link
Owner

bxparks commented Jan 14, 2020

Looks like I need to keep the extern "C" in main.cpp to match the declarations in Arduino.h which wraps the whole bunch in extern "C" { }.

@mcspr
Copy link
Contributor Author

mcspr commented Jan 14, 2020

Thanks a lot!

As far as I understood the extern "C" stuff while declared like that needs slightly more changes; e.g. if in my case I write some test file as .c and include Arduino.h (but that now fails with Print class code anyway)

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