1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
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).
|