diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index 1e85120d02dc488d40860e01cfcf9e56ae5ec57c..651c7c2da4572c5d20245ae9ea48ccd9272968f6 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, diff --git a/lib/std/Target.zig b/lib/std/Target.zig index f73d6d4f80ee092a2918e1b44eeecdabf322c5e8..62a6a61edafb4339db2e67ab5c62650f8ce26a27 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -105,6 +105,7 @@ pub const Os = struct { .plan9 => arch.plan9Ext(), else => switch (arch) { .wasm32, .wasm64 => ".wasm", + .spork8 => ".bin", else => "", }, }; @@ -791,6 +792,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"); @@ -1079,6 +1081,7 @@ pub const ObjectFormat = enum { else => switch (arch) { .spirv32, .spirv64 => .spirv, .wasm32, .wasm64 => .wasm, + .spork8 => .raw, else => .elf, }, }; @@ -1128,6 +1131,7 @@ pub fn toElfMachine(target: *const Target) std.elf.EM { .spirv64, .wasm32, .wasm64, + .spork8, => .NONE, }; } @@ -1195,6 +1199,7 @@ pub fn toCoffMachine(target: *const Target) std.coff.IMAGE.FILE.MACHINE { .xcore, .xtensa, .xtensaeb, + .spork8, => .UNKNOWN, }; } @@ -1399,6 +1404,7 @@ pub const Cpu = struct { sheb, sparc, sparc64, + spork8, spirv32, spirv64, thumb, @@ -1451,6 +1457,7 @@ pub const Cpu = struct { xcore, xtensa, z80, + spork8, }; pub inline fn family(arch: Arch) Family { @@ -1489,6 +1496,7 @@ pub const Cpu = struct { .x86_16, .x86, .x86_64 => .x86, .xcore => .xcore, .xtensa, .xtensaeb => .xtensa, + .spork8 => .spork8, }; } @@ -1753,6 +1761,7 @@ pub const Cpu = struct { .sparc, .sparc64, .xtensaeb, + .spork8, => .big, // GPU endianness is opaque. For now, assume little endian. @@ -2011,6 +2020,9 @@ pub const Cpu = struct { .ez80_cet, .ez80_tiflags, => &.{.ez80}, + + .spork8, + => &.{.spork8}, }; } }; @@ -2980,6 +2992,7 @@ pub fn ptrBitWidth_arch_abi(cpu_arch: Cpu.Arch, abi: Abi) u16 { .avr, .msp430, .x86_16, + .spork8, => 16, .ez80, @@ -3104,6 +3117,8 @@ pub fn stackAlignment(target: *const Target) u16 { .kvx => return 32, + .spork8 => return 256, + else => {}, } @@ -3123,6 +3138,7 @@ pub fn stackGrowth(target: *const Target) StackGrowth { return switch (target.cpu.arch) { .hppa, .hppa64, + .spork8, => .up, else => .down, }; @@ -3210,7 +3226,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, @@ -3218,49 +3234,56 @@ 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, + }, + // 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) { - .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, @@ -3280,9 +3303,9 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { .loongarch32, .loongarch64, .ve, - => return 128, + => 128, - else => return 64, + else => 64, }, }, }, @@ -3309,60 +3332,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, }, }, @@ -3382,43 +3405,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, }, }, @@ -3430,75 +3453,75 @@ 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, }, .gba => switch (c_type) { @@ -3509,41 +3532,41 @@ pub fn cTypeBitSize(target: *const Target, c_type: CType) ?u16 { }, .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. @@ -3552,6 +3575,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) { @@ -3650,6 +3674,7 @@ pub fn cTypeAlignment(target: *const Target, c_type: CType) ?u16 { .avr, .ez80, + .spork8, => unreachable, // Handled above. }), ); @@ -3659,6 +3684,7 @@ pub fn cMaxIntAlignment(target: *const Target) u16 { return switch (target.cpu.arch) { .avr, .ez80, + .spork8, => 1, .msp430, @@ -3804,6 +3830,7 @@ pub fn cCallingConvention(target: *const Target) ?std.builtin.CallingConvention .nvptx, .nvptx64 => .nvptx_device, .spirv32, .spirv64 => .spirv_device, .ez80 => .ez80_cet, + .spork8 => .spork8, }; } diff --git a/lib/std/debug.zig b/lib/std/debug.zig index a855098e0010a76e97b2eb13a063291233c75b0e..305ca90fbe88c7c57f29b876709772544f0d97ed 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 => void, - .c, .hex, .raw => unreachable, + .plan9, .spirv, .wasm, .raw, .hex => void, + .c => unreachable, }; } diff --git a/lib/std/lang.zig b/lib/std/lang.zig index 1c47d2d8919dd40b950d389c98a88d26c3e4dcb4..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. @@ -1319,6 +1323,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/zig/llvm/Builder.zig b/lib/std/zig/llvm/Builder.zig index beb2ce7ae966ede2b9fdfa48bb5e24163ac36619..257d5627ba7f39866025be8352074aae97e96f65 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(). @@ -577,6 +578,7 @@ pub const DataLayout = struct { .sheb, .x86_16, .xtensaeb, + .spork8, => unreachable, }; } diff --git a/src/Zcu.zig b/src/Zcu.zig index 1d1eadffc5545c5de94e84fe6258f28773b3193b..1d309e0d599476fec9b3a5780b6ca1fe5615f5b0 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) { + .spork8, .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..b4efad778181ae69f35265f5e5edfe29e565921a 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, @@ -78,6 +80,7 @@ pub fn legalizeFeatures(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) ?*co .stage2_x86, .stage2_riscv64, .stage2_sparc64, + .zsf_spork8, .stage2_spirv, => |backend| { dev.check(devFeatureForBackend(backend)); @@ -107,6 +110,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) { @@ -118,6 +122,7 @@ pub const AnyMir = union { .stage2_wasm => "wasm", .stage2_c => "c", .stage2_spirv => "spirv", + .zsf_spork8 => "spork8", else => unreachable, }; } @@ -135,6 +140,7 @@ pub const AnyMir = union { .stage2_wasm, .stage2_c, .stage2_spirv, + .zsf_spork8, => |backend_ct| @field(mir, tag(backend_ct)).deinit(gpa), } } @@ -164,6 +170,7 @@ pub fn generateFunction( .stage2_x86_64, .stage2_wasm, .stage2_c, + .zsf_spork8, .stage2_spirv, => |backend| { dev.check(devFeatureForBackend(backend)); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a0cb2ac891f08409e40c780c1ec4049057dc887c..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, }; } @@ -4720,6 +4721,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..f163d9347e239495f1d5217bf522a859b9c608f4 --- /dev/null +++ b/src/codegen/spork8/CodeGen.zig @@ -0,0 +1,676 @@ +const std = @import("std"); +const mem = std.mem; +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, +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), + +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, + + .expand_packed_load, + .expand_packed_store, + .expand_packed_agg_field_val, + .expand_packed_aggregate_init, + .expand_array_to_vector, + + .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: *const Air, + liveness: *const ?Air.Liveness, +) link.Error!Mir { + _ = bin_file; + const zcu = pt.zcu; + const gpa = zcu.gpa; + const cg = zcu.funcInfo(func_index); + + var code_gen: CodeGen = .{ + .gpa = gpa, + .pt = pt, + .air = air.*, + .liveness = liveness.*.?, + .owner_nav = cg.owner_nav, + .func_index = func_index, + .mir_instructions = .empty, + .mir_extra = .empty, + }; + defer code_gen.deinit(); + + return generateInner(&code_gen) catch |err| switch (err) { + error.AlreadyReported, + error.OutOfMemory, + => |e| return e, + }; +} + +pub fn deinit(cg: *CodeGen) void { + cg.* = undefined; +} + +const InnerError = error{ + AlreadyReported, + OutOfMemory, +}; + +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 .{ + .instructions = cg.mir_instructions.toOwnedSlice(), + .extra = cg.mir_extra.toOwnedSliceAssert(), + }; +} + +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[@backingInt(inst)]) { + .inferred_alloc, .inferred_alloc_comptime => unreachable, + + .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, + .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, + + .array_elem_val, + .array_to_slice, + .alloc, + .arg, + .block, + .breakpoint, + .br, + .repeat, + .switch_dispatch, + .cond_br, + .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, + .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, + .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, + + .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, + .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, + .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: {t}", .{tag}), + + .unreach => cg.airUnreachable(inst), + .assembly => cg.airAssembly(inst), + .trap => cg.airTrap(inst), + + .work_item_id, + .work_group_size, + .work_group_id, + => unreachable, + }; +} + +fn airUnreachable(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + _ = cg; + _ = inst; +} + +fn airTrap(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { + _ = inst; + 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)); + } + } + + { + var lines = mem.tokenizeScalar(u8, unwrapped_asm.source, '\n'); + while (lines.next()) |line| { + 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}); + 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 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}); + } else { + break :v std.fmt.parseInt(u8, value, 0) catch |err| + return cg.fail("invalid LoadI immediate value: {t}", .{err}); + } + }; + if (register != .OutA) { + return cg.fail("TODO: support 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); +} + +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); + 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); +} + +const AsmInstType = enum(u8) { + /// 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) { + A, + B, + C, + PCnt, + MAdr, + Stack, + OutA, + Shift, + Swap, +}; diff --git a/src/codegen/spork8/Mir.zig b/src/codegen/spork8/Mir.zig new file mode 100644 index 0000000000000000000000000000000000000000..4f7b041a66dcb21702af27ddcd797acddbc6b86d --- /dev/null +++ b/src/codegen/spork8/Mir.zig @@ -0,0 +1,54 @@ +const Mir = @This(); +const InternPool = @import("../../InternPool.zig"); + +const builtin = @import("builtin"); +const std = @import("std"); +const assert = std.debug.assert; + +instructions: std.MultiArrayList(Inst).Slice, + +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_outa = 0x15, + /// index + jump = 0x68, + /// nothing + halt = 0xE3, + }; + + /// 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, + nothing: void, + + comptime { + switch (builtin.mode) { + .Debug, .ReleaseSafe => {}, + .ReleaseFast, .ReleaseSmall => assert(@sizeOf(Data) == 4), + } + } + }; +}; + +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 1de9810eb00f5e754fea549b3f0f9fa3d5484d8f..318a689f6db7fa0708105e252195ef6bcb0d61b7 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,15 @@ pub const Env = enum { => true, else => Env.sema.supports(feature), }, + .spork8 => switch (feature) { + .stdio_listen, + .incremental, + .legalize, + .spork8_backend, + .spork8_linker, + => true, + else => Env.sema.supports(feature), + }, }; } @@ -303,6 +318,7 @@ pub const Feature = enum { sparc64_backend, spirv_backend, loongarch_backend, + spork8_backend, lld_linker, coff_linker, @@ -314,6 +330,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..9e200d1d1c850062ccfc5e95cfa10e74fcd98411 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, }; } @@ -1293,7 +1301,10 @@ pub const File = struct { .c => .c, .spirv => .spirv, .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, }; } @@ -1373,6 +1384,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..3487387c55b2d52580494793ce34c5e11a36a764 --- /dev/null +++ b/src/link/Spork8.zig @@ -0,0 +1,271 @@ +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, +/// All MIR instructions for all Zcu functions. +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, + 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 == .raw); + 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 `std.lang.CompilerBackend.zsf_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; + + 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 { + _ = 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(); + const io = spork8.base.comp.io; + const diags = &spork8.base.comp.link_diags; + + _ = arena; + _ = tid; + + // 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), spork8.mir_instructions.items(.data)) |tag, data| { + switch (tag) { + .set_page_i => @panic("TODO"), + .set_addr_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)), + } + } +} + +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..5edf18e9581df4d786b515f6b5a2bfdf33e34561 100644 --- a/src/target.zig +++ b/src/target.zig @@ -271,6 +271,7 @@ pub fn hasLlvmSupport(target: *const std.Target, ofmt: std.Target.ObjectFormat) .sheb, .x86_16, .xtensaeb, + .spork8, => false, }; } @@ -395,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, + }, }; } @@ -433,6 +437,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 +450,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 +935,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, }; }