@@ 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);
@@ 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 <stdint.h>
+
+// 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 <stdio.h>
+#include <inttypes.h>
+#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) $< -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 <stdint.h>
+
+// 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) $< -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">© 2020 Nathan Michaels</div>
+ </body>
+</html>