https://github.com/akkartik/mu/blob/main/mandelbrot-fixed.mu
  1 # Mandelbrot set using fixed-point numbers.
  2 #
  3 # Install:
  4 #   $ git clone https://github.com/akkartik/mu
  5 #   $ cd mu
  6 # Build on Linux:
  7 #   $ ./translate mandelbrot-fixed.mu
  8 # Build on other platforms (slow):
  9 #   $ ./translate_emulated mandelbrot-fixed.mu
 10 # Run:
 11 #   $ qemu-system-i386 code.img
 12 
 13 fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
 14   mandelbrot screen
 15 }
 16 
 17 # Since they still look like int types, we'll append a '-f' suffix to variable
 18 # names to designate fixed-point numbers.
 19 
 20 fn int-to-fixed in: int -> _/eax: int {
 21   var result-f/eax: int <- copy in
 22   result-f <- shift-left 8/fixed-precision
 23   {
 24     break-if-not-overflow
 25     abort "int-to-fixed: overflow"
 26   }
 27   return result-f
 28 }
 29 
 30 fn fixed-to-int in-f: int -> _/eax: int {
 31   var result/eax: int <- copy in-f
 32   result <- shift-right-signed 8/fixed-precision
 33   return result
 34 }
 35 
 36 # The process of throwing bits away always adjusts a number towards -infinity.
 37 fn test-fixed-conversion {
 38   # 0
 39   var f/eax: int <- int-to-fixed 0
 40   var result/eax: int <- fixed-to-int f
 41   check-ints-equal result, 0, "F - test-fixed-conversion - 0"
 42   # 1
 43   var f/eax: int <- int-to-fixed 1
 44   var result/eax: int <- fixed-to-int f
 45   check-ints-equal result, 1, "F - test-fixed-conversion - 1"
 46   # -1
 47   var f/eax: int <- int-to-fixed -1
 48   var result/eax: int <- fixed-to-int f
 49   check-ints-equal result, -1, "F - test-fixed-conversion - -1"
 50   # 0.5 = 1/2
 51   var f/eax: int <- int-to-fixed 1
 52   f <- shift-right-signed 1
 53   var result/eax: int <- fixed-to-int f
 54   check-ints-equal result, 0, "F - test-fixed-conversion - 0.5"
 55   # -0.5 = -1/2
 56   var f/eax: int <- int-to-fixed -1
 57   f <- shift-right-signed 1
 58   var result/eax: int <- fixed-to-int f
 59   check-ints-equal result, -1, "F - test-fixed-conversion - -0.5"
 60   # 1.5 = 3/2
 61   var f/eax: int <- int-to-fixed 3
 62   f <- shift-right-signed 1
 63   var result/eax: int <- fixed-to-int f
 64   check-ints-equal result, 1, "F - test-fixed-conversion - 1.5"
 65   # -1.5 = -3/2
 66   var f/eax: int <- int-to-fixed -3
 67   f <- shift-right-signed 1
 68   var result/eax: int <- fixed-to-int f
 69   check-ints-equal result, -2, "F - test-fixed-conversion - -1.5"
 70   # 1.25 = 5/4
 71   var f/eax: int <- int-to-fixed 5
 72   f <- shift-right-signed 2
 73   var result/eax: int <- fixed-to-int f
 74   check-ints-equal result, 1, "F - test-fixed-conversion - 1.25"
 75   # -1.25 = -5/4
 76   var f/eax: int <- int-to-fixed -5
 77   f <- shift-right-signed 2
 78   var result/eax: int <- fixed-to-int f
 79   check-ints-equal result, -2, "F - test-fixed-conversion - -1.25"
 80 }
 81 
 82 # special routines for multiplying and dividing fixed-point numbers
 83 
 84 fn multiply-fixed a-f: int, b-f: int -> _/eax: int {
 85   var result/eax: int <- copy a-f
 86   result <- multiply b-f
 87   {
 88     break-if-not-overflow
 89     abort "multiply-fixed: overflow"
 90   }
 91   result <- shift-right-signed 8/fixed-precision
 92   return result
 93 }
 94 
 95 fn divide-fixed a-f: int, b-f: int -> _/eax: int {
 96   var result-f/eax: int <- copy a-f
 97   result-f <- shift-left 8/fixed-precision
 98   {
 99     break-if-not-overflow
100     abort "divide-fixed: overflow"
101   }
102   var dummy-remainder/edx: int <- copy 0
103   result-f, dummy-remainder <- integer-divide result-f, b-f
104   return result-f
105 }
106 
107 # multiplying or dividing by an integer can use existing instructions.
108 
109 # adding and subtracting two fixed-point numbers can use existing instructions.
110 
111 fn mandelbrot screen: (addr screen) {
112   var a/eax: int <- copy 0
113   var b/ecx: int <- copy 0
114   a, b <- screen-size screen
115   var width-f/esi: int <- copy a
116   width-f <- shift-left 0xb/log2-font-width-and-fixed-precision  # 3 + 8 = 11
117   var height-f/edi: int <- copy b
118   height-f <- shift-left 0xc/log2-font-height-and-fixed-precision  # 4 + 8 = 12
119   var y/ecx: int <- copy 0
120   {
121     compare y, height-f
122     break-if->=
123     var imaginary-f/ebx: int <- viewport-to-imaginary-f y, width-f, height-f
124     var x/eax: int <- copy 0
125     {
126       compare x, width-f
127       break-if->=
128       var real-f/edx: int <- viewport-to-real-f x, width-f
129       var iterations/esi: int <- mandelbrot-iterations-for-point real-f, imaginary-f, 0x400/max
130       compare iterations, 0x400/max
131       {
132         break-if->=
133         pixel screen, x, y, 0xf/white
134       }
135       compare iterations, 0x400/max
136       {
137         break-if-<
138         pixel screen, x, y, 0/black
139       }
140       x <- increment
141       loop
142     }
143     y <- increment
144     loop
145   }
146 }
147 
148 fn mandelbrot-iterations-for-point real-f: int, imaginary-f: int, max: int -> _/esi: int {
149   var x-f/esi: int <- copy 0
150   var y-f/edi: int <- copy 0
151   var iterations/ecx: int <- copy 0
152   {
153     var done?/eax: boolean <- mandelbrot-done? x-f, y-f
154     compare done?, 0/false
155     break-if-!=
156     compare iterations, max
157     break-if->=
158     var x2-f/edx: int <- mandelbrot-x x-f, y-f, real-f
159     var y2-f/ebx: int <- mandelbrot-y x-f, y-f, imaginary-f
160     x-f <- copy x2-f
161     y-f <- copy y2-f
162     iterations <- increment
163     loop
164   }
165   return iterations
166 }
167 
168 fn mandelbrot-done? x-f: int, y-f: int -> _/eax: boolean {
169   # x*x + y*y > 4
170   var tmp-f/eax: int <- multiply-fixed x-f, x-f
171   var result-f/ecx: int <- copy tmp-f
172   tmp-f <- multiply-fixed y-f, y-f
173   result-f <- add tmp-f
174   compare result-f, 0x400/4
175   {
176     break-if->
177     return 0/false
178   }
179   return 1/true
180 }
181 
182 fn mandelbrot-x x-f: int, y-f: int, real-f: int -> _/edx: int {
183   # x*x - y*y + real
184   var tmp-f/eax: int <- multiply-fixed x-f, x-f
185   var result-f/ecx: int <- copy tmp-f
186   tmp-f <- multiply-fixed y-f, y-f
187   result-f <- subtract tmp-f
188   result-f <- add real-f
189   return result-f
190 }
191 
192 fn mandelbrot-y x-f: int, y-f: int, imaginary-f: int -> _/ebx: int {
193   # 2*x*y + imaginary
194   var result-f/eax: int <- copy x-f
195   result-f <- shift-left 1/log2
196   result-f <- multiply-fixed result-f, y-f
197   result-f <- add imaginary-f
198   return result-f
199 }
200 
201 # Scale (x, y) pixel coordinates to a complex plane where the viewport width
202 # ranges from -2 to +2. Viewport height just follows the viewport's aspect
203 # ratio.
204 
205 fn viewport-to-real-f x: int, width-f: int -> _/edx: int {
206   # (x - width/2)*4/width
207   var result-f/eax: int <- int-to-fixed x
208   var half-width-f/ecx: int <- copy width-f
209   half-width-f <- shift-right-signed 1/log2
210   result-f <- subtract half-width-f
211   result-f <- shift-left 2/log4
212   result-f <- divide-fixed result-f, width-f
213   return result-f
214 }
215 
216 fn viewport-to-imaginary-f y: int, width-f: int, height-f: int -> _/ebx: int {
217   # (y - height/2)*4/width
218   var result-f/eax: int <- int-to-fixed y
219   shift-right-signed height-f, 1/log2
220   result-f <- subtract height-f
221   result-f <- shift-left 2/log4
222   result-f <- divide-fixed result-f, width-f
223   return result-f
224 }