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