Skip to content

Default handlers get mono-morphized needlessly #19

Closed

Description

@jpochyla

Since #17, identical copies of exception::default_handler get included in the binary, thanks to the token generic parameter. It's not a lot of code, but it seems a bit unfortunate. Is there any way around this?

Thanks for all the work you do for embedded Rust, btw! <3

Activity

  1. japaric commented on Mar 19, 2017

    @japaric
    Member

    Is there any way around this?

    Not that I know of. I was hoping LLVM would merge the identical functions but it doesn't. I don't know if it's capable of doing that at all.

    cc @eddyb Do you know if LLVM should be able to optimize something like this and we are not giving it enough information or maybe it's not possible to do at all:

    extern "C" fn default_handler<T>(_: T) {
        // code that doesn't use `T` at all
    }
    
    struct Foo;
    struct Bar;
    struct Baz;
    struct Quux;
    
    #[repr(C)]
    struct Exceptions {
        foo: extern "C" fn(Foo),
        bar: extern "C" fn(Bar),
        baz: extern "C" fn(Baz),
        quux: extern "C" fn(Quux),
    }
    
    #[no_mangle]
    pub static _EXCEPTIONS: Exceptions = Exceptions {
        foo: default_handler,
        bar: default_handler,
        baz: default_handler,
        quux: default_handler,
    };

    Today, we get something like this (release + LTO):

    08000008 <_EXCEPTIONS>:
     8000008:       080003f3 0800033f 080002ef 08000439     ....?.......9...
    
    080002ee <cortex_m::exception::default_handler>:
     80003f2:       f3ef 8008       mrs     r0, MSP
     80003f6:       6941            ldr     r1, [r0, #20]
     80003f8:       f000 b863       b.w     80004c2 <cortex_m::exception::default_handler::handler>
    
    0800033e <cortex_m::exception::default_handler>:
     80003fc:       f3ef 8008       mrs     r0, MSP
     8000400:       6941            ldr     r1, [r0, #20]
     8000402:       f000 b85e       b.w     80004c2 <cortex_m::exception::default_handler::handler>
    
    080003f2 <cortex_m::exception::default_handler>:
     8000406:       f3ef 8008       mrs     r0, MSP
     800040a:       6941            ldr     r1, [r0, #20]
     800040c:       f000 b859       b.w     80004c2 <cortex_m::exception::default_handler::handler>
    
    08000438 <cortex_m::exception::default_handler>:
     8000410:       f3ef 8008       mrs     r0, MSP
     8000414:       6941            ldr     r1, [r0, #20]
     8000416:       f000 b854       b.w     80004c2 <cortex_m::exception::default_handler::handler>
    

    Ideally, we'd like something like this:

    08000008 <_EXCEPTIONS>:
     8000008:       080002ef 080002ef 080002ef 080002ef     ................
    
    080002ee <cortex_m::exception::default_handler>:
     80003f2:       f3ef 8008       mrs     r0, MSP
     80003f6:       6941            ldr     r1, [r0, #20]
     80003f8:       f000 b863       b.w     80004c2 <cortex_m::exception::default_handler::handler>
    
  2. eddyb commented on Mar 19, 2017

    @eddyb

    @japaric You need -C opt-level=3 IIRC, and even then it might be disabled.
    You can try -C passes=mergefunc to force it to run, but I'm not sure it will be placed right to do its thing.

  3. japaric commented on Mar 19, 2017

    @japaric
    Member

    Thanks for the suggestion, @eddyb. Unfortunately, it didn't work. We were already using -C opt-level=3 because of Cargo's --release flag and recompiling the world with -C passes didn't help. -C passes actually made one test program 8 bytes larger (originally, 1270 bytes in size) 😆.

  4. whitequark commented on Apr 30, 2017

    @whitequark
    Contributor

    Running a debug opt with -debug -debug-only mergefunc reveals the cause:

    _ZN8cortex_m9exception15default_handler17h022e0dbc834bf513E is to small to bother merging
    _ZN8cortex_m9exception15default_handler17h07a6735a3f66597aE is to small to bother merging
    _ZN8cortex_m9exception15default_handler17h0912171e374cbd1fE is to small to bother merging
    

    etc.

    Well, turns out it's a FIXME in LLVM:

      // Don't merge tiny functions, since it can just end up making the function
      // larger.
      // FIXME: Should still merge them if they are unnamed_addr and produce an
      // alias.
      if (NewFunction->size() == 1) {
        if (NewFunction->front().size() <= 2) {
          DEBUG(dbgs() << NewFunction->getName()
                       << " is to small to bother merging\n");
          return false;
        }
      }

    I've looked it and it seems very straightforward to fix. Take the logic from writeThunkOrAlias, check that !F->isInterposable() && !G->isInterposable(), then writeAlias(F, G);. I'll review and merge a patch if someone writes it (with a test).

  5. whitequark commented on Jun 29, 2017

    @whitequark
    Contributor
  6. japaric commented on Jul 18, 2017

    @japaric
    Member

    Another fix has been proposed in rust-embedded/cortex-m-rt#27 and rust-embedded/svd2rust#130. It uses global_asm! to define exception / interrupt handlers as weak aliases of the DEFAUT_HANDLER symbol. The fix saves 4*N + 32 bytes of Flash memory where N is the number of interrupt handlers the device has.

  7. pftbest commented on Jul 18, 2017

    @pftbest
    Contributor

    Hello, @whitequark! Any updates on your patches? I think they can also save some space on panic handlers as well.

    For each panic in a program there is a separate function that just calls the panic_fmt

    /// === Normal function that can produce 2 panics:
        ...
    0000c084 <.LBB4_6>:
        c084:       3c 40 00 c1     mov     #49408, r12     ;#0xc100
        c088:       b0 12 a0 c0     call    #49312          ;#0xc0a0
    
    0000c08c <.LBB4_7>:
        c08c:       3d 40 03 00     mov     #3,     r13     ;
        c090:       b0 12 94 c0     call    #49300          ;#0xc094
    
    /// === Two extra functions that just call panic_fmt:
    
    0000c094 <_ZN4core9panicking18panic_bounds_check17h5df2045dec8a376aE>:
        c094:       b0 12 98 c0     call    #49304          ;#0xc098
    
    0000c0a0 <_ZN4core9panicking5panic17hb78b2a90052a7056E>:
        c0a0:       b0 12 98 c0     call    #49304          ;#0xc098
    
    /// === The real panic_fmt handler:
    
    0000c098 <_ZN4core9panicking9panic_fmt17hceb300dda72a8369E>:
        c098:       32 c2           dint
        ...
    

    For example AT2XT project by @cr1901, has 5 of this functions, and that is too much.

  8. whitequark commented on Jul 18, 2017

    @whitequark
    Contributor

    @pftbest I will take another look at them soon. Half of the patch is already LGTM'd, there's very little left.

    @japaric Let's not merge this hacky workaround. I'll try to be more responsive this time.

  9. pftbest commented on Jul 18, 2017

    @pftbest
    Contributor

    But the workaround hack will give 4 bytes shorter result :)

  10. whitequark commented on Jul 18, 2017

    @whitequark
    Contributor

    @pftbest The proper solution may well benefit other functions.

  11. pftbest commented on Jul 18, 2017

    @pftbest
    Contributor

    Agreed, but using a hack for this specific case will not cancel the benefits of a proper solution.
    And since it's not a public interface, but a private implementation detail, we can always change it back if we need to.

  12. japaric commented on Jul 18, 2017

    @japaric
    Member

    I think weak aliases is the way to solve this. Semantically it is what we want to do: unless overriden a handler will be the DEFAULT_HANDLER. The need for global_asm! is a bit unfortunate but that's because of the lack of a proper aliasing mechanism in rustc (gcc and clang have such mechanism).

    Relying on LLVM to merge exactly looking function works too but requires explicitly enabling the LLVM pass -- it's not enabled by default, right? -- and may not work (well) if not compiling with optimizations; I haven't actually tested that scenario. Whereas weak aliases work regardless of the optimization settings.

    Of course, making mergefunc better is a welcome addition. It might help in other cases like in the panicking stuff that @pftbest mentioned.

  13. whitequark commented on Jul 18, 2017

    @whitequark
    Contributor

    and may not work (well) if not compiling with optimizations

    Take a look at the code generated with svd2rust that's built without optimizations.

  14. whitequark commented on Jul 18, 2017

    @whitequark
    Contributor

    requires explicitly enabling the LLVM pass -- it's not enabled by default, right?

    I suspect that it can actually produce better results if enabled in rustc in general, given how many functions may well monomorphize to the same thing. For example, every single time you have phantom types.

  15. 3 remaining items

  16. japaric commented on Jul 20, 2017

    @japaric
    Member

    @therealprof I can't land these patches until rust-lang/rust#43313 makes it way into the nightly channel. Otherwise we'll hit LLVM assertions.

  17. eddyb commented on Jul 20, 2017

    @eddyb

    @japaric I'm pretty sure LLVM just has a list of passes and you can add as many copies of anything as you'd like (some might break because the IR isn't what they expect but that's it).

  18. japaric commented on Jul 22, 2017

    @japaric
    Member

    The global_asm! fix has landed and it's available in cortex-m-rt v0.3.5 and svd2rust v0.11.2. Note that you'll need to nightly-2017-07-21 or newer if targetting the Cortex-M architecture (or you'll hit an LLVM assertion). If these changes cause problems in the future we can always revert them as they are not part of the public / user-facing API.

  19. pftbest commented on Aug 22, 2017

    @pftbest
    Contributor

    Hello, @whitequark . Any news on your patches?

  20. whitequark commented on Aug 22, 2017

    @whitequark
    Contributor

    @pftbest Yes, I got some of them merged upstream, but the others seem to fail tests in weird ways. I have this on my TODO list and will return to it shortly.

  21. pftbest commented on Aug 22, 2017

    @pftbest
    Contributor

    @whitequark that is great, thank you! global_asm hack didn't work that well, and we still have an extra function in binary, so it's not saving us any space compared to your solution.

  22. whitequark commented on Aug 22, 2017

    @whitequark
    Contributor

    @japaric in light of above: reopen this?

  23. pftbest commented on Aug 22, 2017

    @pftbest
    Contributor

    Well, the issue is fixed for now, the question is do we want to remove dependency on global_asm (and go back to naked functions, which is also not ideal, but already used for other things) when @whitequark 's patches will be merged.

  24. japaric commented on Aug 24, 2017

    @japaric
    Member

    I don't think we should reopen this issue as the problem is already 99% fixed and changing the underlying solution will at most reduce binary size by 4 or 8 bytes (I forget the exact number).

    However, in general, I'm 👍 on reducing the number of unstable features this crate, and the whole cortex-m stack, uses so we can open an issue about that (reducing unstable features) and list the global_asm -> naked_function switch there.

  25. whitequark commented on Oct 15, 2017

    @whitequark
    Contributor

    @pftbest All done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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