From 3cd05f8a1b6fdb39f0ad5aaea4665af908c7840a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 15 May 2025 13:11:55 -0700 Subject: [PATCH 01/13] init spork8 backend --- lib/std/Build/Configuration.zig | 2 + lib/std/Target.zig | 33 +++ lib/std/debug.zig | 2 +- lib/std/lang.zig | 3 + lib/std/start.zig | 4 +- lib/std/zig.zig | 1 + lib/std/zig/llvm/Builder.zig | 2 + src/Compilation/Config.zig | 2 +- src/Zcu.zig | 5 + src/codegen.zig | 3 + src/codegen/llvm.zig | 1 + src/codegen/spork8/CodeGen.zig | 367 ++++++++++++++++++++++++++++++++ src/codegen/spork8/Mir.zig | 46 ++++ src/dev.zig | 16 ++ src/link.zig | 10 + src/link/Spork8.zig | 188 ++++++++++++++++ src/target.zig | 5 + 17 files changed, 687 insertions(+), 3 deletions(-) create mode 100644 src/codegen/spork8/CodeGen.zig create mode 100644 src/codegen/spork8/Mir.zig create mode 100644 src/link/Spork8.zig diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index f0337507418a420489755e6eebf4607bbe19054c..6eea9c9fffd24b817989fcaa2045ac1b39982501 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -2341,6 +2341,7 @@ pub const TargetQuery = struct { sheb, sparc, sparc64, + spork8, spirv32, spirv64, thumb, @@ -2442,6 +2443,7 @@ pub const TargetQuery = struct { elf, hex, macho, + spork8, plan9, raw, spirv, diff --git a/lib/std/Target.zig b/lib/std/Target.zig index b28ebd50ecaf35a117a5a84c5fc2a4cc4a81e086..659caa75b5ed7f0cbd473f5f995cad5386d75c1d 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -104,6 +104,7 @@ pub const Os = struct { .plan9 => arch.plan9Ext(), else => switch (arch) { .wasm32, .wasm64 => ".wasm", + .spork8 => ".spork8", else => "", }, }; @@ -795,6 +796,24 @@ pub const xcore = @import("Target/xcore.zig"); pub const xtensa = @import("Target/xtensa.zig"); pub const z80 = @import("Target/generic.zig"); +pub const spork8 = struct { + pub const Feature = enum {}; + pub const featureSet = Cpu.Feature.FeatureSetFns(Feature).featureSet; + pub const featureSetHas = Cpu.Feature.FeatureSetFns(Feature).featureSetHas; + pub const featureSetHasAny = Cpu.Feature.FeatureSetFns(Feature).featureSetHasAny; + pub const featureSetHasAll = Cpu.Feature.FeatureSetFns(Feature).featureSetHasAll; + + pub const cpu = struct { + pub const generic: Cpu.Model = .{ + .name = "generic", + .llvm_name = null, + .features = .empty, + }; + }; + + pub const all_features: [0]Cpu.Feature = .{}; +}; + pub const Abi = enum { none, gnu, @@ -1046,6 +1065,7 @@ pub const ObjectFormat = enum { hex, /// The Mach object format used by macOS and other Apple platforms. macho, + spork8, /// The a.out format used by Plan 9 from Bell Labs. plan9, /// Machine code with no metadata. @@ -1061,6 +1081,7 @@ pub const ObjectFormat = enum { .coff => ".obj", .elf, .macho, .wasm => ".o", .hex => ".ihex", + .spork8 => ".spork8", .plan9 => arch.plan9Ext(), .raw => ".bin", .spirv => ".spv", @@ -1075,6 +1096,7 @@ pub const ObjectFormat = enum { else => switch (arch) { .spirv32, .spirv64 => .spirv, .wasm32, .wasm64 => .wasm, + .spork8 => .spork8, else => .elf, }, }; @@ -1124,6 +1146,7 @@ pub fn toElfMachine(target: *const Target) std.elf.EM { .spirv64, .wasm32, .wasm64, + .spork8, => .NONE, }; } @@ -1191,6 +1214,7 @@ pub fn toCoffMachine(target: *const Target) std.coff.IMAGE.FILE.MACHINE { .xcore, .xtensa, .xtensaeb, + .spork8, => .UNKNOWN, }; } @@ -1395,6 +1419,7 @@ pub const Cpu = struct { sheb, sparc, sparc64, + spork8, spirv32, spirv64, thumb, @@ -1447,6 +1472,7 @@ pub const Cpu = struct { xcore, xtensa, z80, + spork8, }; pub inline fn family(arch: Arch) Family { @@ -1485,6 +1511,7 @@ pub const Cpu = struct { .x86_16, .x86, .x86_64 => .x86, .xcore => .xcore, .xtensa, .xtensaeb => .xtensa, + .spork8 => .spork8, }; } @@ -1749,6 +1776,7 @@ pub const Cpu = struct { .sparc, .sparc64, .xtensaeb, + .spork8, => .big, // GPU endianness is opaque. For now, assume little endian. @@ -2971,6 +2999,7 @@ pub fn ptrBitWidth_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) u16 { .avr, .msp430, .x86_16, + .spork8, => 16, .ez80, @@ -3536,6 +3565,7 @@ pub fn cTypeAlignment(target: *const Target, c_type: CType) ?u16 { switch (target.cpu.arch) { .avr, .ez80, + .spork8, => return 1, .x86 => switch (target.os.tag) { .windows, .uefi => switch (c_type) { @@ -3634,6 +3664,7 @@ pub fn cTypeAlignment(target: *const Target, c_type: CType) ?u16 { .avr, .ez80, + .spork8, => unreachable, // Handled above. }), ); @@ -3643,6 +3674,7 @@ pub fn cMaxIntAlignment(target: *const Target) u16 { return switch (target.cpu.arch) { .avr, .ez80, + .spork8, => 1, .msp430, @@ -3788,6 +3820,7 @@ pub fn cCallingConvention(target: *const Target) ?std.builtin.CallingConvention .nvptx, .nvptx64 => .nvptx_device, .spirv32, .spirv64 => .spirv_device, .ez80 => .ez80_cet, + .spork8 => .naked, }; } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index ca1bf3f6edf8a79fb2459fe481f311ec7f50c4b1..768b6bca673d1fb22415b5de126e006ed2adbe13 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -75,7 +75,7 @@ pub fn TargetInfo(os: std.Target.Os.Tag, arch: std.Target.Cpu.Arch) type { else => @import("debug/SelfInfo/Elf.zig"), }, .macho => @import("debug/SelfInfo/MachO.zig"), - .plan9, .spirv, .wasm => void, + .plan9, .spirv, .wasm, .spork8 => void, .c, .hex, .raw => unreachable, }; } diff --git a/lib/std/lang.zig b/lib/std/lang.zig index 1c47d2d8919dd40b950d389c98a88d26c3e4dcb4..471b627ea661d00a86705760a82abccebd7a1d20 100644 --- a/lib/std/lang.zig +++ b/lib/std/lang.zig @@ -1319,6 +1319,9 @@ pub const CompilerBackend = enum(u64) { /// The reference implementation self-hosted compiler of Zig, using the /// loongarch backend. stage2_loongarch = 13, + /// The Zig Software Foundation self-hosted implementation of Zig. Backend + /// originally contributed by Ben Anderman in 2026. + zsf_spork8 = 14, _, }; diff --git a/lib/std/start.zig b/lib/std/start.zig index 228b51e08ef9cd2b0eaaa8d8f5e974e1613076d7..470f60e1a198ade629f0a55a68207feac9ab6c65 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -19,7 +19,9 @@ comptime { // decls there get run. _ = root; - if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { + if (builtin.cpu.arch == .spork8) { + // always freestanding, even exes + } else if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { const dll_main_crt_startup = if (builtin.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup"; if (native_os == .windows and !builtin.link_libc and !@hasDecl(root, dll_main_crt_startup)) { @export(&DllMainCRTStartup, .{ .name = dll_main_crt_startup }); diff --git a/lib/std/zig.zig b/lib/std/zig.zig index e18a6414c6e9ba130640e061307c68b16ff02456..edf19d1848c27eb51e703d7a632e8e8cdc50cdc9 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -259,6 +259,7 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}), .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}), .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}), + .spork8 => return std.fmt.allocPrint(allocator, "{s}.spork8", .{root_name}), .plan9 => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ diff --git a/lib/std/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index 34e1bf7e6fe3c2644cb2b79449ba276aae9c6a58..0c827c0868b865a5a92c136ffeadd3f290a6d7ab 100644 --- a/lib/std/zig/llvm/Builder.zig +++ b/lib/std/zig/llvm/Builder.zig @@ -162,6 +162,7 @@ pub fn tripleForTarget(allocator: Allocator, target: *const std.Target) ![]const .propeller, .sh, .sheb, + .spork8, .x86_16, .xtensaeb, => unreachable, // Gated by hasLlvmSupport(). @@ -576,6 +577,7 @@ pub const DataLayout = struct { .sheb, .x86_16, .xtensaeb, + .spork8, => unreachable, }; } diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 85915ded1b97d4e324a1cebbdb1385758ebea6f0..9b97d4a2cbafbc2fc8c964ea4ba8aa0159483625 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -501,7 +501,7 @@ pub fn resolve(options: Options) ResolveError!Config { .windows, .uefi => .code_view, else => .{ .dwarf = .@"32" }, }, - .spirv, .hex, .raw, .plan9 => .strip, + .spirv, .hex, .raw, .plan9, .spork8 => .strip, }; }; diff --git a/src/Zcu.zig b/src/Zcu.zig index 1d1eadffc5545c5de94e84fe6258f28773b3193b..f77f59a478c419b6517a69becac310f9fe444ad8 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -4042,6 +4042,7 @@ pub fn atomicPtrAlignment( const target = zcu.getTarget(); const max_atomic_bits: u16 = switch (target.cpu.arch) { .ez80, + .spork8, => 8, .aarch64, @@ -4697,6 +4698,10 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum) .loongarch64_lp64, .loongarch32_ilp32, .naked => true, else => false, }, + .zsf_spork8 => switch (cc) { + .naked => true, + else => false, + }, }; if (!backend_ok) return .{ .bad_backend = backend }; return .ok; diff --git a/src/codegen.zig b/src/codegen.zig index a505721eccea3794c4ff9df5a7018d10e8639527..d54901cbd4253bacb0423ec09204b4d2fcfadb95 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -38,6 +38,7 @@ fn devFeatureForBackend(backend: std.lang.CompilerBackend) dev.Feature { .stage2_powerpc => unreachable, .stage2_riscv64 => .riscv64_backend, .stage2_sparc64 => .sparc64_backend, + .zsf_spork8 => .spork8_backend, .stage2_spirv => .spirv_backend, .stage2_wasm => .wasm_backend, .stage2_x86 => .x86_backend, @@ -58,6 +59,7 @@ fn importBackend(comptime backend: std.lang.CompilerBackend) type { .stage2_riscv64 => @import("codegen/riscv64/CodeGen.zig"), .stage2_sparc64 => @import("codegen/sparc64/CodeGen.zig"), .stage2_spirv => @import("codegen/spirv/CodeGen.zig"), + .zsf_spork8 => @import("codegen/spork8/CodeGen.zig"), .stage2_wasm => @import("codegen/wasm/CodeGen.zig"), .stage2_x86, .stage2_x86_64 => @import("codegen/x86_64/CodeGen.zig"), _ => unreachable, @@ -107,6 +109,7 @@ pub const AnyMir = union { wasm: if (dev.env.supports(.wasm_backend)) @import("codegen/wasm/Mir.zig") else noreturn, c: if (dev.env.supports(.c_backend)) @import("codegen/c.zig").Mir else noreturn, spirv: if (dev.env.supports(.spirv_backend)) @import("codegen/spirv/Mir.zig") else noreturn, + spork8: if (dev.env.supports(.spork8_backend)) @import("codegen/spork8/Mir.zig") else noreturn, pub inline fn tag(comptime backend: std.lang.CompilerBackend) []const u8 { return switch (backend) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a0cb2ac891f08409e40c780c1ec4049057dc887c..0de67cfaaccf5f63aec1140a8f7c4fdbce8f49e3 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4720,6 +4720,7 @@ pub fn initializeLLVMTarget(io: Io, arch: std.Target.Cpu.Arch) void { .propeller, .sh, .sheb, + .spork8, .x86_16, .xtensaeb, => unreachable, diff --git a/src/codegen/spork8/CodeGen.zig b/src/codegen/spork8/CodeGen.zig new file mode 100644 index 0000000000000000000000000000000000000000..9c579365db817acf783885d824663c867bdb9520 --- /dev/null +++ b/src/codegen/spork8/CodeGen.zig @@ -0,0 +1,367 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +const CodeGen = @This(); +const link = @import("../../link.zig"); +const Spork8 = link.File.Spork8; +const Zcu = @import("../../Zcu.zig"); +const InternPool = @import("../../InternPool.zig"); +const Air = @import("../../Air.zig"); +const Liveness = Air.Liveness; +const Mir = @import("Mir.zig"); + +air: Air, +liveness: Liveness, +gpa: Allocator, +spork8: *Spork8, +pt: Zcu.PerThread, +owner_nav: InternPool.Nav.Index, +func_index: InternPool.Index, +mir_instructions: *std.MultiArrayList(Mir.Inst), +/// Contains extra data for MIR +mir_extra: *std.ArrayListUnmanaged(u32), +start_mir_extra_off: u32, + +pub const Error = error{ + OutOfMemory, + /// Compiler was asked to operate on a number larger than supported. + Overflow, + /// Indicates the error is already stored in Zcu `failed_codegen`. + CodegenFail, +}; + +pub const Function = extern struct { + /// Index into `Spork8.mir_instructions`. + mir_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + mir_len: u32, + /// Index into `Spork8.mir_extra`. + mir_extra_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + mir_extra_len: u32, +}; + +pub fn function( + spork8: *Spork8, + pt: Zcu.PerThread, + func_index: InternPool.Index, + air: Air, + liveness: Liveness, +) Error!Function { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const func_info = zcu.funcInfo(func_index); + + var code_gen: CodeGen = .{ + .gpa = gpa, + .pt = pt, + .air = air, + .liveness = liveness, + .owner_nav = func_info.owner_nav, + .spork8 = spork8, + .func_index = func_index, + .mir_instructions = &spork8.mir_instructions, + .mir_extra = &spork8.mir_extra, + .start_mir_extra_off = @intCast(spork8.mir_extra.items.len), + }; + defer code_gen.deinit(); + + return functionInner(&code_gen) catch |err| switch (err) { + error.CodegenFail => return error.CodegenFail, + else => |e| return code_gen.fail("failed to generate function: {s}", .{@errorName(e)}), + }; +} + +fn deinit(cg: *CodeGen) void { + cg.* = undefined; +} + +const InnerError = error{ + CodegenFail, + OutOfMemory, +}; + +fn functionInner(cg: *CodeGen) InnerError!Function { + const spork8 = cg.spork8; + + const start_mir_off: u32 = @intCast(spork8.mir_instructions.len); + + // Generate MIR for function body + try cg.genBody(cg.air.getMainBody()); + + return .{ + .mir_off = start_mir_off, + .mir_len = @intCast(spork8.mir_instructions.len - start_mir_off), + .mir_extra_off = cg.start_mir_extra_off, + .mir_extra_len = cg.extraLen(), + }; +} + +fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { + const zcu = cg.pt.zcu; + const ip = &zcu.intern_pool; + + for (body) |inst| { + if (cg.liveness.isUnused(inst) and !cg.air.mustLower(inst, ip)) continue; + try cg.genInst(inst); + } +} + +fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + const air_tags = cg.air.instructions.items(.tag); + return switch (air_tags[@intFromEnum(inst)]) { + .inferred_alloc, .inferred_alloc_comptime => unreachable, + + .unreach => cg.airUnreachable(inst), + + .add, + .add_sat, + .add_wrap, + .sub, + .sub_sat, + .sub_wrap, + .mul, + .mul_sat, + .mul_wrap, + .div_float, + .div_exact, + .div_trunc, + .div_floor, + .bit_and, + .bit_or, + .bool_and, + .bool_or, + .rem, + .mod, + .shl, + .shl_exact, + .shl_sat, + .shr, + .shr_exact, + .xor, + .max, + .min, + .mul_add, + + .sqrt, + .sin, + .cos, + .tan, + .exp, + .exp2, + .log, + .log2, + .log10, + .floor, + .ceil, + .round, + .trunc_float, + .neg, + + .abs, + + .add_with_overflow, + .sub_with_overflow, + .shl_with_overflow, + .mul_with_overflow, + + .clz, + .ctz, + + .cmp_eq, + .cmp_gte, + .cmp_gt, + .cmp_lte, + .cmp_lt, + .cmp_neq, + + .cmp_vector, + .cmp_lt_errors_len, + + .array_elem_val, + .array_to_slice, + .alloc, + .arg, + .bitcast, + .block, + .trap, + .breakpoint, + .br, + .repeat, + .switch_dispatch, + .cond_br, + .intcast, + .fptrunc, + .fpext, + .int_from_float, + .float_from_int, + .get_union_tag, + + .@"try", + .try_cold, + .try_ptr, + .try_ptr_cold, + + .dbg_stmt, + .dbg_empty_stmt, + .dbg_inline_block, + .dbg_var_ptr, + .dbg_var_val, + .dbg_arg_inline, + + .call, + .call_always_tail, + .call_never_tail, + .call_never_inline, + + .is_err, + .is_non_err, + + .is_null, + .is_non_null, + .is_null_ptr, + .is_non_null_ptr, + + .load, + .loop, + .memset, + .memset_safe, + .not, + .optional_payload, + .optional_payload_ptr, + .optional_payload_ptr_set, + .ptr_add, + .ptr_sub, + .ptr_elem_ptr, + .ptr_elem_val, + .ret, + .ret_safe, + .ret_ptr, + .ret_load, + .splat, + .select, + .shuffle, + .reduce, + .aggregate_init, + .union_init, + .prefetch, + .popcount, + .byte_swap, + .bit_reverse, + + .slice, + .slice_len, + .slice_elem_val, + .slice_elem_ptr, + .slice_ptr, + .ptr_slice_len_ptr, + .ptr_slice_ptr_ptr, + .store, + .store_safe, + + .set_union_tag, + .struct_field_ptr, + .struct_field_ptr_index_0, + .struct_field_ptr_index_1, + .struct_field_ptr_index_2, + .struct_field_ptr_index_3, + .struct_field_val, + .field_parent_ptr, + + .switch_br, + .loop_switch_br, + .trunc, + + .wrap_optional, + .unwrap_errunion_payload, + .unwrap_errunion_payload_ptr, + .unwrap_errunion_err, + .unwrap_errunion_err_ptr, + .wrap_errunion_payload, + .wrap_errunion_err, + .errunion_payload_ptr_set, + .error_name, + + .wasm_memory_size, + .wasm_memory_grow, + + .memcpy, + + .ret_addr, + .tag_name, + + .error_set_has_value, + .frame_addr, + + .assembly, + .is_err_ptr, + .is_non_err_ptr, + + .err_return_trace, + .set_err_return_trace, + .save_err_return_trace_index, + .is_named_enum_value, + .addrspace_cast, + .vector_store_elem, + .c_va_arg, + .c_va_copy, + .c_va_end, + .c_va_start, + .memmove, + + .atomic_load, + .atomic_store_unordered, + .atomic_store_monotonic, + .atomic_store_release, + .atomic_store_seq_cst, + .atomic_rmw, + .cmpxchg_weak, + .cmpxchg_strong, + + .add_optimized, + .sub_optimized, + .mul_optimized, + .div_float_optimized, + .div_trunc_optimized, + .div_floor_optimized, + .div_exact_optimized, + .rem_optimized, + .mod_optimized, + .neg_optimized, + .cmp_lt_optimized, + .cmp_lte_optimized, + .cmp_eq_optimized, + .cmp_gte_optimized, + .cmp_gt_optimized, + .cmp_neq_optimized, + .cmp_vector_optimized, + .reduce_optimized, + .int_from_float_optimized, + .add_safe, + .sub_safe, + .mul_safe, + .intcast_safe, + => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}), + + .work_item_id, + .work_group_size, + .work_group_id, + => unreachable, + }; +} + +fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + _ = cg; + _ = inst; +} + +fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { + const zcu = cg.pt.zcu; + const func = zcu.funcInfo(cg.func_index); + return zcu.codegenFail(func.owner_nav, fmt, args); +} + +fn extraLen(cg: *const CodeGen) u32 { + return @intCast(cg.mir_extra.items.len - cg.start_mir_extra_off); +} diff --git a/src/codegen/spork8/Mir.zig b/src/codegen/spork8/Mir.zig new file mode 100644 index 0000000000000000000000000000000000000000..c049dd20894af18734752eb9e721cf2f3ce3292a --- /dev/null +++ b/src/codegen/spork8/Mir.zig @@ -0,0 +1,46 @@ +const Mir = @This(); +const InternPool = @import("../../InternPool.zig"); + +const builtin = @import("builtin"); +const std = @import("std"); +const assert = std.debug.assert; + +instruction_tags: []const Inst.Tag, +instruction_datas: []const Inst.Data, +extra: []const u32, + +pub const Inst = struct { + tag: Tag, + data: Data, + + /// The position of a given MIR isntruction with the instruction list. + pub const Index = enum(u32) { + _, + }; + + pub const Tag = enum(u8) { + /// imm8 + set_page_i = 0x04, + /// imm8 + set_addr_i = 0x09, + /// imm8 + load_i = 0x10, + /// index + jump, + }; + + /// All instructions contain a 4-byte payload, which is contained within + /// this union. `Tag` determines which union tag is active, as well as + /// how to interpret the data within. + pub const Data = union { + imm8: u8, + index: Index, + + comptime { + switch (builtin.mode) { + .Debug, .ReleaseSafe => {}, + .ReleaseFast, .ReleaseSmall => assert(@sizeOf(Data) == 4), + } + } + }; +}; diff --git a/src/dev.zig b/src/dev.zig index 1de9810eb00f5e754fea549b3f0f9fa3d5484d8f..249566889df0efec1b588e4ff5622fbe766f6c27 100644 --- a/src/dev.zig +++ b/src/dev.zig @@ -60,6 +60,10 @@ pub const Env = enum { /// - `zig build-* -fincremental -fno-llvm -fno-lld -target loongarch(32/64)-linux --listen=-` @"loongarch-linux", + /// - sema + /// - `zig build-* -fno-llvm -fno-lld -target spork8-* --listen=-` + spork8, + pub inline fn supports(comptime dev_env: Env, comptime feature: Feature) bool { return switch (dev_env) { .full => true, @@ -102,6 +106,7 @@ pub const Env = enum { .sparc64_backend, .spirv_backend, .loongarch_backend, + .spork8_backend, .lld_linker, .coff_linker, .coff2_linker, @@ -112,6 +117,7 @@ pub const Env = enum { .wasm_linker, .spirv_linker, .plan9_linker, + .spork8_linker, .jit_command, => true, .cc_command, @@ -239,6 +245,14 @@ pub const Env = enum { => true, else => Env.sema.supports(feature), }, + .spork8 => switch (feature) { + .stdio_listen, + .incremental, + .spork8_backend, + .spork8_linker, + => true, + else => Env.sema.supports(feature), + }, }; } @@ -303,6 +317,7 @@ pub const Feature = enum { sparc64_backend, spirv_backend, loongarch_backend, + spork8_backend, lld_linker, coff_linker, @@ -314,6 +329,7 @@ pub const Feature = enum { wasm_linker, spirv_linker, plan9_linker, + spork8_linker, }; /// Makes the code following the call to this function unreachable if `feature` is disabled. diff --git a/src/link.zig b/src/link.zig index eeab7e4f56f441070fc8817c7d8969c0558a4ee0..1e9cbb18c059511eba1a631f21ed18068d3fdf35 100644 --- a/src/link.zig +++ b/src/link.zig @@ -673,6 +673,7 @@ pub const File = struct { }); }, .plan9 => unreachable, + .spork8 => dev.check(.spork8_linker), } } @@ -750,6 +751,7 @@ pub const File = struct { }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), .plan9 => unreachable, + .spork8 => dev.check(.spork8_linker), } } @@ -1025,6 +1027,7 @@ pub const File = struct { .spirv => unreachable, .wasm => unreachable, .plan9 => unreachable, + .spork8 => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).getNavVAddr(pt, nav_index, reloc_info); @@ -1047,6 +1050,7 @@ pub const File = struct { .spirv => unreachable, .wasm => unreachable, .plan9 => unreachable, + .spork8 => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).lowerUav(pt, decl_val, decl_align); @@ -1064,6 +1068,7 @@ pub const File = struct { .spirv => unreachable, .wasm => unreachable, .plan9 => unreachable, + .spork8 => unreachable, inline else => |tag| { dev.check(tag.devFeature()); return @as(*tag.Type(), @fieldParentPtr("base", base)).getUavVAddr(decl_val, reloc_info); @@ -1088,6 +1093,7 @@ pub const File = struct { .spirv, .plan9, .lld, + .spork8, => return .unimplemented, inline else => |tag| { dev.check(tag.devFeature()); @@ -1266,6 +1272,7 @@ pub const File = struct { c, wasm, spirv, + spork8, plan9, lld, @@ -1280,6 +1287,7 @@ pub const File = struct { .spirv => SpirV, .lld => Lld, .plan9 => comptime unreachable, + .spork8 => Spork8, }; } @@ -1292,6 +1300,7 @@ pub const File = struct { .plan9 => .plan9, .c => .c, .spirv => .spirv, + .spork8 => .spork8, .hex => @panic("TODO implement hex object format"), .raw => @panic("TODO implement raw object format"), }; @@ -1373,6 +1382,7 @@ pub const File = struct { pub const Lld = @import("link/Lld.zig"); pub const C = @import("link/C.zig"); pub const Coff2 = @import("link/Coff.zig"); + pub const Spork8 = @import("link/Spork8.zig"); pub const Elf = @import("link/Elf.zig"); pub const Elf2 = @import("link/Elf2.zig"); pub const MachO = @import("link/MachO.zig"); diff --git a/src/link/Spork8.zig b/src/link/Spork8.zig new file mode 100644 index 0000000000000000000000000000000000000000..bd2d3000d5b5848ae2cdac45573faddb937716a5 --- /dev/null +++ b/src/link/Spork8.zig @@ -0,0 +1,188 @@ +const Spork8 = @This(); +const builtin = @import("builtin"); +const build_options = @import("build_options"); + +const std = @import("std"); +const Io = std.Io; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; +const Path = std.Build.Cache.Path; +const log = std.log.scoped(.link); + +const Air = @import("../Air.zig"); +const InternPool = @import("../InternPool.zig"); +const Zcu = @import("../Zcu.zig"); +const CodeGen = @import("../codegen/spork8/CodeGen.zig"); +const codegen = @import("../codegen.zig"); +const Mir = @import("../codegen/spork8/Mir.zig"); +const link = @import("../link.zig"); +const Compilation = @import("../Compilation.zig"); +const Liveness = @import("../Air/Liveness.zig"); +const dev = @import("../dev.zig"); +const Value = @import("../Value.zig"); + +base: link.File, +funcs: std.AutoArrayHashMapUnmanaged(InternPool.Index, CodeGen.Function) = .empty, +/// All MIR instructions for all Zcu functions. +mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, +/// Corresponds to `mir_instructions`. +mir_extra: std.ArrayListUnmanaged(u32) = .empty, + +pub fn open( + arena: Allocator, + comp: *Compilation, + emit: Path, + options: link.File.OpenOptions, +) !*Spork8 { + // TODO: restore saved linker state, don't truncate the file, and + // participate in incremental compilation. + return createEmpty(arena, comp, emit, options); +} + +pub fn createEmpty( + arena: Allocator, + comp: *Compilation, + emit: Path, + options: link.File.OpenOptions, +) !*Spork8 { + const target = comp.root_mod.resolved_target.result; + assert(target.ofmt == .spork8); + assert(comp.config.output_mode == .Exe); + const io = comp.io; + + const spork8 = try arena.create(Spork8); + spork8.* = .{ + .base = .{ + .tag = .spork8, + .comp = comp, + .emit = emit, + .gc_sections = options.gc_sections orelse true, + .print_gc_sections = options.print_gc_sections, + .stack_size = options.stack_size orelse switch (target.os.tag) { + .freestanding => 1 * 1024 * 1024, // 1 MiB + else => 16 * 1024 * 1024, // 16 MiB + }, + .allow_shlib_undefined = options.allow_shlib_undefined orelse false, + .file = null, + .build_id = options.build_id, + }, + }; + errdefer spork8.base.destroy(); + + spork8.base.file = try emit.root_dir.handle.createFile(io, emit.sub_path, .{ + .truncate = true, + .read = true, + }); + + return spork8; +} + +pub fn deinit(spork8: *Spork8) void { + const gpa = spork8.base.comp.gpa; + _ = gpa; +} + +pub fn updateFunc( + spork8: *Spork8, + pt: Zcu.PerThread, + func_index: InternPool.Index, + any_mir: *const codegen.AnyMir, +) !void { + dev.check(.spork8_backend); + // This linker implementation only works with codegen backend `.stage2_wasm`. + const mir = &any_mir.spork8; + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + const owner_nav = zcu.funcInfo(func_index).owner_nav; + _ = gpa; + _ = mir; + _ = spork8; + log.debug("updateFunc {f}", .{ip.getNav(owner_nav).fqn.fmt(ip)}); +} + +// Generate code for the "Nav", storing it in memory to be later written to +// the file on flush(). +pub fn updateNav(spork8: *Spork8, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { + _ = spork8; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const nav = ip.getNav(nav_index); + log.debug("updateNav {f}", .{nav.fqn.fmt(ip)}); +} + +pub fn updateLineNumber(spork8: *Spork8, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void { + _ = spork8; + _ = pt; + _ = ti_id; +} + +pub fn deleteExport( + spork8: *Spork8, + exported: Zcu.Exported, + name: InternPool.NullTerminatedString, +) void { + const zcu = spork8.base.comp.zcu.?; + const ip = &zcu.intern_pool; + const name_slice = name.toSlice(ip); + switch (exported) { + .nav => |nav_index| { + log.debug("deleteExport '{s}' nav={d}", .{ name_slice, @backingInt(nav_index) }); + }, + .uav => |uav_index| { + log.debug("deleteExport '{s}' uav={d}", .{ name_slice, @backingInt(uav_index) }); + }, + } +} + +pub fn updateExports( + spork8: *Spork8, + pt: Zcu.PerThread, + export_indices: []const Zcu.Export.Index, +) !void { + _ = spork8; + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + + for (export_indices) |export_idx| { + const exp = export_idx.ptr(zcu); + const name_slice = exp.opts.name.toSlice(ip); + switch (exp.exported) { + .nav => |nav_index| { + log.debug("updateExports {q} nav={d}", .{ name_slice, @backingInt(nav_index) }); + }, + .uav => |uav_index| { + log.debug("updateExports {q} uav={d}", .{ name_slice, @backingInt(uav_index) }); + }, + } + } +} + +pub fn loadInput(spork8: *Spork8, input: link.Input) !void { + _ = input; + const comp = spork8.base.comp; + const diags = &comp.link_diags; + return diags.failParse("spork8 does not support linking files together", .{}); +} + +pub fn flush( + spork8: *Spork8, + arena: Allocator, + tid: Zcu.PerThread.Id, + prog_node: std.Progress.Node, +) link.Error!void { + const sub_prog_node = prog_node.start("Spork8 Flush", 0); + defer sub_prog_node.end(); + + _ = spork8; + _ = arena; + _ = tid; + log.debug("TODO implement flush", .{}); +} + +pub fn prelink(spork8: *Spork8, prog_node: std.Progress.Node) link.Error!void { + const sub_prog_node = prog_node.start("Spork8 Prelink", 0); + defer sub_prog_node.end(); + + _ = spork8; +} diff --git a/src/target.zig b/src/target.zig index fda6a4cbf6e45b6b398320d237293cb3965beab5..02cd9c2f93561df3b191aa6e47be1e8b97eda5aa 100644 --- a/src/target.zig +++ b/src/target.zig @@ -189,6 +189,7 @@ pub fn hasLlvmSupport(target: *const std.Target, ofmt: std.Target.ObjectFormat) // LLVM does not support these object formats: .c, .plan9, + .spork8, => return false, .coff, @@ -271,6 +272,7 @@ pub fn hasLlvmSupport(target: *const std.Target, ofmt: std.Target.ObjectFormat) .sheb, .x86_16, .xtensaeb, + .spork8, => false, }; } @@ -433,6 +435,7 @@ pub fn canBuildLibCompilerRt(target: *const std.Target) enum { no, yes, llvm_onl } switch (target.cpu.arch) { .spirv32, .spirv64 => return .no, + .spork8 => return .no, // Remove this once https://github.com/ziglang/zig/issues/23714 is fixed .amdgcn => return .no, else => {}, @@ -445,6 +448,7 @@ pub fn canBuildLibCompilerRt(target: *const std.Target) enum { no, yes, llvm_onl pub fn canBuildLibUbsanRt(target: *const std.Target) enum { no, yes, llvm_only, llvm_lld_only } { switch (target.cpu.arch) { + .spork8 => return .no, .spirv32, .spirv64 => return .no, // Remove this once https://github.com/ziglang/zig/issues/23715 is fixed .nvptx, .nvptx64 => return .no, @@ -929,6 +933,7 @@ pub fn zigBackend(target: *const std.Target, use_llvm: bool) std.lang.CompilerBa .wasm32, .wasm64 => .stage2_wasm, .x86 => .stage2_x86, .x86_64 => .stage2_x86_64, + .spork8 => .zsf_spork8, else => .other, }; } -- 2.54.0 From fb6cce8cf3f2977d1828cff4aa3a9cb1b6ff6b5f Mon Sep 17 00:00:00 2001 From: Ben Anderman Date: Thu, 20 Aug 2026 01:22:01 -0700 Subject: [PATCH 02/13] Update spork8 backend to modern internal compiler APIs --- src/codegen.zig | 4 + src/codegen/spork8/CodeGen.zig | 208 ++++++++++++++++++++++++--------- src/codegen/spork8/Mir.zig | 13 ++- src/dev.zig | 1 + src/link/Spork8.zig | 1 - 5 files changed, 164 insertions(+), 63 deletions(-) diff --git a/src/codegen.zig b/src/codegen.zig index d54901cbd4253bacb0423ec09204b4d2fcfadb95..17dcc14f7036d4631e3225890c0404f2dd828410 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -80,6 +80,7 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co .stage2_x86, .stage2_riscv64, .stage2_sparc64, + .stage2_spork8, .stage2_spirv, => |backend| { dev.check(devFeatureForBackend(backend)); @@ -121,6 +122,7 @@ pub const AnyMir = union { .stage2_wasm => "wasm", .stage2_c => "c", .stage2_spirv => "spirv", + .stage2_spork8 => "spork8", else => unreachable, }; } @@ -138,6 +140,7 @@ pub const AnyMir = union { .stage2_wasm, .stage2_c, .stage2_spirv, + .stage2_spork8, => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa), } } @@ -167,6 +170,7 @@ pub fn generateFunction( .stage2_x86_64, .stage2_wasm, .stage2_c, + .stage2_spork8, .stage2_spirv, => |backend| { dev.check(devFeatureForBackend(backend)); diff --git a/src/codegen/spork8/CodeGen.zig b/src/codegen/spork8/CodeGen.zig index 9c579365db817acf783885d824663c867bdb9520..07007a41083e5d2535f9d0a5cdded39e75437658 100644 --- a/src/codegen/spork8/CodeGen.zig +++ b/src/codegen/spork8/CodeGen.zig @@ -14,87 +14,163 @@ const Mir = @import("Mir.zig"); air: Air, liveness: Liveness, gpa: Allocator, -spork8: *Spork8, pt: Zcu.PerThread, owner_nav: InternPool.Nav.Index, func_index: InternPool.Index, -mir_instructions: *std.MultiArrayList(Mir.Inst), +mir_instructions: std.MultiArrayList(Mir.Inst), /// Contains extra data for MIR -mir_extra: *std.ArrayListUnmanaged(u32), -start_mir_extra_off: u32, +mir_extra: std.ArrayListUnmanaged(u32), -pub const Error = error{ - OutOfMemory, - /// Compiler was asked to operate on a number larger than supported. - Overflow, - /// Indicates the error is already stored in Zcu `failed_codegen`. - CodegenFail, -}; +pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features { + return comptime &.initMany(&.{ + .expand_bit_cast_safe, + .expand_int_cast_safe, + .expand_int_from_float_safe, + .expand_int_from_float_optimized_safe, + .expand_add_safe, + .expand_sub_safe, + .expand_mul_safe, -pub const Function = extern struct { - /// Index into `Spork8.mir_instructions`. - mir_off: u32, - /// This is unused except for as a safety slice bound and could be removed. - mir_len: u32, - /// Index into `Spork8.mir_extra`. - mir_extra_off: u32, - /// This is unused except for as a safety slice bound and could be removed. - mir_extra_len: u32, -}; + .expand_packed_load, + .expand_packed_store, + .expand_packed_agg_field_val, + .expand_packed_aggregate_init, + .expand_array_to_vector, -pub fn function( - spork8: *Spork8, + .scalarize_add, + .scalarize_add_optimized, + .scalarize_add_wrap, + .scalarize_add_sat, + .scalarize_sub, + .scalarize_sub_optimized, + .scalarize_sub_wrap, + .scalarize_sub_sat, + .scalarize_mul, + .scalarize_mul_optimized, + .scalarize_mul_wrap, + .scalarize_mul_sat, + .scalarize_div_float, + .scalarize_div_float_optimized, + .scalarize_div_trunc, + .scalarize_div_trunc_optimized, + .scalarize_div_floor, + .scalarize_div_floor_optimized, + .scalarize_div_ceil, + .scalarize_div_ceil_optimized, + .scalarize_div_exact, + .scalarize_div_exact_optimized, + .scalarize_rem, + .scalarize_rem_optimized, + .scalarize_mod, + .scalarize_mod_optimized, + .scalarize_max, + .scalarize_min, + .scalarize_add_with_overflow, + .scalarize_sub_with_overflow, + .scalarize_mul_with_overflow, + .scalarize_shl_with_overflow, + .scalarize_bit_and, + .scalarize_bit_or, + .scalarize_shr, + .scalarize_shr_exact, + .scalarize_shl, + .scalarize_shl_exact, + .scalarize_shl_sat, + .scalarize_xor, + .scalarize_not, + .scalarize_clz, + .scalarize_ctz, + .scalarize_popcount, + .scalarize_byte_swap, + .scalarize_bit_reverse, + .scalarize_sqrt, + .scalarize_sin, + .scalarize_cos, + .scalarize_tan, + .scalarize_exp, + .scalarize_exp2, + .scalarize_log, + .scalarize_log2, + .scalarize_log10, + .scalarize_abs, + .scalarize_floor, + .scalarize_ceil, + .scalarize_round, + .scalarize_trunc_float, + .scalarize_neg, + .scalarize_neg_optimized, + .scalarize_cmp_vector, + .scalarize_cmp_vector_optimized, + .scalarize_fptrunc, + .scalarize_fpext, + .scalarize_int_cast, + .scalarize_ptr_cast, + .scalarize_ptr_from_int, + .scalarize_int_from_ptr, + .scalarize_trunc, + .scalarize_int_from_float, + .scalarize_int_from_float_optimized, + .scalarize_float_from_int, + .scalarize_reduce, + .scalarize_reduce_optimized, + .scalarize_shuffle_one, + .scalarize_shuffle_two, + .scalarize_select, + .scalarize_mul_add, + + .scalarize_bit_cast_padded_elems, + }); +} + +pub fn generate( + bin_file: *link.File, pt: Zcu.PerThread, func_index: InternPool.Index, - air: Air, - liveness: Liveness, -) Error!Function { + air: *const Air, + liveness: *const ?Air.Liveness, +) link.Error!Mir { + _ = bin_file; const zcu = pt.zcu; const gpa = zcu.gpa; - const func_info = zcu.funcInfo(func_index); + const cg = zcu.funcInfo(func_index); var code_gen: CodeGen = .{ .gpa = gpa, .pt = pt, - .air = air, - .liveness = liveness, - .owner_nav = func_info.owner_nav, - .spork8 = spork8, + .air = air.*, + .liveness = liveness.*.?, + .owner_nav = cg.owner_nav, .func_index = func_index, - .mir_instructions = &spork8.mir_instructions, - .mir_extra = &spork8.mir_extra, - .start_mir_extra_off = @intCast(spork8.mir_extra.items.len), + .mir_instructions = .empty, + .mir_extra = .empty, }; defer code_gen.deinit(); - return functionInner(&code_gen) catch |err| switch (err) { - error.CodegenFail => return error.CodegenFail, - else => |e| return code_gen.fail("failed to generate function: {s}", .{@errorName(e)}), + return generateInner(&code_gen) catch |err| switch (err) { + error.AlreadyReported, + error.OutOfMemory, + => |e| return e, }; } -fn deinit(cg: *CodeGen) void { +pub fn deinit(cg: *CodeGen) void { cg.* = undefined; } const InnerError = error{ - CodegenFail, + AlreadyReported, OutOfMemory, }; -fn functionInner(cg: *CodeGen) InnerError!Function { - const spork8 = cg.spork8; - - const start_mir_off: u32 = @intCast(spork8.mir_instructions.len); - +fn generateInner(cg: *CodeGen) InnerError!Mir { // Generate MIR for function body try cg.genBody(cg.air.getMainBody()); + try cg.mir_extra.shrinkToLen(cg.gpa); + return .{ - .mir_off = start_mir_off, - .mir_len = @intCast(spork8.mir_instructions.len - start_mir_off), - .mir_extra_off = cg.start_mir_extra_off, - .mir_extra_len = cg.extraLen(), + .instructions = cg.mir_instructions.toOwnedSlice(), + .extra = cg.mir_extra.toOwnedSliceAssert(), }; } @@ -130,8 +206,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .div_floor, .bit_and, .bit_or, - .bool_and, - .bool_or, .rem, .mod, .shl, @@ -177,13 +251,11 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .cmp_neq, .cmp_vector, - .cmp_lt_errors_len, .array_elem_val, .array_to_slice, .alloc, .arg, - .bitcast, .block, .trap, .breakpoint, @@ -191,7 +263,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .repeat, .switch_dispatch, .cond_br, - .intcast, .fptrunc, .fpext, .int_from_float, @@ -241,7 +312,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .ret_load, .splat, .select, - .shuffle, .reduce, .aggregate_init, .union_init, @@ -266,7 +336,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .struct_field_ptr_index_1, .struct_field_ptr_index_2, .struct_field_ptr_index_3, - .struct_field_val, .field_parent_ptr, .switch_br, @@ -303,7 +372,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .save_err_return_trace_index, .is_named_enum_value, .addrspace_cast, - .vector_store_elem, .c_va_arg, .c_va_copy, .c_va_end, @@ -341,7 +409,31 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .add_safe, .sub_safe, .mul_safe, - .intcast_safe, + .div_ceil, + .div_ceil_optimized, + .bit_cast, + .bit_cast_safe, + .ptr_cast, + .ptr_from_int, + .int_from_ptr, + .error_cast, + .error_from_int, + .int_from_error, + .union_from_enum, + .int_cast, + .int_cast_safe, + .agg_field_val, + .array_to_vector, + .int_from_float_safe, + .int_from_float_optimized_safe, + .shuffle_one, + .shuffle_two, + .cmp_lte_errors_len, + .runtime_nav_ptr, + .spirv_runtime_array_len, + .legalize_vec_store_elem, + .legalize_vec_elem_val, + .legalize_compiler_rt_call, => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}), .work_item_id, @@ -356,7 +448,7 @@ fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { _ = inst; } -fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } { +fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } { const zcu = cg.pt.zcu; const func = zcu.funcInfo(cg.func_index); return zcu.codegenFail(func.owner_nav, fmt, args); diff --git a/src/codegen/spork8/Mir.zig b/src/codegen/spork8/Mir.zig index c049dd20894af18734752eb9e721cf2f3ce3292a..f81fbd9a2157a756ea4da5df862bc4e10b5d6e70 100644 --- a/src/codegen/spork8/Mir.zig +++ b/src/codegen/spork8/Mir.zig @@ -5,8 +5,8 @@ const builtin = @import("builtin"); const std = @import("std"); const assert = std.debug.assert; -instruction_tags: []const Inst.Tag, -instruction_datas: []const Inst.Data, +instructions: std.MultiArrayList(Inst).Slice, + extra: []const u32, pub const Inst = struct { @@ -24,9 +24,9 @@ pub const Inst = struct { /// imm8 set_addr_i = 0x09, /// imm8 - load_i = 0x10, + load_i = 0x11, /// index - jump, + jump = 0x68, }; /// All instructions contain a 4-byte payload, which is contained within @@ -44,3 +44,8 @@ pub const Inst = struct { } }; }; + +pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { + mir.instructions.deinit(gpa); + mir.* = undefined; +} diff --git a/src/dev.zig b/src/dev.zig index 249566889df0efec1b588e4ff5622fbe766f6c27..318a689f6db7fa0708105e252195ef6bcb0d61b7 100644 --- a/src/dev.zig +++ b/src/dev.zig @@ -248,6 +248,7 @@ pub const Env = enum { .spork8 => switch (feature) { .stdio_listen, .incremental, + .legalize, .spork8_backend, .spork8_linker, => true, diff --git a/src/link/Spork8.zig b/src/link/Spork8.zig index bd2d3000d5b5848ae2cdac45573faddb937716a5..e2786080d393f48282bc4a57d44d3a5c3300b60e 100644 --- a/src/link/Spork8.zig +++ b/src/link/Spork8.zig @@ -22,7 +22,6 @@ const dev = @import("../dev.zig"); const Value = @import("../Value.zig"); base: link.File, -funcs: std.AutoArrayHashMapUnmanaged(InternPool.Index, CodeGen.Function) = .empty, /// All MIR instructions for all Zcu functions. mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, /// Corresponds to `mir_instructions`. -- 2.54.0 From d101570c5cc78ba0cbed3b75abba29fa00749d2a Mon Sep 17 00:00:00 2001 From: Ben Anderman Date: Fri, 21 Aug 2026 00:40:50 -0700 Subject: [PATCH 03/13] Add spork8 support for halt --- src/Compilation/Config.zig | 1 + src/codegen/spork8/CodeGen.zig | 19 +++++-- src/codegen/spork8/Mir.zig | 3 ++ src/link/Spork8.zig | 93 +++++++++++++++++++++++++++++++--- 4 files changed, 107 insertions(+), 9 deletions(-) diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 9b97d4a2cbafbc2fc8c964ea4ba8aa0159483625..58a69ba70100065ab0175fd94319ebaf0ae8cce4 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -487,6 +487,7 @@ pub fn resolve(options: Options) ResolveError!Config { const root_strip = b: { if (options.root_strip) |x| break :b x; if (root_optimize_mode == .small) break :b true; + if (target.cpu.arch == .spork8) break :b true; if (!target_util.hasDebugInfo(target)) break :b true; break :b false; }; diff --git a/src/codegen/spork8/CodeGen.zig b/src/codegen/spork8/CodeGen.zig index 07007a41083e5d2535f9d0a5cdded39e75437658..21e172e203865f5342b9a736ef4184ecf3681b67 100644 --- a/src/codegen/spork8/CodeGen.zig +++ b/src/codegen/spork8/CodeGen.zig @@ -189,8 +189,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { return switch (air_tags[@intFromEnum(inst)]) { .inferred_alloc, .inferred_alloc_comptime => unreachable, - .unreach => cg.airUnreachable(inst), - .add, .add_sat, .add_wrap, @@ -257,7 +255,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .alloc, .arg, .block, - .trap, .breakpoint, .br, .repeat, @@ -436,6 +433,9 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .legalize_compiler_rt_call, => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}), + .unreach => cg.airUnreachable(inst), + .trap => cg.airTrap(inst), + .work_item_id, .work_group_size, .work_group_id, @@ -448,6 +448,19 @@ fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { _ = inst; } +fn airTrap(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + _ = inst; + try cg.addTag(.halt); +} + +pub fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void { + try cg.mir_instructions.append(cg.gpa, inst); +} + +pub fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { + try cg.addInst(.{ .tag = tag, .data = .{ .nothing = {} } }); +} + fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } { const zcu = cg.pt.zcu; const func = zcu.funcInfo(cg.func_index); diff --git a/src/codegen/spork8/Mir.zig b/src/codegen/spork8/Mir.zig index f81fbd9a2157a756ea4da5df862bc4e10b5d6e70..6f191e88b5f896a1617dbd576dbe7030034e8f34 100644 --- a/src/codegen/spork8/Mir.zig +++ b/src/codegen/spork8/Mir.zig @@ -27,6 +27,8 @@ pub const Inst = struct { load_i = 0x11, /// index jump = 0x68, + /// nothing + halt = 0xE3, }; /// All instructions contain a 4-byte payload, which is contained within @@ -35,6 +37,7 @@ pub const Inst = struct { pub const Data = union { imm8: u8, index: Index, + nothing: void, comptime { switch (builtin.mode) { diff --git a/src/link/Spork8.zig b/src/link/Spork8.zig index e2786080d393f48282bc4a57d44d3a5c3300b60e..3f005570cd7b7113423a7853bb523ba3a8f782be 100644 --- a/src/link/Spork8.zig +++ b/src/link/Spork8.zig @@ -26,6 +26,8 @@ base: link.File, mir_instructions: std.MultiArrayList(Mir.Inst) = .{}, /// Corresponds to `mir_instructions`. mir_extra: std.ArrayListUnmanaged(u32) = .empty, +/// When the key is an enum type, this represents a `@tagName` function. +zcu_funcs: std.array_hash_map.Auto(InternPool.Index, ZcuFunc) = .empty, pub fn open( arena: Allocator, @@ -88,18 +90,74 @@ pub fn updateFunc( any_mir: *const codegen.AnyMir, ) !void { dev.check(.spork8_backend); - // This linker implementation only works with codegen backend `.stage2_wasm`. + // This linker implementation only works with codegen backend `.stage2_spork8`. const mir = &any_mir.spork8; const zcu = pt.zcu; const gpa = zcu.gpa; const ip = &zcu.intern_pool; const owner_nav = zcu.funcInfo(func_index).owner_nav; - _ = gpa; - _ = mir; - _ = spork8; + log.debug("updateFunc {f}", .{ip.getNav(owner_nav).fqn.fmt(ip)}); + + // For Spork8, we do not lower the MIR to code just yet. That lowering happens during `flush`, + // after garbage collection, which can affect function and global indexes, which affects the + // LEB integer encoding, which affects the output binary size. + + // However, we do move the MIR into a more efficient in-memory representation, where the arrays + // for all functions are packed together rather than keeping them each in their own `Mir`. + const mir_instructions_off: u32 = @intCast(spork8.mir_instructions.len); + const mir_extra_off: u32 = @intCast(spork8.mir_extra.items.len); + { + // Copying MultiArrayList data is a little non-trivial. Resize, then memcpy both slices. + const old_len = spork8.mir_instructions.len; + try spork8.mir_instructions.resize(gpa, old_len + mir.instructions.len); + const dest_slice = spork8.mir_instructions.slice().subslice(old_len, mir.instructions.len); + const src_slice = mir.instructions; + @memcpy(dest_slice.items(.tag), src_slice.items(.tag)); + @memcpy(dest_slice.items(.data), src_slice.items(.data)); + } + try spork8.mir_extra.appendSlice(gpa, mir.extra); + + try spork8.zcu_funcs.ensureUnusedCapacity(gpa, 1); + + // This converts AIR to MIR but does not yet lower to Spork8 code. + spork8.zcu_funcs.putAssumeCapacity(func_index, .{ .function = .{ + .instructions_off = mir_instructions_off, + .instructions_len = @intCast(mir.instructions.len), + .extra_off = mir_extra_off, + .extra_len = @intCast(mir.extra.len), + } }); } +pub const ZcuFunc = union { + function: Function, + + pub const Function = extern struct { + /// Index into `Spork8.mir_instructions`. + instructions_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + instructions_len: u32, + /// Index into `Spork8.mir_extra`. + extra_off: u32, + /// This is unused except for as a safety slice bound and could be removed. + extra_len: u32, + }; + + /// Index into `Spork8.zcu_funcs`. + /// Note that swapRemove is sometimes performed on `zcu_funcs`. + pub const Index = enum(u32) { + _, + + pub fn key(i: @This(), spork8: *const Spork8) *InternPool.Index { + return &spork8.zcu_funcs.keys()[@backingInt(i)]; + } + + pub fn value(i: @This(), spork8: *const Spork8) *ZcuFunc { + return &spork8.zcu_funcs.values()[@backingInt(i)]; + } + }; +}; + // Generate code for the "Nav", storing it in memory to be later written to // the file on flush(). pub fn updateNav(spork8: *Spork8, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void { @@ -172,11 +230,34 @@ pub fn flush( ) link.Error!void { const sub_prog_node = prog_node.start("Spork8 Flush", 0); defer sub_prog_node.end(); + const io = spork8.base.comp.io; + const diags = &spork8.base.comp.link_diags; - _ = spork8; _ = arena; _ = tid; - log.debug("TODO implement flush", .{}); + + // Finally, write the entire binary into the file. + var buffer: [1000]u8 = undefined; + var file_writer = spork8.base.file.?.writer(io, &buffer); + mirToMC(spork8, &file_writer.interface) catch |err| switch (err) { + error.WriteFailed => return diags.fail("failed writing to file: {t}", .{file_writer.err.?}), + }; + file_writer.end() catch |err| switch (err) { + error.WriteFailed => return diags.fail("failed writing to file: {t}", .{file_writer.err.?}), + else => |e| return diags.fail("failed writing to file: {t}", .{e}), + }; +} + +fn mirToMC(spork8: *Spork8, w: *Io.Writer) !void { + for (spork8.mir_instructions.items(.tag)) |tag| { + switch (tag) { + .set_page_i => @panic("TODO"), + .set_addr_i => @panic("TODO"), + .load_i => @panic("TODO"), + .jump => @panic("TODO"), + .halt => try w.writeByte(@backingInt(tag)), + } + } } pub fn prelink(spork8: *Spork8, prog_node: std.Progress.Node) link.Error!void { -- 2.54.0 From a95b6d22efa89e2496201ed4b02788426638bbf5 Mon Sep 17 00:00:00 2001 From: Ben Anderman Date: Fri, 21 Aug 2026 17:55:10 -0700 Subject: [PATCH 04/13] Add support for Spork8 inline assembly with LoadI OutA --- src/codegen/spork8/CodeGen.zig | 149 ++++++++++++++++++++++++++++++++- src/codegen/spork8/Mir.zig | 73 +++++++++++++++- src/link/Spork8.zig | 7 +- 3 files changed, 225 insertions(+), 4 deletions(-) diff --git a/src/codegen/spork8/CodeGen.zig b/src/codegen/spork8/CodeGen.zig index 21e172e203865f5342b9a736ef4184ecf3681b67..451175b42e300c2b5100cccedd0584566271f92b 100644 --- a/src/codegen/spork8/CodeGen.zig +++ b/src/codegen/spork8/CodeGen.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -360,7 +361,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .error_set_has_value, .frame_addr, - .assembly, .is_err_ptr, .is_non_err_ptr, @@ -434,6 +434,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}), .unreach => cg.airUnreachable(inst), + .assembly => cg.airAssembly(inst), .trap => cg.airTrap(inst), .work_item_id, @@ -453,6 +454,77 @@ fn airTrap(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { try cg.addTag(.halt); } +fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + const unwrapped_asm = cg.air.unwrapAsm(inst); + const outputs = unwrapped_asm.outputs; + // const inputs = unwrapped_asm.inputs; + + const zcu = cg.pt.zcu; + // const output_ty = cg.typeOfIndex(inst); + + if (outputs.len != 0) { + @panic("TODO: Support assembly outputs"); + } + + var constValues: std.array_hash_map.String(u8) = .empty; + defer constValues.deinit(zcu.gpa); + { + var it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| { + const constraint = input.constraint; + if (!mem.eql(u8, constraint, "I")) { + return cg.fail("Assembly constraint {q} not supported", .{constraint}); + } + const operand = input.operand.toInterned() orelse { + return cg.fail("Immediate argument to inline assembly must be compile-time value", .{}); + }; + const name = input.name; + + const value = switch (zcu.intern_pool.indexToKey(operand)) { + .int => |val| v: { + if (val.ty != .u8_type) { + return cg.fail("Non-u8 type used in inline assembly value: {}", .{val.ty}); + } + break :v val.storage.u64; + }, + else => return cg.fail("Non-int operands not supported", .{}), + }; + + try constValues.put(zcu.gpa, name, @intCast(value)); + + // return cg.fail("TODO: Constraint={q}, name={q}, value={}", .{ constraint, name, value }); + } + } + + { + var lines = mem.tokenizeScalar(u8, unwrapped_asm.source, '\n'); + while (lines.next()) |line| { + var tokens = mem.tokenizeScalar(u8, line, ' '); + const op = tokens.next().?; + const instType = std.meta.stringToEnum(AsmInstType, op) orelse return cg.fail("Invalid asm instruction: {q}", .{op}); + switch (instType) { + .LoadI => { + const register = std.meta.stringToEnum(Register, tokens.next().?).?; + const value = tokens.next().?; + const intValue = v: { + if (mem.startsWith(u8, value, "%[")) { + const name = value[2 .. value.len - 1]; + break :v constValues.get(name).?; + } else { + break :v std.fmt.parseInt(u8, value, 0) catch unreachable; + } + }; + if (register != .OutA) { + return cg.fail("TODO: other variants of LoadI", .{}); + } + try cg.addTagImm8(.load_i_outa, intValue); + }, + else => return cg.fail("TODO: support asm instruction: {t}", .{instType}), + } + } + } +} + pub fn addInst(cg: *CodeGen, inst: Mir.Inst) error{OutOfMemory}!void { try cg.mir_instructions.append(cg.gpa, inst); } @@ -461,6 +533,10 @@ pub fn addTag(cg: *CodeGen, tag: Mir.Inst.Tag) error{OutOfMemory}!void { try cg.addInst(.{ .tag = tag, .data = .{ .nothing = {} } }); } +pub fn addTagImm8(cg: *CodeGen, tag: Mir.Inst.Tag, imm8: u8) error{OutOfMemory}!void { + try cg.addInst(.{ .tag = tag, .data = .{ .imm8 = imm8 } }); +} + fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemory, AlreadyReported } { const zcu = cg.pt.zcu; const func = zcu.funcInfo(cg.func_index); @@ -470,3 +546,74 @@ fn fail(cg: *CodeGen, comptime fmt: []const u8, args: anytype) error{ OutOfMemor fn extraLen(cg: *const CodeGen) u32 { return @intCast(cg.mir_extra.items.len - cg.start_mir_extra_off); } + +const AsmInstType = enum(u8) { + SetPageReg, // Set the memory address high byte to a register value. + SetPageI, // Set the memory address high byte to a constant value. + SetAddrReg, // Set the memory address low byte to a register value. + SetAddrI, // Set the memory address low byte to a constant value. + Load, // Load a value from a constant address into a register. + LoadI, // Load a constant value into a register. + LoadP, // Load a value from a constant address (setting low byte only) into a register. + LoadInc, // Load a value from the currently set memory address into a register, and increment the address n times. + LoadStck, // Load a value from an offset on the current stack frame into a register. + Store, // Store a value to a constant address from a register. + StoreI, // Store a constant value into a constant address. + StoreP, // Store a value to a constant address (low byte only) from a register. + StoreInc, // Store a value from the currently set memory address from a register, and increment the address n times. + StoreStck, // Store a value to an offset on the current stack frame, from a register. + StoreNStck, // Store a value to an offset on the next stack frame, from a register. + StorePStck, // Store a value to an offset on the previous stack frame, from a register. + StoreStckI, // Store a constant value to an offset on the current stack frame. + StoreNStckI, // Store a constant value to an offset on the next stack frame. + StorePStckI, // Store a constant value to an offset on the previous stack frame. + Copy, // Copy a value from one register to another register. + Jump, // Jump to a constant location. + JumpReg, // Jump to a register A (high byte) + register B (low byte). + JumpMem, // Jump to a location pointed to by memory at the current memory address (high byte first). + Call, // Call a function. + Return, // Return from a function. + CmpI, // Compare A to a constant value (sets flags, but discards result). + CmpAndI, // Compare A to a constant value with bitwise AND (sets flags, but discards result). + Cmp, // Compare A to a value from memory (sets flags, but discards result). + CmpAnd, // Compare A to a value in memory with bitwise AND (sets flags, but discards result). + CmpReg, // Compare A to a value from a register (sets flags, but discards result). + CmpAndReg, // Compare A to a value from a register with bitwise AND (sets flags, but discards result). + ShiftL, // Shift B left by 1. + ShiftR, // Shift B right by 1. + RotateL, // Rotate B left by 1. + RotateR, // Rotate B right by 1. + AddI, // Add a constant value to A. + SubI, // Subtract a constant value from A. + AndI, // Bitwise-AND A with a constant value. + AddINF, // Add a constant value to A, without updating flags. + SubINF, // Subtract a constant value from A, without updating flags. + AndINF, // Bitwise-AND A with a constant value, without updating flags. + AccumulateAdd, // Add register B to A -> A. + AccumulateSub, // Subtract register B from A -> A. + AccumulateAnd, // A & B -> A. + OrI, // Bitwise OR B with A -> A. + XorI, // Bitwise OR a constant value with A -> A. + Not, // Invert register A. + Add, // Add a value from memory to A. + Sub, // Subtract a value from memory from A. + And, // AND A with a value from memory. + Or, // OR A with a value from memory. + Xor, // XOR A with a value from memory. + Nop, // No-op. + Nop1, // No-op with 1 extra clock cycle. + Nop2, // No-op with 2 extra clock cycles. + Halt, // Halt - stop the program forever (until reset). +}; + +const Register = enum(u8) { + A, + B, + C, + PCnt, + MAdr, + Stack, + OutA, + Shift, + Swap, +}; diff --git a/src/codegen/spork8/Mir.zig b/src/codegen/spork8/Mir.zig index 6f191e88b5f896a1617dbd576dbe7030034e8f34..4034320a9336b40f3651c50aeedb5659237126ef 100644 --- a/src/codegen/spork8/Mir.zig +++ b/src/codegen/spork8/Mir.zig @@ -24,7 +24,7 @@ pub const Inst = struct { /// imm8 set_addr_i = 0x09, /// imm8 - load_i = 0x11, + load_i_outa = 0x15, /// index jump = 0x68, /// nothing @@ -52,3 +52,74 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.instructions.deinit(gpa); mir.* = undefined; } + +const AsmInstType = enum(u8) { + SetPageReg, // Set the memory address high byte to a register value. + SetPageI, // Set the memory address high byte to a constant value. + SetAddrReg, // Set the memory address low byte to a register value. + SetAddrI, // Set the memory address low byte to a constant value. + Load, // Load a value from a constant address into a register. + LoadI, // Load a constant value into a register. + LoadP, // Load a value from a constant address (setting low byte only) into a register. + LoadInc, // Load a value from the currently set memory address into a register, and increment the address n times. + LoadStck, // Load a value from an offset on the current stack frame into a register. + Store, // Store a value to a constant address from a register. + StoreI, // Store a constant value into a constant address. + StoreP, // Store a value to a constant address (low byte only) from a register. + StoreInc, // Store a value from the currently set memory address from a register, and increment the address n times. + StoreStck, // Store a value to an offset on the current stack frame, from a register. + StoreNStck, // Store a value to an offset on the next stack frame, from a register. + StorePStck, // Store a value to an offset on the previous stack frame, from a register. + StoreStckI, // Store a constant value to an offset on the current stack frame. + StoreNStckI, // Store a constant value to an offset on the next stack frame. + StorePStckI, // Store a constant value to an offset on the previous stack frame. + Copy, // Copy a value from one register to another register. + Jump, // Jump to a constant location. + JumpReg, // Jump to a register A (high byte) + register B (low byte). + JumpMem, // Jump to a location pointed to by memory at the current memory address (high byte first). + Call, // Call a function. + Return, // Return from a function. + CmpI, // Compare A to a constant value (sets flags, but discards result). + CmpAndI, // Compare A to a constant value with bitwise AND (sets flags, but discards result). + Cmp, // Compare A to a value from memory (sets flags, but discards result). + CmpAnd, // Compare A to a value in memory with bitwise AND (sets flags, but discards result). + CmpReg, // Compare A to a value from a register (sets flags, but discards result). + CmpAndReg, // Compare A to a value from a register with bitwise AND (sets flags, but discards result). + ShiftL, // Shift B left by 1. + ShiftR, // Shift B right by 1. + RotateL, // Rotate B left by 1. + RotateR, // Rotate B right by 1. + AddI, // Add a constant value to A. + SubI, // Subtract a constant value from A. + AndI, // Bitwise-AND A with a constant value. + AddINF, // Add a constant value to A, without updating flags. + SubINF, // Subtract a constant value from A, without updating flags. + AndINF, // Bitwise-AND A with a constant value, without updating flags. + AccumulateAdd, // Add register B to A -> A. + AccumulateSub, // Subtract register B from A -> A. + AccumulateAnd, // A & B -> A. + OrI, // Bitwise OR B with A -> A. + XorI, // Bitwise OR a constant value with A -> A. + Not, // Invert register A. + Add, // Add a value from memory to A. + Sub, // Subtract a value from memory from A. + And, // AND A with a value from memory. + Or, // OR A with a value from memory. + Xor, // XOR A with a value from memory. + Nop, // No-op. + Nop1, // No-op with 1 extra clock cycle. + Nop2, // No-op with 2 extra clock cycles. + Halt, // Halt - stop the program forever (until reset). +}; + +const Registers = enum(u8) { + A, + B, + C, + PCnt, + MAdr, + Stack, + OutA, + Shift, + Swap, +}; diff --git a/src/link/Spork8.zig b/src/link/Spork8.zig index 3f005570cd7b7113423a7853bb523ba3a8f782be..1baa60d166b667c20855b03b8a613509f7d0073f 100644 --- a/src/link/Spork8.zig +++ b/src/link/Spork8.zig @@ -249,11 +249,14 @@ pub fn flush( } fn mirToMC(spork8: *Spork8, w: *Io.Writer) !void { - for (spork8.mir_instructions.items(.tag)) |tag| { + for (spork8.mir_instructions.items(.tag), spork8.mir_instructions.items(.data)) |tag, data| { switch (tag) { .set_page_i => @panic("TODO"), .set_addr_i => @panic("TODO"), - .load_i => @panic("TODO"), + .load_i_outa => { + try w.writeByte(@backingInt(tag)); + try w.writeByte(data.imm8); + }, .jump => @panic("TODO"), .halt => try w.writeByte(@backingInt(tag)), } -- 2.54.0 From 66014c3de19a49d0c1001dfbbef5a85c375f4e32 Mon Sep 17 00:00:00 2001 From: Ben Anderman Date: Sun, 23 Aug 2026 15:09:11 -0400 Subject: [PATCH 05/13] Add error handling to Spork8 codegen --- src/codegen/spork8/CodeGen.zig | 16 ++++---- src/codegen/spork8/Mir.zig | 71 ---------------------------------- 2 files changed, 8 insertions(+), 79 deletions(-) diff --git a/src/codegen/spork8/CodeGen.zig b/src/codegen/spork8/CodeGen.zig index 451175b42e300c2b5100cccedd0584566271f92b..184af44b4aaa0162b512c2f945a8869df4ba2305 100644 --- a/src/codegen/spork8/CodeGen.zig +++ b/src/codegen/spork8/CodeGen.zig @@ -491,8 +491,6 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { }; try constValues.put(zcu.gpa, name, @intCast(value)); - - // return cg.fail("TODO: Constraint={q}, name={q}, value={}", .{ constraint, name, value }); } } @@ -500,22 +498,24 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { var lines = mem.tokenizeScalar(u8, unwrapped_asm.source, '\n'); while (lines.next()) |line| { var tokens = mem.tokenizeScalar(u8, line, ' '); - const op = tokens.next().?; + // If there's no tokens, then it must be a blank line, so just skip it. + const op = tokens.next() orelse continue; const instType = std.meta.stringToEnum(AsmInstType, op) orelse return cg.fail("Invalid asm instruction: {q}", .{op}); switch (instType) { .LoadI => { - const register = std.meta.stringToEnum(Register, tokens.next().?).?; - const value = tokens.next().?; + const registerString = tokens.next() orelse return cg.fail("Missing register for LoadI instruction", .{}); + const register = std.meta.stringToEnum(Register, registerString) orelse return cg.fail("Invalid register: {q}", .{registerString}); + const value = tokens.next() orelse return cg.fail("Missing immediate value for LoadI", .{}); const intValue = v: { if (mem.startsWith(u8, value, "%[")) { const name = value[2 .. value.len - 1]; - break :v constValues.get(name).?; + break :v constValues.get(name) orelse return cg.fail("Constraint name {q} not included in constraints for inline asm", .{name}); } else { - break :v std.fmt.parseInt(u8, value, 0) catch unreachable; + break :v std.fmt.parseInt(u8, value, 0) catch return cg.fail("Couldn't parse u8 from LoadI immediate value", .{}); } }; if (register != .OutA) { - return cg.fail("TODO: other variants of LoadI", .{}); + return cg.fail("TODO: support other variants of LoadI", .{}); } try cg.addTagImm8(.load_i_outa, intValue); }, diff --git a/src/codegen/spork8/Mir.zig b/src/codegen/spork8/Mir.zig index 4034320a9336b40f3651c50aeedb5659237126ef..4f7b041a66dcb21702af27ddcd797acddbc6b86d 100644 --- a/src/codegen/spork8/Mir.zig +++ b/src/codegen/spork8/Mir.zig @@ -52,74 +52,3 @@ pub fn deinit(mir: *Mir, gpa: std.mem.Allocator) void { mir.instructions.deinit(gpa); mir.* = undefined; } - -const AsmInstType = enum(u8) { - SetPageReg, // Set the memory address high byte to a register value. - SetPageI, // Set the memory address high byte to a constant value. - SetAddrReg, // Set the memory address low byte to a register value. - SetAddrI, // Set the memory address low byte to a constant value. - Load, // Load a value from a constant address into a register. - LoadI, // Load a constant value into a register. - LoadP, // Load a value from a constant address (setting low byte only) into a register. - LoadInc, // Load a value from the currently set memory address into a register, and increment the address n times. - LoadStck, // Load a value from an offset on the current stack frame into a register. - Store, // Store a value to a constant address from a register. - StoreI, // Store a constant value into a constant address. - StoreP, // Store a value to a constant address (low byte only) from a register. - StoreInc, // Store a value from the currently set memory address from a register, and increment the address n times. - StoreStck, // Store a value to an offset on the current stack frame, from a register. - StoreNStck, // Store a value to an offset on the next stack frame, from a register. - StorePStck, // Store a value to an offset on the previous stack frame, from a register. - StoreStckI, // Store a constant value to an offset on the current stack frame. - StoreNStckI, // Store a constant value to an offset on the next stack frame. - StorePStckI, // Store a constant value to an offset on the previous stack frame. - Copy, // Copy a value from one register to another register. - Jump, // Jump to a constant location. - JumpReg, // Jump to a register A (high byte) + register B (low byte). - JumpMem, // Jump to a location pointed to by memory at the current memory address (high byte first). - Call, // Call a function. - Return, // Return from a function. - CmpI, // Compare A to a constant value (sets flags, but discards result). - CmpAndI, // Compare A to a constant value with bitwise AND (sets flags, but discards result). - Cmp, // Compare A to a value from memory (sets flags, but discards result). - CmpAnd, // Compare A to a value in memory with bitwise AND (sets flags, but discards result). - CmpReg, // Compare A to a value from a register (sets flags, but discards result). - CmpAndReg, // Compare A to a value from a register with bitwise AND (sets flags, but discards result). - ShiftL, // Shift B left by 1. - ShiftR, // Shift B right by 1. - RotateL, // Rotate B left by 1. - RotateR, // Rotate B right by 1. - AddI, // Add a constant value to A. - SubI, // Subtract a constant value from A. - AndI, // Bitwise-AND A with a constant value. - AddINF, // Add a constant value to A, without updating flags. - SubINF, // Subtract a constant value from A, without updating flags. - AndINF, // Bitwise-AND A with a constant value, without updating flags. - AccumulateAdd, // Add register B to A -> A. - AccumulateSub, // Subtract register B from A -> A. - AccumulateAnd, // A & B -> A. - OrI, // Bitwise OR B with A -> A. - XorI, // Bitwise OR a constant value with A -> A. - Not, // Invert register A. - Add, // Add a value from memory to A. - Sub, // Subtract a value from memory from A. - And, // AND A with a value from memory. - Or, // OR A with a value from memory. - Xor, // XOR A with a value from memory. - Nop, // No-op. - Nop1, // No-op with 1 extra clock cycle. - Nop2, // No-op with 2 extra clock cycles. - Halt, // Halt - stop the program forever (until reset). -}; - -const Registers = enum(u8) { - A, - B, - C, - PCnt, - MAdr, - Stack, - OutA, - Shift, - Swap, -}; -- 2.54.0 From 03cc522fc0e3cd2434510eec7c39aa09282f4433 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 24 Aug 2026 15:37:09 -0700 Subject: [PATCH 06/13] spork8: object format is .bin extension (raw machine code) --- lib/std/Build/Configuration.zig | 1 - lib/std/Target.zig | 6 ++---- lib/std/debug.zig | 4 ++-- lib/std/zig.zig | 1 - src/Compilation/Config.zig | 2 +- src/codegen.zig | 8 ++++---- src/link.zig | 6 ++++-- src/link/Spork8.zig | 4 ++-- src/target.zig | 1 - 9 files changed, 15 insertions(+), 18 deletions(-) diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 6eea9c9fffd24b817989fcaa2045ac1b39982501..fb24f8b5042a87979c638aeecdbc8743f0e74403 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -2443,7 +2443,6 @@ pub const TargetQuery = struct { elf, hex, macho, - spork8, plan9, raw, spirv, diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 659caa75b5ed7f0cbd473f5f995cad5386d75c1d..17fd85e035125edbfe9fdc57c682802954adec18 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -104,7 +104,7 @@ pub const Os = struct { .plan9 => arch.plan9Ext(), else => switch (arch) { .wasm32, .wasm64 => ".wasm", - .spork8 => ".spork8", + .spork8 => ".bin", else => "", }, }; @@ -1065,7 +1065,6 @@ pub const ObjectFormat = enum { hex, /// The Mach object format used by macOS and other Apple platforms. macho, - spork8, /// The a.out format used by Plan 9 from Bell Labs. plan9, /// Machine code with no metadata. @@ -1081,7 +1080,6 @@ pub const ObjectFormat = enum { .coff => ".obj", .elf, .macho, .wasm => ".o", .hex => ".ihex", - .spork8 => ".spork8", .plan9 => arch.plan9Ext(), .raw => ".bin", .spirv => ".spv", @@ -1096,7 +1094,7 @@ pub const ObjectFormat = enum { else => switch (arch) { .spirv32, .spirv64 => .spirv, .wasm32, .wasm64 => .wasm, - .spork8 => .spork8, + .spork8 => .raw, else => .elf, }, }; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 768b6bca673d1fb22415b5de126e006ed2adbe13..1e83e71ba04025eda0c7f61a3be22ea259c5d610 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -75,8 +75,8 @@ pub fn TargetInfo(os: std.Target.Os.Tag, arch: std.Target.Cpu.Arch) type { else => @import("debug/SelfInfo/Elf.zig"), }, .macho => @import("debug/SelfInfo/MachO.zig"), - .plan9, .spirv, .wasm, .spork8 => void, - .c, .hex, .raw => unreachable, + .plan9, .spirv, .wasm, .raw, .hex => void, + .c => unreachable, }; } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index edf19d1848c27eb51e703d7a632e8e8cdc50cdc9..e18a6414c6e9ba130640e061307c68b16ff02456 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -259,7 +259,6 @@ pub fn binNameAlloc(allocator: Allocator, options: BinNameOptions) error{OutOfMe .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}), .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}), .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}), - .spork8 => return std.fmt.allocPrint(allocator, "{s}.spork8", .{root_name}), .plan9 => switch (options.output_mode) { .Exe => return allocator.dupe(u8, root_name), .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 58a69ba70100065ab0175fd94319ebaf0ae8cce4..affb7cf5c1e9ddb85e137eaef26dcb7750966657 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -502,7 +502,7 @@ pub fn resolve(options: Options) ResolveError!Config { .windows, .uefi => .code_view, else => .{ .dwarf = .@"32" }, }, - .spirv, .hex, .raw, .plan9, .spork8 => .strip, + .spirv, .hex, .raw, .plan9 => .strip, }; }; diff --git a/src/codegen.zig b/src/codegen.zig index 17dcc14f7036d4631e3225890c0404f2dd828410..b4efad778181ae69f35265f5e5edfe29e565921a 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -80,7 +80,7 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co .stage2_x86, .stage2_riscv64, .stage2_sparc64, - .stage2_spork8, + .zsf_spork8, .stage2_spirv, => |backend| { dev.check(devFeatureForBackend(backend)); @@ -122,7 +122,7 @@ pub const AnyMir = union { .stage2_wasm => "wasm", .stage2_c => "c", .stage2_spirv => "spirv", - .stage2_spork8 => "spork8", + .zsf_spork8 => "spork8", else => unreachable, }; } @@ -140,7 +140,7 @@ pub const AnyMir = union { .stage2_wasm, .stage2_c, .stage2_spirv, - .stage2_spork8, + .zsf_spork8, => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa), } } @@ -170,7 +170,7 @@ pub fn generateFunction( .stage2_x86_64, .stage2_wasm, .stage2_c, - .stage2_spork8, + .zsf_spork8, .stage2_spirv, => |backend| { dev.check(devFeatureForBackend(backend)); diff --git a/src/link.zig b/src/link.zig index 1e9cbb18c059511eba1a631f21ed18068d3fdf35..9e200d1d1c850062ccfc5e95cfa10e74fcd98411 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1300,9 +1300,11 @@ pub const File = struct { .plan9 => .plan9, .c => .c, .spirv => .spirv, - .spork8 => .spork8, .hex => @panic("TODO implement hex object format"), - .raw => @panic("TODO implement raw object format"), + // This may seem surprising at first, but with a little massaging, the spork8 linker + // could and probably should be generalized into a "raw linker" which is used to output + // bare machine code for any architecture for which a corresponding backend exists. + .raw => .spork8, }; } diff --git a/src/link/Spork8.zig b/src/link/Spork8.zig index 1baa60d166b667c20855b03b8a613509f7d0073f..3487387c55b2d52580494793ce34c5e11a36a764 100644 --- a/src/link/Spork8.zig +++ b/src/link/Spork8.zig @@ -47,7 +47,7 @@ pub fn createEmpty( options: link.File.OpenOptions, ) !*Spork8 { const target = comp.root_mod.resolved_target.result; - assert(target.ofmt == .spork8); + assert(target.ofmt == .raw); assert(comp.config.output_mode == .Exe); const io = comp.io; @@ -90,7 +90,7 @@ pub fn updateFunc( any_mir: *const codegen.AnyMir, ) !void { dev.check(.spork8_backend); - // This linker implementation only works with codegen backend `.stage2_spork8`. + // This linker implementation only works with `std.lang.CompilerBackend.zsf_spork8`. const mir = &any_mir.spork8; const zcu = pt.zcu; const gpa = zcu.gpa; diff --git a/src/target.zig b/src/target.zig index 02cd9c2f93561df3b191aa6e47be1e8b97eda5aa..48c29feb3899b97ce0a47f83cb64f6435263a435 100644 --- a/src/target.zig +++ b/src/target.zig @@ -189,7 +189,6 @@ pub fn hasLlvmSupport(target: *const std.Target, ofmt: std.Target.ObjectFormat) // LLVM does not support these object formats: .c, .plan9, - .spork8, => return false, .coff, -- 2.54.0 From 7e643ae8ab105bf3c63d2879ee3fb4a57b5bfbe8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 24 Aug 2026 15:50:34 -0700 Subject: [PATCH 07/13] spork8: use snake2p calling convention --- lib/std/Target.zig | 5 ++++- lib/std/lang.zig | 4 ++++ src/Zcu.zig | 2 +- src/codegen/llvm.zig | 1 + 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 17fd85e035125edbfe9fdc57c682802954adec18..985e67b951b56315fd1d3ada71ed7d2ed75372bf 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -2033,6 +2033,9 @@ pub const Cpu = struct { .ez80_cet, .ez80_tiflags, => &.{.ez80}, + + .spork8, + => &.{.spork8}, }; } }; @@ -3818,7 +3821,7 @@ pub fn cCallingConvention(target: *const Target) ?std.builtin.CallingConvention .nvptx, .nvptx64 => .nvptx_device, .spirv32, .spirv64 => .spirv_device, .ez80 => .ez80_cet, - .spork8 => .naked, + .spork8 => .spork8, }; } diff --git a/lib/std/lang.zig b/lib/std/lang.zig index 471b627ea661d00a86705760a82abccebd7a1d20..c73e40cc9278b4c524296be0684a31509b6905b9 100644 --- a/lib/std/lang.zig +++ b/lib/std/lang.zig @@ -391,6 +391,10 @@ pub const CallingConvention = union(enum(u8)) { ez80_cet, ez80_tiflags, + // Calling convention used by + // [snake2p example program](https://github.com/benanderman/spork-8/blob/1bce10a2c3a3888a3f4ca8208112afbc5973fda4/Code/programs/snake2p.asm) + spork8, + /// Options shared across most calling conventions. pub const CommonOptions = struct { /// The boundary the stack is aligned to when the function is called. diff --git a/src/Zcu.zig b/src/Zcu.zig index f77f59a478c419b6517a69becac310f9fe444ad8..1d309e0d599476fec9b3a5780b6ca1fe5615f5b0 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -4699,7 +4699,7 @@ pub fn callconvSupported(zcu: *Zcu, cc: std.lang.CallingConvention) union(enum) else => false, }, .zsf_spork8 => switch (cc) { - .naked => true, + .spork8, .naked => true, else => false, }, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 0de67cfaaccf5f63aec1140a8f7c4fdbce8f49e3..ec493f5540a0b55aa49cff29746dfd73c4859e29 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4393,6 +4393,7 @@ pub fn toLlvmCallConvTag(cc_tag: std.lang.CallingConvention.Tag, target: *const .spirv_vertex, .spirv_task, .spirv_mesh, + .spork8, => null, }; } -- 2.54.0 From 22e6b1d609c6d2dd87b5206bf21d2bf94b30f2d4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 24 Aug 2026 16:05:01 -0700 Subject: [PATCH 08/13] start: no special handling for spork8 needed All the existing conditionals already handle it correctly. --- lib/std/start.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/std/start.zig b/lib/std/start.zig index 470f60e1a198ade629f0a55a68207feac9ab6c65..228b51e08ef9cd2b0eaaa8d8f5e974e1613076d7 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -19,9 +19,7 @@ comptime { // decls there get run. _ = root; - if (builtin.cpu.arch == .spork8) { - // always freestanding, even exes - } else if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { + if (builtin.output_mode == .Lib and builtin.link_mode == .dynamic) { const dll_main_crt_startup = if (builtin.abi.isGnu()) "DllMainCRTStartup" else "_DllMainCRTStartup"; if (native_os == .windows and !builtin.link_libc and !@hasDecl(root, dll_main_crt_startup)) { @export(&DllMainCRTStartup, .{ .name = dll_main_crt_startup }); -- 2.54.0 From 5a8c5e322b67c16be6cdcec7621c4a59ae86dd85 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 24 Aug 2026 16:05:43 -0700 Subject: [PATCH 09/13] frontend: default strip=true on hex and raw object formats --- src/Compilation/Config.zig | 1 - src/target.zig | 43 ++++++++++++++++++++------------------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index affb7cf5c1e9ddb85e137eaef26dcb7750966657..85915ded1b97d4e324a1cebbdb1385758ebea6f0 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -487,7 +487,6 @@ pub fn resolve(options: Options) ResolveError!Config { const root_strip = b: { if (options.root_strip) |x| break :b x; if (root_optimize_mode == .small) break :b true; - if (target.cpu.arch == .spork8) break :b true; if (!target_util.hasDebugInfo(target)) break :b true; break :b false; }; diff --git a/src/target.zig b/src/target.zig index 48c29feb3899b97ce0a47f83cb64f6435263a435..5edf18e9581df4d786b515f6b5a2bfdf33e34561 100644 --- a/src/target.zig +++ b/src/target.zig @@ -396,26 +396,29 @@ pub fn classifyCompilerRtLibName(name: []const u8) CompilerRtClassification { } pub fn hasDebugInfo(target: *const std.Target) bool { - return switch (target.cpu.arch) { - // TODO: We should make newer PTX versions depend on older ones so we'd just check `ptx75`. - .nvptx, .nvptx64 => target.cpu.hasAny(.nvptx, &.{ - .ptx75, - .ptx76, - .ptx77, - .ptx78, - .ptx80, - .ptx81, - .ptx82, - .ptx83, - .ptx84, - .ptx85, - .ptx86, - .ptx87, - .ptx88, - .ptx90, - }), - .bpfel, .bpfeb => false, - else => true, + return switch (target.ofmt) { + .raw, .hex => false, + else => switch (target.cpu.arch) { + // TODO: We should make newer PTX versions depend on older ones so we'd just check `ptx75`. + .nvptx, .nvptx64 => target.cpu.hasAny(.nvptx, &.{ + .ptx75, + .ptx76, + .ptx77, + .ptx78, + .ptx80, + .ptx81, + .ptx82, + .ptx83, + .ptx84, + .ptx85, + .ptx86, + .ptx87, + .ptx88, + .ptx90, + }), + .bpfel, .bpfeb => false, + else => true, + }, }; } -- 2.54.0 From dec706c243ab7d0c2e87fd64c77cda9cd8847520 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 24 Aug 2026 16:09:52 -0700 Subject: [PATCH 10/13] spork8: coding style conformance --- src/codegen/spork8/CodeGen.zig | 193 +++++++++++++++++++++------------ 1 file changed, 125 insertions(+), 68 deletions(-) diff --git a/src/codegen/spork8/CodeGen.zig b/src/codegen/spork8/CodeGen.zig index 184af44b4aaa0162b512c2f945a8869df4ba2305..f163d9347e239495f1d5217bf522a859b9c608f4 100644 --- a/src/codegen/spork8/CodeGen.zig +++ b/src/codegen/spork8/CodeGen.zig @@ -187,7 +187,7 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const air_tags = cg.air.instructions.items(.tag); - return switch (air_tags[@intFromEnum(inst)]) { + return switch (air_tags[@backingInt(inst)]) { .inferred_alloc, .inferred_alloc_comptime => unreachable, .add, @@ -431,7 +431,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { .legalize_vec_store_elem, .legalize_vec_elem_val, .legalize_compiler_rt_call, - => |tag| return cg.fail("TODO: implement spork8 inst: {s}", .{@tagName(tag)}), + => |tag| return cg.fail("TODO: implement spork8 inst: {t}", .{tag}), .unreach => cg.airUnreachable(inst), .assembly => cg.airAssembly(inst), @@ -473,21 +473,21 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { while (it.next()) |input| { const constraint = input.constraint; if (!mem.eql(u8, constraint, "I")) { - return cg.fail("Assembly constraint {q} not supported", .{constraint}); + return cg.fail("assembly constraint {q} not supported", .{constraint}); } const operand = input.operand.toInterned() orelse { - return cg.fail("Immediate argument to inline assembly must be compile-time value", .{}); + return cg.fail("immediate argument to inline assembly must be compile-time value", .{}); }; const name = input.name; const value = switch (zcu.intern_pool.indexToKey(operand)) { .int => |val| v: { if (val.ty != .u8_type) { - return cg.fail("Non-u8 type used in inline assembly value: {}", .{val.ty}); + return cg.fail("non-u8 type used in inline assembly value: {}", .{val.ty}); } break :v val.storage.u64; }, - else => return cg.fail("Non-int operands not supported", .{}), + else => return cg.fail("non-int operands not supported", .{}), }; try constValues.put(zcu.gpa, name, @intCast(value)); @@ -500,18 +500,19 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { var tokens = mem.tokenizeScalar(u8, line, ' '); // If there's no tokens, then it must be a blank line, so just skip it. const op = tokens.next() orelse continue; - const instType = std.meta.stringToEnum(AsmInstType, op) orelse return cg.fail("Invalid asm instruction: {q}", .{op}); + const instType = std.meta.stringToEnum(AsmInstType, op) orelse return cg.fail("invalid asm instruction: {q}", .{op}); switch (instType) { .LoadI => { - const registerString = tokens.next() orelse return cg.fail("Missing register for LoadI instruction", .{}); - const register = std.meta.stringToEnum(Register, registerString) orelse return cg.fail("Invalid register: {q}", .{registerString}); - const value = tokens.next() orelse return cg.fail("Missing immediate value for LoadI", .{}); + const registerString = tokens.next() orelse return cg.fail("missing register for LoadI instruction", .{}); + const register = std.meta.stringToEnum(Register, registerString) orelse return cg.fail("invalid register: {q}", .{registerString}); + const value = tokens.next() orelse return cg.fail("missing immediate value for LoadI", .{}); const intValue = v: { if (mem.startsWith(u8, value, "%[")) { const name = value[2 .. value.len - 1]; - break :v constValues.get(name) orelse return cg.fail("Constraint name {q} not included in constraints for inline asm", .{name}); + break :v constValues.get(name) orelse return cg.fail("constraint name {q} not included in constraints for inline asm", .{name}); } else { - break :v std.fmt.parseInt(u8, value, 0) catch return cg.fail("Couldn't parse u8 from LoadI immediate value", .{}); + break :v std.fmt.parseInt(u8, value, 0) catch |err| + return cg.fail("invalid LoadI immediate value: {t}", .{err}); } }; if (register != .OutA) { @@ -548,62 +549,118 @@ fn extraLen(cg: *const CodeGen) u32 { } const AsmInstType = enum(u8) { - SetPageReg, // Set the memory address high byte to a register value. - SetPageI, // Set the memory address high byte to a constant value. - SetAddrReg, // Set the memory address low byte to a register value. - SetAddrI, // Set the memory address low byte to a constant value. - Load, // Load a value from a constant address into a register. - LoadI, // Load a constant value into a register. - LoadP, // Load a value from a constant address (setting low byte only) into a register. - LoadInc, // Load a value from the currently set memory address into a register, and increment the address n times. - LoadStck, // Load a value from an offset on the current stack frame into a register. - Store, // Store a value to a constant address from a register. - StoreI, // Store a constant value into a constant address. - StoreP, // Store a value to a constant address (low byte only) from a register. - StoreInc, // Store a value from the currently set memory address from a register, and increment the address n times. - StoreStck, // Store a value to an offset on the current stack frame, from a register. - StoreNStck, // Store a value to an offset on the next stack frame, from a register. - StorePStck, // Store a value to an offset on the previous stack frame, from a register. - StoreStckI, // Store a constant value to an offset on the current stack frame. - StoreNStckI, // Store a constant value to an offset on the next stack frame. - StorePStckI, // Store a constant value to an offset on the previous stack frame. - Copy, // Copy a value from one register to another register. - Jump, // Jump to a constant location. - JumpReg, // Jump to a register A (high byte) + register B (low byte). - JumpMem, // Jump to a location pointed to by memory at the current memory address (high byte first). - Call, // Call a function. - Return, // Return from a function. - CmpI, // Compare A to a constant value (sets flags, but discards result). - CmpAndI, // Compare A to a constant value with bitwise AND (sets flags, but discards result). - Cmp, // Compare A to a value from memory (sets flags, but discards result). - CmpAnd, // Compare A to a value in memory with bitwise AND (sets flags, but discards result). - CmpReg, // Compare A to a value from a register (sets flags, but discards result). - CmpAndReg, // Compare A to a value from a register with bitwise AND (sets flags, but discards result). - ShiftL, // Shift B left by 1. - ShiftR, // Shift B right by 1. - RotateL, // Rotate B left by 1. - RotateR, // Rotate B right by 1. - AddI, // Add a constant value to A. - SubI, // Subtract a constant value from A. - AndI, // Bitwise-AND A with a constant value. - AddINF, // Add a constant value to A, without updating flags. - SubINF, // Subtract a constant value from A, without updating flags. - AndINF, // Bitwise-AND A with a constant value, without updating flags. - AccumulateAdd, // Add register B to A -> A. - AccumulateSub, // Subtract register B from A -> A. - AccumulateAnd, // A & B -> A. - OrI, // Bitwise OR B with A -> A. - XorI, // Bitwise OR a constant value with A -> A. - Not, // Invert register A. - Add, // Add a value from memory to A. - Sub, // Subtract a value from memory from A. - And, // AND A with a value from memory. - Or, // OR A with a value from memory. - Xor, // XOR A with a value from memory. - Nop, // No-op. - Nop1, // No-op with 1 extra clock cycle. - Nop2, // No-op with 2 extra clock cycles. - Halt, // Halt - stop the program forever (until reset). + /// Set the memory address high byte to a register value. + SetPageReg, + /// Set the memory address high byte to a constant value. + SetPageI, + /// Set the memory address low byte to a register value. + SetAddrReg, + /// Set the memory address low byte to a constant value. + SetAddrI, + /// Load a value from a constant address into a register. + Load, + /// Load a constant value into a register. + LoadI, + /// Load a value from a constant address (setting low byte only) into a register. + LoadP, + /// Load a value from the currently set memory address into a register, and increment the address n times. + LoadInc, + /// Load a value from an offset on the current stack frame into a register. + LoadStck, + /// Store a value to a constant address from a register. + Store, + /// Store a constant value into a constant address. + StoreI, + /// Store a value to a constant address (low byte only) from a register. + StoreP, + /// Store a value from the currently set memory address from a register, and increment the address n times. + StoreInc, + /// Store a value to an offset on the current stack frame, from a register. + StoreStck, + /// Store a value to an offset on the next stack frame, from a register. + StoreNStck, + /// Store a value to an offset on the previous stack frame, from a register. + StorePStck, + /// Store a constant value to an offset on the current stack frame. + StoreStckI, + /// Store a constant value to an offset on the next stack frame. + StoreNStckI, + /// Store a constant value to an offset on the previous stack frame. + StorePStckI, + /// Copy a value from one register to another register. + Copy, + /// Jump to a constant location. + Jump, + /// Jump to a register A (high byte) + register B (low byte). + JumpReg, + /// Jump to a location pointed to by memory at the current memory address (high byte first). + JumpMem, + /// Call a function. + Call, + /// Return from a function. + Return, + /// Compare A to a constant value (sets flags, but discards result). + CmpI, + /// Compare A to a constant value with bitwise AND (sets flags, but discards result). + CmpAndI, + /// Compare A to a value from memory (sets flags, but discards result). + Cmp, + /// Compare A to a value in memory with bitwise AND (sets flags, but discards result). + CmpAnd, + /// Compare A to a value from a register (sets flags, but discards result). + CmpReg, + /// Compare A to a value from a register with bitwise AND (sets flags, but discards result). + CmpAndReg, + /// Shift B left by 1. + ShiftL, + /// Shift B right by 1. + ShiftR, + /// Rotate B left by 1. + RotateL, + /// Rotate B right by 1. + RotateR, + /// Add a constant value to A. + AddI, + /// Subtract a constant value from A. + SubI, + /// Bitwise-AND A with a constant value. + AndI, + /// Add a constant value to A, without updating flags. + AddINF, + /// Subtract a constant value from A, without updating flags. + SubINF, + /// Bitwise-AND A with a constant value, without updating flags. + AndINF, + /// Add register B to A -> A. + AccumulateAdd, + /// Subtract register B from A -> A. + AccumulateSub, + /// A & B -> A. + AccumulateAnd, + /// Bitwise OR B with A -> A. + OrI, + /// Bitwise OR a constant value with A -> A. + XorI, + /// Invert register A. + Not, + /// Add a value from memory to A. + Add, + /// Subtract a value from memory from A. + Sub, + /// AND A with a value from memory. + And, + /// OR A with a value from memory. + Or, + /// XOR A with a value from memory. + Xor, + /// No-op. + Nop, + /// No-op with 1 extra clock cycle. + Nop1, + /// No-op with 2 extra clock cycles. + Nop2, + /// Halt - stop the program forever (until reset). + Halt, }; const Register = enum(u8) { -- 2.54.0 From f9f79508fc5317a5cf1c0a0a1be9f48e242e3d08 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 25 Aug 2026 15:09:08 -0700 Subject: [PATCH 11/13] spork8: reuse Target/generic.zig --- lib/std/Target.zig | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 985e67b951b56315fd1d3ada71ed7d2ed75372bf..608400e37e41cba8ed8b1a2fbd94041c9b57b5c8 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -789,6 +789,7 @@ pub const s390x = @import("Target/s390x.zig"); pub const sh = @import("Target/generic.zig"); pub const sparc = @import("Target/sparc.zig"); pub const spirv = @import("Target/spirv.zig"); +pub const spork8 = @import("Target/generic.zig"); pub const ve = @import("Target/ve.zig"); pub const wasm = @import("Target/wasm.zig"); pub const x86 = @import("Target/x86.zig"); @@ -796,24 +797,6 @@ pub const xcore = @import("Target/xcore.zig"); pub const xtensa = @import("Target/xtensa.zig"); pub const z80 = @import("Target/generic.zig"); -pub const spork8 = struct { - pub const Feature = enum {}; - pub const featureSet = Cpu.Feature.FeatureSetFns(Feature).featureSet; - pub const featureSetHas = Cpu.Feature.FeatureSetFns(Feature).featureSetHas; - pub const featureSetHasAny = Cpu.Feature.FeatureSetFns(Feature).featureSetHasAny; - pub const featureSetHasAll = Cpu.Feature.FeatureSetFns(Feature).featureSetHasAll; - - pub const cpu = struct { - pub const generic: Cpu.Model = .{ - .name = "generic", - .llvm_name = null, - .features = .empty, - }; - }; - - pub const all_features: [0]Cpu.Feature = .{}; -}; - pub const Abi = enum { none, gnu, -- 2.54.0 From cc8f27d2db253cf75b7a62228f998b02b8a3c527 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 26 Aug 2026 20:49:31 -0700 Subject: [PATCH 12/13] std.Target: refactor cTypeBitSize 1 return statement instead of N return expressions --- lib/std/Target.zig | 286 ++++++++++++++++++++++----------------------- 1 file changed, 143 insertions(+), 143 deletions(-) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 608400e37e41cba8ed8b1a2fbd94041c9b57b5c8..b0329d1bbf8acc55af342ac5ff2b9388eb66fd89 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -3214,7 +3214,7 @@ pub fn cTypeByteSize(t: *const Target, c_type: CType) ?u16 { /// Returns `null` if no C ABI is defined for this target. pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { - switch (target.os.tag) { + return switch (target.os.tag) { .freestanding, .other, .ashetos, @@ -3222,49 +3222,49 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { .msp430, .x86_16, => switch (c_type) { - .char => return 8, - .short, .ushort, .int, .uint => return 16, - .float, .long, .ulong => return 32, - .longlong, .ulonglong, .double, .longdouble => return 64, + .char => 8, + .short, .ushort, .int, .uint => 16, + .float, .long, .ulong => 32, + .longlong, .ulonglong, .double, .longdouble => 64, }, .avr => switch (c_type) { - .char => return 8, - .short, .ushort, .int, .uint => return 16, - .long, .ulong, .float, .double, .longdouble => return 32, - .longlong, .ulonglong => return 64, + .char => 8, + .short, .ushort, .int, .uint => 16, + .long, .ulong, .float, .double, .longdouble => 32, + .longlong, .ulonglong => 64, }, .mips64, .mips64el, => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, .long, .ulong => switch (target.abi) { - .abin32 => return 32, - else => return 64, + .abin32 => 32, + else => 64, }, - .longlong, .ulonglong, .double => return 64, - .longdouble => return 128, + .longlong, .ulonglong, .double => 64, + .longdouble => 128, }, .x86_64 => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, .long, .ulong => switch (target.abi) { - .x32 => return 32, - else => return 64, + .x32 => 32, + else => 64, }, - .longlong, .ulonglong, .double => return 64, - .longdouble => return 80, + .longlong, .ulonglong, .double => 64, + .longdouble => 80, }, else => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong => return target.ptrBitWidth(), - .longlong, .ulonglong, .double => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong => target.ptrBitWidth(), + .longlong, .ulonglong, .double => 64, .longdouble => switch (target.cpu.arch) { - .x86 => return 80, + .x86 => 80, .alpha, .riscv32, @@ -3284,9 +3284,9 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { .loongarch32, .loongarch64, .ve, - => return 128, + => 128, - else => return 64, + else => 64, }, }, }, @@ -3313,60 +3313,60 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { .mips64, .mips64el, => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, .long, .ulong => switch (target.abi) { - .gnuabin32, .muslabin32, .abin32 => return 32, - else => return 64, + .gnuabin32, .muslabin32, .abin32 => 32, + else => 64, }, - .longlong, .ulonglong, .double => return 64, - .longdouble => return 128, + .longlong, .ulonglong, .double => 64, + .longdouble => 128, }, .x86_64 => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, .long, .ulong => switch (target.abi) { - .gnux32, .muslx32, .x32 => return 32, - else => return 64, + .gnux32, .muslx32, .x32 => 32, + else => 64, }, - .longlong, .ulonglong, .double => return 64, - .longdouble => return 80, + .longlong, .ulonglong, .double => 64, + .longdouble => 80, }, else => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong => return target.ptrBitWidth(), - .longlong, .ulonglong, .double => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong => target.ptrBitWidth(), + .longlong, .ulonglong, .double => 64, .longdouble => switch (target.cpu.arch) { .x86 => switch (target.abi) { - .android => return 64, - else => return 80, + .android => 64, + else => 80, }, .powerpc, .powerpcle, => switch (target.abi) { - .musleabi, .musleabihf => return 64, + .musleabi, .musleabihf => 64, else => switch (target.os.tag) { .netbsd, .openbsd, - => return 64, - else => return 128, + => 64, + else => 128, }, }, .powerpc64, .powerpc64le, => switch (target.abi) { - .musl => return 64, + .musl => 64, else => switch (target.os.tag) { .freebsd, .openbsd, - => return 64, - else => return 128, + => 64, + else => 128, }, }, @@ -3386,43 +3386,43 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { .loongarch32, .loongarch64, .ve, - => return 128, + => 128, - else => return 64, + else => 64, }, }, }, .windows, .uefi => switch (target.cpu.arch) { .x86 => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong => return 32, - .longlong, .ulonglong, .double => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong => 32, + .longlong, .ulonglong, .double => 64, .longdouble => switch (target.abi) { - .gnu => return 80, - else => return 64, + .gnu => 80, + else => 64, }, }, .x86_64 => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong => return 32, - .longlong, .ulonglong, .double => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong => 32, + .longlong, .ulonglong, .double => 64, .longdouble => switch (target.abi) { - .gnu => return 80, - else => return 64, + .gnu => 80, + else => 64, }, }, else => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong => return 32, - .longlong, .ulonglong, .double => return 64, - .longdouble => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong => 32, + .longlong, .ulonglong, .double => 64, + .longdouble => 64, }, }, @@ -3434,113 +3434,113 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { .visionos, .watchos, => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, .long, .ulong => switch (target.cpu.arch) { - .x86_64 => return 64, + .x86_64 => 64, else => switch (target.abi) { - .ilp32 => return 32, - else => return 64, + .ilp32 => 32, + else => 64, }, }, - .longlong, .ulonglong, .double => return 64, + .longlong, .ulonglong, .double => 64, .longdouble => switch (target.cpu.arch) { - .x86_64 => return 80, - else => return 64, + .x86_64 => 80, + else => 64, }, }, .nvcl, .cuda => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, .long, .ulong => switch (target.cpu.arch) { - .nvptx => return 32, - .nvptx64 => return 64, - else => return 64, + .nvptx => 32, + .nvptx64 => 64, + else => 64, }, - .longlong, .ulonglong, .double => return 64, - .longdouble => return 64, + .longlong, .ulonglong, .double => 64, + .longdouble => 64, }, .amdhsa, .amdpal, .mesa3d => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong, .longlong, .ulonglong, .double => return 64, - .longdouble => return 128, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong, .longlong, .ulonglong, .double => 64, + .longdouble => 128, }, .opencl, .vulkan => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong, .double => return 64, - .longlong, .ulonglong => return 128, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong, .double => 64, + .longlong, .ulonglong => 128, // Note: The OpenCL specification does not guarantee a particular size for long double, // but clang uses 128 bits. - .longdouble => return 128, + .longdouble => 128, }, .@"3ds" => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float, .long, .ulong => return 32, - .longlong, .ulonglong, .double, .longdouble => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float, .long, .ulong => 32, + .longlong, .ulonglong, .double, .longdouble => 64, }, .wiiu => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float, .long, .ulong => return 32, - .longlong, .ulonglong, .double, .longdouble => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float, .long, .ulong => 32, + .longlong, .ulonglong, .double, .longdouble => 64, }, .@"switch" => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong, .longlong, .ulonglong, .double => return 64, - .longdouble => return 128, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong, .longlong, .ulonglong, .double => 64, + .longdouble => 128, }, .psx => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .long, .ulong, .float => return 32, - .longlong, .ulonglong, .double, .longdouble => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .long, .ulong, .float => 32, + .longlong, .ulonglong, .double, .longdouble => 64, }, .ps4, .ps5 => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong => return 64, - .longlong, .ulonglong, .double => return 64, - .longdouble => return 80, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong => 64, + .longlong, .ulonglong, .double => 64, + .longdouble => 80, }, .psp, .vita => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint, .float => return 32, - .long, .ulong => return 64, - .longlong, .ulonglong, .double, .longdouble => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint, .float => 32, + .long, .ulong => 64, + .longlong, .ulonglong, .double, .longdouble => 64, }, .tios => switch (c_type) { - .char => return 8, - .short, .ushort => return 16, - .int, .uint => return 24, - .long, .ulong, .float, .double => return 32, - .longlong, .ulonglong, .longdouble => return 64, + .char => 8, + .short, .ushort => 16, + .int, .uint => 24, + .long, .ulong, .float, .double => 32, + .longlong, .ulonglong, .longdouble => 64, }, - .opengl => return null, + .opengl => null, .ps3, .contiki, .managarm, => @panic("specify the C integer and float type sizes for this OS"), - } + }; } /// Returns `null` if no C ABI is defined for this target. -- 2.54.0 From 521cc292e6ff43cffc0f54d9b1a5243ba0a766e6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 26 Aug 2026 20:54:12 -0700 Subject: [PATCH 13/13] std.Target: update spork8 C ABI information in accordance with the specification --- lib/std/Target.zig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/std/Target.zig b/lib/std/Target.zig index b0329d1bbf8acc55af342ac5ff2b9388eb66fd89..05527573ce7acafd7c5c0d772f9e2bdf90dc3bbb 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -3108,6 +3108,8 @@ pub fn stackAlignment(target: *const Target) u16 { .kvx => return 32, + .spork8 => return 256, + else => {}, } @@ -3127,6 +3129,7 @@ pub fn stackGrowth(target: *const Target) StackGrowth { return switch (target.cpu.arch) { .hppa, .hppa64, + .spork8, => .up, else => .down, }; @@ -3233,6 +3236,13 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { .long, .ulong, .float, .double, .longdouble => 32, .longlong, .ulonglong => 64, }, + // https://github.com/benanderman/spork-8/blob/main/Programming.md + .spork8 => switch (c_type) { + .char => 8, + .short, .ushort, .int, .uint => 16, + .long, .ulong, .float => 32, + .double, .longdouble, .longlong, .ulonglong => 64, + }, .mips64, .mips64el, => switch (c_type) { -- 2.54.0