diff --git a/README.md b/README.md index a6f6a46..9cbcecb 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,8 @@ int main(int argc, char *argv[]) { * [apps/rust-hello](apps/rust-hello) Rust hello world program (note, the version of rust installed by brew on mac has issues, use the official rust installer from https://rust-lang.org/learn/get-started/) * [apps/zig-mandel](apps/zig-mandel) Zig ASCII mandelbrot generator program * [apps/zigtris](apps/zigtris) Zig Tetris (https://github.com/ringtailsoftware/zigtris) + * [apps/zigalloc](apps/zigalloc) Demonstration of using extram with zig allocator + * [apps/heap](apps/heap) Demonstration of `malloc()` on extram in C ## Quickstart (docker) diff --git a/apps/Makefile b/apps/Makefile index af42dd4..e5bbdb3 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -12,6 +12,7 @@ all: (cd maze && make) (cd heap && make) (cd memtest && make) + (cd zigalloc && make) clean: (cd sketch && make clean) @@ -27,4 +28,5 @@ clean: (cd maze && make clean) (cd heap && make clean) (cd memtest && make clean) + (cd zigalloc && make clean) diff --git a/apps/zigalloc/Makefile b/apps/zigalloc/Makefile new file mode 100644 index 0000000..43549f4 --- /dev/null +++ b/apps/zigalloc/Makefile @@ -0,0 +1,18 @@ +PROJECT=zigalloc +TOPDIR=../.. + +HEAP_SIZE=$(shell echo "1024 * 1024 * 8" | bc) +HOST_EXTRA=-e ${HEAP_SIZE} -i 100000 + +all: + @# zig's objcopy is broken, so use external tool + @# https://ziggit.dev/t/addobjcopy-producing-zero-padding-at-start-of-binary/13384 + zig build -Dheapsize=${HEAP_SIZE} && ${PREFIX}objcopy zig-out/bin/${PROJECT} -O binary ${PROJECT}.bin + +clean: clean_common + rm -rf zig-out .zig-cache + +test: all + ${TOPDIR}/hosts/host/host ${HOST_EXTRA} ${PROJECT}.bin + +include ${TOPDIR}/apps/makefile.common diff --git a/apps/zigalloc/build.zig b/apps/zigalloc/build.zig new file mode 100644 index 0000000..ee02aa0 --- /dev/null +++ b/apps/zigalloc/build.zig @@ -0,0 +1,56 @@ +const std = @import("std"); +const CrossTarget = @import("std").zig.CrossTarget; +const Target = @import("std").Target; +const Feature = @import("std").Target.Cpu.Feature; + +pub fn build(b: *std.Build) void { + var options = b.addOptions(); + const heapsize = b.option(u32, "heapsize", "heap size in bytes") orelse 0; // -Dheapsize=u32 + options.addOption(u32, "heapsize", heapsize); + + const features = Target.riscv.Feature; + var disabled_features = Feature.Set.empty; + var enabled_features = Feature.Set.empty; + + // disable all CPU extensions + disabled_features.addFeature(@intFromEnum(features.a)); + disabled_features.addFeature(@intFromEnum(features.c)); + disabled_features.addFeature(@intFromEnum(features.d)); + disabled_features.addFeature(@intFromEnum(features.e)); + disabled_features.addFeature(@intFromEnum(features.f)); + // except multiply + enabled_features.addFeature(@intFromEnum(features.m)); + + const target = b.resolveTargetQuery(.{ + .cpu_arch = Target.Cpu.Arch.riscv32, + .os_tag = Target.Os.Tag.freestanding, + .abi = Target.Abi.none, + .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32}, + .cpu_features_sub = disabled_features, + .cpu_features_add = enabled_features + }); + + const exe = b.addExecutable(.{ + .name = "zigalloc", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = .ReleaseSmall, + }), + }); + exe.root_module.addOptions("buildopts", options); + + b.installArtifact(exe); + + exe.addAssemblyFile(b.path("../crt0.S")); + exe.setLinkerScript(b.path("../linker.ld")); + exe.addIncludePath(b.path("../../common")); + + const bin = b.addObjCopy(exe.getEmittedBin(), .{ + .format = .bin, + }); + bin.step.dependOn(&exe.step); + + const copy_bin = b.addInstallBinFile(bin.getOutput(), "zigalloc.bin"); + b.default_step.dependOn(©_bin.step); +} diff --git a/apps/zigalloc/src/console.zig b/apps/zigalloc/src/console.zig new file mode 100644 index 0000000..c5ae395 --- /dev/null +++ b/apps/zigalloc/src/console.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +extern fn console_write(data: [*]const u8, len: usize) void; +var wbuf:[4096]u8 = undefined; +var cw = ConsoleWriter.init(&wbuf); +const uvm = @import("uvm.zig"); + +pub const WriteError = error{ Unsupported, NotConnected }; + +pub const ConsoleWriter = struct { + interface: std.Io.Writer, + err: ?WriteError = null, + + fn drain(w: *std.Io.Writer, data: []const []const u8, splat: usize) std.Io.Writer.Error!usize { + var ret: usize = 0; + + const b = w.buffered(); + uvm.print(b); + _ = w.consume(b.len); + + for (data) |d| { + uvm.print(d); + ret += d.len; + } + + const pattern = data[data.len - 1]; + for (0..splat) |_| { + uvm.print(pattern); + ret += pattern.len; + } + + return ret; + } + + pub fn init(buf: []u8) ConsoleWriter { + return ConsoleWriter{ + .interface = .{ + .buffer = buf, + .vtable = &.{ + .drain = drain, + }, + }, + }; + } +}; + +pub fn getWriter() *std.Io.Writer { + return &cw.interface; +} + diff --git a/apps/zigalloc/src/main.zig b/apps/zigalloc/src/main.zig new file mode 100644 index 0000000..d884980 --- /dev/null +++ b/apps/zigalloc/src/main.zig @@ -0,0 +1,18 @@ +const uvm = @import("uvm.zig"); +const console = @import("console.zig").getWriter(); + +fn submain() !void { + try console.print("Hello world\n", .{}); + try console.flush(); + + const foo = try uvm.allocator().dupe(u8, "copy me"); + try console.print("dupe={s}\n", .{foo}); + try console.flush(); + +} + +export fn main() void { + _ = submain() catch { + uvm.println("Caught err"); + }; +} diff --git a/apps/zigalloc/src/uvm.zig b/apps/zigalloc/src/uvm.zig new file mode 100644 index 0000000..15ae3ec --- /dev/null +++ b/apps/zigalloc/src/uvm.zig @@ -0,0 +1,65 @@ +const uvm32 = @cImport({ + @cDefine("USE_MAIN", "1"); + @cInclude("uvm32_target.h"); +}); +const buildopts = @import("buildopts"); +const std = @import("std"); + +const extram:[*]u8 = @ptrFromInt(uvm32.UVM32_EXTRAM_BASE); +const extram_len = buildopts.heapsize; + +var fba:std.heap.FixedBufferAllocator = .init(extram[0..extram_len]); + +pub fn allocator() std.mem.Allocator { + return fba.allocator(); +} + +pub inline fn syscall(id: u32, param1: u32, param2: u32) u32 { + var val: u32 = undefined; + asm volatile ("ecall" + : [val] "={a1}" (val), + : [param1] "{a0}" (param1), [param2] "{a1}" (param2), + [id] "{a7}" (id), + : .{ .memory = true }); + return val; +} + +pub inline fn getc() ?u8 { + const key = syscall(uvm32.UVM32_SYSCALL_GETC, 0, 0); + if (key == 0xFFFFFFFF) { + return null; + } else { + return @truncate(key); + } +} + +pub inline fn millis() u32 { + return syscall(uvm32.UVM32_SYSCALL_MILLIS, 0, 0); +} + +// dupeZ would be better, but want to avoid using an allocator +// this is of course, unsafe... +var termination_buf:[128]u8 = undefined; + +pub inline fn print(m: []const u8) void { + @memcpy(termination_buf[0..m.len], m); + termination_buf[m.len] = 0; + const s = termination_buf[0..m.len :0]; + _ = syscall(uvm32.UVM32_SYSCALL_PRINT, @intFromPtr(s.ptr), 0); +} + +pub inline fn println(m: []const u8) void { + @memcpy(termination_buf[0..m.len], m); + termination_buf[m.len] = 0; + const s = termination_buf[0..m.len :0]; + _ = syscall(uvm32.UVM32_SYSCALL_PRINTLN, @intFromPtr(s.ptr), 0); +} + +pub inline fn yield() void { + _ = syscall(uvm32.UVM32_SYSCALL_YIELD, 0, 0); +} + +pub inline fn putc(c:u8) void { + _ = syscall(uvm32.UVM32_SYSCALL_PUTC, c, 0); +} + diff --git a/precompiled/memtest.bin b/precompiled/memtest.bin new file mode 100755 index 0000000..f2ee777 Binary files /dev/null and b/precompiled/memtest.bin differ diff --git a/precompiled/zigalloc.bin b/precompiled/zigalloc.bin new file mode 100755 index 0000000..b70669c Binary files /dev/null and b/precompiled/zigalloc.bin differ