diff options
Diffstat (limited to 'guile-zig-local/README.md')
-rw-r--r-- | guile-zig-local/README.md | 447 |
1 files changed, 447 insertions, 0 deletions
diff --git a/guile-zig-local/README.md b/guile-zig-local/README.md new file mode 100644 index 0000000..ec4a664 --- /dev/null +++ b/guile-zig-local/README.md @@ -0,0 +1,447 @@ +# Guile & Zig + +Zig is a blazingly fast new programing language that adhers to a stable ABI and that makes it suitable to link against other programming languages. Zig comes withouth a garbage collector, and that makes it even better. + +This repo contains code to show how we can link Zig with Guile - but, really, zig directly supports any language that allows for a C FFI. + +## Install + +To create an environment with GNU Guix you can run with [guix.scm](guix.scm). + +``` +guix shell -C -D -f guix.scm +zig version +guile -v +``` + +This way guile can be run in an emacs shell with M-x shell -> guile. + +## Compile zig + +Run zit source tests with + +``` +zig test my.zig +``` + +Create libmy.so and show exported symbols with: + +``` +zig build-lib -dynamic my.zig +nm -D --demangle libmy.so +``` + +## Print zig hello world from C + +The quick way is to create a C function that calls a zig function (no guile yet): + +@1 +``` +zig build-lib -dynamic my.zig +zig cc `pkg-config --cflags guile-3.0` test.c -fPIC libmy.so $GUIX_ENVIRONMENT/lib/libguile-3.0.so -o test +env LD_LIBRARY_PATH=.:$GUIX_ENVIRONMENT/lib ./test +hello world +``` + +The `LD_LIBRARY_PATH` tells the runtime to look in the current working directory for mylib.so. + +## Print zig hello from guile + +Now we have a shared lib it should be easy to load. + +A shared library can be loaded into a running Guile process with the function load-extension. See the [guile documentation](https://www.gnu.org/software/guile/manual/html_node/Combining-with-C.html). In addition to the name of the library to load, this function also expects the name of a function from that library that will be called to initialize it: + + +// gcc `pkg-config --cflags guile-3.0` \ +// -shared -o libmy.so -fPIC .c + +``` +zig cc `pkg-config --cflags guile-3.0` test.c -fPIC libmy.so -o test +env LD_LIBRARY_PATH=. guile +``` + +```guile +(load-extension "libmy" "init_module") +``` +Should loads without error. Next we can call a function after we add a mapping to the init_module. What we want to do is: + +```guile +(hello_zig) +possibly unbound variable hello_zig +``` + +Hello stranger. + +We need to do a bit more work to make guile accept zig calls. An quick example in C is [here](https://www.gnu.org/software/guile/manual/html_node/A-Sample-Guile-Extension.html#A-Sample-Guile-Extension). libguile.h contains a range of C macros to facilite bindings. C macros (shudder - we want to get rid of those). GNU Guile is a dynamic language - that means values carry information about their type at runtime. This requires mapping values back and forth. This is true for any dynamic language - including Python, Ruby, R, etc. + +One fun aspect of GNU Guile is that almost all its functions are implemented in C with a scm_ prefix. So, if you look up functions, you'll the the guile call and the C call next to each other. In the Guile manual you'll find more information on [translating types](https://www.gnu.org/software/guile/manual/html_node/Dynamic-Types.html). You'll find a C example: + +```C +SCM my_incrementing_function (SCM a, SCM flag) +{ + SCM result; + + if (scm_is_true (flag)) + result = scm_sum (a, scm_from_int (1)); + else + result = a; + + return result; +} +``` + +that shows that the SCM macro represents different types of parameters in Guile. SCM is defined in scm.h saying `SCM values can hold proper scheme objects only.' + +If we add this function to test.c we can see its expanded version as + +``` +zig cc -E -I$GUIX_ENVIRONMENT/include/guile/3.0 test.c libmy.so -L$GUIX_ENVIRONMENT -lguile -o test +``` + +note the `-E` switch. The function expands to + +```C +SCM my_incrementing_function (SCM a, SCM flag) +{ + SCM result; + + if ((!((((((scm_t_bits) (0? (*(volatile SCM *)0=((flag))): (flag))) & ~(((scm_t_bits) (0? (*(volatile SCM + *)0=(((SCM) ((((((1)) << 8) + scm_tc8_flag)))))): ((SCM) ((((((1)) << 8) + scm_tc8_flag)))))) ^ ((scm_t_bi +ts) (0? (*(volatile SCM *)0=(((SCM) ((((((0)) << 8) + scm_tc8_flag)))))): ((SCM) ((((((0)) << 8) + scm_tc8_ +flag)))))))) == (((scm_t_bits) (0? (*(volatile SCM *)0=(((SCM) ((((((1)) << 8) + scm_tc8_flag)))))): ((SCM) + ((((((1)) << 8) + scm_tc8_flag)))))) & ((scm_t_bits) (0? (*(volatile SCM *)0=(((SCM) ((((((0)) << 8) + scm +_tc8_flag)))))): ((SCM) ((((((0)) << 8) + scm_tc8_flag)))))))))))) + result = scm_sum (a, scm_from_int32 (1)); + else + result = a; + + return result; +} +``` + +There is a bit of work going on, but mostly it is simple packing and unpacking described [here](https://www.gnu.org/software/guile/manual/html_node/Relationship-Between-SCM-and-scm_005ft_005fbits.html). To use this in Zig we'll need to translate the C macros for some conversions. Note that `scm_sum` and `scm_from_int` are simple function calls. The real challenge is converting SCM to boolean with `scm_is_true`. + +Zig comes with a utility that does that: + +@2 +``` +zig translate-c -I$GUIX_ENVIRONMENT/include/guile/3.0/ $GUIX_ENVIRONMENT/include/guile/3.0/libguile.h > test-include.zig + +``` + +And, interestingly translates all of that including + +```zig +pub const scm_t_timespec = struct_timespec; +pub const scm_t_off = i64; +pub const scm_t_signed_bits = isize; +pub const scm_t_bits = usize; +pub const struct_scm_unused_struct = extern struct { + scm_unused_field: u8, +}; +pub const SCM = [*c]struct_scm_unused_struct; +pub const scm_tc8_flag: c_int = 4; +pub const scm_tc8_char: c_int = 12; +pub const scm_tc8_unused_0: c_int = 20; +pub const scm_tc8_unused_1: c_int = 28; +pub const enum_scm_tc8_tags = c_uint; +pub const scm_t_subr = ?*anyopaque; +pub const struct_scm_dynamic_state = opaque {}; +pub const scm_t_dynamic_state = struct_scm_dynamic_state; +pub const struct_scm_print_state = extern struct { + handle: SCM, + revealed: c_int, + writingp: c_ulong, + fancyp: c_ulong, + level: c_ulong, + length: c_ulong, + hot_ref: SCM, + list_offset: c_ulong, + top: c_ulong, + ceiling: c_ulong, + ref_vect: SCM, + highlight_objects: SCM, +}; +pub const scm_print_state = struct_scm_print_state; +pub const struct_scm_dynstack = extern struct { + base: [*c]scm_t_bits, + top: [*c]scm_t_bits, + limit: [*c]scm_t_bits, +}; +``` + +that may look familiar to Guile hackers and matches the C + +```C +typedef struct scm_unused_struct { char scm_unused_field; } *SCM; +``` + +typed struct * that is SCM. Now from Zig, unless we start using real Guile internals we may get away with simply using our own opaque pointer that is passed around. + +Let's try compiling this conversion and it gives one error + +```sh +zig test test-include.zig +test-include.zig:6522:30: error: use of undeclared identifier 'sched_priority' +pub const __sched_priority = sched_priority; +``` + +Commenting out that line and the compile passes. That must be too good to be true(!) +Let's try compiling test.c with + +``` +zig cc `pkg-config --cflags guile-3.0` test.c -fPIC libmy.so $GUIX_ENVIRONMENT/lib/libguile-3.0.so -o test +env LD_LIBRARY_PATH=$GUIX_ENVIRONMENT/lib:. ./test +Hello world from 3 to 4 +``` + +Now we add the zig call without `scm_is_false` + +``` +zig build-lib -dynamic my.z +env LD_LIBRARY_PATH=$GUIX_ENVIRONMENT/lib:. ./test +Hello world from 3 to 4 + from 3 to 4 +``` + +This worked! With reintroduced `scm_is_false` we get + +``` +./test-include.zig:6119:62: error: unable to evaluate constant expression +pub inline fn scm_is_true(x: anytype) @TypeOf(!(scm_is_false(x) != 0)) { +``` + +Remember that is a C macro converted automatically. If we use a C function it works: + +``` +zig build-lib -dynamic my.zig +zig cc `pkg-config --cflags guile-3.0` test.c -fPIC libmy.so $GUIX_ENVIRONMENT/lib/libguile-3.0.so -o test +env LD_LIBRARY_PATH=$GUIX_ENVIRONMENT/lib:. ./test +Hello world from 3 to 4 +Hello world from 4 to 5 +``` + +Now let's try loading guile again + +``` +env LD_LIBRARY_PATH=. guile +``` + +and + +``` +(load-extension "libmy" "init_module") +(hello_zig) +possibly unbound variable hello_zig +``` + +while + +``` +nm -D libmy.so +000000000000aa80 T hello_zig +000000000000ab40 T my_incrementing_zig_function +000000000003d6c0 W __zig_probe_stack +``` + +shows the symbols are exported. Hmm. We need to tell guile first there exists a function. Apparently I am not the [first](https://github.com/ziglang/zig/issues/1675). After a bit of try and error with zig 0.9 we get: + +```zig +pub export fn ping_zig(i: SCM) SCM { + return i; +} + +export fn init_module() void { + // var c_ping_zig = @intToPtr(*anyopaque, @ptrToInt(&ping_zig)); <- ampersand for zig 0.10 and after + var c_ping_zig = @intToPtr(*anyopaque, @ptrToInt(ping_zig)); + var res = guile.scm_c_define_gsubr("ping_zig", 1, 0, 0, c_ping_zig); + _ = res; +} +``` + +which runs in guile with + +```guile +(load-extension "libmy" "init_module") +(ping_zig 0) +$1 = 0 +(ping_zig "TEST") +$2 = "TEST" +``` + +Woot! That works great. Note we did not have to deal with a type system. + +With a recent version of zig the function pointer needs an ampersand. + +## Lists + +The work horse of data structures in Lisp is the list. Let us try to find an element that equals 2 and increment the value from Zig. First attempt + +```zig +export fn my_increment_in_list_zig(lst: SCM) SCM { + var lst2 = guile.scm_memv(guile.scm_from_int(2), lst); + return lst2; +} +``` + +```guile +scheme@(guile-user)> (load-extension "libmy" "init_module") +scheme@(guile-user)> (incr_list_zig '(3 4 2 "test")) +$1 = (2 "test") +``` + +Now let's increment with + +```zig +export fn my_increment_in_list_zig(lst: SCM) SCM { + var lst2 = guile.scm_memv(guile.scm_from_int(2), lst); + var lst3 = guile.scm_list_set_x(lst2, 1, guile.scm_from_int(3)); + return lst3; +} +``` + +Now this segfaults. Let's try the debugger with + +``` +env LD_LIBRARY_PATH=. gdb --args guile +``` + +and we get + +``` +Thread 1 "guile" received signal SIGSEGV, Segmentation fault. +0x00007ffff7f02dc5 in scm_to_uint64 () from /gnu/store/qlmpcy5zi84m6dikq3fnx5dz38qpczlc-guile-3.0.8/lib/libguile-3.0.so.1 +``` + +ah, I think the index 1 should be a SCM value. Indeed does the job + +```zig +export fn my_increment_in_list_zig(lst: SCM) SCM { + var lst2 = guile.scm_memv(guile.scm_from_int(2), lst); + lst2 = guile.scm_list_set_x(lst2, guile.scm_from_int(0), guile.scm_from_int(3)); + return lst; // note original is updated! +} +``` + +```guile +(load-extension "libmy" "init_module") +(incr_list_zig (list 3 4 2 "test")) +$1 = (3 4 3 "test") +``` + +## Arrays + +Arrays are continuous in memory. Guile provides a special page on [accessing arrays from C](https://www.gnu.org/software/guile/manual/html_node/Accessing-Arrays-from-C.html). + +It says: Accessing Arrays from C. +For interworking with external C code, Guile provides an API to allow C code to access the elements of a Scheme array. +In particular, for uniform numeric arrays, the API exposes the underlying uniform data as a C array of numbers of the relevant type. +This can be very useful. Note that the C and Zig code need to protect and array when it starts modifying it. And for multithreaded access locking is also required. See above documentation for more. + +We'll want to create an [unboxed vector](https://www.gnu.org/software/guile/docs/docs-2.2/guile-ref/Uniform-Numeric-Vectors.html#Uniform-Numeric-Vectors) of double. This is the scheme version: + +```guile +(use-modules (srfi srfi-4)) +(define v #f64(3.1415 2.71 1.0)) +(f64vector-length v) +$28 = 3 +(f64vector-set! v 1 3.71) +v +$29 = #f64(3.1415 3.71 1.0) +``` + +The zig version: + +```zig +// Increment the second value in the unboxed vector +export fn my_increment_in_f64vector_zig(vec: SCM) SCM { + _ = guile.scm_f64vector_set_x(vec, guile.scm_from_int(1), guile.scm_from_double(3.7)); + return vec; // note original is updated! +} +``` + +does it + +```guile +(define v #f64(3.1415 2.71 1.0)) +(incr_f64vector_zig v) +$1 = #f64(3.1415 3.7 1.0) +``` + +Now in the final step we want to update the value as a raw Zig array. The +C Function: + +```C +const double * scm_f64vector_elements(SCM vec, scm_t_array_handle *handle, size_t *lenp, ssize_t *incp) +``` + +should return a pointer. The Zig translated version is + +```zig +pub extern fn scm_f64vector_elements(uvec: SCM, h: [*c]scm_t_array_handle, lenp: [*c]usize, incp: [*c]isize) [*c]const f64; +``` + +This code unboxes the double vector: + +```zig +export fn my_increment_in_f64vector_zig(vec: SCM) SCM { + // _ = guile.scm_f64vector_set_x(vec, guile.scm_from_int(1), guile.scm_from_double(3.7)); + var handle: guile.scm_t_array_handle = undefined; + var lenp: usize = undefined; + var incp: isize = undefined; + var data = guile.scm_f64vector_elements(vec, &handle, &lenp, &incp); + p("\nZig says {any},{any},{any}\n",.{data[0],data[1],data[2]}); + return vec; // note original is updated! +} +``` + +When you try to mutate it will complain! We need to set the vector to mutable. Replacing +the line with: + +``` +var data = guile.scm_f64vector_writable_elements(vec, &handle, &lenp, &incp); +``` + +gets a complaint: + +``` +Wrong type (expecting mutable f64vector): #f64(3.1415 2.71 1.0) +``` + +Try again by createing a mutable vector. This is the full code + +```zig +export fn my_increment_in_f64vector_zig(vec: SCM) SCM { + // _ = guile.scm_f64vector_set_x(vec, guile.scm_from_int(1), guile.scm_from_double(3.7)); + var handle: guile.scm_t_array_handle = undefined; + var lenp: usize = undefined; + var incp: isize = undefined; + var data = guile.scm_f64vector_writable_elements(vec, &handle, &lenp, &incp); + p("\nZig says {any},{any},{any}\n",.{data[0],data[1],data[2]}); + data[1] += 1.0; + guile.scm_array_handle_release(&handle); + return vec; // note original is updated! +} +``` + +```guile +(load-extension "libmy" "init_module") +(define v (list->f64vector '(3.1415 2.71 1.0))) +(display "Guile says ") +(display v) +(incr_f64vector_zig v) +(display "Guile now says ") +(display v) +``` + +displays + +``` +Guile says #f64(3.1415 2.71 1.0) +Zig says 3.1415e+00,2.71e+00,1.0e+00 +Guile now says #f64(3.1415 3.71 1.0) +``` + +## Sharing a pointer + +If you want to move pointers around, both guile and zig can handle 'foreign pointers'. Zig has *anyopaque and guile has [foreign pointers]( https://www.gnu.org/software/guile/manual/html_node/Foreign-Pointers.html#Foreign-Pointers). |