Skip to content

mbed requires use of printf/scanf through any use of Stream #4830

Closed
@fahhem

Description

@fahhem

Description

  • Type: Enhancement
  • Priority: Minor

Enhancement

Reason to enhance or problem with existing solution
scanf/printf can be heavyweight (nanolib takes >2.5kb in flash) and hard to optimize even when only used once due to source->static lib->source traversal.

When you use a Stream subclass (like Serial), here's what happens:
Stream::Stream() calls fdopen->mbed_fdopen

In mbed_retarget.cpp, mbed_fdopen() calls sprintf just to do ":%p" % this, then calls std::fopen, which eventually leads to _open, which calls sscanf to rip the pointer out of ":%p".

Suggested enhancement
Option A: optimize these specific uses of printf/scanf out.
printf: A simpler (computationally) way to calculate a buf that will be checked by _open is:

// Around line 828
std::FILE *mbed_fdopen(FileHandle *fh, const char *mode) {
    // char buf[12]; /* :0x12345678 + null byte */
    // std::sprintf(buf, ":%p", fh);
    char buf[2 + sizeof(fh) + 1]; 
    static_assert(sizeof(buf) == 7, "Pointers aren't 4 bytes?");
    buf[0] = ':';
    memcpy(buf + 1, &fh, sizeof(fh));
    buf[1 + sizeof(fh)] = '\0';

// Around line 178
extern "C" FILEHANDLE PREFIX(_open)(const char* name, int openmode) {
...
// Around line 232
      if (name[0] == ':') {
        void *p;
        // std::sscanf(name, ":%p", &p);
        memcpy(&p, name + 1, sizeof(p));
        res = (FileHandle*)p;

This saves ~3.3kB from my program, though I still have these symbols in my Map and I can't trace them any further: svfprintf, vfprintf, fseek, setvbuf.

Option B: Allow Serial to not support file-like operations (via define?, or a SerialLite?)
To compare apples to apples, I first wrote the following code to ensure I was linking everything in:

mbed::Callback<void(int)> null_cb(nullptr);
char buf[80];
int written = std::sprintf(buf, "Some stuff: %d", 9);
serial.write((uint8_t*)buf, written, null_cb));

This uses the async write() code since the synchronous write() is implemented by Stream. Then I removed Stream as a base class of Serial in both Serial.h and Serial.cpp. Compiling before and after that base class removal, I got a 5kB shrink in text (and 300 in bss because mbed_retarget's filehandles aren't linked in anymore).

Pros
Both options can be done, though B is more intrusive and more work as it seems the purpose of Stream was to capitalize on the stdlib's implementations of fprintf, fflush, etc. However, for more production-worthy systems, being able to use Serial ports without linking in string-manipulation libraries and/or file-manipulation libraries means being able to put more code in the same microcontroller or to cost-down and use one with less flash (and less ram, etc).

Option A:

  • Save ~3kB in firmware not using scanf elsewhere

Option B:

  • Save ~5kB in firmware not using file-like abilities on Serial.
  • Since Stream::printf calls fprintf synchronously, but SerialBase::write is an async write, adding/moving Stream::write to SerialBase or a new StreamLitewould give Serial a synchronous write that doesn't link in file handling.

To be clear, I haven't thought through Option B, partly because I don't know mbed's long-term vision for Serial, Stream, etc. I just know that ripping out file handling code brings my blinky+serial code down to ~15kB from ~23kB. While that's still really large for the functionality, it's mostly UART and RCC related plus 4 printf-related functions that I'll figure out some other way.

Cons
Option A:

  • The filename string is less human-readable (not really a problem)
  • Slightly more code to use memcpy over sscanf and sprintf.

Option B:

  • This requires more thinking/decisions and a lot more work than what I've done here. If you're okay with more defines, I can make one to just rip out Stream as a subclass, but that seems hacky.

Conclusion

I'll send a PR for Option A to discuss the exact implementation since that seems reasonable. For Option B, I'd like to hear your thoughts and see what path you would prefer.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions