Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const Optimize = std.builtin.OptimizeMode;

pub fn build(b: *std.Build) void {
var builder = Builder.init(b);
builder.c();
builder.examples();
builder.tests();
}
Expand All @@ -16,6 +17,7 @@ const Builder = struct {
const TestSuite = enum { integration, simulation };

b: *std.Build,
options: *std.Build.Step.Options,
target: Target,
optimize: Optimize,

Expand All @@ -37,11 +39,32 @@ const Builder = struct {

return .{
.b = b,
.options = b.addOptions(),
.target = target,
.optimize = optimize,
};
}

pub fn c(self: *Self) void {
const step = self.b.step("c", "Build core as a c library");
const static_lib = self.b.addLibrary(.{
.name = self.b.fmt("rtcore", .{}),
.root_module = self.module(.core),
.linkage = .static,
});

const exports = @import("core/exports.zig");
const write_files = self.b.addWriteFiles();
const header_path = write_files.add("rt_core.h", exports.generateHeader());

const install_header_file = self.b.addInstallHeaderFile(header_path, "rt_core.h");
const install_static_lib = self.b.addInstallArtifact(static_lib, .{});

install_header_file.step.dependOn(&write_files.step);
step.dependOn(&install_static_lib.step);
step.dependOn(&install_header_file.step);
}

pub fn examples(self: *Self) void {
const step = self.b.step("example", "Run an example");

Expand Down Expand Up @@ -89,6 +112,7 @@ const Builder = struct {
.target = self.target,
.optimize = self.optimize,
});

step.dependOn(&self.b.addRunArtifact(compile).step);
}

Expand Down Expand Up @@ -142,6 +166,10 @@ const Builder = struct {
}

fn addImport(self: *Self, compile: *std.Build.Step.Compile, comptime package: Package) void {
compile.root_module.addImport(@tagName(package), self.b.modules.getPtr(@tagName(package)).?.*);
compile.root_module.addImport(@tagName(package), self.module(package));
}

fn module(self: *Self, comptime package: Package) *std.Build.Module {
return self.b.modules.getPtr(@tagName(package)).?.*;
}
};
54 changes: 52 additions & 2 deletions core/System.zig
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const std = @import("std");

// TODO: Add storage interface.

pub const Clock = struct {
const Self = @This();

Expand All @@ -15,5 +13,57 @@ pub const Clock = struct {

pub const Rng = std.Random;

pub const SimpleClock = struct {
pub const Callback = *const fn () callconv(.c) u64;

const Self = @This();

monotonicMicrosFn: Callback,

pub fn init(monotonicMicrosFn: Callback) Self {
return .{
.monotonicMicrosFn = monotonicMicrosFn,
};
}

pub fn monotonicMicros(ptr: *anyopaque) u64 {
const self: *Self = @ptrCast(@alignCast(ptr));
return self.monotonicMicrosFn();
}

pub fn clock(self: *Self) Clock {
return .{
.ptr = self,
.monotonicMicrosFn = monotonicMicros,
};
}
};

pub const SimpleRng = struct {
pub const Callback = *const fn (buf: [*]u8, length: usize) callconv(.c) void;

const Self = @This();

rngFillFn: Callback,

pub fn init(rngFillFn: Callback) Self {
return .{
.rngFillFn = rngFillFn,
};
}

pub fn rngFill(ptr: *anyopaque, buf: []u8) void {
const self: *Self = @ptrCast(@alignCast(ptr));
self.rngFillFn(buf.ptr, buf.len);
}

pub fn rng(self: *Self) Rng {
return .{
.ptr = self,
.fillFn = rngFill,
};
}
};

clock: Clock,
rng: Rng,
187 changes: 187 additions & 0 deletions core/exports.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! This file is not part of the core module and therefore can contain non-freestanding code.
//! Be aware that this file will be imported into the build executable in order to generate the header file.

const builtin = @import("builtin");
const std = @import("std");
const lib = @import("lib.zig");

const Allocator = std.mem.Allocator;
const Gpa = std.heap.GeneralPurposeAllocator(.{});

const Self = @This();

var gpa: ?Gpa = null;
var allocator: ?Allocator = null;
var clock: lib.System.SimpleClock = undefined;
var rng: lib.System.SimpleRng = undefined;
var system: lib.System = undefined;

// Exported structs and functions.

pub const Error = enum(c_int) {
none = 0,
missing_allocator = 1,
out_of_memory = 2,
leaked_memory = 3,
unknown = 255,
};

pub fn libInit(
monotonicMicros: lib.System.SimpleClock.Callback,
rngFill: lib.System.SimpleRng.Callback,
) callconv(.c) Error {
if (allocator != null) return .missing_allocator;

gpa = Gpa{};
allocator = gpa.?.allocator();
clock = lib.System.SimpleClock.init(monotonicMicros);
rng = lib.System.SimpleRng.init(rngFill);
system = lib.System{
.clock = clock.clock(),
.rng = rng.rng(),
};

return .none;
}

pub fn libDeinit() callconv(.c) Error {
if (gpa == null or allocator == null) return .none;

if (gpa) |*g| {
if (g.deinit() == .leak) {
return .leaked_memory;
}
}

return .none;
}

pub fn makeNode(node_ptr: **anyopaque) callconv(.c) Error {
const ally = allocator orelse return .missing_allocator;

const node = ally.create(lib.Node) catch |e| return convertError(Allocator.Error, e);
errdefer ally.destroy(node);

node_ptr.* = node;
node.* = lib.Node.init(
ally,
&system,
null,
.{},
) catch |e| return convertError(lib.Node.Error, e);

return .none;
}

fn convertError(comptime E: type, e: E) Error {
if (E == lib.Node.Error) {
return switch (e) {
error.OutOfMemory => .out_of_memory,
else => .unknown,
};
}

return .unknown;
}

// Perform exports.
// This is currently also adding these symbols to the build executable.
// As far as I can tell it won't cause any issues and can probably be changed later.
comptime {
for (@typeInfo(@This()).@"struct".decls) |declaration| {
const field = @field(@This(), declaration.name);
const info = @typeInfo(@TypeOf(field));

if (info != .@"fn") continue;
if (std.mem.eql(u8, declaration.name, "generateHeader")) continue;

const function: *const anyopaque = @ptrCast(&field);
const export_options = std.builtin.ExportOptions{
.name = deriveName(declaration.name),
.linkage = .strong,
};

@export(function, export_options);
}
}

/// This needs to be public for use in the build c step.
pub fn generateHeader() []const u8 {
comptime var header: []const u8 =
\\#ifndef RT_CORE_H
\\#define RT_CORE_H
\\
\\#include <stddef.h>
\\#include <stdint.h>
\\
\\
;

inline for (@typeInfo(Self).@"struct".decls) |declaration| {
const field = @field(Self, declaration.name);
const info = @typeInfo(@TypeOf(field));

if (info != .@"fn") continue;
if (comptime std.mem.eql(u8, declaration.name, "generateHeader")) continue;

const function = info.@"fn";
const name = comptime deriveName(declaration.name);
const return_type = switch (function.return_type.?) {
Error => "int",
*anyopaque => "void*",
c_int => "int",
else => @typeName(function.return_type.?),
};

comptime var forward_declaration: []const u8 = return_type ++ " " ++ name ++ "(";

inline for (function.params, 0..) |param, i| {
const param_type = switch (param.type.?) {
lib.System.SimpleClock.Callback => "int64_t (*monotonic_micros)(void)",
lib.System.SimpleRng.Callback => "void (*rng_fill)(uint8_t* buf, size_t length)",
*anyopaque => "void*",
**anyopaque => "void**",
c_int => "int",
else => @typeName(param.type.?),
};

forward_declaration = forward_declaration ++ param_type;

if (i != function.params.len - 1) {
forward_declaration = forward_declaration ++ ", ";
}
}

forward_declaration = forward_declaration ++ ");\n";

header = header ++ forward_declaration;
}

header = header ++
\\
\\#endif
\\
;

return header;
}

fn deriveName(comptime name: []const u8) []const u8 {
var c_name: []const u8 = "rt_";

inline for (name) |char| {
if (std.ascii.isUpper(char)) {
c_name = c_name ++ .{ '_', std.ascii.toLower(char) };
} else {
c_name = c_name ++ .{char};
}
}

const arch = builtin.target.cpu.arch;

if (arch == .wasm32 or arch == .wasm64) {
return name;
} else {
return c_name;
}
}
3 changes: 3 additions & 0 deletions core/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ comptime {
const builtin = @import("builtin");
const std = @import("std");

// We import the file so that the exports actually run for this module.
_ = @import("exports.zig");

// Ensure unit tests are ran by referencing relevant files.
if (builtin.is_test) {
// Private files must be specifically enumerated.
Expand Down
5 changes: 2 additions & 3 deletions examples/tcp.zig
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ pub fn main() !void {
defer _ = gpa.deinit();

const ally = gpa.allocator();
var clock = try io.os.Clock.init();
var rng = io.os.Rng.init();
var clock = try io.system.Clock.init();
var system = core.System{
.clock = clock.clock(),
.rng = rng.rng(),
.rng = std.crypto.random,
};

var node = try core.Node.init(ally, &system, null, .{});
Expand Down
2 changes: 1 addition & 1 deletion io/lib.zig
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub const driver = @import("driver.zig");
pub const os = @import("os.zig");
pub const system = @import("system.zig");
5 changes: 0 additions & 5 deletions io/os.zig

This file was deleted.

26 changes: 0 additions & 26 deletions io/os/Clock.zig

This file was deleted.

13 changes: 0 additions & 13 deletions io/os/Rng.zig

This file was deleted.

Loading