Image for post
Image for post
Photo by Markus Spiske from Pexels

Go Tools: The Compiler — Part 1 Assembly Language and Go

Martin Ombura Jr.
May 10, 2019 · 6 min read

The Go compiler is at the heart of Go’s build process, taking code and generating executables from that code. Go’s compiler is available for those interested to tinker with it using the go tool compiler command.

Assembly and the Assembler

Assembly is programming language meant to be understood by humans, and is often characterized as a low-level programming language. In some languages, the compiler generates assembly language. In most cases Assembly is the penultimate step in the hierarchy of programming abstractions before you know how to speak to the machine.

Assembly cannot be directly executed as machine code or by the host machine, and it needs an assembler (which is just another program) to be able to convert it into machine code. Assembly differs from machine code in that, assembly does not contain binaries, it cannot be directly executed by machine code and assembly is meant to be “human readable”. Typically as you ascend the hierarchy of programming abstractions. e.g. from high-level programming languages, to low-level ones, more emphasis is placed on interfacing with the actual characteristics of the architecture you’re running on. This arguably makes assembler highly relevant when pursuing performance or accessing low-level hardware functionality such as in embedded systems.

Assembler gives access to the runtime which allows for functionality such as context switching and better access to the stack, that allows for efficient communication of data in primitives such as channels. One interesting use case is the math/big package in the Go standard library.

How Assembly is used in Go

In a talk given by Rob Pike, he walked through some of the compilers architectural changes from early versions of the Go language, to modern day implementation. The figure shown below (Figure 1), showcases a variety steps compilers can take to transform code to linked programs.

Image for post
Image for post
Figure 1: Old representation of Golang compiler

The top row, is the canonical way programs that use assembly compile code. Code is compiled into assembly, and that assembly code is then linked. gcc is an example of a compiler that does this. The red dotted line describes binary representation of pseudo instructions that are generated at that point. The subsequent rows represent how Plan9 architectures go about creating executable binaries from code.

As of Go 1.3, in a bid to rid the Go standard library (STL) of C code, the Go language designers opted for a trade off that would sacrifice compile speeds, for faster builds.

Image for post
Image for post

In the newer architecture, the bottom two rows (Go’s compilation process), the compiler encompasses what the traditional compiler would be doing (shown in the first row), as well as the role of the assembler. This is important, as now the compilation phase not only handles both the high-level code (golang) and the generation of assembly, it generates real instructions in the form of an intermediate representation known as obj. obj generates real instructions for the linker and is also agnostic to the type of assembler.

Where does Golang fit in all this?

Assembly is included in a small set of go packages, some of these are runtime, syscall,math, crypto and reflect

The math and math/big package are one of the few packages in the standard library that actually has assembly code that your program calls into in order to achieve better performance with regards to the calculation of highly computational constructs such as big numbers i.e. numbers greater than int64 i.e 9,223,372,036,854,775,807 or less than -9,223,372,036,854,775,807.

In the words of Rob Pike, sometimes using assembler allows you to come up with something better than what the compiler could come up with on its own. math/big also has assembly functions for arithmetic operations on vectors that are more efficient to compute in assembly than in Go or C. Some trigonometrical polynomial coefficients and certain constants are referenced and computed in assembly such as pi, the computation of arccos, arcsin, arctan as well as other hyperbolic trignometric functions e.g. sinh, cosh, tanh etc.

As hardware evolves and becomes more capable of computing cryptographic keys very quickly, the software needs to catch up to this. The designers of Go thought it would be better to include Assembly code for some of the crypto functionality (as it can be hardware dependent, especially when trying to achieve fast cryptography), and not have to include it as actual Go code in the STL. Here are some examples listed below:

  1. The crypto/aes package contains assembly code for various CPU architectures to optimize the encryption of certain blocks.
  2. The crypto/elliptic package contains assembly code for the computation of fast prime field elliptic curves that contain 256-bit primes.
  3. The crypto/md5 package has assembly for different architectures for the computation of various md5 hashes.
  4. The crypto/sha256 & crypto/sha512package uses assembly for the optimization of its SHA256 hashes.

The reflect package is Go’s go to for functionality that involves the use of reflection. Reflection is best defined by this answer from StackOverflow

The ability to inspect the code in the system and see object types is not reflection, but rather Type Introspection. Reflection is then the ability to make modifications at runtime by making use of introspection. The distinction is necessary here as some languages support introspection, but do not support reflection. One such example is C++

Reflection can have the tendency to be slow, or computationally expensive. Some low-level implementations done in assembly can speed it up.

The cgo tool that enables the creation of C code from Go. Some of the cross calling that happens between both languages can be optimized by the use of assembly. One technique is to efficiently reuse registers between callee and caller programs saving computation and memory usage. Some assembly optimizations also can be done for the gcc compiler. In other cases Go’s take on assembly is simply used to standardize the calling between code on all different CPU platforms.

Some low-level synchronization primitives are handled by Assembler. This is shown both the runtime/atomic and sync/atomic packages.

It would make sense that calls to the kernel are optimized through the use of Assembly. In Linux based GOOS, the actual Syscall command itself is performed in Assembly. Even retrieving core OS information such as time of day, is done in assembly. Perhaps there is value in optimizing that function as a lot of tools require access to GOOS time regularly and often.

Generating Assembly in Go

With all this talk of Assembly in Go, let’s actually generate some Assembly from Go code, using the go tools. So we’ll write an extremely simple Hello World application. I’m keeping it simple because the output tends to be very verbose.

// main.go
package main
func main()
print("Hello world")
// Generates obj file as main.o
go tool compile main.go

Generates an obj file (that we speak about earlier). It is simply binary, but you can inspect it if you are curious.

// Generates assembly, and sends it to a new main.asm
go tool compile -S main.go > main.asm

See link for output

This will send the output generated by the -S flag into a main.asm file so you can inspect it. Note how verbose the output is, as well as all the underlying instructions.

To see the full contents of the generated files, check out this GitHub repo.


Assembly is important, it has been around for a long time, however as the need for performance and higher access to low-level hardware arises, it’s value is inexolerable.

If you have never seen Assembly language, here’s a code snippet from one of my 2nd Year Computer Science assignments. This code is written for MIPS architecture and checks to see if a given text is a palindrome (i.e is the same when read forward and in reverse). Note that Assembly varies based on the CPU architecture

Additional Links

Martin Ombura Jr.

An assortment of all my articles and research in various…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store