Author Topic: NASMX, Guile, and some Fractals....  (Read 22766 times)

Offline Bryant Keller

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 360
  • Country: us
    • About Bryant Keller
NASMX, Guile, and some Fractals....
« on: February 15, 2016, 05:29:42 AM »
So I got bored and thought I would familiarize myself with some of the new features of NASM-X...

Code: (turtle.asm) [Select]
;;; project: turtle
;;; author: S. Bryant Keller <ga.geek@yahoo.com>
;;;
;;; purpose:
;;;    The purpose of this project was mostly to familiarize myself
;;;    with new features of NASM-X. Also, I thought it would be useful
;;;    to have a slightly larger GNU/Linux demo. And more importantly,
;;;    it was fun.
;;;
;;;    This is basically a NASM-X translation (and refactoring) of
;;;    the GNU foundation's Tutorial Introduction to Guile available
;;;    in the references section below. You should check that site for
;;;    example scheme/guile code to test on this. It should work well.
;;;
;;; disclaimer:
;;;    The following code is not a demonstration of good coding practices,
;;;    for your own sanity, don't use this as a style guide. I don't
;;;    take any responsibility if you break something (or lose your
;;;    sanity) while trying to read or use my code. You do both at your
;;;    own risk.
;;;
;;; warnings?
;;;    Throughout this source you'll see warnings about global state,
;;;    this is a habit I've picked up from doing a lot of functional
;;;    programming in recent years. Basically, anything that modifies
;;;    global data gets marked with a warning so, in the event I ever
;;;    decide to reuse the code, I know it's not thread-safe and
;;;    bound to some global data.
;;;
;;; references:
;;;    Tutorial Introduction to Guile by Daniel Kraft (d@domob.eu)
;;;    https://www.gnu.org/software/guile/docs/guile-tut/tutorial.html
;;;
;;; build: nasm -f elf32 -o turtle.o turtle.asm
;;;        gcc -o turtle turtle.o -lc -lguile-2.0 -lgc

    BITS 32

    %include 'nasmx.inc'
    %include 'linux/libc.inc'

    %ixdefine bool_t uint32_t
    %ixdefine SCM_t ptrdiff_t

    %define TRUE 1
    %define FALSE 0
    %define NULL 0
    %define SCM_UNSPECIFIED 2052

    %define WIDTH 10
    %define HEIGHT 10
    %define NULL 0
    %define M_PI  3.141592653589793
    %define M_ROT 180.0

    ENTRY application

    IMPORT pipe
    IMPORT fork
    IMPORT dup2
    IMPORT execlp
    IMPORT fdopen
    IMPORT fprintf
    IMPORT fflush

    IMPORT scm_from_double
    IMPORT scm_to_double
    IMPORT scm_list_2
    IMPORT scm_c_define_gsubr
    IMPORT scm_with_guile
    IMPORT scm_shell

    SECTION .rodata

dblPiVal:
    declare(double_t) M_PI
dblRotVal:
    declare(double_t) M_ROT
dblZeroVal:
    declare(double_t) 0.0

szGnuPlot:
    declare(NASMX_TCHAR) NASMX_TEXT("gnuplot"), 0

szWriteMode:
    declare(NASMX_TCHAR) NASMX_TEXT("w"), 0

szClear:
    declare(NASMX_TCHAR) NASMX_TEXT(`clear\n`), 0

szInitString:
    declare(NASMX_TCHAR) NASMX_TEXT(`set multiplot\n`)
    declare(NASMX_TCHAR) NASMX_TEXT(`set parametric\n`)
    declare(NASMX_TCHAR) NASMX_TEXT(`set xrange [-%d:%d]\n`)
    declare(NASMX_TCHAR) NASMX_TEXT(`set yrange [-%d:%d]\n`)
    declare(NASMX_TCHAR) NASMX_TEXT(`set size ratio -1\n`)
    declare(NASMX_TCHAR) NASMX_TEXT(`unset xtics\n`)
    declare(NASMX_TCHAR) NASMX_TEXT(`unset ytics\n`), 0

szDrawLine:
    declare(NASMX_TCHAR) NASMX_TEXT(`plot [0:1] %f + %f * t, %f + %f * t notitle\n`), 0

szTurtleReset:
    declare(NASMX_TCHAR) NASMX_TEXT("turtle-reset"), 0
szTurtlePenup:
    declare(NASMX_TCHAR) NASMX_TEXT("turtle-penup"), 0
szTurtlePendown:
    declare(NASMX_TCHAR) NASMX_TEXT("turtle-pendown"), 0
szTurtleTurn:
    declare(NASMX_TCHAR) NASMX_TEXT("turtle-turn"), 0
szTurtleMove:
    declare(NASMX_TCHAR) NASMX_TEXT("turtle-move"), 0

    SECTION .data

bPenDown:
    declare(bool_t) TRUE
dblXcoord:
    declare(double_t) 0.0
dblYcoord:
    declare(double_t) 0.0
dblDirection:
    declare(double_t) 0.0
fdGnuPlot:
    declare(ptrdiff_t) NULL

    SECTION .text

;;; start_gnuplot : nothing -> ptrdiff_t.
;;; create an instance of the 'gnuplot' process and produce a
;;; file descriptor for use later on.
proc start_gnuplot
locals
    local fdOutput, ptrdiff_t
    local iaPipes, uint32_t, 3
endlocals

    lea eax, [var(.iaPipes)]
    invoke pipe, eax
    invoke fork

    cmp eax, 0
    jne .parent

.child:

    lea eax, [var(.iaPipes)]
    invoke dup2, dword [eax], STDIN_FILENO
    invoke execlp, szGnuPlot, NULL
    return

.parent:

    lea eax, [var(.iaPipes)]
    invoke fdopen, dword [eax + 4], szWriteMode
    mov dword [var(.fdOutput)], eax

    invoke fprintf, dword [var(.fdOutput)], szInitString, \
        dword WIDTH, dword WIDTH, dword HEIGHT, dword HEIGHT

    invoke fflush, dword [var(.fdOutput)]

    return dword [var(.fdOutput)]

endproc

;;; draw_line : ptrdiff_t double_t double_t double_t double_t -> nothing.
;;; draw a line on the 'gnuplot' instance.
proc draw_line, ptrdiff_t fdOutput, double_t dblX_1, double_t dblY_1, double_t dblX_2, double_t dblY_2
locals
    local dblXrel, double_t
    local dblYrel, double_t
endlocals

    fld qword [argv(.dblX_2)]
    fsub qword [argv(.dblX_1)]
    fstp qword [var(.dblXrel)]

    fld qword [argv(.dblY_2)]
    fsub qword [argv(.dblY_1)]
    fstp qword [var(.dblYrel)]

    invoke fprintf, dword [argv(.fdOutput)], szDrawLine, \
        dword [argv(.dblX_1)], dword [argv(.dblX_1) + 4], \
        dword [var(.dblXrel)], dword [var(.dblXrel) + 4], \
        dword [argv(.dblY_1)], dword [argv(.dblY_1) + 4], \
        dword [var(.dblYrel)], dword [var(.dblYrel) + 4]

    invoke fflush, dword [argv(.fdOutput)]
    return

endproc

;;; degrees_to_radians : double_t -> double_t.
;;; convert from degrees to radians.
;;; using radians = PI / 180.0 * degrees.
;;; given degrees =  0.0000 expect 0.0000#i,
;;;       degrees = 10.0000 expect 0.1745#i,
;;;       degrees = 20.0000 expect 0.3491#i,
;;;       degrees = 30.0000 expect 0.5236#i,
;;;       degrees = 40.0000 expect 0.6981#i,
;;;       degrees = 50.0000 expect 0.8727#i,
;;;       degrees = 60.0000 expect 1.0472#i,
;;;       degrees = 70.0000 expect 1.2217#i,
;;;       degrees = 80.0000 expect 1.3963#i,
;;;       degrees = 90.0000 expect 1.5708#i.
proc degrees_to_radians, double_t dblDegrees
locals none
    ;; RPN: PI 180.0 / degrees *
    fld qword [dblPiVal]
    fdiv qword [dblRotVal]
    fmul qword [argv(.dblDegrees)]
    return
endproc

;;; radians_to_degrees : double_t -> double_t.
;;; convert from degrees to radians.
;;; using degrees = radians * 180.0 / PI.
;;; given radians =  0.0000 expect    0.000000#i,
;;;       radians = 10.0000 expect  572.957795#i,
;;;       radians = 20.0000 expect 1145.915590#i,
;;;       radians = 30.0000 expect 1718.873385#i,
;;;       radians = 40.0000 expect 2291.831181#i,
;;;       radians = 50.0000 expect 2864.788976#i,
;;;       radians = 60.0000 expect 3437.746771#i,
;;;       radians = 70.0000 expect 4010.704566#i,
;;;       radians = 80.0000 expect 4583.662361#i,
;;;       radians = 90.0000 expect 5156.620156#i.
proc radians_to_degrees, double_t dblRadians
locals none
    ;; RPN: radians 180.0 * PI /
    fld qword [argv(.dblRadians)]
    fmul qword [dblRotVal]
    fdiv qword [dblPiVal]
    return
endproc

;;; compute_rotation : double_t -> double_t.
;;; compute and update the current direction.
;;; warning: this procedure maintains global state!
;;; Computational process:
;;;   dblDirection := dblDirection + degrees_to_radians (degrees)
;;;   compute_rotation := radians_to_degrees (dblDirection)
;;; test case:
;;;   S1 : direction = 0, input = 10, output = 10
;;;   S2 : direction = 10, input = 10, output = 20
;;;   S3 : direction = 20, input = 90, output = 110
;;;   ...
proc compute_rotation, double_t dblDegrees
locals none
    invoke degrees_to_radians, qword [argv(.dblDegrees)]
    fadd qword [dblDirection]
    fstp qword [dblDirection]
    invoke radians_to_degrees, qword [dblDirection]
    return
endproc

;;; translate_x : double_t -> double_t.
;;; translate the current x coordinate.
;;; Computational process:
;;;   newX = x + length * cos (direction);
proc translate_x, double_t dblLength
locals none
    ;; RPN: direction COS length * x +
    fld qword [dblDirection]
    fcos
    fld qword [argv(.dblLength)]
    fmul
    fadd qword [dblXcoord]
    return

endproc

;;; translate_y : double_t -> double_t.
;;; translate the current y coordinate.
;;; Computational process:
;;;   newY = y + length * sin (direction);
proc translate_y, double_t dblLength
locals none
    ;; RPN: direction SIN length * y +
    fld qword [dblDirection]
    fsin
    fld qword [argv(.dblLength)]
    fmul
    fadd qword [dblYcoord]
    return

endproc

;;; reset_position : nothing -> nothing.
;;; reset the x, y, and direction variables to zero.
;;; warning: this procedure maintains global state!
;;; Computational process:
;;;   x := 0 ; y := 0 ; dir := 0
proc reset_position
locals none
    finit
    fld qword [dblZeroVal]
    fst qword [dblXcoord]
    fst qword [dblYcoord]
    fstp qword [dblDirection]
    return
endproc

;;; scm_from_bool : bool_t -> SCM_t.
;;; translate from bool_t to a guile boolean.
;;; this is implemented as a macro in C, so we
;;; need to do this ourself.
proc scm_from_bool, bool_t bExpr
locals none
    mov eax, [argv(.bExpr)]
    cmp eax, FALSE
    jne .is_true
    mov eax, 4 ; #f
    jmp .done
.is_true:
    cmp eax, TRUE
    jne .done
    mov eax, 1028 ; #t
.done:
    return eax
endproc

;;; turtle_reset : nothing -> SCM_t.
;;; clear the screen and reset to starting position.
;;; warning: this procedure maintains global state!
proc turtle_reset
locals none
    invoke reset_position
    mov bool_t [bPenDown], TRUE
    invoke fprintf, [fdGnuPlot], szClear
    invoke fflush, [fdGnuPlot]
    return SCM_UNSPECIFIED
endproc

;;; turtle_pendown : nothing -> SCM_t.
;;; set the state of our pen to "down".
;;; warning: this procedure maintains global state!
proc turtle_pendown
locals none
    mov eax, bool_t [bPenDown]
    mov bool_t [bPenDown], TRUE
    invoke scm_from_bool, eax
    return eax
endproc

;;; turtle_penup : nothing -> SCM_t.
;;; set the state of our pen to "up".
;;; warning: this procedure maintains global state!
proc turtle_penup
locals none
    mov eax, bool_t [bPenDown]
    mov bool_t [bPenDown], FALSE
    invoke scm_from_bool, eax
    return eax
endproc

;;; turtle_turn : SCM_t -> SCM_t.
;;; rotate our turtle's direction by some degrees.
;;; warning: this procedure maintains global state!
proc turtle_turn, SCM_t scmDegrees
locals
    local dblTemp, double_t
endlocals
    invoke scm_to_double, [argv(.scmDegrees)]
    fstp qword [var(.dblTemp)]

    invoke compute_rotation, qword [var(.dblTemp)]
    fstp qword [var(.dblTemp)]

    invoke scm_from_double, dword [var(.dblTemp)], \
                            dword [var(.dblTemp) + 4]
    return eax
endproc

;;; turtle_move : SCM_t -> SCM_t.
;;; move our turtle a distance of length.
;;; warning: this procedure maintains global state!
proc turtle_move, SCM_t scmLength
locals
    local dblLen,  double_t
    local dblXnew, double_t
    local dblYnew, double_t
    local scmXcoord, SCM_t
    local scmYcoord, SCM_t
endlocals
    invoke scm_to_double, [argv(.scmLength)]
    fstp qword [var(.dblLen)]

    invoke translate_x, qword [var(.dblLen)]
    fstp qword [var(.dblXnew)]

    invoke translate_y, qword [var(.dblLen)]
    fstp qword [var(.dblYnew)]

    cmp bool_t [bPenDown], TRUE
    jne .endif

    invoke draw_line, ptrdiff_t [fdGnuPlot], \
        qword [dblXcoord], qword [dblYcoord], \
        qword [var(.dblXnew)], qword [var(.dblYnew)]

.endif:
    invoke scm_from_double, qword [var(.dblXnew)]
    mov SCM_t [var(.scmXcoord)], eax
    fld qword [var(.dblXnew)]
    fstp qword [dblXcoord]

    invoke scm_from_double, qword [var(.dblYnew)]
    mov SCM_t [var(.scmYcoord)], eax
    fld qword [var(.dblYnew)]
    fstp qword [dblYcoord]

    invoke scm_list_2, SCM_t [var(.scmXcoord)], SCM_t [var(.scmYcoord)]
    return

endproc

;;; register_functions : nothing -> ptrdiff_t.
;;; register our functions with guile.
proc register_functions
locals none
    invoke scm_c_define_gsubr, szTurtleReset, 0, 0, 0, turtle_reset
    invoke scm_c_define_gsubr, szTurtlePenup, 0, 0, 0, turtle_penup
    invoke scm_c_define_gsubr, szTurtlePendown, 0, 0, 0, turtle_pendown
    invoke scm_c_define_gsubr, szTurtleTurn, 1, 0, 0, turtle_turn
    invoke scm_c_define_gsubr, szTurtleMove, 1, 0, 0, turtle_move
    xor eax, eax
    return
endproc

;;; application : uint32_t (ptrdiff_t . ptrdiff_t) -> uint32_t
;;; the application entrypoint.
;;; warning: this procedure maintains global state!
proc application, uint32_t iArgCount, ptrdiff_t pszCmdLine
locals none

    invoke start_gnuplot
    mov dword [fdGnuPlot], eax

    invoke turtle_reset

    invoke scm_with_guile, register_functions, NULL
    invoke scm_shell, [argv(.iArgCount)], [argv(.pszCmdLine)]

    xor eax, eax
    return
endproc

The original code was from Tutorial Introduction to Guile.

Here's something you can use with it...

Code: (fractal.scm) [Select]
(define (sierpinski length depth sign)
    (define (move-forward x)  (turtle-move x))
    (define (move-backward x) (turtle-move (* -1 x)))
    (define (turn-left x)     (turtle-turn x))
    (define (turn-right x)    (turtle-turn (* -1 x)))
    (define (draw-triangle length)
        (let iterate ((i 0))
            (if (< i 3)
                (begin
                    (turtle-move length)
                    (turtle-turn (* sign 120))
            (iterate (1+ i))))))
    (if (zero? depth)
        (draw-triangle length)
        (begin
            (sierpinski (/ length 2) (1- depth) sign)
            (move-forward (/ length 2))
            (sierpinski (/ length 2) (1- depth) sign)
            (move-backward (/ length 2))
            (turn-left 60)
            (move-forward (/ length 2))
            (turn-right 60)
            (sierpinski (/ length 2) (1- depth) sign)
            (turn-left 60)
            (move-backward (/ length 2))
            (turn-right 60))))

Hope someone enjoys it.  :)

Best Regards,
Bryant Keller

EDIT: I noticed my use of different editors has butched the white spacing so I fixed that.. also added a comment to explain the warning's I write in my code. I thought, since this is a demo of sorts, I would leave all the experimental comments I usually write while prototyping my ideas. This includes annotations for non-thread safe routines, test case comments (though I didn't bother to include my unit tests as those tend to be larger than the actual project), and the common habit I have of writing any FPU routines as RPN to make translating easier (the FPU is a stack machine, if you can solve your problem in RPN it makes translating to FPU code MUCH easier).
« Last Edit: February 16, 2016, 07:33:52 AM by Bryant Keller »

About Bryant Keller
bkeller@about.me

Offline Bryant Keller

  • Forum Moderator
  • Full Member
  • *****
  • Posts: 360
  • Country: us
    • About Bryant Keller
Re: NASMX, Guile, and some Fractals....
« Reply #1 on: February 16, 2016, 07:41:15 AM »
I've updated the main thread with the latest revision. I've added dblPiVal and dblRotVal to the rodata section to make the code much more readable (rad->deg and deg->rad are now much simpler). I've also made all variables to use the same naming style, the previous example was put together from multiple unit test files (during which time I got lazy and ignored any/all conventions).. the updated version should be easier to read.

About Bryant Keller
bkeller@about.me