commit 736215ffa80014359c33be738c9442b666396869 Author: David Gonzalez Martin Date: Mon Jul 10 23:56:04 2023 -0600 First commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..4589729 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# Default behavior, if core.autocrlf is unset. +* text=auto + +# Files to be converted to native line endings on checkout. +*.cpp text +*.h text + +# Text files to always have LF (unix) line endings on checkout. +*.zig text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..76c5590 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + pull_request: + paths: + - ".github/workflows/ci.yml" + - "**.zig" + push: + paths: + - ".github/workflows/ci.yml" + - "**.zig" + branches: + - main + schedule: + - cron: "0 0 * * *" +concurrency: + # Cancels pending runs when a PR gets updated. + group: ${{ github.head_ref || github.run_id }}-${{ github.actor }} + cancel-in-progress: true + +jobs: + build: + strategy: + matrix: + os: [ + ubuntu-latest, + ] + runs-on: ${{ matrix.os }} + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up Zig + uses: goto-bus-stop/setup-zig@v2 + with: + version: master + - name: Zig environment variables + run: zig env + - name: Test + run: zig build test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c82b07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +zig-cache +zig-out diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..dcb261d --- /dev/null +++ b/build.zig @@ -0,0 +1,70 @@ +const std = @import("std"); + +// Although this function looks imperative, note that its job is to +// declaratively construct a build graph that will be executed by an external +// runner. +pub fn build(b: *std.Build) void { + // Standard target options allows the person running `zig build` to choose + // what target to build for. Here we do not override the defaults, which + // means any target is allowed, and the default is native. Other options + // for restricting supported target set are available. + const target = b.standardTargetOptions(.{}); + + // Standard optimization options allow the person running `zig build` to select + // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not + // set a preferred release mode, allowing the user to decide how to optimize. + const optimize = b.standardOptimizeOption(.{}); + + const exe = b.addExecutable(.{ + .name = "compiler", + // In this case the main source file is merely a path, however, in more + // complicated build scripts, this could be a generated file. + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + // This declares intent for the executable to be installed into the + // standard location when the user invokes the "install" step (the default + // step when running `zig build`). + b.installArtifact(exe); + + // This *creates* a Run step in the build graph, to be executed when another + // step is evaluated that depends on it. The next line below will establish + // such a dependency. + const run_cmd = b.addRunArtifact(exe); + + // By making the run step depend on the install step, it will be run from the + // installation directory rather than directly from within the cache directory. + // This is not necessary, however, if the application depends on other installed + // files, this ensures they will be present and in the expected location. + run_cmd.step.dependOn(b.getInstallStep()); + + // This allows the user to pass arguments to the application in the build + // command itself, like this: `zig build run -- arg1 arg2 etc` + if (b.args) |args| { + run_cmd.addArgs(args); + } + + // This creates a build step. It will be visible in the `zig build --help` menu, + // and can be selected like this: `zig build run` + // This will evaluate the `run` step rather than the default, which is "install". + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // Creates a step for unit testing. This only builds the test executable + // but does not run it. + const unit_tests = b.addTest(.{ + .root_source_file = .{ .path = "src/main.zig" }, + .target = target, + .optimize = optimize, + }); + + const run_unit_tests = b.addRunArtifact(unit_tests); + + // Similar to creating the run step earlier, this exposes a `test` step to + // the `zig build --help` menu, providing a way for the user to request + // running the unit tests. + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_unit_tests.step); +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..b62e3e4 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,124 @@ +const std = @import("std"); +const page_size = std.mem.page_size; +const assert = std.debug.assert; +const expectEqual = std.testing.expectEqual; + +const Section = struct { + content: []align(page_size) u8, + index: usize = 0, +}; + +const Image = struct { + text: Section, + rodata: Section, + data: Section, + entry_point: u32 = 0, + + fn create() !Image { + return Image{ + .text = .{ .content = try mmap(page_size, .{ .executable = true }) }, + .rodata = .{ .content = try mmap(page_size, .{ .executable = false }) }, + .data = .{ .content = try mmap(page_size, .{ .executable = false }) }, + }; + } + + inline fn mmap(size: usize, flags: packed struct { + executable: bool, + }) ![]align(page_size) u8 { + const protection_flags = std.os.PROT.READ | std.os.PROT.WRITE | if (flags.executable) std.os.PROT.EXEC else 0; + const mmap_flags = std.os.MAP.ANONYMOUS | std.os.MAP.PRIVATE; + + return std.os.mmap(null, size, protection_flags, mmap_flags, -1, 0); + } + + fn appendCode(image: *Image, code: []const u8) void { + const destination = image.text.content[image.text.index..][0..code.len]; + @memcpy(destination, code); + image.text.index += code.len; + } + + fn getEntryPoint(image: *const Image, comptime Function: type) *const Function { + comptime { + assert(@typeInfo(Function) == .Fn); + } + + assert(image.text.content.len > 0); + return @as(*const Function, @ptrCast(&image.text.content[image.entry_point])); + } +}; + +const Rex = enum(u8) { + b = upper_4_bits | (1 << 0), + x = upper_4_bits | (1 << 1), + r = upper_4_bits | (1 << 2), + w = upper_4_bits | (1 << 3), + + const upper_4_bits = 0b100_0000; +}; + +const prefix_rex_w = [1]u8{@intFromEnum(Rex.w)}; +const prefix_16_bit_operand = [1]u8{0x66}; + +const ret = [1]u8{0xc3}; +const movabs_to_register_a = [1]u8{0xb8}; + +inline fn intToArrayOfBytes(integer: anytype) [@sizeOf(@TypeOf(integer))]u8 { + comptime { + assert(@typeInfo(@TypeOf(integer)) == .Int); + } + + return @as([@sizeOf(@TypeOf(integer))]u8, @bitCast(integer)); +} + +inline fn movU16ToA(integer: u16) [4]u8 { + return prefix_16_bit_operand ++ movabs_to_register_a ++ intToArrayOfBytes(integer); +} + +inline fn movU32ToA(integer: u32) [5]u8 { + return movabs_to_register_a ++ intToArrayOfBytes(integer); +} + +inline fn movU64ToA(integer: u64) [10]u8 { + return prefix_rex_w ++ movabs_to_register_a ++ intToArrayOfBytes(integer); +} + +test "ret void" { + var image = try Image.create(); + image.appendCode(&ret); + + const function_pointer = image.getEntryPoint(fn () callconv(.C) void); + function_pointer(); +} + +test "ret unsigned integer 16-bit" { + var image = try Image.create(); + const expected_number = 0xffff; + image.appendCode(&movU16ToA(expected_number)); + image.appendCode(&ret); + + const function_pointer = image.getEntryPoint(fn () callconv(.C) u16); + const result = function_pointer(); + try expectEqual(result, expected_number); +} + +test "ret unsigned integer 32-bit" { + var image = try Image.create(); + const expected_number = 0xffff_ffff; + image.appendCode(&movU32ToA(expected_number)); + image.appendCode(&ret); + + const function_pointer = image.getEntryPoint(fn () callconv(.C) u32); + const result = function_pointer(); + try expectEqual(result, expected_number); +} + +test "ret unsigned integer 64-bit" { + var image = try Image.create(); + const expected_number = 0xffff_ffff_ffff_ffff; + image.appendCode(&movU64ToA(expected_number)); + image.appendCode(&ret); + + const function_pointer = image.getEntryPoint(fn () callconv(.C) u64); + const result = function_pointer(); + try expectEqual(result, expected_number); +}