55be1f742512 — Nathan Michaels 1 year, 3 months ago
Update for latest Zig master.

Zig commit: 8f3ccbbe367bea66d7f0f364a957870eb2cc95a0

This is the path to 0.12.0.

Also added the HTML to the repo, so we can keep them in sync.
8 files changed, 429 insertions(+), 19 deletions(-)

A => .hgignore
M Makefile
M add.c
M add.h
M build.zig
A => html/c-library.html
M main.c
M src/main.zig
A => .hgignore +7 -0
@@ 0,0 1,7 @@ 
+syntax: glob
+
+*.a
+main
+*.o
+zig-cache
+zig-out

          
M Makefile +7 -6
@@ 3,23 3,24 @@ CC=gcc
 CFLAGS=-Wall -Werror -Wextra -Os -fPIE
 LD=ld
 LDFLAGS=-melf_x86_64 -r --whole-archive
-LPATH=-L.
-ZIGFLAGS=-Drelease-fast
+ZIGOUT=zig-out/lib
+LPATH=-L$(ZIGOUT) -L.
 
 .PHONY: clean libadd.a
 
 %.o:: %.c
 	$(CC) -o $@ -c $(CFLAGS) $^
 
-libadd.a:
+$(ZIGOUT)/libadd.a:
+	zig fmt build.zig src/*.zig
 	zig build
-	touch libadd.a
 
-libi32math.a: libadd.a mul.o
-	$(LD) $(LDFLAGS) -o $@ $^
+libi32math.a: $(ZIGOUT)/libadd.a mul.o
+	$(LD) $(LDFLAGS) $(LPATH) -o $@ $^
 
 main: main.o libi32math.a
 	$(CC) -o main $(CFLAGS) $(LPATH) $< -li32math
 
 clean:
 	rm -f *.o *.a main
+	rm -rf zig-out zig-cache

          
M add.c +1 -1
@@ 1,6 1,6 @@ 
 #include "add.h"
 
-int32_t add(int32_t a, int32_t b)
+int32_t add_i32(int32_t a, int32_t b)
 {
     return a + b;
 }

          
M add.h +1 -1
@@ 4,6 4,6 @@ 
 #include <stdint.h>
 
 // Add two numbers together and return the sum.
-int32_t add(int32_t a, int32_t b);
+int32_t add_i32(int32_t a, int32_t b);
 
 #endif

          
M build.zig +20 -8
@@ 1,19 1,31 @@ 
 const Builder = @import("std").build.Builder;
 
 pub fn build(b: *Builder) void {
-    const mode = b.standardReleaseOptions();
-    const lib = b.addStaticLibrary("add", "src/main.zig");
-    lib.setBuildMode(mode);
-    switch (mode) {
+    const target = b.standardTargetOptions(.{});
+    const optimize = b.standardOptimizeOption(.{});
+
+    const lib = b.addStaticLibrary(
+        .{
+            .name = "add",
+            .root_source_file = .{ .path = "src/main.zig" },
+            .target = target,
+            .optimize = optimize,
+        },
+    );
+
+    b.installArtifact(lib);
+
+    switch (optimize) {
         .Debug, .ReleaseSafe => lib.bundle_compiler_rt = true,
         .ReleaseFast, .ReleaseSmall => lib.disable_stack_probing = true,
     }
     lib.force_pic = true;
-    lib.setOutputDir(".");
-    lib.install();
 
-    var main_tests = b.addTest("src/main.zig");
-    main_tests.setBuildMode(mode);
+    var main_tests = b.addTest(.{
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
 
     const test_step = b.step("test", "Run library tests");
     test_step.dependOn(&main_tests.step);

          
A => html/c-library.html +390 -0
@@ 0,0 1,390 @@ 
+<html>
+  <head>
+    <link rel="stylesheet" type="text/css" href="/site.css">
+    <title>Extending C with Zig</title>
+  </head>
+  <body>
+    <div class="post">
+      <h1>Extending C with Zig</h1>
+      <p>One of the most compelling wedges for Zig to gain a foothold
+        is in development and maintenance of libraries that already
+        have significant amounts of C code. Since Zig can export
+        symbols understood by C linkers, a library written in C can be
+        incrementally migrated to Zig without breaking the
+        interface.</p>
+      <h2>Math</h2>
+      <p>For our example, we'll start with a trivial addition
+        library. Here's our C library's interface:</p>
+      <pre class="code"><code>
+// add.h
+#ifndef ADD_H
+#define ADD_H
+
+#include &lt;stdint.h&gt;
+
+// Add two numbers together and return the sum.
+int32_t add_i32(int32_t a, int32_t b);
+
+#endif
+
+      </code></pre>
+      <p>And here's the implementation.</p>
+<pre class="code"><code>
+// add.c
+#include "add.h"
+
+int32_t add_i32(int32_t a, int32_t b)
+{
+    return a + b;
+}
+
+</code></pre>
+      <p>Simple, right? Let's also make a program that uses it and a
+        <code>Makefile</code> to build the whole thing.</p>
+      <pre class="code"><code>
+// main.c
+#include &lt;stdio.h&gt;
+#include &lt;inttypes.h&gt;
+#include "add.h"
+
+int main(void)
+{
+    printf("7 + 3 = %"PRIi32"\n", add_i32(7, 3));
+    return 0;
+}
+
+</code></pre>
+      <pre class="code"><code>
+# Makefile
+CC=gcc
+CFLAGS=-Wall -Werror -Wextra -Os -fPIE
+LD=ld
+LDFLAGS=-melf_x86_64 -r
+LPATH=-L.
+
+.PHONY: clean
+
+%.o: %.c
+	$(CC) -o $@ -c $(CFLAGS) $^
+
+libadd.a: add.o
+	$(LD) $(LDFLAGS) -o $@ $^
+
+main: main.o libadd.a
+	$(CC) -o main $(CFLAGS) $(LPATH) $&lt; -ladd
+
+clean:
+	rm -f *.o *.a main
+
+</code></pre>
+      <p>A bit more complexity, but if you've ever built a library it
+        should mostly look familiar.</p>
+      <h2>Add Zig</h2>
+      <p>Now it's time to make the <code>add</code> library, but in
+      Zig. Coincidentally, Zig's default library initializer matches
+      this one's interface perfectly.</p>
+      <pre class="code"><code>
+$ zig init-lib
+Created build.zig
+Created src/main.zig
+
+Next, try `zig build --help` or `zig build test`
+      </code></pre>
+      <p>Excellent. Zig has built a <code>build.zig</code> file for us
+            (Zig's equivalent of a <code>Makefile</code>) and put some
+            code in <code>main.zig</code> for us. Let's take a
+            look.</p>
+      <pre class="code"><code>
+// src/main.zig
+const std = @import("std");
+const testing = std.testing;
+
+export fn add_i32(a: i32, b: i32) i32 {
+    return a + b;
+}
+
+test "basic add functionality" {
+    testing.expect(add_i32(3, 7) == 10);
+}
+      </code></pre>
+      <p>and the default <code>build.zig</code>:</p>
+      <pre class="code"><code>
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+    const target = b.standardTargetOptions(.{});
+
+    const optimize = b.standardOptimizeOption(.{});
+
+    const lib = b.addStaticLibrary(.{
+        .name = "temp",
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    b.installArtifact(lib);
+
+    const main_tests = b.addTest(.{
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    const run_main_tests = b.addRunArtifact(main_tests);
+
+    const test_step = b.step("test", "Run library tests");
+    test_step.dependOn(&run_main_tests.step);
+}
+      </code></pre>
+      <p>The code is already there. Our Zig implementation
+        of <code>add</code> is already marked with <code>export</code>
+        and it thoughtfully included a test. All we need now is to hook
+        the build system up in a way that can generate
+        a <code>libadd.a</code> that will link
+        with <code>main.o</code>. For that, we need to fiddle a bit with
+        our <code>build.zig</code>.</p>
+      <h2>Building a Library</h2>
+      <p>The <code>build.zig</code> that was generated by <code>zig
+          init-lib</code> works fine for making libraries to be used
+          with Zig code, but it's missing a few things that we need to
+        link from C.</p>
+      <p>First, Zig comes with a bunch of cool safety checks, but in
+      order to use them we need to include the compiler runtime. On
+      the other hand, if we're trying to build a tiny executable, we
+      might not want to include some extra symbols that Zig would use
+      to keep our stack safe.</p>
+      <p>Finally, modern linkers expect (and modern C compilers emit)
+        position-independent code. It's easy to tell Zig's build
+        system to do the same, by adding the line <code>lib.force_pic
+        = true;</code>. With that in place, our build.zig looks like
+        this:</p>
+      <pre class="code"><code>
+const Builder = @import("std").build.Builder;
+
+pub fn build(b: *Builder) void {
+    const target = b.standardTargetOptions(.{});
+    const optimize = b.standardOptimizeOption(.{});
+
+    const lib = b.addStaticLibrary(
+        .{
+            .name = "add",
+            .root_source_file = .{ .path = "src/main.zig" },
+            .target = target,
+            .optimize = optimize,
+        },
+    );
+
+    b.installArtifact(lib);
+
+    lib.force_pic = true;
+
+    var main_tests = b.addTest(.{
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    const test_step = b.step("test", "Run library tests");
+    test_step.dependOn(&main_tests.step);
+}
+      </code></pre>
+      <p>All set. Let's run <code>zig build</code> and try <code>make
+              main</code> and see what happens.</p>
+      <pre class="code"><code>
+$ make main
+gcc -o main.o -c -Wall -Werror -Wextra -Os -fPIE main.c
+gcc -o main -Wall -Werror -Wextra -Os -fPIE -Lzig-out/lib main.o -ladd
+/usr/bin/ld: ./libadd.a(/.../add.o): in function `std.fs.Dir.openFile':
+/home/nathan/lib/zig/std/fs.zig:639: undefined reference to `__zig_probe_stack'
+/usr/bin/ld: ./libadd.a(/.../add.o): in function `std.os.toPosixPath':
+/home/nathan/lib/zig/std/os.zig:4171: undefined reference to `__zig_probe_stack'
+/usr/bin/ld: ./libadd.a(/.../add.o): in function `std.fs.file.File.stat':
+/home/nathan/lib/zig/std/fs/file.zig:306: undefined reference to `__muloti4'
+/usr/bin/ld: /home/nathan/lib/zig/std/fs/file.zig:307: undefined reference to `__muloti4'
+/usr/bin/ld: /home/nathan/lib/zig/std/fs/file.zig:308: undefined reference to `__muloti4'
+/usr/bin/ld: ./libadd.a(/.../add.o): in function `std.debug.printLineFromFileAnyOs':
+/home/nathan/lib/zig/std/debug.zig:1007: undefined reference to `__zig_probe_stack'
+/usr/bin/ld: ./libadd.a(/.../add.o): in function `std.dwarf.DwarfInfo.getLineNumberInfo':
+/home/nathan/lib/zig/std/dwarf.zig:693: undefined reference to `__zig_probe_stack'
+collect2: error: ld returned 1 exit status
+make: *** [Makefile:17: main] Error 1
+      </code></pre>
+      <p>It blew up! That's because some of Zig's safety features
+        happen at run time. Specifically, the stack protection code
+        needs to be linked in. But what if our library is super small?
+        Sure, safety is nice, but maybe this has to fit into tiny
+        microcontrollers. Fortunately, Zig has us covered. Zig's
+        different <a href="https://ziglang.org/documentation/master/#Build-Mode">build
+          modes</a> let us choose different points on the safety/speed
+        tradeoff spectrum, and Zig's build system gives us access to the
+        current build mode.</p>
+      <p>The two things we're choosing between are bundling the
+        compiler's runtime, which means sticking those symbols in our
+        compiled .a file, and turning off the stack checking. One is
+        accessed with the <code>bundle_compiler_rt</code> property and
+        the other with <code>disable_stack_probing</code>. In the safe
+        build modes (ReleaseSafe and Debug) we don't mind the extra
+        bloat. Whereas in the unsafe build modes (ReleaseFast,
+        ReleaseSmall) we do. Let's add that sentiment to our build.zig
+        now.</p>
+      <pre class="code"><code>
+// build.zig
+const Builder = @import("std").build.Builder;
+
+pub fn build(b: *Builder) void {
+    const target = b.standardTargetOptions(.{});
+    const optimize = b.standardOptimizeOption(.{});
+
+    const lib = b.addStaticLibrary(
+        .{
+            .name = "add",
+            .root_source_file = .{ .path = "src/main.zig" },
+            .target = target,
+            .optimize = optimize,
+        },
+    );
+
+    b.installArtifact(lib);
+
+    switch (optimize) {
+        .Debug, .ReleaseSafe => lib.bundle_compiler_rt = true,
+        .ReleaseFast, .ReleaseSmall => lib.disable_stack_probing = true,
+    }
+    lib.force_pic = true;
+
+    var main_tests = b.addTest(.{
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    const test_step = b.step("test", "Run library tests");
+    test_step.dependOn(&main_tests.step);
+}
+      </code></pre>
+      <p>And now we can try it out. Note that since Zig's build system
+      caches its output, we have to touch <code>libadd.a</code> to
+      update its timestamp.</p>
+      <pre class="code"><code>
+$ make clean
+rm -f *.o *.a main
+$ zig build -Drelease-safe
+$ touch libadd.a 
+$ make main
+gcc -o main.o -c -Wall -Werror -Wextra -Os -fPIE main.c
+gcc -o main -Wall -Werror -Wextra -Os -fPIE -L. main.o -ladd
+$ zig build -Drelease-small
+$ make clean
+rm -f *.o *.a main
+$ zig build -Drelease-safe
+$ make main
+gcc -o main.o -c -Wall -Werror -Wextra -Os -fPIE main.c
+gcc -o main -Wall -Werror -Wextra -Os -fPIE -L. main.o -ladd
+$ ./main
+7 + 3 = 10
+$ zig build -Drelease-small
+$ touch libadd.a
+$ make main
+gcc -o main -Wall -Werror -Wextra -Os -fPIE -L. main.o -ladd
+$ ./main
+7 + 3 = 10
+      </code></pre>
+      <p>Hooray, it works, and in different build modes! Zig also has
+        an option to auto-generate header files
+        (called <code>emit_h</code>) but it's not entirely complete, and
+        since we already had a hand-written header file whose interface
+        we match exactly, it's not necessary. Feel free to take a look
+        at what it does generate, though.</p>
+      <h2>Incremental Rewriting</h2>
+      <p>Way back in the first paragraph, I said that incrementally
+        replacing a C library with Zig was the use case. But what
+        we've done so far is <em>entirely</em> replace a C library
+        with Zig. If we want to replace <em>part</em> of a library,
+        we'll need a larger library to start with. Let's take
+        our <code>libadd</code> and add some extra math in a separate
+        file, along with its interface.</p>
+      <pre class="code"><code>
+// mul.h
+#ifndef MUL_H
+#define MUL_H
+
+#include &lt;stdint.h&gt;
+
+// Multiply two numbers together and return the product.
+int32_t mul(int32_t a, int32_t b);
+
+#endif
+
+// mul.c
+#include "mul.h"
+
+int32_t mul(int32_t a, int32_t b)
+{
+    return a * b;
+}
+
+      </code></pre>
+      <p>That was easy. Now we just have to tweak our Makefile to
+        link <code>libadd.a</code> and <code>mul.o</code> into a larger
+        library. Let's call it <code>libi32math</code>.
+        <pre class="code"><code>
+# Makefile
+CC=gcc
+CFLAGS=-Wall -Werror -Wextra -Os -fPIE
+LD=ld
+LDFLAGS=-melf_x86_64 -r --whole-archive
+ZIGOUT=zig-out/lib
+LPATH=-L$(ZIGOUT) -L.
+
+.PHONY: clean libadd.a
+
+%.o:: %.c
+	$(CC) -o $@ -c $(CFLAGS) $^
+
+$(ZIGOUT)/libadd.a:
+	zig fmt build.zig src/*.zig
+	zig build
+
+libi32math.a: $(ZIGOUT)/libadd.a mul.o
+	$(LD) $(LDFLAGS) $(LPATH) -o $@ $^
+
+main: main.o libi32math.a
+	$(CC) -o main $(CFLAGS) $(LPATH) $&lt; -li32math
+
+clean:
+	rm -f *.o *.a main
+	rm -rf zig-out zig-cache
+        </code></pre>
+        <p>And just like that, we have a small part of
+          our <code>i32math</code> library written in Zig. Let's try
+          running a program that uses it.</p>
+        <pre class="code"><code>
+$ make clean
+rm -f *.o *.a main
+$ make main
+gcc -o main.o -c -Wall -Werror -Wextra -Os -fPIE main.c
+zig build
+touch libadd.a
+gcc -o mul.o -c -Wall -Werror -Wextra -Os -fPIE mul.c
+ld -melf_x86_64 -r --whole-archive -o libi32math.a libadd.a mul.o
+gcc -o main -Wall -Werror -Wextra -Os -fPIE -L. main.o -li32math
+$ ./main
+7 + 3 = 10
+7 * 3 = 21
+        </code></pre>
+      <p>If we wanted to migrate the library's build system over to
+        Zig's build system, we could. Zig can build C code and its
+        build system can do the job that Make is doing for our
+        little <code>i32math</code> library. That, however, is a
+        project for another day.</p>
+      <h2>Done</h2>
+      <p>That's all there is to it. The code for the completed i32math
+      library can be
+      found <a href="https://hg.sr.ht/~nmichaels/zig-extend-c-lib/browse">on
+      sourcehut</a>
+      or <a href="https://github.com/nmichaels/zig-extend-c-lib">github</a>.</p>
+    </div>
+    <div class="copyright">&copy; 2020 Nathan Michaels</div>
+  </body>
+</html>

          
M main.c +1 -1
@@ 5,7 5,7 @@ 
 
 int main(void)
 {
-    printf("7 + 3 = %"PRIi32"\n", add(7, 3));
+    printf("7 + 3 = %"PRIi32"\n", add_i32(7, 3));
     printf("7 * 3 = %"PRIi32"\n", mul(7, 3));
     return 0;
 }

          
M src/main.zig +2 -2
@@ 1,10 1,10 @@ 
 const std = @import("std");
 const testing = std.testing;
 
-export fn add(a: i32, b: i32) i32 {
+export fn add_i32(a: i32, b: i32) i32 {
     return a + b;
 }
 
 test "basic add functionality" {
-    testing.expect(add(3, 7) == 10);
+    try testing.expect(add_i32(3, 7) == 10);
 }