Backend Development 8 min read

How Go Compiles Range Loops into Assembly: A Deep Dive

This article examines how the Go compiler translates range loops into low‑level assembly instructions, walks through a concrete example with code and assembly output, explains each instruction’s role, and highlights recent compiler improvements that enhance safety and scheduler interaction.

360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
360 Zhihui Cloud Developer
How Go Compiles Range Loops into Assembly: A Deep Dive

The article shares an analysis of Golang range loops at the assembly level, showing how the compiler transforms high‑level loop constructs into machine instructions.

1 Loop Assembly

Range loops can iterate over arrays, slices, or channels. The following Go program sums the elements of a slice:

<code>func main() {
   l := []int{9, 45, 23, 67, 78}
   t := 0
   for _, v := range l {
      t += v
   }
   println(t)
}</code>

Running go tool compile -S main.go dumps the generated assembly. A fragment of the output is:

<code>0x0041 00065 (main.go:4)   XORL   AX, AX
0x0043 00067 (main.go:4)   XORL   CX, CX
0x0045 00069 (main.go:7)   JMP    82
0x0047 00071 (main.go:7)   MOVQ   ""..autotmp_5+16(SP)(AX*8), DX
0x004c 00076 (main.go:7)   INCQ   AX
0x004f 00079 (main.go:8)   ADDQ   DX, CX
0x0052 00082 (main.go:7)   CMPQ   AX, $5
0x0056 00086 (main.go:7)   JLT    71
0x0058 00088 (main.go:11)  MOVQ   CX, "".t+8(SP)</code>

The first two instructions initialize registers AX and CX to zero. AX holds the loop index, while CX accumulates the sum (variable t ).

The JMP 82 instruction starts the loop, jumping to the instruction at address 82. The jump target is highlighted in the following diagram:

The CMPQ AX, $5 instruction compares the index register with the slice length (5). If the result is less than zero, the JLT 71 instruction jumps back to continue the loop.

The loop body consists of three key instructions:

MOVQ ""..autotmp_5+16(SP)(AX*8), DX loads the current slice element into DX . The address is computed from the stack pointer (SP) plus an offset based on the index.

INCQ AX increments the index register.

ADDQ DX, CX adds the loaded value to the accumulator CX .

When the index reaches 5, the loop exits and the final MOVQ CX, "".t+8(SP) stores the accumulated sum back to the variable t . The final state is illustrated below:

2 Improvements

Prior to Go 1.10, the compiler generated loops that used unsafe pointers, which could advance past the slice’s end and hinder pre‑emptive scheduling. The older implementation looked like this:

<code>func main() {
   l := []int{9, 45, 23, 67, 78}
   t := 0
   i := 0
   var tmp int
   p := uintptr(unsafe.Pointer(&l[0]))
   if i >= 5 { goto end }
 body:
   tmp = *(*int)(unsafe.Pointer(p))
   p += unsafe.Sizeof(l[0])
   i++
   t += tmp
   if i < 5 { goto body }
 end:
   println(t)
}</code>

This approach could create pointers that point beyond the allocated memory, making the loop unsafe and difficult to pre‑empt. Modern Go compilers avoid such past pointers, improving safety and scheduler cooperation.

The current compilation strategy can be expressed in Go as:

<code>func main() {
   l := []int{9, 45, 23, 67, 78}
   t := 0
   i := 0
   var tmp int
   goto end
 start:
   tmp = l[i]
   i++
   t += tmp
 end:
   if i < 5 { goto start }
   println(t)
}</code>

Generating assembly for this revised program yields the same output as the earlier version, but with safer pointer handling.

Compilerbackend developmentGoassemblyLoop
360 Zhihui Cloud Developer
Written by

360 Zhihui Cloud Developer

360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.