Skip to content

Instantly share code, notes, and snippets.

@yalue
Last active July 30, 2025 18:59
Show Gist options
  • Select an option

  • Save yalue/52c602521e60248eb1e4bac20fcad19f to your computer and use it in GitHub Desktop.

Select an option

Save yalue/52c602521e60248eb1e4bac20fcad19f to your computer and use it in GitHub Desktop.
Mandelbrot set printed to stdout on Linux in ARM32 assembly
// Assemble using:
// arm-linux-gnueabihf-as -mfpu=neon -o mandelbrot.o mandelbrot.S
// arm-linux-gnueabihf-ld -o mandelbrot mandelbrot.o
.syntax unified
.section .data
message:
canvas_width = 40
canvas_height = 30
max_iters = 100
delta_col:
.float 0.1
delta_row:
.float 0.1333333333
.section .bss
buffer: .space 8
.section .text
// Prints a char held in r0 to stdout without clobbering any registers.
put_char:
push {r0-r2, r7, lr}
ldr r1, =buffer
strb r0, [r1]
mov r0, 1
mov r2, 1
mov r7, 4
swi 0
pop {r0-r2, r7, pc}
// Expects s0 and s1 to be the real and imaginary points on the canvas. Prints
// a '#' if the point escapes, and a '.' if it's in the set.
iterate_mandelbrot:
push {r0, lr}
mov r0, max_iters
// s9-s15 are caller-preserved, so we'll clobber them (though we only use
// s10-s14).
// s10 = current_x, x11 = current_y
vmov.f32 s10, s0
vmov.f32 s11, s1
// We compare against 4.0 every loop.
vmov.f32 s12, 4.0
m_loop:
subs r0, 1
beq m_in_set
// s13 = x * x, s14 = y * y
vmul.f32 s13, s10, s10
vmul.f32 s14, s11, s11
// s11 = 2 * x * y
vmul.f32 s11, s10, s11
vadd.f32 s11, s11, s11
// s10 = (x * x) - (y * y)
vsub.f32 s10, s13, s14
// Add x_0 and y_0
vadd.f32 s10, s10, s0
vadd.f32 s11, s11, s1
// Compute s13 = (x * x) + (y * y)
vadd.f32 s13, s13, s14
// Compare with the escape value
vcmpe.f32 s13, s12
vmrs APSR_nzcv, fpscr
blt m_loop
mov r0, '#'
bl put_char
b m_done
m_in_set:
mov r0, '.'
bl put_char
m_done:
pop {r0, pc}
.global _start
_start:
// Initialize coordinates on the canvas to -2.0, -2.0
vmov.f32 s4, 2.0
vneg.f32 s4, s4
vmov.f32 s0, s4
vmov.f32 s1, s4
// Load the deltas between characters on the canvas.
ldr r0, =delta_col
vldr.f32 s2, [r0]
ldr r0, =delta_row
vldr.f32 s3, [r0]
// Iterate the mandelbrot set. r1 = current col, r2 = current row.
mov r2, canvas_height
row_loop:
mov r1, canvas_width
col_loop:
// s0 and s1 are already correct, so just call iterate_mandelbrot
bl iterate_mandelbrot
// Update the col position, and move to the next row if we're over the
// canvas width in chars.
vadd.f32 s0, s0, s2
subs r1, 1
bne col_loop
// A row is done, print a newline
mov r0, '\n'
bl put_char
// Update the row position and index. Finish looping if we're over the
// canvas height in chars.
vadd.f32 s1, s1, s3
vmov.f32 s0, s4
subs r2, 1
bne row_loop
// Exit syscall
mov r0, 0
mov r7, 1
swi 0
@yalue
Copy link
Author

yalue commented Jul 30, 2025

Line 62: technically we're checking (x^2 + y^2) from the previous iteration rather than the current iteration. But it's so nice and simple to reuse the existing x^2 and y^2 in s13 and s14, and it won't break anything logically. (Just wastes an additional iteration.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment