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/esi: int <- copy a
    116   width <- shift-left 3/log2-font-width
    117   var height/edi: int <- copy b
    118   height <- shift-left 4/log2-font-height
    119   var y/ecx: int <- copy 0
    120   {
    121     compare y, height
    122     break-if->=
    123     var imaginary-f/ebx: int <- viewport-to-imaginary-f y, width, height
    124     var x/eax: int <- copy 0
    125     {
    126       compare x, width
    127       break-if->=
    128       var real-f/edx: int <- viewport-to-real-f x, width
    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: int -> _/edx: int {
    206   # (x - width/2)*4/width
    207   var result-f/eax: int <- int-to-fixed x
    208   var width-f/ecx: int <- copy width
    209   width-f <- shift-left 8/fixed-precision
    210   var half-width-f/edx: int <- copy width-f
    211   half-width-f <- shift-right-signed 1/log2
    212   result-f <- subtract half-width-f
    213   result-f <- shift-left 2/log4
    214   result-f <- divide-fixed result-f, width-f
    215   return result-f
    216 }
    217
    218 fn viewport-to-imaginary-f y: int, width: int, height: int -> _/ebx: int {
    219   # (y - height/2)*4/width
    220   var result-f/eax: int <- int-to-fixed y
    221   var half-height-f/ecx: int <- copy height
    222   half-height-f <- shift-left 8/fixed-precision
    223   half-height-f <- shift-right-signed 1/log2
    224   result-f <- subtract half-height-f
    225   result-f <- shift-left 2/log4
    226   var width-f/ecx: int <- copy width
    227   width-f <- shift-left 8/fixed-precision
    228   result-f <- divide-fixed result-f, width-f
    229   return result-f
    230 }