Intermediate ASM code

Is it practical, or even possible, to generate ASM code from the C source ? If not, where is the hex file stored ? I have a very short sketch, which I believe is well within the capabilities of the processor but it gets confused when I run the timer/s at full speed.
I dread the thought of having to use a Windows machine to debug. I use Mint.

Thanks in advance.

Someone will tell you. My question is, what use will the ASM code be for solving that problem?

If you generate verbose output during compilation, a listing file is stored in some temp directory, which contains the assembly code along with the C/C++. I agree with others that this won't help.

About the only way to debug Arduino code is to sprinkle print statements around, to see if variables are what you expect.

On the other hand, if you post your code properly, using code tags, someone may spot the mistakes.

Been there and done that and quickly dropped the idea.

Back about 1989, I had a plan to write code for an IBM PC to communicate with a Unisys Check REader/Sorter. It used IBM bi-sync transparent protocol at 19.2 KB/sec. My plan was to write the C code and get the assembly output and modify it for the speed I needed, just like you.

After looking at the output assembly code, I quickly decided to just use the C code. The Assembly made it's own labels, it's own subroutines and function calls. Rather hard to follow. Eventually the C code ran fast and was not a problem.

If you have to gain a little speed, unroll all your "while", "for" and any other types of loops. Sure it will be a bit more memory, but will run faster.

Paul

Just remembered more. IF any multiply or divide is necessary, use shift, add and subtract instead.

Here's how I do it:

  • Go to File->Preferences and check the boxes for Show verbose output during: both compilation and upload
  • Build the project
  • Build it again. This is to minimize the number of lines I have to look through to find what I want.
  • Scroll up until I find the line that says, "Compiling sketch..."
  • Select and copy to scratchpad the entire next line.
  • Open a command line terminal and cd into the sketch directory. On my machine it looks something like /tmp/arduino_build_535744/sketch. If you have problems finding it, the line you just copied into the scratchpad has it in there somewhere.
  • Paste the contents of the scratchpad into the command line and run the command. It should compile the sketch again.
  • Hit the up arrow and start using the left arrow. This command line has to be modified in three ways.
  • I change the output file name from .ino.cpp.o to .ino.cpp.txt. You can do .S if you want, but then the next time you build the project it will assemble that code and try to link it in.
  • I add the -S option right after the -c option. This instructs the compiler to create Assembly code.
  • Delete the -flto option. Otherwise the generated code is not human readable. Some variants don't have this, and some earlier versions of the IDE don't either. But I'm using 1.8.5 and it does.

It will create a text file in the same directory. The .hex file is also in that directory.

Is it practical, or even possible, to generate ASM code from the C source ?

It is possible.

If not, where is the hex file stored ?

It's stored in a mysteriously named temp directory that is supplied by some combination of java and the OS. The best way to find it is to run on verbose compilation and look at the log file. It will be something like:

/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876

In there will be a .elf file, which is the object code + symbol information + debugging information, and the best way to look at the assembler is to disassembly that binary with a command like "avr-objdump -SC *.elf" (avr-objdump is inside the arduino in the same directory with avr-gcc and a bunch of other tools.) This way you get link-time optimized code that has been properly relocated to final addresses.

Since the compilation is using link-time-optimization, it is no longer useful to use objdump on individual .o files (unless you recompile them without the -lto options.) Even then, all the jump targets are "unresolved" and show as 0, which makes it difficult to interpret.

Once you've done the "verbose" output from the IDE, you can also copy and modify individual file compiles to produce "intermediate" assembler files. But they're really ugly - they include all the assembler directives to produce the debugging info as well as the actual code.

Here are some examples, using Blink...

Partial cut&paste from verbose compiler output:

Generating function prototypes...
"/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -flto -w -x c++ -E -CC -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/variants/standard" "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/sketch/Blink.ino.cpp" -o "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/preproc/ctags_target_for_gcc_minus_e.cpp"
"/Applications/arduino/Arduino-1.8.5.app/Contents/Java/tools-builder/ctags/5.8-arduino11/ctags" -u --language-force=c++ -f - --c++-kinds=svpf --fields=KSTtzns --line-directives "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/preproc/ctags_target_for_gcc_minus_e.cpp"
Compiling sketch...
"/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/variants/standard" "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/sketch/Blink.ino.cpp" -o "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/sketch/Blink.ino.cpp.o"
Compiling libraries...
Compiling core...
"/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/tools/avr/bin/avr-gcc" -c -g -x assembler-with-cpp -flto -MMD -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/variants/standard" "/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/cores/arduino/wiring_pulse.S" -o "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/core/wiring_pulse.S.o"

Here's the extracted compile command for the user sketch file itself, with some of the changeable options highlighted.

"/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -c -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -flto -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/variants/standard" "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/sketch/Blink.ino.cpp" -o "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/sketch/Blink.ino.cpp.o"

It's nicely "fully specified", so you can paste it into some command-line window (at least on Mac/Linux) without having to set up anything else.
You can change the "-c" (means "compile only") to "-S" (generate assembly), get rid of "-flto" and give it a different output file:

"/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/tools/avr/bin/avr-g++" -S -g -Os -w -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threadsafe-statics -MMD -mmcu=atmega328p -DF_CPU=16000000L -DARDUINO=10805 -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/cores/arduino" "-I/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/variants/standard" "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_build_821876/sketch/Blink.ino.cpp" -o foo.s

And you get about 1000 lines of foo.s that includes the "intermediate" assembler:

        .file   "Blink.ino.cpp"
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__SREG__ = 0x3f
__tmp_reg__ = 0
__zero_reg__ = 1
        .text
.Ltext0:
        .cfi_sections   .debug_frame
        .section        .text.setup,"ax",@progbits
.global setup
        .type   setup, @function
setup:
.LFB112:
        .file 1 "/var/folders/jz/5yb8f2hr8xjcpf0059bsfz4r0000gn/T/arduino_modified_sketch_211523/Blink.ino"
        .loc 1 27 0
        .cfi_startproc
        push r28
.LCFI0:
        .cfi_def_cfa_offset 3
        .cfi_offset 28, -2
        push r29
.LCFI1:
        .cfi_def_cfa_offset 4
        .cfi_offset 29, -3
/* prologue: function */
/* frame size = 0 */
/* stack size = 2 */
.L__stack_usage = 2
.LVL0:
.LBB5:
.LBB6:
.LBB7:
        .file 2 "/Applications/arduino/Arduino-1.8.5.app/Contents/Java/hardware/arduino/avr/cores/arduino/HardwareSerial.h"
        .loc 2 121 0
        ldi r18,lo8(6)
        ldi r20,0
        ldi r21,lo8(-62)
        ldi r22,lo8(1)
        ldi r23,0
        ldi r24,lo8(Serial)
        ldi r25,hi8(Serial)
        call _ZN14HardwareSerial5beginEmh
.LVL1:
.LBE7:
.LBE6:
        .loc 1 31 0
        ldi r22,lo8(1)
        ldi r24,lo8(13)
        call pinMode

I dread the thought of having to use a Windows machine to debug. I use Mint.

Why would this involve Windows at all?

The "obvious" heavy-duty "debug and look at object code" toolset is "Atmel Studio", which runs only on Windows.

If you generate verbose output during compilation, a listing file is stored in some temp directory,

No, it's not. Note that by the time lto is through with your sketch, it's probably inlined a lot of called-one-time functions like "loop" and "setup", making the output a bit difficult to interpret.
As others have implied while not answering your question, you are unlikely to achieve great speedups by using assembler rather than C. However, I do find that it can be useful to look at the assembly code to see what the compiler is doing with "strange" IO manipulations and such.

I have been very impressed with the efficiency of the code that the avr g++ compiler generates. However, that's not the main reason I look at assembly code. Sometimes when I'm debugging and something isn't doing what I expect, looking at the assembly code can give some insight and spark some ideas for further debugging. Sometimes I find ways to improve my code. For example just today I was debugging something un-related and I found that the sdcc compiler for STM8 compiles #defines different than const bytes for pin assignments. I prefer to use const byte =, but the code is bigger and slower. So from now on when using those boards I will use #defines instead.

When you're using an API that someone else wrote (and you're ALWAYS using some API that someone else wrote) sometimes it helps to go look at exactly how things are being done. That's one of the reasons I love working with Arduino code -- in general the APIs are open source and I can go take a deeper look when I need to.

Over in another thread, I just noticed that something like:

struct {
volatile uint8_t a, b, c;
volatile uint16_t d, e, f;
volatile int32_t g, h;
} x;

void setup() {
  x.a = x.b = x.c  = digitalRead(1);
  x.d = x.e = x.f = digitalRead(1);
  x.g = x.h =  digitalRead(1);

produces pretty significantly better code than having separate globals for each of the variables, for ARM...