ARM integrated assembler generates incorrect nop opcode when switching from arm to thumb mode

I am seeing a problem with the way nops are emitted in the integrated
assembler for ARM. When switching from arm to thumb mode in an assembly file
we still emit the arm nop opcode. Look at this small example:

$ cat align.s
.syntax unified
.code 16
foo:
  add r0, r0
.align 3
  add r0, r0

$ llvm-mc -triple armv7-none-linux align.s -filetype=obj -o t.o &&
llvm-objdump -triple thumbv7 -d t.o
t.o: file format ELF32-arm

Disassembly of section .text:
foo:
       0: 00 44 add r0,
r0
       2: 00 f0 20 e3 blx
#4195904
       6: 00 00 movs r0,
r0
       8: 00 44 add r0,
r0

This shows that we have actually emitted an arm nop (e320f000) instead of a
thumb nop. Unfortunately, this encodes to a thumb branch which causes bad
things to happen when compiling assembly code with align directives.

The ARMAsmBackend class is responsible for emitting these nops. It keeps
track of whether it should emit arm or thumb nop. The first problem is that
MCElfStreamer does not pass on the `.code 16` directive to the ARMAsmBackend
class (using handleAssemblerFlag). In the example above we start assembling
in arm mode (because of the -triple) and so the ARMAsmBackend always thinks
we are in arm mode and it emits the wrong opcode.

We actually can assemble this example correctly for darwin because the
MCMachOStreamer does pass on the directives. It looks like we need to modify
the MCElfStreamer to pass the assembler directives down to the ARMAsmBackend
to match the behavior of the MCMachOStreamer.

Unfortunately, this change will not solve the full problem, even though the
integrated assembler works correctly for MachO in this example:

$ llvm-mc -triple armv7-apple-darwin align.s -filetype=obj -o t.o &&
llvm-objdump -triple thumbv7 -d t.o
t.o: file format Mach-O arm

Disassembly of section __TEXT,__text:
foo:
       0: 00 44 add r0,
r0
       2: 00 bf nop
       4: 00 bf nop
       6: 00 bf nop
       8: 00 44 add r0,
r0

The problem is that the nops are written after the assembly is complete when
it writes the MCAlignFragment to the output. The ARMAsmBackend writes nops
using the last mode it knew about. So it can write bad nop data if the nops
are in a location that is a mode that does not match the bit stored in the
backend. We can see the problem by simply adding a `.code 32` directive to
the end of the example:

$ echo ".code 32" >> align.s
$ llvm-mc -triple armv7-apple-darwin align.s -filetype=obj -o t.o &&
llvm-objdump -triple thumbv7 -d t.o
t.o: file format Mach-O arm

Disassembly of section __TEXT,__text:
foo:
       0: 00 44 add r0,
r0
       2: 00 f0 20 e3 blx
#4195904
       6: 00 00 movs r0,
r0
       8: 00 44 add r0,
r0

It seems that the MCAlignFragment needs to know if it is aligning in thumb
mode or in arm mode. How should we solve this problem? Should we store the
current mode in the fragment when assembling the file and use that mode when
writing nop data?

-- Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted
by The Linux Foundation