Bazel & Introduction: Part Three – Cross Compiling

The modern world does a very good job of hiding a fundamental fact of our modern, processor-based world: every computer runs a fundamentally different set of binary instructions. Both the flexibility of modern operating systems, and the inability of the common luser to deal with any real computer issues means that this challenge is hidden deliberately. No one wants to have to think about which processor is inside their computer.

In this post, we’re going to think about it. Most consumer-facing electronics run a unique set of processors for the applications they run. Let me go into just a little bit of detail through examples.

μarch

First, though, let me explain what’s actually different. The difference between these processors occurs in what’s known as the ‘microarchitecture,’ which is abbreviated as ‘uarch’ or ‘μarch.’ Basically, it’s how the processor manufacturer decided to implement instructions on their processor. If you recall in post 2, I showed you some byte code (machine code). That tells parts of the processor to do different things. Each instruction means something specific to the processor that we’re on. That code that I gave as an example (which is complete gibberish) would only work on one processor (if it actually meant anything).

Everyday examples
  • Android: Android devices run a variety of armeabi, x86, x86-64, or mips architectures. On Android devices, however, the device producer has already compiled the Java Virtual Machine, which is (supposed to be) fully cross-platform and allows your code to run on any device. Everything seems the same.

  • PC: Most PCs run Intel processors or AMD, which are actually both x86-64, and thus you don’t need to worry about which you have. Tada, difference disappears.

  • iOS devices: Apple has one device, so it seems the same.

  • Everything else: Usually, when you compile C++ code, you compile it on the hardware you’re targeting, so you don’t have to worry about the different architectures. If you build code on armhf, it works on armhf.

“Everything Else”

On the “bright side,” I can think of multiple things in FRC which don’t fall into these categories. Yes, I did say “everything else”. I lied. You’ll notice that my “everything else” category refers to compiling a project on the target. Well, from part one we know that the FIRST-provided setup (using ant) builds code on the computer, and then deploys it to the robot. This is also how you build things for the NVIDIA Tegra K1 and Tegra X1, both of which we use. As a result, our code needs to work on three (or four?) chipsets:

  1. Whatever your computer is
  2. armhf (for the ARM Cortex-A15 Processor)
  3. The Xilinux Znyq chipset in the RoboRIO (not really sure what this uses) which we will refer to as ‘roborio’ because the compiler package is called ‘arm-frc-linux-gnueabi, and that’s pretty meaningless.
  4. ??? (for the A57 processor in the Tegra X1. Not sure if I need to do this separately from the A15)
Byte Code!

Don’t believe me? Let’s have an example.

The first column is the memory address, the next 8 columns are 8 bytes of code, and the last column is the string-ified version, where those which can be represented as ASCII characters are parsed as such. Google ‘hexadecimal’ if you have no idea what’s going on.

Here are the first 20 lines of hex from my test binary compiled for roborio:

0000000: 7f45 4c46 0101 0103 0000 0000 0000 0000  .ELF............
0000010: 0300 2800 0100 0000 708d 0000 3400 0000  ..(.....p...4...
0000020: 88bd 2e00 0202 0005 3400 2000 0a00 2800  ........4. ...(.
0000030: 2c00 2900 0100 0070 7c69 0600 7c69 0600  ,.)....p|i..|i..
0000040: 7c69 0600 5843 0000 5843 0000 0400 0000  |i..XC..XC......
0000050: 0400 0000 0600 0000 3400 0000 3400 0000  ........4...4...
0000060: 3400 0000 4001 0000 4001 0000 0500 0000  4...@...@.......
0000070: 0400 0000 0300 0000 7401 0000 7401 0000  ........t...t...
0000080: 7401 0000 1300 0000 1300 0000 0400 0000  t...............
0000090: 0100 0000 0100 0000 0000 0000 0000 0000  ................
00000a0: 0000 0000 d8ac 0600 d8ac 0600 0500 0000  ................
00000b0: 0000 0100 0100 0000 f0b2 0600 f0b2 0700  ................
00000c0: f0b2 0700 3c0e 0000 d0bc 0100 0600 0000  ....<...........
00000d0: 0000 0100 0200 0000 58b8 0600 58b8 0700  ........X...X...
00000e0: 58b8 0700 3001 0000 3001 0000 0600 0000  X...0...0.......
00000f0: 0400 0000 0400 0000 8801 0000 8801 0000  ................
0000100: 8801 0000 4000 0000 4000 0000 0400 0000  ....@...@.......
0000110: 0400 0000 0700 0000 f0b2 0600 f0b2 0700  ................
0000120: f0b2 0700 0000 0000 0c00 0000 0400 0000  ................
0000130: 0400 0000 51e5 7464 0000 0000 0000 0000  ....Q.td........

And here are the first 20 lines of hex from my test binary for my chromebook:

0000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
0000010: 0300 3e00 0100 0000 e099 0000 0000 0000  ..>.............
0000020: 4000 0000 0000 0000 5015 2700 0000 0000  @.......P.'.....
0000030: 0000 0000 4000 3800 0a00 4000 2e00 2d00  ....@.8...@...-.
0000040: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
0000050: 4000 0000 0000 0000 4000 0000 0000 0000  @.......@.......
0000060: 3002 0000 0000 0000 3002 0000 0000 0000  0.......0.......
0000070: 0800 0000 0000 0000 0300 0000 0400 0000  ................
0000080: 7002 0000 0000 0000 7002 0000 0000 0000  p.......p.......
0000090: 7002 0000 0000 0000 1c00 0000 0000 0000  p...............
00000a0: 1c00 0000 0000 0000 0100 0000 0000 0000  ................
00000b0: 0100 0000 0500 0000 0000 0000 0000 0000  ................
00000c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000d0: a0e7 0700 0000 0000 a0e7 0700 0000 0000  ................
00000e0: 0010 0000 0000 0000 0100 0000 0600 0000  ................
00000f0: d0f5 0700 0000 0000 d005 0800 0000 0000  ................
0000100: d005 0800 0000 0000 481c 0000 0000 0000  ........H.......
0000110: d0b0 0200 0000 0000 0010 0000 0000 0000  ................
0000120: 0200 0000 0600 0000 a800 0800 0000 0000  ................
0000130: a810 0800 0000 0000 a810 0800 0000 0000  ................

As you can see, they’re completely different, and there’s no way one would run on the other. In fact, they’re radically different even though the first 160 bytes are pretty boring. Clearly, there’s no reason/way one would run on the other system, and in fact, they do not.

Why is this relevant?

This is relevant because this is one of the reasons we’re using Bazel. It lets us define a bunch of different targets to compile for, and then allows us to switch between them with a simple --cpu=roborio. It’s not the biggest reason, but it’s good to have.


As always, email me if you have any questions or want elaboration. The next post will either be on the advantages of a monorepo, or the actually running an executable file. Probably the former, because if I go too deep into the bowels of computers I’ll end up stuck there.

Again, I’m always available via email.

edit: next post is available here