Last active
July 30, 2025 18:59
-
-
Save yalue/52c602521e60248eb1e4bac20fcad19f to your computer and use it in GitHub Desktop.
Mandelbrot set printed to stdout on Linux in ARM32 assembly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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 |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.)